From 4d5cb90f4cce3f74e736335cad46e929f4e0c202 Mon Sep 17 00:00:00 2001 From: qq281113270 <281113270@qq.com> Date: Wed, 15 Jul 2020 12:01:15 +0800 Subject: [PATCH 1/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8a2edd..fc3d2b2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ - vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了,差diff那部分,因为考虑到自己要换工作了,所以暂缓下来先,diff那块后期我会补上去。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。 + vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。 说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。 From 42596327871fe5a1fa839fdc82fc2703993d0577 Mon Sep 17 00:00:00 2001 From: yaoguanshou <281113270@qq.com> Date: Mon, 2 Sep 2024 00:26:09 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "README - \345\211\257\346\234\254.md" | 28 + README.md | 630 ++- vue.js | 6009 ++++++++++++------------ 3 files changed, 3694 insertions(+), 2973 deletions(-) create mode 100644 "README - \345\211\257\346\234\254.md" diff --git "a/README - \345\211\257\346\234\254.md" "b/README - \345\211\257\346\234\254.md" new file mode 100644 index 0000000..fc3d2b2 --- /dev/null +++ "b/README - \345\211\257\346\234\254.md" @@ -0,0 +1,28 @@ + + + + + vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。 + + 说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。 + + 1.vue源码解读流程 1.new Vue 调用的是 Vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initLifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。 + + 2.双数据绑定原理是:obersve()方法判断value没有没有__ob___属性并且是不是Obersve实例化的, + value是不是Vonde实例化的,如果不是则调用Obersve 去把数据添加到观察者中,为数据添加__ob__属性, Obersve 则调用defineReactive方法,该方法是连接Dep和wacther方法的一个通道,利用Object.definpropty() 中的get和set方法 监听数据。get方法中是new Dep调用depend()。为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 然后set方法就是调用dep中的notify 方法调用wacther中的run 更新视图 + 3.vue从字符串模板怎么到真实的dom呢?是通过$mount挂载模板,就是获取到html,然后通过paseHTML这个方法转义成ast模板,他大概算法是 while(html) 如果匹配到开始标签,结束标签,或者是属性,都会截取掉html,然后收集到一个对象中,知道循环结束 html被截取完。最后变成一个ast对象,ast对象好了之后,在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数,编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过diff算法变成正真正的dom元素。 + + + +具体看我源码和流程图,这里文字就不描述这么多了,流程图是下面这中的网盘,源码是vue.js,基本每一行都有注释,然后diff待更新中 + +链接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng +提取码:1fnu + + +上面的vue.js 就是我基于vue源码中每行加有注释的vue.js, 其他文件就是我看vue.js源码的时候抽出来的vue.js 源码小demo + + + + + 作者:姚观寿 diff --git a/README.md b/README.md index fc3d2b2..9332d3d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,626 @@ + vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。 + 说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。 - vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。 - - 说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。 +vue 如何去看vue源码呢?其实mvvm源码并没有想象中那么神秘,从12年开始到至今mvvm发展已经有了十几年历史了,从以前直接操作dom的jq发展有十几年历史,但是这十几年历史发展,并没有多大的改变,思想还是那些,模块还是分为几大块: + +1. 模板转换,就是我们写的 vue 模板 或者是 react jsx 我们都可以理解是模板,然后他会经过 模板编译转换,像vue的话是进过一个方法paseHTML方法转换成ast树,里面的paseHTML用while 循环模板,然后经过正则 匹配到vue指令,还有vue的属性,事件方法等,收集到一个ast树中。 +2. 数据相应,vue是一个双数据相应的框架,底层用的是Object.defineProperty 监听和挟持数据改变,然后调用回调方法更新视图更新。双数据绑定原理是:obersve()方法判断value没有没有__ob___属性并且是不是Obersve实例化的, value是不是Vonde实例化的,如果不是则调用Obersve 去把数据添加到观察者中,为数据添加__ob__属性, Obersve 则调用defineReactive方法,该方法是连接Dep和wacther方法的一个通道,利用Object.definpropty() 中的get和set方法 监听数据。get方法中是new Dep调用depend()。为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 然后set方法就是调用dep中的notify 方法调用wacther中的run 更新视图 +3. 虚拟dom,vnode,在vue用vnode是通过 ast对象,在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数,编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过diff算法变成正真正的dom元素。 + + 4.diif算法,vue2 的diff 算法是深度优先算法遍历,然后对比算法是通过 新旧的vnode对比先对比他们的基本属性,比如key 标签等,如果是相同则通过diff算法对比然后diff算法是新旧的vnode对比,然后有四个指针索引,两个新的vnode开始指针和新的 vnode 结束指针,两个旧的vnode开始指针和旧的 vnode 结束指针。然后先判断vnode是否为空,如果为空就往中间靠拢 开始的指针++ 结束的指针 --。然后两头对比之后,在交叉对比,直到找不到相同的vnode之后如果多出的就删除,如果少的话就新增,然后对比完之后在更新到真实dom。 + + + + + + + +源码入口流程 vue源码解读流程 1.new Vue 调用的是 Vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initLifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。 + + + +``` + Vue.prototype._init = function (options) { //初始化函数 + //... 省略code + + initLifecycle(vm); //初始化生命周期 标志 + initEvents(vm); //初始化事件 + initRender(vm); // 初始化渲染 + callHook(vm, 'beforeCreate'); //触发beforeCreate钩子函数 + initInjections(vm); // resolve injections before data/props 在数据/道具之前解决注入问题 //初始化 inject + initState(vm); // //初始化状态 + initProvide(vm); // resolve provide after data/props 解决后提供数据/道具 provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性,用于组件之间通信。 + callHook(vm, 'created'); //触发created钩子函数 + + + //... 省略code + // 然后挂载模板,这里大概就是把模板转换成ast的入口 + vm.$mount(vm.$options.el); + + } +``` + + + +​ vm.$mount 进入这个挂载模板方法,判断是否有 render 函数 或者是template,如果没有则使用el.outerHTML , 实际上这里就是要拿到模板的html内容 + +``` + Vue.prototype.$mount = function (el, hydrating) { + //... 省略code + el = el && query(el); //获取dom + if (!options.render) { + if (template) { + + }else if (template.nodeType) { + template = template.innerHTML; + } else if (el) { + template = getOuterHTML(el); + } + } + + + // render 函数 也是 ast 转换 方法 + var ref = compileToFunctions( + template, //模板字符串 + { + shouldDecodeNewlines: shouldDecodeNewlines, //flase //IE在属性值中编码换行,而其他浏览器则不会 + shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, //true chrome在a[href]中编码内容 + delimiters: options.delimiters, //改变纯文本插入分隔符。修改指令的书写风格,比如默认是{{mgs}} delimiters: ['${', '}']之后变成这样 ${mgs} + comments: options.comments //当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。 + }, + this + ); + + + + + //... 省略code + //执行$mount方法 用$mount的方法把扩展挂载到dom上 + return mount.call( + this, + el, //真实的dom + hydrating //undefined + ) + + } +``` + + + + + +调用 Vue.prototype.$mount 方法之后 拿到模板之后 就会进入以下这几个方法,这几个方法用了很多函数式编程 + +``` +compileToFunctions + +createCompiler + +createCompilerCreator + +baseCompile + +parse + +parseHTML + +``` + +这里比较重点的是parseHTML 他是 while (html) { //循环html 然后 然后经过正则 匹配到vue指令,还有vue的属性,事件方法等,收集到一个ast树中。 + +``` + function parseHTML( + html, //字符串模板 + options //参数 + ) { + var stack = []; // parseHTML 节点标签堆栈 + var expectHTML = options.expectHTML; //true + var isUnaryTag$$1 = options.isUnaryTag || no; //函数匹配标签是否是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr' + var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no; //函数 //判断标签是否是 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' + var index = 0; + var last, // + lastTag; // + console.log(html) + + + + while (html) { //循环html + last = html; // + // Make sure we're not in a plaintext content element like script/style 确保我们不在像脚本/样式这样的纯文本内容元素中 + if ( + !lastTag || //lastTag 不存在 + !isPlainTextElement(lastTag) // 如果标签不是script,style,textarea + ) { + + var textEnd = html.indexOf('<'); //匹配开始标签或者结束标签的位置 + if (textEnd === 0) { //标识是开始标签 + // Comment: + if (comment.test(html)) { //匹配 开始字符串为'); //获取注释标签的结束位置 + + if (commentEnd >= 0) { //如果注释标签结束标签位置大于0,则有注释内容 + console.log(html.substring(4, commentEnd)) + if (options.shouldKeepComment) { //shouldKeepComment为真时候。获取注释标签内容 + + //截取注释标签的内容 + options.comment(html.substring(4, commentEnd)); + } + //截取字符串重新循环 while 跳出循环就是靠该函数,每次匹配到之后就截取掉字符串,知道最后一个标签被截取完没有匹配到则跳出循环 + advance(commentEnd + 3); + continue + } + } + + //这里思路是先匹配到注释节点,在匹配到这里的ie浏览器加载样式节点 + // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment + if (conditionalComment.test(html)) { //匹配开始为 匹配这样动态加ie浏览器的 字符串 + //匹配ie浏览器动态加样式结束符号 + var conditionalEnd = html.indexOf(']>'); + + if (conditionalEnd >= 0) { + //截取字符串重新循环 while 跳出循环就是靠该函数,每次匹配到之后就截取掉字符串,知道最后一个标签被截取完没有匹配到则跳出循环 + advance(conditionalEnd + 2); + continue + } + } + + // Doctype: + //匹配html的头文件 + var doctypeMatch = html.match(doctype); + if (doctypeMatch) { + //截取字符串重新循环 while 跳出循环就是靠该函数,每次匹配到之后就截取掉字符串,知道最后一个标签被截取完没有匹配到则跳出循环 + advance(doctypeMatch[0].length); + continue + } + + // End tag: + //匹配开头必需是]*> + var endTagMatch = html.match(endTag); + if (endTagMatch) { + + var curIndex = index; + //标签分隔函数 while 跳出循环就是靠该函数,每次匹配到之后就截取掉字符串,知道最后一个标签被截取完没有匹配到则跳出循环 + advance(endTagMatch[0].length); + console.log(endTagMatch) + console.log(curIndex, index) + //查找parseHTML的stack栈中与当前tagName标签名称相等的标签, + //调用options.end函数,删除当前节点的子节点中的最后一个如果是空格或者空的文本节点则删除, + //为stack出栈一个当前标签,为currentParent变量获取到当前节点的父节点 + parseEndTag( + endTagMatch[1], + curIndex, + index + ); + continue + } + + // Start tag: + //解析开始标记 标记开始标签 + // 获取开始标签的名称,属性集合,开始位置和结束位置,并且返回该对象 + var startTagMatch = parseStartTag(); + + if (startTagMatch) { + //把数组对象属性值循环变成对象,这样可以过滤相同的属性 + //为parseHTML 节点标签堆栈 插入一个桟数据 + //调用options.start 为parse函数 stack标签堆栈 添加一个标签 + handleStartTag(startTagMatch); + //匹配tag标签是pre,textarea,并且第二个参数的第一个字符是回车键 + if (shouldIgnoreFirstNewline(lastTag, html)) { + //去除回车键空格 + advance(1); + } + continue + } + } + + var text = (void 0), + rest = (void 0), + next = (void 0); + if (textEnd >= 0) { + + rest = html.slice(textEnd); //截取字符串 var textEnd = html.indexOf('<'); //匹配开始标签或者结束标签的位置 + console.log(rest) + + while ( + !endTag.test(rest) && //匹配开头必需是]*>)', 'i')); + var rest$1 = html.replace(reStackedTag, function (all, text, endTag) { + endTagLength = endTag.length; + if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') { + text = text + .replace(//g, '$1') // #7298 + .replace(//g, '$1'); + } + //匹配tag标签是pre,textarea,并且第二个参数的第一个字符是回车键 + if (shouldIgnoreFirstNewline(stackedTag, text)) { + text = text.slice(1); + } + if (options.chars) { + options.chars(text); + } + return '' + }); + index += html.length - rest$1.length; + html = rest$1; + parseEndTag(stackedTag, index - endTagLength, index); + } + + if (html === last) { + options.chars && options.chars(html); + if ("development" !== 'production' && !stack.length && options.warn) { + options.warn(("Mal-formatted tag at end of template: \"" + html + "\"")); + } + break + } + } + + - 1.vue源码解读流程 1.new Vue 调用的是 Vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initLifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。 - 2.双数据绑定原理是:obersve()方法判断value没有没有__ob___属性并且是不是Obersve实例化的, - value是不是Vonde实例化的,如果不是则调用Obersve 去把数据添加到观察者中,为数据添加__ob__属性, Obersve 则调用defineReactive方法,该方法是连接Dep和wacther方法的一个通道,利用Object.definpropty() 中的get和set方法 监听数据。get方法中是new Dep调用depend()。为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 然后set方法就是调用dep中的notify 方法调用wacther中的run 更新视图 - 3.vue从字符串模板怎么到真实的dom呢?是通过$mount挂载模板,就是获取到html,然后通过paseHTML这个方法转义成ast模板,他大概算法是 while(html) 如果匹配到开始标签,结束标签,或者是属性,都会截取掉html,然后收集到一个对象中,知道循环结束 html被截取完。最后变成一个ast对象,ast对象好了之后,在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数,编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过diff算法变成正真正的dom元素。 + + + // Clean up any remaining tags + //查找parseHTML的stack栈中与当前tagName标签名称相等的标签, + //调用options.end函数,删除当前节点的子节点中的最后一个如果是空格或者空的文本节点则删除, + //为stack出栈一个当前标签,为currentParent变量获取到当前节点的父节点 + parseEndTag(); + //while 跳出循环就是靠该函数,每次匹配到之后就截取掉字符串,知道最后一个标签被截取完没有匹配到则跳出循环 + function advance(n) { + index += n; //让索引叠加 + html = html.substring(n); //截取当前索引 和 后面的字符串。 + } + + //获取开始标签的名称,收集属性集合,开始位置和结束位置,并且返回该对象 + function parseStartTag() { + var start = html.match(startTagOpen); //匹配开始标签 匹配开头必需是< 后面可以忽略是任何字符串 ^<((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*) + console.log(start) + console.log(start[0].length) + + if (start) { + var match = { + tagName: start[1], //标签名称 + attrs: [], //标签属性集合 + start: index //标签的开始索引 + }; + //标记开始标签的位置,截取了开始标签 + advance(start[0].length); + var end, attr; + + while ( + !(end = html.match(startTagClose)) //没有到 关闭标签 > 标签 + && (attr = html.match(attribute)) //收集属性 + ) { + console.log(html) + //截取属性标签 + advance(attr[0].length); + match.attrs.push(attr); //把属性收集到一个集合 + } + if (end) { + match.unarySlash = end[1]; //如果是/>标签 则unarySlash 是/。 如果是>标签 则unarySlash 是空 + console.log(end) + + //截取掉开始标签,并且更新索引 + advance(end[0].length); + match.end = index; //开始标签的结束位置 + return match + } + } + } + + //把数组对象属性值循环变成对象,这样可以过滤相同的属性 + //为parseHTML 节点标签堆栈 插入一个桟数据 + //调用options.start 为parse函数 stack标签堆栈 添加一个标签 + function handleStartTag(match) { + /* + * match = { + tagName: start[1], //标签名称 + attrs: [], //标签属性集合 + start: index, //开始标签的开始索引 + match:index , //开始标签的 结束位置 + unarySlash:'' //如果是/>标签 则unarySlash 是/。 如果是>标签 则unarySlash 是空 + }; + * */ + + var tagName = match.tagName; //开始标签名称 + var unarySlash = match.unarySlash; //如果是/>标签 则unarySlash 是/。 如果是>标签 则unarySlash 是空 + console.log(expectHTML) + console.log('lastTag==') + console.log(lastTag) + console.log(tagName) + + if (expectHTML) { //true + + if ( + lastTag === 'p' //上一个标签是p + /* + 判断标签是否是 + 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + + 'title,tr,track' + */ + && isNonPhrasingTag(tagName) + ) { + //查找parseHTML的stack栈中与当前tagName标签名称相等的标签, + //调用options.end函数,删除当前节点的子节点中的最后一个如果是空格或者空的文本节点则删除, + //为stack出栈一个当前标签,为currentParent变量获取到当前节点的父节点 + parseEndTag(lastTag); + } + if ( + canBeLeftOpenTag$$1(tagName) && //判断标签是否是 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' + lastTag === tagName //上一个标签和现在标签相同
  • 编译成
  • 但是这种情况是不会出现的 因为浏览器解析的时候会自动补全如果是
  • 我是li标签
  • 浏览器自动解析成
  • 我是li标签
  • + ) { + //查找parseHTML的stack栈中与当前tagName标签名称相等的标签, + //调用options.end函数,删除当前节点的子节点中的最后一个如果是空格或者空的文本节点则删除, + //为stack出栈一个当前标签,为currentParent变量获取到当前节点的父节点 + parseEndTag(tagName); + } + } + + var unary = isUnaryTag$$1(tagName) || //函数匹配标签是否是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr' + !!unarySlash; //如果是/> 则为真 + + var l = match.attrs.length; + var attrs = new Array(l); //数组属性对象转换正真正的数组对象 + for (var i = 0; i < l; i++) { + var args = match.attrs[i]; //获取属性对象 + // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 + //对FF bug进行黑客攻击:https://bugzilla.mozilla.org/show_bug.cgi?id=369778 + if ( + IS_REGEX_CAPTURING_BROKEN && //这个应该是 火狐浏览器私有 标志 + args[0].indexOf('""') === -1 + ) { + if (args[3] === '') { + delete args[3]; + } + if (args[4] === '') { + delete args[4]; + } + if (args[5] === '') { + delete args[5]; + } + } + var value = args[3] || args[4] || args[5] || ''; + var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' + ? options.shouldDecodeNewlinesForHref // true chrome在a[href]中编码内容 + : options.shouldDecodeNewlines; //flase //IE在属性值中编码换行,而其他浏览器则不会 + + attrs[i] = { //把数组对象属性值循环变成对象,这样可以过滤相同的属性 + name: args[1], //属性名称 + //属性值 + value: decodeAttr(value, shouldDecodeNewlines) //替换html 中的特殊符号,转义成js解析的字符串,替换 把 <替换 < , > 替换 > , "替换 ", &替换 & , 替换\n , 替换\t + + }; + + } + + console.log('==!unary==') + console.log(!unary) + + if (!unary) { //如果不是单标签 + + // 为parseHTML 节点标签堆栈 插入一个桟数据 + stack.push({ //标签堆栈 + tag: tagName, //开始标签名称 + lowerCasedTag: tagName.toLowerCase(), //变成小写记录标签 + attrs: attrs //获取属性 + }); + //设置结束标签 + lastTag = tagName; + console.log('== parseHTML handleStartTag stack==') + console.log(stack) + + } + + + // + if (options.start) { + + //标签开始函数, 创建一个ast标签dom, 判断获取v-for属性是否存在如果有则转义 v-for指令 把for,alias,iterator1,iterator2属性添加到虚拟dom中 + //获取v-if属性,为el虚拟dom添加 v-if,v-eles,v-else-if 属性 + //获取v-once 指令属性,如果有有该属性 为虚拟dom标签 标记事件 只触发一次则销毁 + //校验属性的值,为el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性 + // 标志当前的currentParent当前的 element + //为parse函数 stack标签堆栈 添加一个标签 + options.start( + tagName, //标签名称 + attrs, //标签属性 + unary, // 如果不是单标签则为真 + match.start, //开始标签的开始位置 + match.end //开始标签的结束的位置 + ); + } + + + } + + + + //查找parseHTML的stack栈中与当前tagName标签名称相等的标签, + //调用options.end函数,删除当前节点的子节点中的最后一个如果是空格或者空的文本节点则删除, + //为stack出栈一个当前标签,为currentParent变量获取到当前节点的父节点 + function parseEndTag( + tagName, //标签名称 + start, //结束标签开始位置 + end //结束标签结束位置 + ) { + var pos, + lowerCasedTagName; + if (start == null) { //如果没有传开始位置 + start = index; //就那当前索引 + } + if (end == null) { //如果没有传结束位置 + end = index; //就那当前索引 + } + + if (tagName) { //结束标签名称 + lowerCasedTagName = tagName.toLowerCase(); //将字符串转化成小写 + } + + // Find the closest opened tag of the same type 查找最近打开的相同类型的标记 + if (tagName) { + // 获取stack堆栈最近的匹配标签 + for (pos = stack.length - 1; pos >= 0; pos--) { + //找到最近的标签相等 + if (stack[pos].lowerCasedTag === lowerCasedTagName) { + break + } + } + } else { + // If no tag name is provided, clean shop + //如果没有提供标签名称,请清理商店 + pos = 0; + } + + + if (pos >= 0) { //这里就获取到了stack堆栈的pos索引 + // Close all the open elements, up the stack 关闭所有打开的元素,向上堆栈 + console.log(pos) + + for (var i = stack.length - 1; i >= pos; i--) { + + if ("development" !== 'production' && //如果stack中找不到tagName 标签的时候就输出警告日志,找不到标签 + (i > pos || !tagName) && + options.warn + ) { + options.warn( + ("tag <" + (stack[i].tag) + "> has no matching end tag.") + ); + } + if (options.end) { + console.log(options.end) + //调用options.end函数,删除当前节点的子节点中的最后一个如果是空格或者空的文本节点则删除, + //为stack出栈一个当前标签,为currentParent变量获取到当前节点的父节点 + options.end( + stack[i].tag,//结束标签名称 + start, //结束标签开始位置 + end //结束标签结束位置 + ); + } + } + // Remove the open elements from the stack + //从堆栈中删除打开的元素 + // console.log(stack[pos].tag) + // 为parseHTML 节点标签堆栈 出桟当前匹配到的标签 + stack.length = pos; + //获取到上一个标签,就是当前节点的父节点 + lastTag = pos && stack[pos - 1].tag; + console.log(stack) + console.log(lastTag) + + + + + } else if (lowerCasedTagName === 'br') { + if (options.start) { + //标签开始函数, 创建一个ast标签dom, 判断获取v-for属性是否存在如果有则转义 v-for指令 把for,alias,iterator1,iterator2属性添加到虚拟dom中 + //获取v-if属性,为el虚拟dom添加 v-if,v-eles,v-else-if 属性 + //获取v-once 指令属性,如果有有该属性 为虚拟dom标签 标记事件 只触发一次则销毁 + //校验属性的值,为el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性 + // 标志当前的currentParent当前的 element + //为parse函数 stack标签堆栈 添加一个标签 + options.start( + tagName, + [], true, + start, + end + ); + } + } else if (lowerCasedTagName === 'p') { + if (options.start) { + //标签开始函数, 创建一个ast标签dom, 判断获取v-for属性是否存在如果有则转义 v-for指令 把for,alias,iterator1,iterator2属性添加到虚拟dom中 + //获取v-if属性,为el虚拟dom添加 v-if,v-eles,v-else-if 属性 + //获取v-once 指令属性,如果有有该属性 为虚拟dom标签 标记事件 只触发一次则销毁 + //校验属性的值,为el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性 + // 标志当前的currentParent当前的 element + //为parse函数 stack标签堆栈 添加一个标签 + options.start( + tagName, + [], false, + start, + end); + } + if (options.end) { + //删除当前节点的子节点中的最后一个如果是空格或者空的文本节点则删除, + //为stack出栈一个当前标签,为currentParent变量获取到当前节点的父节点 + options.end( + tagName, + start, + end + ); + } + } + console.log(lastTag) + + } + } +``` + + + +一些匹配模板正则 + +``` + var onRE = /^@|^v-on:/;//判断是否是 @或者v-on:属性开头的 + var dirRE = /^v-|^@|^:/; //判断是否是 v-或者@或者: 属性开头的 + var forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/; //匹配 含有 字符串 in 字符串 或者 字符串 of 字符串 + var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/; //匹配上, 但是属于两边是 [{ , 点 , }] 所以匹配上 ,+字符串 + var stripParensRE = /^\(|\)$/g; //匹配括号 () + + var argRE = /:(.*)$/; //匹配字符串是否含有: + var bindRE = /^:|^v-bind:/; //开始匹配是 :或者是v-bind + var modifierRE = /\.[^.]+/g; // 匹配以点开头的分组 不属于点 data.object.info.age 匹配到 ['.object','.info' , '.age'] + + var decodeHTMLCached = cached(he.decode); //获取 真是dom的textContent文本 +``` + + + + + + + + + + @@ -18,11 +628,11 @@ 链接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng 提取码:1fnu - + 上面的vue.js 就是我基于vue源码中每行加有注释的vue.js, 其他文件就是我看vue.js源码的时候抽出来的vue.js 源码小demo - - + + 作者:姚观寿 diff --git a/vue.js b/vue.js index 4940753..8b44868 100644 --- a/vue.js +++ b/vue.js @@ -15,13 +15,13 @@ 'use strict'; /* */ -//Object.freeze()阻止修改现有属性的特性和值,并阻止添加新属性。 + //Object.freeze()阻止修改现有属性的特性和值,并阻止添加新属性。 var emptyObject = Object.freeze({}); -// these helpers produces better vm code in JS engines due to their -// explicitness and function inlining // these helpers produces better vm code in JS engines due to their -// explicitness and function inlining + // explicitness and function inlining + // these helpers produces better vm code in JS engines due to their + // explicitness and function inlining //判断数据 是否是undefined或者null function isUndef(v) { return v === undefined || v === null @@ -71,7 +71,7 @@ /** * Get the raw type string of a value e.g. [object Object] */ - //获取toString 简写 + //获取toString 简写 var _toString = Object.prototype.toString; function toRawType(value) { @@ -117,8 +117,8 @@ return val == null ? '' : typeof val === 'object' - ? JSON.stringify(val, null, 2) - : String(val) + ? JSON.stringify(val, null, 2) + : String(val) } /** @@ -139,7 +139,7 @@ * 并且传进一个key值取值,这里用到策略者模式 */ function makeMap(str, - expectsLowerCase) { + expectsLowerCase) { var map = Object.create(null); //创建一个新的对象 var list = str.split(','); //按字符串,分割 for (var i = 0; i < list.length; i++) { @@ -147,11 +147,11 @@ } return expectsLowerCase ? function (val) { - return map[val.toLowerCase()]; - } //返回一个柯里化函数 toLowerCase转换成小写 + return map[val.toLowerCase()]; + } //返回一个柯里化函数 toLowerCase转换成小写 : function (val) { - return map[val]; - } //返回一个柯里化函数 并且把map中添加一个 属性建 + return map[val]; + } //返回一个柯里化函数 并且把map中添加一个 属性建 } /** @@ -290,8 +290,8 @@ var l = arguments.length; return l ? l > 1 - ? fn.apply(ctx, arguments) - : fn.call(ctx, a) + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) : fn.call(ctx) } @@ -304,7 +304,7 @@ return fn.bind(ctx) } -//bing 改变this上下文 + //bing 改变this上下文 var bind = Function.prototype.bind ? nativeBind : polyfillBind; @@ -409,11 +409,11 @@ */ function genStaticKeys(modules) { return modules.reduce( - function (keys, m) { - //累加staticKeys的值变成数组 - return keys.concat(m.staticKeys || []) - }, - [] + function (keys, m) { + //累加staticKeys的值变成数组 + return keys.concat(m.staticKeys || []) + }, + [] ).join(',') //转换成字符串 } @@ -435,16 +435,16 @@ if (isArrayA && isArrayB) { //如果a和b都是数组 // every 条件判断 return a.length === b.length && a.every(function (e, i) { //如果a长度和b长度一样的时候 - return looseEqual(e, b[i]) //递归 - }) + return looseEqual(e, b[i]) //递归 + }) } else if (!isArrayA && !isArrayB) { //或者a和b都不是数组 var keysA = Object.keys(a); // 获取到a的key值 变成一个数组 var keysB = Object.keys(b); // 获取到b的key值 变成一个数组 //他们的对象key值长度是一样的时候 则加载every 条件函数 return keysA.length === keysB.length && keysA.every(function (key) { - //递归 a和b的值 - return looseEqual(a[key], b[key]) - }) + //递归 a和b的值 + return looseEqual(a[key], b[key]) + }) } else { //如果不是对象跳槽循环 /* istanbul ignore next */ @@ -463,8 +463,8 @@ } } -// 判断 arr数组中的数组 是否和val相等。 -// 或者 arr数组中的对象,或者对象数组 是否和val 相等 + // 判断 arr数组中的数组 是否和val相等。 + // 或者 arr数组中的对象,或者对象数组 是否和val 相等 function looseIndexOf(arr, val) { for (var i = 0; i < arr.length; i++) { if (looseEqual(arr[i], val)) { @@ -488,7 +488,7 @@ } } -//ssr标记属性 + //ssr标记属性 var SSR_ATTR = 'data-server-rendered'; var ASSET_TYPES = [ @@ -682,18 +682,18 @@ /* */ -// can we use __proto__? + // can we use __proto__? var hasProto = '__proto__' in {}; -// Browser environment sniffing -//判断设备和浏览器 + // Browser environment sniffing + //判断设备和浏览器 var inBrowser = typeof window !== 'undefined'; -//如果不是浏览器 + //如果不是浏览器 var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; //weex 环境 一个 vue做app包的框架 var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();//weex 环境 一个 vue做app包的框架 -//window.navigator.userAgent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,通过这个属性来判断浏览器类型 + //window.navigator.userAgent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,通过这个属性来判断浏览器类型 var UA = inBrowser && window.navigator.userAgent.toLowerCase(); //获取浏览器 var isIE = UA && /msie|trident/.test(UA); //ie var isIE9 = UA && UA.indexOf('msie 9.0') > 0; //ie9 @@ -702,10 +702,10 @@ var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); //ios var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; //谷歌浏览器 -// Firefox has a "watch" function on Object.prototype... + // Firefox has a "watch" function on Object.prototype... var nativeWatch = ({}).watch; -//兼容火狐浏览器写法 + //兼容火狐浏览器写法 var supportsPassive = false; if (inBrowser) { try { @@ -721,11 +721,11 @@ } } -// this needs to be lazy-evaled because vue may be required before -// vue-server-renderer can set VUE_ENV + // this needs to be lazy-evaled because vue may be required before + // vue-server-renderer can set VUE_ENV //vue 服务器渲染 可以设置 VUE_ENV var _isServer; -//判断是不是node 服务器环境 + //判断是不是node 服务器环境 var isServerRendering = function () { if (_isServer === undefined) { /* istanbul ignore if */ @@ -743,7 +743,7 @@ return _isServer }; -// detect devtools + // detect devtools //检测开发者工具。 var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; @@ -762,7 +762,7 @@ return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) } -//判断是否支持Symbol 数据类型 + //判断是否支持Symbol 数据类型 var hasSymbol = //Symbol es6新出来的一种数据类型,类似于string类型,声明唯一的数据值 typeof Symbol !== 'undefined' && isNative(Symbol) && @@ -772,8 +772,8 @@ var _Set; /* istanbul ignore if */ // $flow-disable-line -//ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 -// Set 本身是一个构造函数,用来生成 Set 数据结构。 + //ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 + // Set 本身是一个构造函数,用来生成 Set 数据结构。 //判断是否有set这个方法 if (typeof Set !== 'undefined' && isNative(Set)) { // use native Set when available. @@ -811,7 +811,7 @@ var hasConsole = typeof console !== 'undefined'; var classifyRE = /(?:^|[-_])(\w)/g; -//非捕获 匹配不分组 。 就是可以包含,但是不匹配上 + //非捕获 匹配不分组 。 就是可以包含,但是不匹配上 //过滤掉class中的 -_ 符号 并且把字母开头的改成大写 var classify = function (str) { return str.replace(classifyRE, @@ -852,8 +852,8 @@ if (hasConsole && (!config.silent)) { // console.warn("[Vue tip]: " + msg + ( - vm ? generateComponentTrace(vm) : '' - )); + vm ? generateComponentTrace(vm) : '' + )); } }; @@ -964,19 +964,19 @@ vm = vm.$parent; } return '\n\nfound in\n\n' + tree - .map(function (vm, i) { - //如果i是0 则输出 ‘---->’ - //如果i 不是0的时候输出组件名称 - return ("" + (i === 0 ? - '---> ' : repeat(' ', 5 + i * 2)) + - ( - Array.isArray(vm) ? - ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") - : formatComponentName(vm) - ) - ); - }) - .join('\n') + .map(function (vm, i) { + //如果i是0 则输出 ‘---->’ + //如果i 不是0的时候输出组件名称 + return ("" + (i === 0 ? + '---> ' : repeat(' ', 5 + i * 2)) + + ( + Array.isArray(vm) ? + ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") + : formatComponentName(vm) + ) + ); + }) + .join('\n') } else { //如果没有父组件则输出一个组件名称 return ("\n\n(found in " + (formatComponentName(vm)) + ")") @@ -998,13 +998,13 @@ * directives subscribing to it.订阅它的指令。 * */ - //主题对象Dep构造函数 主要用于添加发布事件后,用户更新数据的 响应式原理之一函数 + //主题对象Dep构造函数 主要用于添加发布事件后,用户更新数据的 响应式原理之一函数 var Dep = function Dep() { - //uid 初始化为0 - this.id = uid++; - /* 用来存放Watcher对象的数组 */ - this.subs = []; - }; + //uid 初始化为0 + this.id = uid++; + /* 用来存放Watcher对象的数组 */ + this.subs = []; + }; Dep.prototype.addSub = function addSub(sub) { /* 在subs中添加一个Watcher对象 */ @@ -1034,12 +1034,12 @@ } }; -// the current target watcher being evaluated. -// this is globally unique because there could be only one -// watcher being evaluated at any time. -//当前正在评估的目标监视程序。 -//这在全球是独一无二的,因为只有一个 -//观察者在任何时候都被评估。 + // the current target watcher being evaluated. + // this is globally unique because there could be only one + // watcher being evaluated at any time. + //当前正在评估的目标监视程序。 + //这在全球是独一无二的,因为只有一个 + //观察者在任何时候都被评估。 Dep.target = null; var targetStack = []; @@ -1064,14 +1064,14 @@ * */ var VNode = function VNode( - tag, /*当前节点的标签名*/ - data, /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ - children, //子节点 - text, //文本 - elm, /*当前节点的dom */ - context, /*编译作用域*/ - componentOptions, /*组件的option选项*/ - asyncFactory/*异步工厂*/) { + tag, /*当前节点的标签名*/ + data, /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ + children, //子节点 + text, //文本 + elm, /*当前节点的dom */ + context, /*编译作用域*/ + componentOptions, /*组件的option选项*/ + asyncFactory/*异步工厂*/) { /*当前节点的标签名*/ this.tag = tag; @@ -1134,9 +1134,9 @@ this.isAsyncPlaceholder = false; }; //当且仅当该属性描述符的类型可以被改变并且该属性可以从对应对象中删除。默认为 false - var prototypeAccessors = {child: {configurable: true}}; + var prototypeAccessors = { child: { configurable: true } }; -// DEPRECATED: alias for componentInstance for backwards compat. + // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ prototypeAccessors.child.get = function () { @@ -1175,14 +1175,14 @@ ) } -// optimized shallow clone -// used for static nodes and slot nodes because they may be reused across -// multiple renders, cloning them avoids errors when DOM manipulations rely -// on their elm reference. -//优化浅克隆 -//用于静态节点和时隙节点,因为它们可以被重用。 -//多重渲染,克隆它们避免DOM操作依赖时的错误 -//他们的榆树参考。 + // optimized shallow clone + // used for static nodes and slot nodes because they may be reused across + // multiple renders, cloning them avoids errors when DOM manipulations rely + // on their elm reference. + //优化浅克隆 + //用于静态节点和时隙节点,因为它们可以被重用。 + //多重渲染,克隆它们避免DOM操作依赖时的错误 + //他们的榆树参考。 //克隆节点 把节点变成静态节点 function cloneVNode(vnode, deep) { @@ -1390,7 +1390,7 @@ } }; -// helpers + // helpers /** * Augment an target Object or Array by intercepting @@ -1481,10 +1481,10 @@ * 添加观察者 get set方法 */ function defineReactive(obj, //对象 - key,//对象的key - val, //监听的数据 返回的数据 - customSetter, // 日志函数 - shallow //是否要添加__ob__ 属性 + key,//对象的key + val, //监听的数据 返回的数据 + customSetter, // 日志函数 + shallow //是否要添加__ob__ 属性 ) { //实例化一个主题对象,对象中有空的观察者列表 var dep = new Dep(); @@ -1690,7 +1690,7 @@ *值为最终值。 */ - //选择策略 + //选择策略 var strats = config.optionMergeStrategies; /** @@ -1739,77 +1739,77 @@ * mergeDataOrFn递归合并数据 深度拷贝。如果vm不存在,并且childVal不存在就返回parentVal。如果vm不存在并且parentVal不存在则返回childVal。如果vm不存在parentVal和childVal都存在则返回mergedDataFn。如果vm存在则返回 mergedInstanceDataFn函数 */ function mergeDataOrFn( - parentVal, - childVal, - vm - ) { - //vm不存在的时候 - if (!vm) { - // in a Vue.extend merge, both should be functions Vue。扩展合并,两者都应该是函数 - if (!childVal) { - return parentVal - } - if (!parentVal) { - return childVal - } - // when parentVal & childVal are both present, - // we need to return a function that returns the - // merged result of both functions... no need to - // check if parentVal is a function here because - // it has to be a function to pass previous merges. - //当父母和孩子都在场时, - //我们需要返回一个函数,该函数返回 - //两个函数的合并结果…不需要 - //检查parentVal是否是一个函数,因为 - //它必须是一个函数来传递以前的合并。 - return function mergedDataFn() { - //如果childVal,parentVal是函数 先改变this - return mergeData( - typeof childVal === 'function' ? childVal.call(this, this) : childVal, - typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal - ) - } - } else { - //如果vm 存在 则是合并vm的数据 - return function mergedInstanceDataFn() { - // instance merge - var instanceData = typeof childVal === 'function' - ? childVal.call(vm, vm) - : childVal; - - var defaultData = typeof parentVal === 'function' - ? parentVal.call(vm, vm) - : parentVal; - - if (instanceData) { - return mergeData(instanceData, defaultData) - } else { - return defaultData - } - } - } + parentVal, + childVal, + vm + ) { + //vm不存在的时候 + if (!vm) { + // in a Vue.extend merge, both should be functions Vue。扩展合并,两者都应该是函数 + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + //当父母和孩子都在场时, + //我们需要返回一个函数,该函数返回 + //两个函数的合并结果…不需要 + //检查parentVal是否是一个函数,因为 + //它必须是一个函数来传递以前的合并。 + return function mergedDataFn() { + //如果childVal,parentVal是函数 先改变this + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + //如果vm 存在 则是合并vm的数据 + return function mergedInstanceDataFn() { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal; + + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal; + + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } } strats.data = function ( - parentVal, - childVal, - vm - ) { - if (!vm) { - if (childVal && typeof childVal !== 'function') { - "development" !== 'production' && warn( - 'The "data" option should be a function ' + - 'that returns a per-instance value in component ' + - 'definitions.', - vm - ); - - return parentVal - } - return mergeDataOrFn(parentVal, childVal) - } + parentVal, + childVal, + vm + ) { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + "development" !== 'production' && warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); - return mergeDataOrFn(parentVal, childVal, vm) + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) }; /** @@ -1820,17 +1820,17 @@ * 如果不是数组把childVal变成数组在返回出去 */ function mergeHook( - parentVal, - childVal - ) { - return childVal ? (parentVal ? - parentVal.concat(childVal) : - (Array.isArray(childVal) ? - childVal : - [childVal] - ) - ):parentVal - } + parentVal, + childVal + ) { + return childVal ? (parentVal ? + parentVal.concat(childVal) : + (Array.isArray(childVal) ? + childVal : + [childVal] + ) + ) : parentVal + } LIFECYCLE_HOOKS.forEach(function (hook) { strats[hook] = mergeHook; @@ -1851,11 +1851,11 @@ * 如果childVal 存在,则用浅拷贝吧 childVal 合并到res中,返回res对象 */ function mergeAssets( - parentVal, - childVal, - vm, - key - ) { + parentVal, + childVal, + vm, + key + ) { var res = Object.create(parentVal || null); if (childVal) { "development" !== 'production' && assertObjectType(key, childVal, vm); @@ -1881,10 +1881,10 @@ * 循环childVal。获取到子节点childVal的key如果在父亲节点上面有,则先获取到父亲节点的值,如果父亲节点的上没有值得获取子节点的值。 变成数组存在ret对象中。 */ strats.watch = function ( - parentVal, //父节点值 - childVal, //子节点值 - vm, //vm vue实例化的对象 - key) { // key值 + parentVal, //父节点值 + childVal, //子节点值 + vm, //vm vue实例化的对象 + key) { // key值 // work around Firefox's Object.prototype.watch... 在Firefox的对象周围工作。原型 //// Firefox has a "watch" function on Object.prototype... //var nativeWatch = ({}).watch; @@ -1914,9 +1914,9 @@ parent = [parent]; // } ret[key$1] = parent ? parent.concat(child) - : Array.isArray(child) ? - child : - [child]; + : Array.isArray(child) ? + child : + [child]; } return ret }; @@ -1926,29 +1926,29 @@ */ strats.props = strats.methods = - strats.inject = - strats.computed = function ( - parentVal, - childVal, - vm, - key - ) { - if (childVal && "development" !== 'production') { - //判断是否是对象 - assertObjectType(key, childVal, vm); - } - if (!parentVal) { - return childVal - } - var ret = Object.create(null); - //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 - extend(ret, parentVal); - if (childVal) { - //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 - extend(ret, childVal); - } - return ret - }; + strats.inject = + strats.computed = function ( + parentVal, + childVal, + vm, + key + ) { + if (childVal && "development" !== 'production') { + //判断是否是对象 + assertObjectType(key, childVal, vm); + } + if (!parentVal) { + return childVal + } + var ret = Object.create(null); + //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 + extend(ret, parentVal); + if (childVal) { + //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 + extend(ret, childVal); + } + return ret + }; strats.provide = mergeDataOrFn; /** @@ -1965,7 +1965,7 @@ */ function checkComponents(options) { for (var key in options.components) { - // 验证组件名称 必须是大小写,并且是-横杆 + // 验证组件名称 必须是大小写,并且是-横杆 validateComponentName(key); } } @@ -2015,7 +2015,7 @@ //把含有横岗的字符串 变成驼峰写法 name = camelize(val); - res[name] = {type: null}; + res[name] = { type: null }; } else { //当使用数组语法时,道具必须是字符串。 如果是props 是数组必须是字符串 warn('props must be strings when using array syntax.'); @@ -2027,7 +2027,7 @@ name = camelize(key); //把含有横岗的字符串 变成驼峰写法 res[name] = isPlainObject(val) //判断值是不是对象 ? val - : {type: val}; + : { type: val }; } } else { //如果不是对象和数组则警告 @@ -2054,8 +2054,8 @@ * * */ function normalizeInject(options, vm) { - // provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。 - // 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。 + // provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。 + // 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。 var inject = options.inject; if (!inject) { @@ -2064,16 +2064,16 @@ var normalized = options.inject = {}; if (Array.isArray(inject)) { //如果是数组 for (var i = 0; i < inject.length; i++) { - // * 将数组转化成对象 比如 [1,2,3]转化成 - // * normalized[1]={from: 1} - // * normalized[2]={from: 2} - // * normalized[3]={from: 3} - normalized[inject[i]] = {from: inject[i]}; + // * 将数组转化成对象 比如 [1,2,3]转化成 + // * normalized[1]={from: 1} + // * normalized[2]={from: 2} + // * normalized[3]={from: 3} + normalized[inject[i]] = { from: inject[i] }; } } else if (isPlainObject(inject)) { //如果是对象 for (var key in inject) { var val = inject[key]; - normalized[key] = isPlainObject(val) ? extend({from: key}, val) : {from: val}; + normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val }; } } else { warn( @@ -2104,7 +2104,7 @@ if (typeof def === 'function') { //如果是函数 //为该函数添加一个对象和值 - dirs[key] = {bind: def, update: def}; + dirs[key] = { bind: def, update: def }; } } } @@ -2129,8 +2129,8 @@ * 用于实例化和继承的核心实用程序。 */ function mergeOptions(parent, //父值 - child, //子值 优选取子值 - vm) { + child, //子值 优选取子值 + vm) { { //检验子组件 @@ -2147,7 +2147,7 @@ // 将数组转化成对象 比如 [1,2,3]转化成 normalizeInject(child, vm); - // * normalizeDirectives获取到指令对象值。循环对象指令的值,如果是函数则把它变成dirs[key] = {bind: def, update: def} 这种形式 + // * normalizeDirectives获取到指令对象值。循环对象指令的值,如果是函数则把它变成dirs[key] = {bind: def, update: def} 这种形式 normalizeDirectives(child); //子组件是否有需要合并的对象继承 方式 @@ -2181,8 +2181,8 @@ // 就返回父节点parentVal,如果有子节点childVal就返回子节点childVal。 function mergeField(key) { //defaultStrat 获取子值还是父组的值 - var strat =strats[key] || // - defaultStrat; //* 如果没有子节点就返回父节点,如果有子节点就返回子节点 + var strat = strats[key] || // + defaultStrat; //* 如果没有子节点就返回父节点,如果有子节点就返回子节点 //获取子值还是父组的值 options[key] = strat(parent[key], child[key], vm, key); } @@ -2199,9 +2199,9 @@ * */ function resolveAsset(options, //参数 - type, // 类型:directives , filters ,components - id, // 指令的key 属性 - warnMissing //警告的信息 true + type, // 类型:directives , filters ,components + id, // 指令的key 属性 + warnMissing //警告的信息 true ) { console.log('==resolveAsset==') console.log(options) @@ -2279,71 +2279,71 @@ * */ function validateProp( - key, //key - propOptions, //原始props 参数 - propsData, //转义过的组件props数据 - vm // VueComponent 组件构造函数 - ) { //vm this属性 - - - var prop = propOptions[key]; //获取组件定义的props 属性 - var absent = !hasOwn(propsData, key); // 如果该为假的那么可能 a-b 这样的key才能获取到值 - var value = propsData[key]; // 获取值 - // boolean casting - //Boolean 传一个布尔值 但是 一般是函数或者数组函数才有意义,而且是函数声明的函数并不是 函数表达式prop.type 也需要是函数 - //返回的是相同的索引 判断 属性类型定义的是否是Boolean - var booleanIndex = getTypeIndex(Boolean, prop.type); - if (booleanIndex > -1) { //如果是boolean值 - - if (absent && !hasOwn(prop, 'default')) { //如果key 不是propsData 实例化,或者 没有定义default 默认值的时候 设置value 为false - value = false; - } else if ( - value === '' //如果value 是空 - || value === hyphenate(key) //或者key转出 - 形式和value 相等的时候 - ) { // - // only cast empty string / same name to boolean if 仅将空字符串/相同名称转换为boolean if - // boolean has higher priority 获取到相同的 - //判断prop.type 的类型是否是string字符串类型 - var stringIndex = getTypeIndex(String, prop.type); - - if ( - stringIndex < 0 || //如果匹配不到字符串 - booleanIndex < stringIndex) { //或者布尔值索引小于字符串 索引的时候 - value = true; - } - } - } - // check default value 检查默认值 - if (value === undefined) { //如果没有值 value 也不是boolean, 也不是string的时候 - // 有可能是 函数 - value = getPropDefaultValue(vm, prop, key); - // since the default value is a fresh copy, 由于默认值是一个新的副本, - // make sure to observe it. 一定要遵守。 - var prevShouldObserve = shouldObserve; - toggleObserving(true); - console.log('===value===') - console.log(value); - //为 value添加 value.__ob__ 属性,把value添加到观察者中 - observe(value); - toggleObserving(prevShouldObserve); - } - { - console.log( prop, - key, - value, - vm, - absent) - - //检查prop 是否合格 - assertProp( - prop, //属性的type值 - key, //props属性中的key - value, //view 属性的值 - vm, // VueComponent 组件构造函数 - absent //false - ); - } - return value + key, //key + propOptions, //原始props 参数 + propsData, //转义过的组件props数据 + vm // VueComponent 组件构造函数 + ) { //vm this属性 + + + var prop = propOptions[key]; //获取组件定义的props 属性 + var absent = !hasOwn(propsData, key); // 如果该为假的那么可能 a-b 这样的key才能获取到值 + var value = propsData[key]; // 获取值 + // boolean casting + //Boolean 传一个布尔值 但是 一般是函数或者数组函数才有意义,而且是函数声明的函数并不是 函数表达式prop.type 也需要是函数 + //返回的是相同的索引 判断 属性类型定义的是否是Boolean + var booleanIndex = getTypeIndex(Boolean, prop.type); + if (booleanIndex > -1) { //如果是boolean值 + + if (absent && !hasOwn(prop, 'default')) { //如果key 不是propsData 实例化,或者 没有定义default 默认值的时候 设置value 为false + value = false; + } else if ( + value === '' //如果value 是空 + || value === hyphenate(key) //或者key转出 - 形式和value 相等的时候 + ) { // + // only cast empty string / same name to boolean if 仅将空字符串/相同名称转换为boolean if + // boolean has higher priority 获取到相同的 + //判断prop.type 的类型是否是string字符串类型 + var stringIndex = getTypeIndex(String, prop.type); + + if ( + stringIndex < 0 || //如果匹配不到字符串 + booleanIndex < stringIndex) { //或者布尔值索引小于字符串 索引的时候 + value = true; + } + } + } + // check default value 检查默认值 + if (value === undefined) { //如果没有值 value 也不是boolean, 也不是string的时候 + // 有可能是 函数 + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, 由于默认值是一个新的副本, + // make sure to observe it. 一定要遵守。 + var prevShouldObserve = shouldObserve; + toggleObserving(true); + console.log('===value===') + console.log(value); + //为 value添加 value.__ob__ 属性,把value添加到观察者中 + observe(value); + toggleObserving(prevShouldObserve); + } + { + console.log(prop, + key, + value, + vm, + absent) + + //检查prop 是否合格 + assertProp( + prop, //属性的type值 + key, //props属性中的key + value, //view 属性的值 + vm, // VueComponent 组件构造函数 + absent //false + ); + } + return value } /** @@ -2404,60 +2404,60 @@ function assertProp( - prop, //属性的type值 - name, //props属性中的key - value, //view 属性的值 - vm, //组件构造函数 - absent//false - ) { - //必须有required 和 absent - if (prop.required && absent) { - warn( - 'Missing required prop: "' + name + '"', - vm - ); - return - } - //如果vual 为空 或者 不是必填项 则不执行下面代码 - if (value == null && !prop.required) { - return - } - //类型 - var type = prop.type; - - //如果类型为真 或者类型 不存在 - var valid = !type || type === true; - - var expectedTypes = []; - - if (type) { //如果type存在 - if (!Array.isArray(type)) { //如果不是数组 - type = [type]; //再包裹成数组 - } - for (var i = 0; i < type.length && !valid; i++) { - var assertedType = assertType(value, type[i]); - expectedTypes.push(assertedType.expectedType || ''); - valid = assertedType.valid; - } - } - if (!valid) { - warn( - "Invalid prop: type check failed for prop \"" + name + "\"." + - " Expected " + (expectedTypes.map(capitalize).join(', ')) + - ", got " + (toRawType(value)) + ".", - vm - ); - return - } - var validator = prop.validator; - if (validator) { - if (!validator(value)) { - warn( - 'Invalid prop: custom validator check failed for prop "' + name + '".', - vm - ); - } - } + prop, //属性的type值 + name, //props属性中的key + value, //view 属性的值 + vm, //组件构造函数 + absent//false + ) { + //必须有required 和 absent + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + //如果vual 为空 或者 不是必填项 则不执行下面代码 + if (value == null && !prop.required) { + return + } + //类型 + var type = prop.type; + + //如果类型为真 或者类型 不存在 + var valid = !type || type === true; + + var expectedTypes = []; + + if (type) { //如果type存在 + if (!Array.isArray(type)) { //如果不是数组 + type = [type]; //再包裹成数组 + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType || ''); + valid = assertedType.valid; + } + } + if (!valid) { + warn( + "Invalid prop: type check failed for prop \"" + name + "\"." + + " Expected " + (expectedTypes.map(capitalize).join(', ')) + + ", got " + (toRawType(value)) + ".", + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } } //检测数据类型 是否是String|Number|Boolean|Function|Symbol 其中的一个数据类型 @@ -2626,24 +2626,24 @@ } } -// Here we have async deferring wrappers using both microtasks and (macro) tasks. 在这里,我们使用了微任务和宏任务的异步包装器。 -// In < 2.4 we used microtasks everywhere, but there are some scenarios where 在< 2.4中,我们到处使用微任务,但也有一些场景。 -// microtasks have too high a priority and fire in between supposedly 微任务优先级太高,据称介于两者之间。 -// sequential events (e.g. #4521, #6690) or even between bubbling of the same 序贯事件(例如α4521,α6690),甚至在同一气泡之间 -// event (#6566). However, using (macro) tasks everywhere also has subtle problems 事件(α6566)。然而,到处使用(宏)任务也有微妙的问题。 -// when state is changed right before repaint (e.g. #6813, out-in transitions). 当状态在重新绘制之前被正确改变(例如,α6813,在过渡中出现)。 -// Here we use microtask by default, but expose a way to force (macro) task when 这里,我们默认使用微任务,但是暴露一种方法来强制(宏)任务 -// needed (e.g. in event handlers attached by v-on). 需要的(例如在事件处理程序中附加的V-on)。 + // Here we have async deferring wrappers using both microtasks and (macro) tasks. 在这里,我们使用了微任务和宏任务的异步包装器。 + // In < 2.4 we used microtasks everywhere, but there are some scenarios where 在< 2.4中,我们到处使用微任务,但也有一些场景。 + // microtasks have too high a priority and fire in between supposedly 微任务优先级太高,据称介于两者之间。 + // sequential events (e.g. #4521, #6690) or even between bubbling of the same 序贯事件(例如α4521,α6690),甚至在同一气泡之间 + // event (#6566). However, using (macro) tasks everywhere also has subtle problems 事件(α6566)。然而,到处使用(宏)任务也有微妙的问题。 + // when state is changed right before repaint (e.g. #6813, out-in transitions). 当状态在重新绘制之前被正确改变(例如,α6813,在过渡中出现)。 + // Here we use microtask by default, but expose a way to force (macro) task when 这里,我们默认使用微任务,但是暴露一种方法来强制(宏)任务 + // needed (e.g. in event handlers attached by v-on). 需要的(例如在事件处理程序中附加的V-on)。 var microTimerFunc; //微计时器功能 var macroTimerFunc; //宏计时器功能 var useMacroTask = false; //使用宏任务 -// Determine (macro) task defer implementation. 确定(宏)任务延迟实现。 -// Technically setImmediate should be the ideal choice, but it's only available 技术上应该是理想的选择,但它是唯一可用的。 -// in IE. The only polyfill that consistently queues the callback after all DOM 在IE.中,唯一的填充在所有DOM之后始终排队回叫。 -// events triggered in the same loop is by using MessageChannel. 在同一循环中触发的事件是通过使用消息通道。 + // Determine (macro) task defer implementation. 确定(宏)任务延迟实现。 + // Technically setImmediate should be the ideal choice, but it's only available 技术上应该是理想的选择,但它是唯一可用的。 + // in IE. The only polyfill that consistently queues the callback after all DOM 在IE.中,唯一的填充在所有DOM之后始终排队回叫。 + // events triggered in the same loop is by using MessageChannel. 在同一循环中触发的事件是通过使用消息通道。 /* istanbul ignore if */ //判断setImmediate 是否存在,如果存在则判断下是是否是系统内置函数 if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { @@ -2653,10 +2653,10 @@ setImmediate(flushCallbacks); }; } else if (typeof MessageChannel !== 'undefined' && ( - isNative(MessageChannel) || - // PhantomJS - MessageChannel.toString() === '[object MessageChannelConstructor]' - )) { + isNative(MessageChannel) || + // PhantomJS + MessageChannel.toString() === '[object MessageChannelConstructor]' + )) { //如果有 消息体 内置函数则实例化 var channel = new MessageChannel(); //获取端口2 @@ -2679,7 +2679,7 @@ }; } -// Determine microtask defer implementation. + // Determine microtask defer implementation. //确定微任务延迟执行。 /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { @@ -2712,11 +2712,11 @@ function withMacroTask(fn) { //宏任务 return fn._withTask || (fn._withTask = function () { - useMacroTask = true; - var res = fn.apply(null, arguments); - useMacroTask = false; - return res - }) + useMacroTask = true; + var res = fn.apply(null, arguments); + useMacroTask = false; + return res + }) } //为callbacks 收集队列cb 函数 并且根据 pending 状态是否要触发callbacks 队列函数 @@ -3049,12 +3049,12 @@ //更新事件 并且为新的值 添加函数 旧的值删除函数等功能 function updateListeners( - on, //新的事件 - oldOn, //旧的事件 - add, //添加事件函数 - remove$$1, //删除事件函数 - vm//vue 实例化对象 - ) { + on, //新的事件 + oldOn, //旧的事件 + add, //添加事件函数 + remove$$1, //删除事件函数 + vm//vue 实例化对象 + ) { var name, def, cur, old, event; for (name in on) { // 遍历on @@ -3088,7 +3088,7 @@ event.once, //是否只触发一次的状态 event.capture, // 事件俘获或是冒泡行为 event.passive, // 检测事件修饰符 是否是 '&' - event.params //事件参数 + event.params //事件参数 ); } else if (cur !== old) { @@ -3163,90 +3163,90 @@ * * */ function extractPropsFromVNodeData( - data, //tag标签属性数据 - Ctor, //组件构造函数VueComponent - tag //tag标签名称 - ) { - // we are only extracting raw values here. - // validation and default values are handled in the child - // component itself. - //我们只是在这里提取原始值。 - //验证和默认值在孩子中被处理 - //组件本身。 - - //获取Ctor 参数中的 props - var propOptions = Ctor.options.props; //获取组件的props属性 - console.log(Ctor.options) - - //如果propOptions 属性是空或者不存在 这不执行下面代码 - if (isUndef(propOptions)) { - return - } - - - var res = {}; - var attrs = data.attrs; - var props = data.props; - - //如果data中的属性attrs或者props 属性 数据存在 - if (isDef(attrs) || isDef(props)) { - //遍历propOptions props属性中的值 - for (var key in propOptions) { - - //altKey获取到一个函数,该函数功能是把 abCd 驼峰字母改写成 ab-c 如果是 aB cd 则是 ab cd - //大写字母,加完减号又转成小写了 比如把驼峰 aBc 变成了 a-bc - //匹配大写字母并且两面不是空白的 替换成 '-' + '字母' 在全部转换成小写 - - var altKey = hyphenate(key); - - - { - //把key 转换成小写 - var keyInLowerCase = key.toLowerCase(); - //如果他们key不相同 并且 属性attrs存在 并且keyInLowerCase 属性存在 attrs对象中 - if ( - key !== keyInLowerCase && - attrs && hasOwn(attrs, keyInLowerCase) - ) { - //输出一个警告信息 - tip( - "Prop \"" + keyInLowerCase + "\" is passed to component " + - (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + - " \"" + key + "\". " + - "Note that HTML attributes are case-insensitive and camelCased " + - "props need to use their kebab-case equivalents when using in-DOM " + - "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." - ); - } - } - //检查属性 - checkProp( - res, //空对象 - props, //props 属性 - key, //propOptions 的原始key - altKey, //转换后的 横杆key - true - ) || - checkProp( - res, - attrs, - key, - altKey, - false - ); - } - } - return res + data, //tag标签属性数据 + Ctor, //组件构造函数VueComponent + tag //tag标签名称 + ) { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + //我们只是在这里提取原始值。 + //验证和默认值在孩子中被处理 + //组件本身。 + + //获取Ctor 参数中的 props + var propOptions = Ctor.options.props; //获取组件的props属性 + console.log(Ctor.options) + + //如果propOptions 属性是空或者不存在 这不执行下面代码 + if (isUndef(propOptions)) { + return + } + + + var res = {}; + var attrs = data.attrs; + var props = data.props; + + //如果data中的属性attrs或者props 属性 数据存在 + if (isDef(attrs) || isDef(props)) { + //遍历propOptions props属性中的值 + for (var key in propOptions) { + + //altKey获取到一个函数,该函数功能是把 abCd 驼峰字母改写成 ab-c 如果是 aB cd 则是 ab cd + //大写字母,加完减号又转成小写了 比如把驼峰 aBc 变成了 a-bc + //匹配大写字母并且两面不是空白的 替换成 '-' + '字母' 在全部转换成小写 + + var altKey = hyphenate(key); + + + { + //把key 转换成小写 + var keyInLowerCase = key.toLowerCase(); + //如果他们key不相同 并且 属性attrs存在 并且keyInLowerCase 属性存在 attrs对象中 + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + //输出一个警告信息 + tip( + "Prop \"" + keyInLowerCase + "\" is passed to component " + + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + + " \"" + key + "\". " + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." + ); + } + } + //检查属性 + checkProp( + res, //空对象 + props, //props 属性 + key, //propOptions 的原始key + altKey, //转换后的 横杆key + true + ) || + checkProp( + res, + attrs, + key, + altKey, + false + ); + } + } + return res } //检查 属性 检查key和altKey 在hash属性对象中有没有,如果有则赋值给res对象 function checkProp( - res, //需要添加值的对象 - hash, // 属性对象 - key, // 原始key - altKey, //转换后的 横杆key - preserve //是否要删除hash 对象中的属性或者方法 状态 布尔值 + res, //需要添加值的对象 + hash, // 属性对象 + key, // 原始key + altKey, //转换后的 横杆key + preserve //是否要删除hash 对象中的属性或者方法 状态 布尔值 ) { //hash 值存在 if (isDef(hash)) { @@ -3274,18 +3274,18 @@ /* */ -// The template compiler attempts to minimize the need for normalization by 模板编译器试图最小化对规范化的需要。 -// statically analyzing the template at compile time. 在编译时静态分析模板。 -// -// For plain HTML markup, normalization can be completely skipped because the 对于普通HTML标记,可以完全跳过标准化,因为 -// generated render function is guaranteed to return Array. There are 生成的渲染函数保证返回数组。有 -// two cases where extra normalization is needed: 需要额外标准化的两种情况: + // The template compiler attempts to minimize the need for normalization by 模板编译器试图最小化对规范化的需要。 + // statically analyzing the template at compile time. 在编译时静态分析模板。 + // + // For plain HTML markup, normalization can be completely skipped because the 对于普通HTML标记,可以完全跳过标准化,因为 + // generated render function is guaranteed to return Array. There are 生成的渲染函数保证返回数组。有 + // two cases where extra normalization is needed: 需要额外标准化的两种情况: -// 1. When the children contains components - because a functional component 当儿童包含组件时,因为函数组件 -// may return an Array instead of a single root. In this case, just a simple 可以返回数组而不是单个根。在这种情况下,只是一个简单的例子 -// normalization is needed - if any child is an Array, we flatten the whole 规范化是必要的-如果任何一个孩子是一个数组,我们扁平化整个 -// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 和Array.prototype.concat在一起。保证仅为1级深 -// because functional components already normalize their own children. 因为功能组件已经规范了他们自己的孩子。 + // 1. When the children contains components - because a functional component 当儿童包含组件时,因为函数组件 + // may return an Array instead of a single root. In this case, just a simple 可以返回数组而不是单个根。在这种情况下,只是一个简单的例子 + // normalization is needed - if any child is an Array, we flatten the whole 规范化是必要的-如果任何一个孩子是一个数组,我们扁平化整个 + // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 和Array.prototype.concat在一起。保证仅为1级深 + // because functional components already normalize their own children. 因为功能组件已经规范了他们自己的孩子。 //循环子节点children,把他连在一起,其实就是把伪数组变成真正的数组 function simpleNormalizeChildren(children) { @@ -3297,17 +3297,17 @@ return children } -// 2. When the children contains constructs that always generated nested Arrays, 2。当子类包含总是生成嵌套数组的结构时, -// e.g.