Repository: qq281113270/vue-ddf- Branch: master Commit: 770f5592dda4 Files: 176 Total size: 850.1 KB Directory structure: gitextract_aaudxq4u/ ├── 0.html ├── 05自定义指令.html ├── 06provide组件通信.html ├── 07.html ├── 08delimiters.html ├── 09v-model.html ├── 1.html ├── 10tag标签.html ├── 10标签匹配.html ├── 11tag标签.html ├── 11标签匹配.html ├── 12tag标签.html ├── 12高阶组件.html ├── 13组件.html ├── 14高阶组件.html ├── 15 vonde.html ├── 16transition动画1.html ├── 16transition动画2.html ├── 16transition动画3.html ├── 17transition动画中的appear.html ├── 2.html ├── 3.html ├── 4.html ├── 5.html ├── 6.html ├── 6属性.html ├── 7for属性.html ├── 8event属性.html ├── HTMLUnknownElement.html ├── IS_REGEX_CAPTURING_BROKEN.html ├── JS里charCodeAt()和fromCharCode().html ├── JS里charCodeAt()和fromCharCode()方法拓展应用:加密与解密.html ├── MessageChannel.html ├── MessageChannel_0.html ├── MessageChannel_1.html ├── Proxy_1.html ├── Proxy_2.html ├── Proxy_3.html ├── Proxy_4.html ├── Proxy_5.html ├── Proxy_6.html ├── README.md ├── README_EN.md ├── Reflect.ownKeys.html ├── Set.html ├── StrictChecking.html ├── String方法之fromCharCode()和charCodeAt().html ├── Symbol.html ├── Symbol1.html ├── Symbol2.html ├── Symbol3.html ├── _Set.html ├── __proto__.html ├── _c.html ├── add.txt ├── add1.txt ├── advance.html ├── appear.html ├── argMatch.html ├── arrObj.html ├── array.html ├── attribute.html ├── bailRE.html ├── buildRegex.html ├── callbacks.slice(0).html ├── camelize.html ├── charCodeAt.html ├── classify.html ├── classifyRE.html ├── classifyRE1.html ├── comments.html ├── contextmenu.html ├── createElementNS.html ├── ddf.html ├── def.html ├── defineProperty.html ├── exec.html ├── exex.html ├── forIteratorRE.html ├── formatComponentName.html ├── genCheckboxModel.js ├── genStaticKeys.html ├── getComputedStyle.html ├── getHookArgumentsLength.html ├── getOwnPropertyNames.html ├── getShouldDecode.html ├── getTransitionInfo.html ├── getTransitionInfo获取css3 Transition 信息.html ├── getTransitionInfo获取css3 Transition 信息2.html ├── getType.html ├── getTypeIndex.html ├── hookRE.html ├── hyphenate.html ├── hyphenateRE.html ├── ieNSBug.html ├── index.html ├── indexOf.html ├── inline-template.html ├── is.html ├── isReserved.html ├── isUnknownElement.html ├── javascript 匿名函数自执行调用.html ├── js Worker 线程.docx ├── js 字符串截取方法.html ├── js 数组截取.html ├── js_watch.html ├── js_watch1.html ├── js中with、this的用法.html ├── js命令者.html ├── js如何判断数组含有某值,in,includes,inArray,indexOf方案对比.html ├── js观察者.xmind ├── js观察者_1.xmind ├── lastIndexOf.html ├── length.html ├── length0.html ├── modifierRE.html ├── mount.html ├── new Array(val).html ├── new Proxy.html ├── newFunction.html ├── nodeType.html ├── normalizeArrayChildren.html ├── object.html ├── object.keys.html ├── once.html ├── parseFilters.html ├── parseModel.html ├── parseModifiers.html ├── parsePath.html ├── parseStyleText.html ├── parseText.html ├── performance.html ├── performance对象.docx ├── prohibitedKeywordRE.html ├── promise.html ├── proxy.html ├── repeat.html ├── requestAnimationFrame.html ├── requestAnimationFrame2.html ├── setget.html ├── simpleCheckRE.html ├── slice splice.html ├── split.html ├── startTagClose.html ├── stopImmediatePropagation.html ├── stripStringRE.html ├── toUnicodeFun.html ├── transformModel.html ├── transition.html ├── unaryOperatorsRE.html ├── unaryOperatorsRE匹配字符串前一个字符串是什么.html ├── unaryOperatorsRE匹配字符串前一个字符串是什么2.html ├── vm._watcher === watcher.html ├── vue.js ├── vue.js中HOOK函数.docx ├── vue实现单页返回缓存,下一页刷新.doc ├── vue数据监听.docx ├── vue源码分析.docx ├── vue源码帖子分析.docx ├── vue零散代码分析.html ├── window_performance.html ├── window_performance1.html ├── with.html ├── wrapFilter.html ├── 三木分析.html ├── 三木分析1.html ├── 匿名函数执行.html ├── 发布-订阅模式.html ├── 数组api.html ├── 数组的扩展-array.some()和array.every()区别?.docx ├── 正则$.html ├── 理解vue实现原理,实现一个简单的Vue框架.docx ├── 虚拟dom.docx ├── 观察者模式.html ├── 静态方法与原型.html └── 高阶组件.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: 0.html ================================================ Title
{{ message }}
================================================ FILE: 05自定义指令.html ================================================ Title
================================================ FILE: 06provide组件通信.html ================================================ Title
{{ message }}
================================================ FILE: 07.html ================================================ Title
================================================ FILE: 08delimiters.html ================================================ Title
{{ message }}
${ message }
================================================ FILE: 09v-model.html ================================================ Title
================================================ FILE: 1.html ================================================ Title
{{ message }}
================================================ FILE: 10tag标签.html ================================================ Title
i 标签 在纯文本中,要宽容,把它当作文本来对待 {{lovingVue}}
{{select}}
================================================ FILE: 10标签匹配.html ================================================ Title
213
{{message}}
================================================ FILE: 11tag标签.html ================================================ Title

标签h1

我是div
我是div
我是span

标签h1

================================================ FILE: 11标签匹配.html ================================================ Title
{{message}}
================================================ FILE: 12tag标签.html ================================================ Title
span 节点 {{isClass}} {{number}}
================================================ FILE: 12高阶组件.html ================================================ Title
  • {{item.msg}}
  • ================================================ FILE: 13组件.html ================================================ Title
    span 节点 {{isClass}}
    ================================================ FILE: 14高阶组件.html ================================================ Title
  • {{item.msg}}
  • ================================================ FILE: 15 vonde.html ================================================ Title
    span 节点 {{isClass}}
    ================================================ FILE: 16transition动画1.html ================================================ Title

    hello

    ================================================ FILE: 16transition动画2.html ================================================ Title

    hello

    ================================================ FILE: 16transition动画3.html ================================================ Title

    hello

    ================================================ FILE: 17transition动画中的appear.html ================================================ Title

    appear

    ================================================ FILE: 2.html ================================================ Title
    ================================================ FILE: 3.html ================================================ Title
    {{message}}
    ================================================ FILE: 4.html ================================================ Title
    {{ message }}
    ================================================ FILE: 5.html ================================================ Title
    {{message}}
    ================================================ FILE: 6.html ================================================ Title ================================================ FILE: 6属性.html ================================================ Title
    {{message}}
    ================================================ FILE: 7for属性.html ================================================ Title
    ================================================ FILE: 8event属性.html ================================================ Title
    ================================================ FILE: HTMLUnknownElement.html ================================================ ================================================ FILE: IS_REGEX_CAPTURING_BROKEN.html ================================================ Title ================================================ FILE: JS里charCodeAt()和fromCharCode().html ================================================ Title ================================================ FILE: JS里charCodeAt()和fromCharCode()方法拓展应用:加密与解密.html ================================================ Title

    ================================================ FILE: MessageChannel.html ================================================ Title ================================================ FILE: MessageChannel_0.html ================================================ Title ================================================ FILE: MessageChannel_1.html ================================================ Title ================================================ FILE: Proxy_1.html ================================================ Title ================================================ FILE: Proxy_2.html ================================================ Title ================================================ FILE: Proxy_3.html ================================================ Title ================================================ FILE: Proxy_4.html ================================================ Title ================================================ FILE: Proxy_5.html ================================================ Title ================================================ FILE: Proxy_6.html ================================================ Title ================================================ FILE: README.md ================================================ ***English document***: https://github.com/ygs-code/vue/blob/master/README_EN.md # 开始 vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。 说的非常的详细,里面的源码注释,有些是自己多年开发vue经验而获得的,有些是自己跑上下文程序知道的, 如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。 如果大家觉得不错请动动小手指,帮我点一个satr,你们的支持就是我的动力。 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 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。 # new Vue实例化程序入口 ``` 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 ) } ``` # 编译AST和render函数 调用 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 } } // 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文本 ``` ## 双数据响应 双数据绑定 入口 方法在defineReactive函数中 ,不管是 prop 还是 state 还是 属性监听方法 set 方法,还是initInjections 入口都是这里。 首先他会实例化 var dep = new Dep(); 依赖收集 Dep,get方法会添加一个 ​ //添加一个dep ​ dep.depend(); ​ if (childOb) { //如果子节点存在也添加一个dep ​ childOb.dep.depend(); ​ if (Array.isArray(value)) { //判断是否是数组 如果是数组 ​ dependArray(value); //则数组也添加dep ​ } ​ } set 方法是触发更新视图的 //observe 添加 观察者 // 然后在添加依赖 childOb = !shallow && observe(newVal); //更新数据 dep.notify(); ``` /** * Define a reactive property on an Object. * 在对象上定义一个无功属性。 * 更新数据 * 通过defineProperty的set方法去通知notify()订阅者subscribers有新的值修改 * 添加观察者 get set方法 */ function defineReactive(obj, //对象 key,//对象的key val, //监听的数据 返回的数据 customSetter, // 日志函数 shallow //是否要添加__ob__ 属性 ) { //实例化一个主题对象,对象中有空的观察者列表 var dep = new Dep(); //获取描述属性 var property = Object.getOwnPropertyDescriptor(obj, key); var _property = Object.getOwnPropertyNames(obj); //获取实力对象属性或者方法,包括定义的描述属性 console.log(property); console.log(_property); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; console.log('arguments.length=' + arguments.length) if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; console.log(val) //判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性递归把val添加到观察者中 返回 new Observer 实例化的对象 var childOb = !shallow && observe(val); //定义描述 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if (Dep.target) { //Dep.target 静态标志 标志了Dep添加了Watcher 实例化的对象 //添加一个dep dep.depend(); if (childOb) { //如果子节点存在也添加一个dep childOb.dep.depend(); if (Array.isArray(value)) { //判断是否是数组 如果是数组 dependArray(value); //则数组也添加dep } } } return value }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare 新旧值比较 如果是一样则不执行了*/ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare * 不是生产环境的情况下 * */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { //set 方法 设置新的值 setter.call(obj, newVal); } else { //新的值直接给他 val = newVal; } console.log(newVal) //observe 添加 观察者 childOb = !shallow && observe(newVal); //更新数据 dep.notify(); } }); } ``` ## 依赖收集 Dep 在vue数据get获取中,谁读取了该数据,就把它收集起来,所以dep是一个集合,在数据set时,通过遍历dep去触发每个dep的notify方法通过视图更新 dep的主要功能是只作为收集,那在收集了依赖后,如何使视图更新呢 所以需要定义一个新的Watcher类,改类是会实现对视图的更新 dep每收集的一个依赖实际就是一个Watcher ``` //主题对象Dep构造函数 主要用于添加发布事件后,用户更新数据的 响应式原理之一函数 var Dep = function Dep() { //uid 初始化为0 this.id = uid++; /* 用来存放Watcher对象的数组 */ this.subs = []; }; Dep.prototype.addSub = function addSub(sub) { /* 在subs中添加一个Watcher对象 */ this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub(sub) { /*删除 在subs中添加一个Watcher对象 */ remove(this.subs, sub); }; //this$1.deps[i].depend(); //为Watcher 添加 为Watcher.newDeps.push(dep); 一个dep对象 Dep.prototype.depend = function depend() { //添加一个dep target 是Watcher dep就是dep对象 if (Dep.target) { //像指令添加依赖项 Dep.target.addDep(this); } }; /* 通知所有Watcher对象更新视图 */ Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { //更新数据 subs[i].update(); } }; // 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 = []; function pushTarget(_target) { //target 是Watcher dep就是dep对象 if (Dep.target) { //静态标志 Dep当前是否有添加了target //添加一个pushTarget targetStack.push(Dep.target); } Dep.target = _target; } // function popTarget() { // 出盏一个pushTarget Dep.target = targetStack.pop(); } ``` ## 数据检测 Watcher Watcher的功能主要是接口到Dep的通知,然后调用update方法更新视图 在update方法中会触发回调,回调函数实际就是已生成render函数 在调用render函数是,函数里的值就会获取到已经更改后值,所以就会生成新的vnode 新的vnode生成后,就是patch的过程,用新的vnode与旧的vnode进行比对,最终将比对后的vnode转换为实际的dom添加到模板挂载节点上 新的模板挂载后,将旧的模板删除,这样视图就更新完成 ``` * *观察者分析表达式,收集依赖项, *并在表达式值更改时触发回调。 *这用于$watch() api和指令。 * 当前vue实例、updateComponent函数、空函数。 */ var Watcher = function Watcher( vm, //vm dom expOrFn, //获取值的函数,或者是更新viwe试图函数 cb, //回调函数,回调值给回调函数 options, //参数 isRenderWatcher//是否渲染过得观察者 ) { console.log('====Watcher====') this.vm = vm; //是否是已经渲染过得观察者 if (isRenderWatcher) { //把当前 Watcher 对象赋值给 vm._watcher上 vm._watcher = this; } //把观察者添加到队列里面 当前Watcher添加到vue实例上 vm._watchers.push(this); // options if (options) { //如果有参数 this.deep = !!options.deep; //实际 this.user = !!options.user; //用户 this.lazy = !!options.lazy; //懒惰 ssr 渲染 this.sync = !!options.sync; //如果是同步 } else { this.deep = this.user = this.lazy = this.sync = false; } this.cb = cb; //回调函数 this.id = ++uid$1; // uid for batching uid为批处理 监听者id this.active = true; //激活 this.dirty = this.lazy; // for lazy watchers 对于懒惰的观察者 this.deps = []; // 观察者队列 this.newDeps = []; // 新的观察者队列 // 内容不可重复的数组对象 this.depIds = new _Set(); this.newDepIds = new _Set(); // 把函数变成字符串形式 this.expression = expOrFn.toString(); // parse expression for getter //getter的解析表达式 if (typeof expOrFn === 'function') { //获取值的函数 this.getter = expOrFn; } else { //如果是keepAlive 组件则会走这里 //path 因该是路由地址 if (bailRE.test(path)) { // 匹配上 返回 true var bailRE = /[^\w.$]/; //匹配不是 数字字母下划线 $符号 开头的为true return } // //匹配不上 path在已点分割 // var segments = path.split('.'); // return function (obj) { // // for (var i = 0; i < segments.length; i++) { // //如果有参数则返回真 // if (!obj) { // return // } // //将对象中的一个key值 赋值给该对象 相当于 segments 以点拆分的数组做obj 的key // obj = obj[segments[i]]; // } // //否则返回一个对象 // return obj // } //匹配不是 数字字母下划线 $符号 开头的为true this.getter = parsePath(expOrFn); if (!this.getter) { //如果不存在 则给一个空的数组 this.getter = function () { }; "development" !== 'production' && warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } this.value = this.lazy ? // lazy为真的的时候才能获取值 这个有是组件才为真 undefined : this.get(); //计算getter,并重新收集依赖项。 获取值 }; ``` 在Watcher实例构造函数执行时,会触发get 触发了get后就会该Watcher实例进行收集 update为接到Dep通知时触发的方法 update内会调用run方法 在run方法内会调用cb回调方法 cb回到方法实际就是模板编译时render方法 # 虚拟DOM vue中的虚拟DOM,实际就是通过定义一个Vnode类,在该类上添加了dom的一些属性来标识一个dom 主要的作用是降低对实际dom的操作,来减轻对浏览器性能的耗费 ``` /* * 创建标准的vue vnode * * */ var VNode = function VNode( tag, /*当前节点的标签名*/ data, /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ children, //子节点 text, //文本 elm, /*当前节点的dom */ context, /*编译作用域*/ componentOptions, /*组件的option选项*/ asyncFactory/*异步工厂*/) { /*当前节点的标签名*/ this.tag = tag; /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ this.data = data; /*当前节点的子节点,是一个数组*/ this.children = children; /*当前节点的文本*/ this.text = text; /*当前虚拟节点对应的真实dom节点*/ this.elm = elm; /*当前节点的名字空间*/ this.ns = undefined; /*编译作用域 vm*/ this.context = context; this.fnContext = undefined; this.fnOptions = undefined; this.fnScopeId = undefined; /*节点的key属性,被当作节点的标志,用以优化*/ this.key = data && data.key; /*组件的option选项*/ this.componentOptions = componentOptions; /*当前节点对应的组件的实例*/ this.componentInstance = undefined; /*当前节点的父节点*/ this.parent = undefined; /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/ this.raw = false; /*静态节点标志*/ this.isStatic = false; /*是否作为跟节点插入*/ this.isRootInsert = true; /*是否为注释节点*/ this.isComment = false; /*是否为克隆节点*/ this.isCloned = false; /*是否有v-once指令*/ this.isOnce = false; /*异步工厂*/ this.asyncFactory = asyncFactory; this.asyncMeta = undefined; this.isAsyncPlaceholder = false; }; ``` # diff算法 patch ,sameVnode, patchVnode ,updateChildren 这几个方法 入口是patch 然后调用sameVnode ``` //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 function sameVnode(a, b) { return ( a.key === b.key && ( //如果a的key 等于b的key ( a.tag === b.tag && // 如果a的tag 等于b的tag a.isComment === b.isComment && // 如果a和b 都是注释节点 isDef(a.data) === isDef(b.data) && //如果a.data 和 b.data 都定义后,是组件,或者是都含有tag属性 sameInputType(a, b) //相同的输入类型。判断a和b的属性是否相同 ) || ( isTrue(a.isAsyncPlaceholder) && //判断是否是异步的 a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) } ``` 如果调用sameVnode 条件成立 则进入patchVnode 方法, patchVnode 方法主要是对vnode 进行增加和删除,主要还有key更新等。然后 判断 两个虚拟dom都不为空,并且他们不相等的时候oldCh !== ch 就进入updateChildren diff更新算法。 ``` // 对比 虚拟dom function patchVnode( oldVnode, // 旧的虚拟dom vnode, // 新的虚拟dom insertedVnodeQueue, // 删除虚拟dom队列 removeOnly ) { if (oldVnode === vnode) { //如果他们相等 return } var elm = vnode.elm = oldVnode.elm; //获取真实的dom // 判断是否有isAsyncPlaceholder 属性 if (isTrue(oldVnode.isAsyncPlaceholder)) { //判断数据 是否不等于 undefined或者null if (isDef(vnode.asyncFactory.resolved)) { // ssr 渲染 hydrate(oldVnode.elm, vnode, insertedVnodeQueue); } else { vnode.isAsyncPlaceholder = true; } return } // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. //为静态树重用元素。 //注意,只有当vnode被克隆时,我们才这样做 //如果新节点没有克隆,则表示渲染函数已经克隆 //由hot-reload api重置,我们需要做一个适当的重新渲染。 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance; return } var i; var data = vnode.data; // 钩子函数 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode); } var oldCh = oldVnode.children; var ch = vnode.children; //循环组件实例 是否定义有 tag标签 if (isDef(data) && isPatchable(vnode)) { // 触发钩子函数 更新钩子函数 for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } // 触发钩子函数 if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); } } //如果是文本虚拟dom if (isUndef(vnode.text)) { // 两个虚拟dom都存在 if (isDef(oldCh) && isDef(ch)) { // 如果他们不相等 if (oldCh !== ch) { // diff算法更新 updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } } else if (isDef(ch)) { // 如果是有新的虚拟dom // 如果是文本虚拟dom 则 设置 空 if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } // 添加 vnode addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { // 如果旧的有 新的虚拟dom没有则删除 虚拟dom removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { // 如果是文本虚拟dom则设置文本 nodeOps.setTextContent(elm, ''); } } else if (oldVnode.text !== vnode.text) { // 如果新旧的文本不相同则设置文本 nodeOps.setTextContent(elm, vnode.text); } if (isDef(data)) { // 触发钩子 if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } } } ``` # ddif 算法updateChildren diif算法,vue2 的diff 算法是深度优先算法遍历,然后对比算法是通过 新旧的vnode对比先对比他们的基本属性,比如key 标签等,如果是相同则通过diff算法对比然后diff算法是新旧的vnode对比,然后有四个指针索引,两个新的vnode开始指针和新的 vnode 结束指针,两个旧的vnode开始指针和旧的 vnode 结束指针。然后先判断vnode是否为空,如果为空就往中间靠拢 开始的指针++ 结束的指针 --。然后两头对比之后,在交叉对比,直到找不到相同的vnode之后如果多出的就删除,如果少的话就新增,然后对比完之后 在调用patchVnode去增删虚拟dom。然后如果有vnode不相同在调用updateChildren,这样就做到深层递归,也叫深度优先搜索,然后子vnode没有了在更新到真实dom。 ``` // ddif 算法 function updateChildren( parentElm, // 父亲dom oldCh, // 旧的虚拟dom newCh, // 新的虚拟dom insertedVnodeQueue, removeOnly ) { var oldStartIdx = 0; // 旧的虚拟dom开始指针 var newStartIdx = 0; // 新的虚拟dom开始指针 var oldEndIdx = oldCh.length - 1; // 旧的虚拟dom结束指针 var newEndIdx = newCh.length - 1;// 新的虚拟dom结束指针 var oldStartVnode = oldCh[0]; // 旧的虚拟dom开始节点 var newStartVnode = newCh[0]; // 新的虚拟dom开始节点 var oldEndVnode = oldCh[oldEndIdx]; // 旧的虚拟dom结束节点 var newEndVnode = newCh[newEndIdx];// 新的虚拟dom结束节点 var oldKeyToIdx, idxInOld, vnodeToMove, refElm; // removeOnly is a special flag used only by // to ensure removed elements stay in correct relative positions // during leaving transitions var canMove = !removeOnly; { // 检查同一个兄弟节点是否有重复的key,如果有则发出警告日志 checkDuplicateKeys(newCh); } /* diff 算法开始 这里diff算法其实就是 */ while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { // 如果旧的开始节点不存在或者为空 // 如果旧的开始节点指针往中间偏移 oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left } else if (isUndef(oldEndVnode)) { // 如果旧的结束节点不存在或者为空 // 如果旧的结束节点指针往中间偏移 oldEndVnode = oldCh[--oldEndIdx]; } else if (sameVnode(oldStartVnode, newStartVnode)) { //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 // 在对比下虚拟dom patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); //开始指针 两个都往中间偏移 oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (sameVnode(oldEndVnode, newEndVnode)) { //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 // 在对比下虚拟dom patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); // 结束指针 两个都往中间偏移 oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 // 交叉对比 深度优先算法入口 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); // 交叉对比 canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left // 交叉对比 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); // 交叉对比 canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else { // 如果没有key 则给塔新的key if (isUndef(oldKeyToIdx)) { // 创建key 如果没有key 则用索引作为key oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } // 获取 旧的vnode key idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] // 查找旧的vnode key : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx); // 如果旧的 vnode key 未定义则创建新的真实dom if (isUndef(idxInOld)) { // New element //创建真实 dom 节点 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } else { vnodeToMove = oldCh[idxInOld]; if (sameVnode(vnodeToMove, newStartVnode)) { // 对比虚拟dom patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue); oldCh[idxInOld] = undefined; // 真实节点交换 canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm); } else { // same key but different element. treat as new element // 创建真实dom createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } } newStartVnode = newCh[++newStartIdx]; } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; // 添加虚拟dom addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } else if (newStartIdx > newEndIdx) { // 删除虚拟dom removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } } ``` 具体看我源码和流程图,这里文字就不描述这么多了,流程图是下面这中的网盘,源码是vue.js,基本每一行都有注释 链接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng 提取码:1fnu 上面的vue.js 就是我基于vue源码中每行加有注释的vue.js, 其他文件就是我看vue.js源码的时候抽出来的vue.js 源码小demo。如果大家觉得不错请动动小手指,帮我点一个satr,你们的支持就是我的动力 作者:姚观寿 最近失业了,想找一份前端开发工作,希望有朋友公司招聘的可以联系下我,谢谢你们,地点 深圳,或者远程都可以,前端构架,前端高级开发,组长职位都行。 我微信:18529531779 , helloTalk 号@18529531779,邮箱:281113270@qq.com 谢谢了。 ================================================ FILE: README_EN.md ================================================ # begin vue source code spare time to see almost a year, before looking for posts on the Internet, found that many posts are very scattered, are part of a part said, a lot of chapters, so they decided to see line by line, after their own persistence and efforts, now basically read. This vue source line by line analysis, I basically every line on the annotation, plus the whole framework of the process mind map, is basically a small white can also understand the vue source code. Said very detailed, inside the source code notes, some are their own years of experience in developing vue, some are their own context program to know, if there are shortcomings can contact me QQ group: 302817612 modification, or send an email to me 281113270@qq.com thank you. If you feel good, please move your little finger to help me click a satr, your support is my motivation. vue How to see vue source code? In fact, mvvm source code is not as mysterious as imagined, from the beginning of 12 years to the present mvvm development has more than a decade of history, from the previous direct operation of the dom jq development has more than a decade of history, but this decade of historical development, and there is not much change, the idea is still those, the module is still divided into several chunks: ## 1. Template conversion: Is we write a vue template or react jsx we can understand is a template, and then it will go through the template compilation conversion, like vue is into a method paseHTML method converted into the ast tree, paseHTML inside the while loop template, Then through the RE match to the vue instructions, as well as vue properties, event methods, etc., collected into an ast tree. ## 2. Corresponding data: vue is a dual data corresponding framework, the underlying use is Object.defineProperty to listen for and hijack data changes, and then call callback methods to update the view update. The principle of dual data binding is as follows: The obersve() method determines whether value has no __ob___ attribute and is not Obersve instantiated, and whether value is Vonde instantiated. If not, it calls Obersve to add the data to the observer and add the __ob__ attribute to the data. Obersve calls the defineReactive method, which is a channel connecting the Dep and wacther methods, and listens for data using the get and set methods in Object.definpropty(). In the get method, new Dep calls depend(). To add a wacther class to dep, watcher has a method to update the view. run calls update to update the vonde and then updates the view. Then the set method is to call the notify method in dep to call the run update view in wacther ## 3. Virtual dom: vnode, used in vue, is via ast objects, escaped into vonde needs to render functions, such as _c('div' s('')) and such functions, compiled into vonde virtual dom. Then update the data to updata and call __patch__ to turn vonde into a true dom element through diff algorithm. ## 4.diif algorithm: ​ The diff algorithm of vue2 is depth-first traversal, and then the comparison algorithm compares the old vnode with the new vnode, first compares their basic attributes, such as key labels, etc. If they are the same, the diff algorithm compares the old Vnode with the new Vnode, and then there are four pointer indexes. Two new vnode start Pointers and two new vnode end Pointers, two old vnode start Pointers and old vnode end Pointers. Then first determine whether the vnode is empty, if it is empty, move to the center of the start pointer ++ end pointer --. Then after comparing the two sides, cross-compare until you can't find the same vnode, if there are more, delete it, if there are fewer, add it, and then update it to the real dom after comparing. new Vue calls vue.prototype. _init. From this function, after merging with the $options parameter, initLifecycle initializes the life cycle, marking the initialization event, and initializing the rendering function. The initialization state is the data. Add data to the observer for double data binding. # new Vue instantiates the program entry ``` 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); } ``` # Find and mount templates vm.$mount goes to the mount template method and determines whether it has a render function or a template, and if not, uses el.outerHTML, which is essentially getting the html content of the template ``` 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 ) } ``` # Compile the AST and render functions After you call the Vue.prototype.$mount method and get the template, you enter the following methods, which use a lot of functional programming ``` compileToFunctions createCompiler createCompilerCreator baseCompile parse parseHTML ``` The important thing here is that parseHTML is a while (html) {// loop through the html and then through the re match to the vue directive, as well as vue properties, event methods, etc., collected into an ast tree. ``` 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 } } // 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文本 ``` ## Dual data response The dual data binding entry method is in the defineReactive function, whether it is prop or state or property listener set method or initInjections entry. First he instantiates var dep = new Dep(); Depending on the collection Dep, the get method adds one ​ //添加一个dep ​ dep.depend(); ​ if (childOb) { //如果子节点存在也添加一个dep ​ childOb.dep.depend(); ​ if (Array.isArray(value)) { //判断是否是数组 如果是数组 ​ dependArray(value); //则数组也添加dep ​ } ​ } The set method is the trigger for updating the view //observe Add an observer // Then add dependencies childOb = !shallow && observe(newVal); //更新数据 dep.notify(); ``` /** * Define a reactive property on an Object. * 在对象上定义一个无功属性。 * 更新数据 * 通过defineProperty的set方法去通知notify()订阅者subscribers有新的值修改 * 添加观察者 get set方法 */ function defineReactive(obj, //对象 key,//对象的key val, //监听的数据 返回的数据 customSetter, // 日志函数 shallow //是否要添加__ob__ 属性 ) { //实例化一个主题对象,对象中有空的观察者列表 var dep = new Dep(); //获取描述属性 var property = Object.getOwnPropertyDescriptor(obj, key); var _property = Object.getOwnPropertyNames(obj); //获取实力对象属性或者方法,包括定义的描述属性 console.log(property); console.log(_property); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; console.log('arguments.length=' + arguments.length) if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; console.log(val) //判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性递归把val添加到观察者中 返回 new Observer 实例化的对象 var childOb = !shallow && observe(val); //定义描述 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if (Dep.target) { //Dep.target 静态标志 标志了Dep添加了Watcher 实例化的对象 //添加一个dep dep.depend(); if (childOb) { //如果子节点存在也添加一个dep childOb.dep.depend(); if (Array.isArray(value)) { //判断是否是数组 如果是数组 dependArray(value); //则数组也添加dep } } } return value }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare 新旧值比较 如果是一样则不执行了*/ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare * 不是生产环境的情况下 * */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { //set 方法 设置新的值 setter.call(obj, newVal); } else { //新的值直接给他 val = newVal; } console.log(newVal) //observe 添加 观察者 childOb = !shallow && observe(newVal); //更新数据 dep.notify(); } }); } ``` ## Depends on collecting Dep In the vue data get, whoever reads the data will collect it. Therefore, dep is a set. When the data is set, the notify method of each dep is triggered by traversing the dep to update through the view The main function of dep is to act only as a collection, so how do you update the view after collecting the dependencies So you need to define a new Watcher class that will update the view Each dependency collected by the dep is actually a Watcher ``` //主题对象Dep构造函数 主要用于添加发布事件后,用户更新数据的 响应式原理之一函数 var Dep = function Dep() { //uid 初始化为0 this.id = uid++; /* 用来存放Watcher对象的数组 */ this.subs = []; }; Dep.prototype.addSub = function addSub(sub) { /* 在subs中添加一个Watcher对象 */ this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub(sub) { /*删除 在subs中添加一个Watcher对象 */ remove(this.subs, sub); }; //this$1.deps[i].depend(); //为Watcher 添加 为Watcher.newDeps.push(dep); 一个dep对象 Dep.prototype.depend = function depend() { //添加一个dep target 是Watcher dep就是dep对象 if (Dep.target) { //像指令添加依赖项 Dep.target.addDep(this); } }; /* 通知所有Watcher对象更新视图 */ Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { //更新数据 subs[i].update(); } }; // 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 = []; function pushTarget(_target) { //target 是Watcher dep就是dep对象 if (Dep.target) { //静态标志 Dep当前是否有添加了target //添加一个pushTarget targetStack.push(Dep.target); } Dep.target = _target; } // function popTarget() { // 出盏一个pushTarget Dep.target = targetStack.pop(); } ``` ## Data detection Watcher Watcher's main function is to interface to the Dep notification, and then call the update method to update the view The callback is triggered in the update method, and the callback function is actually the generated render function Upon calling the render function, the values in the function will get the changed value, so a new vnode will be generated After the new vnode is generated, it is the patch process. The new vnode is compared with the old vnode. Finally, the vnode after comparison is converted into the actual dom and added to the node to which the template is mounted After the new template is mounted, delete the old template so that the view is updated ``` * *观察者分析表达式,收集依赖项, *并在表达式值更改时触发回调。 *这用于$watch() api和指令。 * 当前vue实例、updateComponent函数、空函数。 */ var Watcher = function Watcher( vm, //vm dom expOrFn, //获取值的函数,或者是更新viwe试图函数 cb, //回调函数,回调值给回调函数 options, //参数 isRenderWatcher//是否渲染过得观察者 ) { console.log('====Watcher====') this.vm = vm; //是否是已经渲染过得观察者 if (isRenderWatcher) { //把当前 Watcher 对象赋值给 vm._watcher上 vm._watcher = this; } //把观察者添加到队列里面 当前Watcher添加到vue实例上 vm._watchers.push(this); // options if (options) { //如果有参数 this.deep = !!options.deep; //实际 this.user = !!options.user; //用户 this.lazy = !!options.lazy; //懒惰 ssr 渲染 this.sync = !!options.sync; //如果是同步 } else { this.deep = this.user = this.lazy = this.sync = false; } this.cb = cb; //回调函数 this.id = ++uid$1; // uid for batching uid为批处理 监听者id this.active = true; //激活 this.dirty = this.lazy; // for lazy watchers 对于懒惰的观察者 this.deps = []; // 观察者队列 this.newDeps = []; // 新的观察者队列 // 内容不可重复的数组对象 this.depIds = new _Set(); this.newDepIds = new _Set(); // 把函数变成字符串形式 this.expression = expOrFn.toString(); // parse expression for getter //getter的解析表达式 if (typeof expOrFn === 'function') { //获取值的函数 this.getter = expOrFn; } else { //如果是keepAlive 组件则会走这里 //path 因该是路由地址 if (bailRE.test(path)) { // 匹配上 返回 true var bailRE = /[^\w.$]/; //匹配不是 数字字母下划线 $符号 开头的为true return } // //匹配不上 path在已点分割 // var segments = path.split('.'); // return function (obj) { // // for (var i = 0; i < segments.length; i++) { // //如果有参数则返回真 // if (!obj) { // return // } // //将对象中的一个key值 赋值给该对象 相当于 segments 以点拆分的数组做obj 的key // obj = obj[segments[i]]; // } // //否则返回一个对象 // return obj // } //匹配不是 数字字母下划线 $符号 开头的为true this.getter = parsePath(expOrFn); if (!this.getter) { //如果不存在 则给一个空的数组 this.getter = function () { }; "development" !== 'production' && warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } this.value = this.lazy ? // lazy为真的的时候才能获取值 这个有是组件才为真 undefined : this.get(); //计算getter,并重新收集依赖项。 获取值 }; ``` get is triggered when the Watcher instance constructor executes After the get is triggered, the Watcher instance is collected update is the method that is triggered when a Dep notification is received The run method is called in update The cb callback method is called inside the run method The cb back method is actually the template compile-time render method # Virtual DOM Virtual DOM in vue actually identifies a dom by defining a Vnode class and adding some dom attributes to the class The main effect is to reduce the manipulation of the actual dom to reduce the cost of browser performance ``` /* * 创建标准的vue vnode * * */ var VNode = function VNode( tag, /*当前节点的标签名*/ data, /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ children, //子节点 text, //文本 elm, /*当前节点的dom */ context, /*编译作用域*/ componentOptions, /*组件的option选项*/ asyncFactory/*异步工厂*/) { /*当前节点的标签名*/ this.tag = tag; /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ this.data = data; /*当前节点的子节点,是一个数组*/ this.children = children; /*当前节点的文本*/ this.text = text; /*当前虚拟节点对应的真实dom节点*/ this.elm = elm; /*当前节点的名字空间*/ this.ns = undefined; /*编译作用域 vm*/ this.context = context; this.fnContext = undefined; this.fnOptions = undefined; this.fnScopeId = undefined; /*节点的key属性,被当作节点的标志,用以优化*/ this.key = data && data.key; /*组件的option选项*/ this.componentOptions = componentOptions; /*当前节点对应的组件的实例*/ this.componentInstance = undefined; /*当前节点的父节点*/ this.parent = undefined; /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/ this.raw = false; /*静态节点标志*/ this.isStatic = false; /*是否作为跟节点插入*/ this.isRootInsert = true; /*是否为注释节点*/ this.isComment = false; /*是否为克隆节点*/ this.isCloned = false; /*是否有v-once指令*/ this.isOnce = false; /*异步工厂*/ this.asyncFactory = asyncFactory; this.asyncMeta = undefined; this.isAsyncPlaceholder = false; }; ``` # diff algorithm patch, sameVnode, patchVnode, updateChildren these methods The entry point is patch and then the sameVnode is called ``` //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 function sameVnode(a, b) { return ( a.key === b.key && ( //如果a的key 等于b的key ( a.tag === b.tag && // 如果a的tag 等于b的tag a.isComment === b.isComment && // 如果a和b 都是注释节点 isDef(a.data) === isDef(b.data) && //如果a.data 和 b.data 都定义后,是组件,或者是都含有tag属性 sameInputType(a, b) //相同的输入类型。判断a和b的属性是否相同 ) || ( isTrue(a.isAsyncPlaceholder) && //判断是否是异步的 a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) } ``` If the sameVnode condition is valid, enter the patchVnode method. The patchVnode method is mainly used to add and delete vnodes and update key. Then determine when neither virtual dom is empty and they are not equal oldCh! == ch enters the updateChildren diff update algorithm. ``` // 对比 虚拟dom function patchVnode( oldVnode, // 旧的虚拟dom vnode, // 新的虚拟dom insertedVnodeQueue, // 删除虚拟dom队列 removeOnly ) { if (oldVnode === vnode) { //如果他们相等 return } var elm = vnode.elm = oldVnode.elm; //获取真实的dom // 判断是否有isAsyncPlaceholder 属性 if (isTrue(oldVnode.isAsyncPlaceholder)) { //判断数据 是否不等于 undefined或者null if (isDef(vnode.asyncFactory.resolved)) { // ssr 渲染 hydrate(oldVnode.elm, vnode, insertedVnodeQueue); } else { vnode.isAsyncPlaceholder = true; } return } // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. //为静态树重用元素。 //注意,只有当vnode被克隆时,我们才这样做 //如果新节点没有克隆,则表示渲染函数已经克隆 //由hot-reload api重置,我们需要做一个适当的重新渲染。 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance; return } var i; var data = vnode.data; // 钩子函数 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode); } var oldCh = oldVnode.children; var ch = vnode.children; //循环组件实例 是否定义有 tag标签 if (isDef(data) && isPatchable(vnode)) { // 触发钩子函数 更新钩子函数 for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } // 触发钩子函数 if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); } } //如果是文本虚拟dom if (isUndef(vnode.text)) { // 两个虚拟dom都存在 if (isDef(oldCh) && isDef(ch)) { // 如果他们不相等 if (oldCh !== ch) { // diff算法更新 updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } } else if (isDef(ch)) { // 如果是有新的虚拟dom // 如果是文本虚拟dom 则 设置 空 if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } // 添加 vnode addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { // 如果旧的有 新的虚拟dom没有则删除 虚拟dom removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { // 如果是文本虚拟dom则设置文本 nodeOps.setTextContent(elm, ''); } } else if (oldVnode.text !== vnode.text) { // 如果新旧的文本不相同则设置文本 nodeOps.setTextContent(elm, vnode.text); } if (isDef(data)) { // 触发钩子 if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } } } ``` # ddif algorithm updateChildren diif algorithm, the diff algorithm of vue2 is a depth-first algorithm for traversal, and then the comparison algorithm compares the old vnode with the new vnode, first compares their basic attributes, such as key labels, etc. If they are the same, the diff algorithm compares the old Vnode with the new Vnode, and then has four pointer indexes. Two new vnode start Pointers and two new vnode end Pointers, two old vnode start Pointers and old vnode end Pointers. Then first determine whether the vnode is empty, if it is empty, move to the center of the start pointer ++ end pointer --. Then, after comparing the two sides, cross-compare until the same vnode is not found. If there are more Vnodes, delete them; if there are fewer, add them. After comparing, call patchVnode to add or delete virtual dom. Then if there are Vnodes that are not the same, updateChildren is called, so deep recursion, also called depth-first search, is done, and then the child Vnodes are not updated to the real dom. ``` // ddif 算法 function updateChildren( parentElm, // 父亲dom oldCh, // 旧的虚拟dom newCh, // 新的虚拟dom insertedVnodeQueue, removeOnly ) { var oldStartIdx = 0; // 旧的虚拟dom开始指针 var newStartIdx = 0; // 新的虚拟dom开始指针 var oldEndIdx = oldCh.length - 1; // 旧的虚拟dom结束指针 var newEndIdx = newCh.length - 1;// 新的虚拟dom结束指针 var oldStartVnode = oldCh[0]; // 旧的虚拟dom开始节点 var newStartVnode = newCh[0]; // 新的虚拟dom开始节点 var oldEndVnode = oldCh[oldEndIdx]; // 旧的虚拟dom结束节点 var newEndVnode = newCh[newEndIdx];// 新的虚拟dom结束节点 var oldKeyToIdx, idxInOld, vnodeToMove, refElm; // removeOnly is a special flag used only by // to ensure removed elements stay in correct relative positions // during leaving transitions var canMove = !removeOnly; { // 检查同一个兄弟节点是否有重复的key,如果有则发出警告日志 checkDuplicateKeys(newCh); } /* diff 算法开始 这里diff算法其实就是 */ while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { // 如果旧的开始节点不存在或者为空 // 如果旧的开始节点指针往中间偏移 oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left } else if (isUndef(oldEndVnode)) { // 如果旧的结束节点不存在或者为空 // 如果旧的结束节点指针往中间偏移 oldEndVnode = oldCh[--oldEndIdx]; } else if (sameVnode(oldStartVnode, newStartVnode)) { //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 // 在对比下虚拟dom patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); //开始指针 两个都往中间偏移 oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (sameVnode(oldEndVnode, newEndVnode)) { //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 // 在对比下虚拟dom patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); // 结束指针 两个都往中间偏移 oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 // 交叉对比 深度优先算法入口 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); // 交叉对比 canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left // 交叉对比 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); // 交叉对比 canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else { // 如果没有key 则给塔新的key if (isUndef(oldKeyToIdx)) { // 创建key 如果没有key 则用索引作为key oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } // 获取 旧的vnode key idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] // 查找旧的vnode key : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx); // 如果旧的 vnode key 未定义则创建新的真实dom if (isUndef(idxInOld)) { // New element //创建真实 dom 节点 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } else { vnodeToMove = oldCh[idxInOld]; if (sameVnode(vnodeToMove, newStartVnode)) { // 对比虚拟dom patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue); oldCh[idxInOld] = undefined; // 真实节点交换 canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm); } else { // same key but different element. treat as new element // 创建真实dom createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } } newStartVnode = newCh[++newStartIdx]; } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; // 添加虚拟dom addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } else if (newStartIdx > newEndIdx) { // 删除虚拟dom removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } } ``` Specifically look at my source code and flow chart, here the text does not describe so much, the flow chart is the following network disk, source code is vue.js, basically every line has comments link:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng password:1fnu The above vue.js is my vue.js based on each line of vue source code with comments, and the other files are the vue.js source code small demo that I pulled out when I looked at Vue.js source code. If you feel good, please move your little finger to help me click a satr, your support is my motivation,thank you Author: Yao Guanshou I've been unemployed recently and I'm looking for a front-end development job. I hope any friends whose companies are recruiting can contact me. Thank you. The location can be Shenzhen or remote. Front-end architecture, senior front-end development, and team leader positions are all fine. My wechat: 18529531779, helloTalk account @18529531779, email: 281113270@qq.com, thank you. ================================================ FILE: Reflect.ownKeys.html ================================================ Title ================================================ FILE: Set.html ================================================ Title ================================================ FILE: StrictChecking.html ================================================ Title ================================================ FILE: String方法之fromCharCode()和charCodeAt().html ================================================ ================================================ FILE: Symbol.html ================================================ ================================================ FILE: Symbol1.html ================================================ ================================================ FILE: Symbol2.html ================================================ ================================================ FILE: Symbol3.html ================================================ ================================================ FILE: _Set.html ================================================ Title ================================================ FILE: __proto__.html ================================================ Title ================================================ FILE: _c.html ================================================ Title ================================================ FILE: add.txt ================================================ ================================================ FILE: add1.txt ================================================ add111111111111111111111 ================================================ FILE: advance.html ================================================ Title ================================================ FILE: appear.html ================================================ Title ================================================ FILE: argMatch.html ================================================ Title ================================================ FILE: arrObj.html ================================================ Title ================================================ FILE: array.html ================================================ Title ================================================ FILE: attribute.html ================================================ Title ================================================ FILE: bailRE.html ================================================ Title ================================================ FILE: buildRegex.html ================================================ Title ================================================ FILE: callbacks.slice(0).html ================================================ Title ================================================ FILE: camelize.html ================================================ Title ================================================ FILE: charCodeAt.html ================================================ Title ================================================ FILE: classify.html ================================================ Title ================================================ FILE: classifyRE.html ================================================ Title ================================================ FILE: classifyRE1.html ================================================ Title ================================================ FILE: comments.html ================================================ Title

    Hello Vue!

    渲染后HTML:

    ================================================ FILE: contextmenu.html ================================================ Title ================================================ FILE: createElementNS.html ================================================ ================================================ FILE: ddf.html ================================================

    Vue mvvm simple model

    ================================================ FILE: def.html ================================================ Title ================================================ FILE: defineProperty.html ================================================ Title ================================================ FILE: exec.html ================================================ Title ================================================ FILE: exex.html ================================================ Title ================================================ FILE: forIteratorRE.html ================================================ Title ================================================ FILE: formatComponentName.html ================================================ Title ================================================ FILE: genCheckboxModel.js ================================================ var $$a = item.selected, $$el = $event.target, $$c = $$el.checked ? (true) : (false); if (Array.isArray($$a)) { var $$v = "index", $$i = _i($$a, $$v); if ($$el.checked) { $$i < 0 && ($set(item, "selected", $$a.concat([$$v]))) } else { $$i > -1 && ($set(item, "selected", $$a.slice(0, $$i).concat($$a.slice($$i + 1)))) } } else { $set(item, "selected", $$c) } ================================================ FILE: genStaticKeys.html ================================================ Title ================================================ FILE: getComputedStyle.html ================================================ Title
    dummy
    ================================================ FILE: getHookArgumentsLength.html ================================================ Title ================================================ FILE: getOwnPropertyNames.html ================================================ Title ================================================ FILE: getShouldDecode.html ================================================ Title ================================================ FILE: getTransitionInfo.html ================================================ Title
    ================================================ FILE: getTransitionInfo获取css3 Transition 信息.html ================================================ Title
    ================================================ FILE: getTransitionInfo获取css3 Transition 信息2.html ================================================ Title
    1
    ================================================ FILE: getType.html ================================================ Title ================================================ FILE: getTypeIndex.html ================================================ Title ================================================ FILE: hookRE.html ================================================ ================================================ FILE: hyphenate.html ================================================ Title ================================================ FILE: hyphenateRE.html ================================================ Title ================================================ FILE: ieNSBug.html ================================================ Title ================================================ FILE: index.html ================================================ Title

    Hello App!

    Go to Foo Go to Bar

    ================================================ FILE: indexOf.html ================================================ Title ================================================ FILE: inline-template.html ================================================ Title
    header foot
    header

    These are compiled as the component's own template

    Not parent's transclusion content

    {{message}}

    foot
    ================================================ FILE: is.html ================================================ Title

    is 与 :is 的用法 ,区别如下:

    ================================================ FILE: isReserved.html ================================================ ================================================ FILE: isUnknownElement.html ================================================ Title ================================================ FILE: javascript 匿名函数自执行调用.html ================================================ Title ================================================ FILE: js 字符串截取方法.html ================================================ ================================================ FILE: js 数组截取.html ================================================ ================================================ FILE: js_watch.html ================================================ Title ================================================ FILE: js_watch1.html ================================================ Title ================================================ FILE: js中with、this的用法.html ================================================ Title ================================================ FILE: js命令者.html ================================================ Title ================================================ FILE: js如何判断数组含有某值,in,includes,inArray,indexOf方案对比.html ================================================ Title ================================================ FILE: lastIndexOf.html ================================================ Title ================================================ FILE: length.html ================================================ Title ================================================ FILE: length0.html ================================================ Title ================================================ FILE: modifierRE.html ================================================ Title ================================================ FILE: mount.html ================================================ ================================================ FILE: new Array(val).html ================================================ ================================================ FILE: new Proxy.html ================================================ Title ================================================ FILE: newFunction.html ================================================ new function 用来检测js错误 可以替代eval() 字符串转js代码检查 字符串编译解析成js指向Function,防止有些前端编译工具报错 ================================================ FILE: nodeType.html ================================================

    请点击按钮来获得 body 元素子节点的节点类型。

    注释:元素中的空格被视作文本,而文本被视作文本节点。

    ================================================ FILE: normalizeArrayChildren.html ================================================ Title ================================================ FILE: object.html ================================================ Title ================================================ FILE: object.keys.html ================================================ Title ================================================ FILE: once.html ================================================ Title
    {{ message }} This will never change: {{message}} This will never change: {{message}}
    ================================================ FILE: parseFilters.html ================================================ Title ================================================ FILE: parseModel.html ================================================ Title ================================================ FILE: parseModifiers.html ================================================ Title ================================================ FILE: parsePath.html ================================================ Title ================================================ FILE: parseStyleText.html ================================================ Title ================================================ FILE: parseText.html ================================================ Title ================================================ FILE: performance.html ================================================ Title ================================================ FILE: prohibitedKeywordRE.html ================================================ Title ================================================ FILE: promise.html ================================================ Title ================================================ FILE: proxy.html ================================================ Title ================================================ FILE: repeat.html ================================================ Title ================================================ FILE: requestAnimationFrame.html ================================================ Title
    ================================================ FILE: requestAnimationFrame2.html ================================================ Title
    ================================================ FILE: setget.html ================================================ Title ================================================ FILE: simpleCheckRE.html ================================================ Title ================================================ FILE: slice splice.html ================================================ Title ================================================ FILE: split.html ================================================ Title ================================================ FILE: startTagClose.html ================================================ Title ================================================ FILE: stopImmediatePropagation.html ================================================ Title

    paragraph

    ================================================ FILE: stripStringRE.html ================================================ Title ================================================ FILE: toUnicodeFun.html ================================================ ================================================ FILE: transformModel.html ================================================ Title ================================================ FILE: transition.html ================================================ Title ================================================ FILE: unaryOperatorsRE.html ================================================ Title ================================================ FILE: unaryOperatorsRE匹配字符串前一个字符串是什么.html ================================================ Title ================================================ FILE: unaryOperatorsRE匹配字符串前一个字符串是什么2.html ================================================ Title ================================================ FILE: vm._watcher === watcher.html ================================================ Title ================================================ FILE: vue.js ================================================ /*! * Vue.js v2.5.16 * (c) 2014-2018 Evan You * Released under the MIT License. * development 开发 * production 生产 /* * 兼容 amd cmd 模块写法 * */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); }(this, (function () { 'use strict'; /* */ //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 //判断数据 是否是undefined或者null function isUndef(v) { return v === undefined || v === null } //判断数据 是否不等于 undefined或者null function isDef(v) { return v !== undefined && v !== null } //判断是否真的等于true function isTrue(v) { return v === true } // 判断是否是false function isFalse(v) { return v === false } /** * Check if value is primitive * //判断数据类型是否是string,number,symbol,boolean */ function isPrimitive(value) { //判断数据类型是否是string,number,symbol,boolean return ( typeof value === 'string' || typeof value === 'number' || // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' ) } /** * Quick object check - this is primarily used to tell * Objects from primitive values when we know the value * is a JSON-compliant type. */ function isObject(obj) { //判断是否是对象 return obj !== null && typeof obj === 'object' } /** * Get the raw type string of a value e.g. [object Object] */ //获取toString 简写 var _toString = Object.prototype.toString; function toRawType(value) { //类型判断 返会Array ,Function,String,Object,Re 等 return _toString.call(value).slice(8, -1) } /** * Strict object type check. Only returns true * for plain JavaScript objects. */ function isPlainObject(obj) { //判断是否是对象 return _toString.call(obj) === '[object Object]' } function isRegExp(v) { //判断是否是正则对象 return _toString.call(v) === '[object RegExp]' } /** * Check if val is a valid array index. */ /** * Check if val is a valid array index. * 检查VAL是否是有效的数组索引。 */ function isValidArrayIndex(val) { //isFinite 检测是否是数据 //Math.floor 向下取整 var n = parseFloat(String(val)); //isFinite 如果 number 是有限数字(或可转换为有限数字),那么返回 true。否则,如果 number 是 NaN(非数字),或者是正、负无穷大的数,则返回 false。 return n >= 0 && Math.floor(n) === n && isFinite(val) } /** * Convert a value to a string that is actually rendered. */ function toString(val) { //将对象或者其他基本数据 变成一个 字符串 return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val) } /** * Convert a input value to a number for persistence. * If the conversion fails, return original string. */ function toNumber(val) { //字符串转数字,如果失败则返回字符串 var n = parseFloat(val); return isNaN(n) ? val : n } /** * Make a map and return a function for checking if a key * is in that map. * * //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true} * 并且传进一个key值取值,这里用到策略者模式 */ function makeMap(str, expectsLowerCase) { var map = Object.create(null); //创建一个新的对象 var list = str.split(','); //按字符串,分割 for (var i = 0; i < list.length; i++) { map[list[i]] = true; //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true} } return expectsLowerCase ? function (val) { return map[val.toLowerCase()]; } //返回一个柯里化函数 toLowerCase转换成小写 : function (val) { return map[val]; } //返回一个柯里化函数 并且把map中添加一个 属性建 } /** * Check if a tag is a built-in tag. * 检查标记是否为内置标记。 */ var isBuiltInTag = makeMap('slot,component', true); /** * Check if a attribute is a reserved attribute. * 检查属性是否为保留属性。 * isReservedAttribute=function(vale){ map{key:true,ref:true,slot-scope:true,is:true,vaule:undefined} } */ var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); /** * Remove an item from an array * //删除数组 */ function remove(arr, item) { if (arr.length) { var index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1) } } } /** * Check whether the object has the property. *检查对象属性是否是实例化还是原型上面的 */ var hasOwnProperty = Object.prototype.hasOwnProperty; function hasOwn(obj, key) { return hasOwnProperty.call(obj, key) } /** * Create a cached version of a pure function. */ /** * Create a cached version of a pure function. * 创建纯函数的缓存版本。 * 创建一个函数,缓存,再return 返回柯里化函数 * 闭包用法 */ /*********************************************************************************************** *函数名 :cached *函数功能描述 : 创建纯函数的缓存版本。 创建一个函数,缓存,再return 返回柯里化函数 闭包用法 *函数参数 : fn 函数 *函数返回值 : fn *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ /* * var aFn = cached(function(string){ * * return string * }) * aFn(string1); * aFn(string2); * aFn(string); * aFn(string1); * aFn(string2); * * aFn 函数会多次调用 里面就能体现了 * 用对象去缓存记录函数 * */ function cached(fn) { var cache = Object.create(null); return (function cachedFn(str) { var hit = cache[str]; return hit || (cache[str] = fn(str)) }) } /** * Camelize a hyphen-delimited string. * 用连字符分隔的字符串。 * camelize = cachedFn(str)=>{ var hit = cache[str]; return hit || (cache[str] = fn(str))} 调用一个camelize 存一个建进来 调用两次 如果建一样就返回 hit 横线-的转换成驼峰写法 可以让这样的的属性 v-model 变成 vModel */ var camelizeRE = /-(\w)/g; var camelize = cached(function (str) { return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) }); /** * Capitalize a string. 将首字母变成大写。 */ var capitalize = cached(function (str) { return str.charAt(0).toUpperCase() + str.slice(1) }); /** * Hyphenate a camelCase string. * \B的用法 \B是非单词分界符,即可以查出是否包含某个字,如“ABCDEFGHIJK”中是否包含“BCDEFGHIJK”这个字。 */ var hyphenateRE = /\B([A-Z])/g; var hyphenate = cached(function (str) { //大写字母,加完减号又转成小写了 比如把驼峰 aBc 变成了 a-bc //匹配大写字母并且两面不是空白的 替换成 '-' + '字母' 在全部转换成小写 return str.replace(hyphenateRE, '-$1').toLowerCase(); }); /** * Simple bind polyfill for environments that do not support it... e.g. * PhantomJS 1.x. Technically we don't need this anymore since native bind is * now more performant in most browsers, but removing it would be breaking for * code that was able to run in PhantomJS 1.x, so this must be kept for * backwards compatibility. * 改变this 上下文 * 执行方式 */ /* istanbul ignore next */ //绑定事件 并且改变上下文指向 function polyfillBind(fn, ctx) { function boundFn(a) { var l = arguments.length; return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx) } boundFn._length = fn.length; return boundFn } //执行方式 function nativeBind(fn, ctx) { return fn.bind(ctx) } //bing 改变this上下文 var bind = Function.prototype.bind ? nativeBind : polyfillBind; /** * Convert an Array-like object to a real Array. * 将假的数组转换成真的数组 */ function toArray(list, start) { start = start || 0; var i = list.length - start; var ret = new Array(i); while (i--) { ret[i] = list[i + start]; } return ret } /** * Mix properties into target object. * * 浅拷贝 */ /*********************************************************************************************** *函数名 :extend *函数功能描述 : 浅拷贝 *函数参数 : to 超类, _from 子类 *函数返回值 : 合并类 *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 function extend(to, _from) { for (var key in _from) { to[key] = _from[key]; } return to } /** * Merge an Array of Objects into a single Object. * */ /*********************************************************************************************** *函数名 :toObject *函数功能描述 : 和并对象数组合并成一个对象 *函数参数 : arr 数组对象类 *函数返回值 : *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ function toObject(arr) { var res = {}; for (var i = 0; i < arr.length; i++) { if (arr[i]) { extend(res, arr[i]); } } return res } /** * Perform no operation. * Stubbing args to make Flow happy without leaving useless transpiled code * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) */ function noop(a, b, c) { } /** * Always return false. * 返回假的 */ var no = function (a, b, c) { return false; }; /** * Return same value *返回相同值 */ var identity = function (_) { return _; }; /** * Generate a static keys string from compiler modules. * * [{ staticKeys:1},{staticKeys:2},{staticKeys:3}] * 连接数组对象中的 staticKeys key值,连接成一个字符串 str=‘1,2,3’ */ function genStaticKeys(modules) { return modules.reduce( function (keys, m) { //累加staticKeys的值变成数组 return keys.concat(m.staticKeys || []) }, [] ).join(',') //转换成字符串 } /** * Check if two values are loosely equal - that is, * if they are plain objects, do they have the same shape? * 检测a和b的数据类型,是否是不是数组或者对象,对象的key长度一样即可,数组长度一样即可 */ function looseEqual(a, b) { if (a === b) { return true } //如果a和b是完全相等 则true var isObjectA = isObject(a); var isObjectB = isObject(b); if (isObjectA && isObjectB) { //如果a和都是对象则让下走 try { var isArrayA = Array.isArray(a); var isArrayB = Array.isArray(b); if (isArrayA && isArrayB) { //如果a和b都是数组 // every 条件判断 return a.length === b.length && a.every(function (e, i) { //如果a长度和b长度一样的时候 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]) }) } else { //如果不是对象跳槽循环 /* istanbul ignore next */ return false } } catch (e) { //如果不是对象跳槽循环 /* istanbul ignore next */ return false } } else if (!isObjectA && !isObjectB) { //b和a 都不是对象的时候 //把a和b变成字符串,判断他们是否相同 return String(a) === String(b) } else { return false } } // 判断 arr数组中的数组 是否和val相等。 // 或者 arr数组中的对象,或者对象数组 是否和val 相等 function looseIndexOf(arr, val) { for (var i = 0; i < arr.length; i++) { if (looseEqual(arr[i], val)) { return i } } return -1 } /** * Ensure a function is called only once. * 确保该函数只调用一次 闭包函数 */ function once(fn) { var called = false; return function () { if (!called) { called = true; fn.apply(this, arguments); } } } //ssr标记属性 var SSR_ATTR = 'data-server-rendered'; var ASSET_TYPES = [ 'component', //组建指令 'directive', //定义指令 指令 'filter' //过滤器指令 ]; var LIFECYCLE_HOOKS = [ 'beforeCreate', // 生命周期 开始实例化 vue 指令 'created', //生命周期 结束实例化完 vue 指令 'beforeMount', //生命周期 开始渲染虚拟dom ,挂载event 事件 指令 'mounted', //生命周期 渲染虚拟dom ,挂载event 事件 完 指令 'beforeUpdate', //生命周期 开始更新view 数据指令 'updated', //生命周期 结束更新view 数据指令 'beforeDestroy', //生命周期 开始销毁 new 实例 指令 'destroyed', //生命周期 结束销毁 new 实例 指令 'activated', //keep-alive组件激活时调用。 'deactivated', //deactivated keep-alive组件停用时调用。 'errorCaptured' // 具有此钩子的组件捕获其子组件树(不包括其自身)中的所有错误(不包括在异步回调中调用的那些)。 ]; /* */ var config = ({ /** * Option merge strategies (used in core/util/options) */ // $flow-disable-line //合并对象 策略 optionMergeStrategies: Object.create(null), /** * Whether to suppress warnings. * * 是否禁止警告。 */ silent: false, /** * Show production mode tip message on boot? * 在引导时显示生产模式提示消息? * webpack打包判断执行环境是不是生产环境,如果是生产环境会压缩并且没有提示警告之类的东西 */ productionTip: "development" !== 'production', /** * Whether to enable devtools * 是否启用DevTools */ devtools: "development" !== 'production', /** * Whether to record perf * 是否记录PERF */ performance: false, /** * Error handler for watcher errors *监视器错误的错误处理程序 */ errorHandler: null, /** * Warn handler for watcher warns * 观察加警告处理。 */ warnHandler: null, /** * Ignore certain custom elements * 忽略某些自定义元素 */ ignoredElements: [], /** * Custom user key aliases for v-on * 用于V-on的自定义用户密钥别名 键盘码 */ // $flow-disable-line keyCodes: Object.create(null), /** * Check if a tag is reserved so that it cannot be registered as a * component. This is platform-dependent and may be overwritten. * 检查是否保留了一个标签,使其不能注册为组件。这是平台相关的,可能会被覆盖。 */ isReservedTag: no, /** * Check if an attribute is reserved so that it cannot be used as a component * prop. This is platform-dependent and may be overwritten. * 检查属性是否被保留,使其不能用作组件支持。这是平台相关的,可能会被覆盖。 */ isReservedAttr: no, /** * Check if a tag is an unknown element. * Platform-dependent. * Check if a tag is an unknown element. Platform-dependent. * 检查标签是否为未知元素依赖于平台的检查,如果标签是未知元素。平台相关的 * */ isUnknownElement: no, /** * Get the namespace of an element * 获取元素的命名空间 */ getTagNamespace: noop, /** * Parse the real tag name for the specific platform. * 解析真实的标签平台 */ parsePlatformTagName: identity, /** * Check if an attribute must be bound using property, e.g. value * Platform-dependent. * 检查属性是否必须使用属性绑定,例如依赖于依赖于平台的属性。 */ mustUseProp: no, /** * Exposed for legacy reasons * 因遗产原因暴露 * 声明周期对象 */ _lifecycleHooks: LIFECYCLE_HOOKS }) /* */ /** * Check if a string starts with $ or _ * 检查一个字符串是否以$或者_开头 */ function isReserved(str) { var c = (str + '').charCodeAt(0); return c === 0x24 || c === 0x5F } /** * Define a property. * 用defineProperty 定义属性 * 详细地址 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty 第一个参数是对象 第二个是key 第三个是vue 第四个是 是否可以枚举 */ function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, //值 enumerable: !!enumerable, //定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。 writable: true, //可以 改写 value configurable: true //configurable特性表示对象的属性是否可以被删除,以及除writable特性外的其他特性是否可以被修改。 }); } /** * Parse simple path. * 解析。 */ var bailRE = /[^\w.$]/; //匹配不是 数字字母下划线 $符号 开头的为true function parsePath(path) { console.log(path) if (bailRE.test(path)) { //匹配上 返回 true return } //匹配不上 path在已点分割 var segments = path.split('.'); return function (obj) { for (var i = 0; i < segments.length; i++) { //如果没有参数则返回 if (!obj) { return } //将对象中的一个key值 赋值给该对象 相当于 obj = obj[segments[segments.length-1]]; obj = obj[segments[i]]; } //否则返回一个对象 return obj } } /* */ // can we use __proto__? var hasProto = '__proto__' in {}; // 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属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,通过这个属性来判断浏览器类型 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 var isEdge = UA && UA.indexOf('edge/') > 0; //ie10 以上 var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); //安卓 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... var nativeWatch = ({}).watch; //兼容火狐浏览器写法 var supportsPassive = false; if (inBrowser) { try { var opts = {}; Object.defineProperty(opts, 'passive', ({ get: function get() { /* istanbul ignore next */ supportsPassive = true; } })); // https://github.com/facebook/flow/issues/285 window.addEventListener('test-passive', null, opts); } catch (e) { } } // 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 服务器环境 var isServerRendering = function () { if (_isServer === undefined) { /* istanbul ignore if */ //如果不是浏览器 并且global 对象存在,那么有可能是node 脚本 if (!inBrowser && typeof global !== 'undefined') { // // detect presence of vue-server-renderer and avoid // Webpack shimming the process //_isServer 设置是服务器渲染 _isServer = global['process'].env.VUE_ENV === 'server'; } else { _isServer = false; } } return _isServer }; // detect devtools //检测开发者工具。 var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; /* istanbul ignore next */ function isNative(Ctor) { //或者判断该函数是不是系统内置函数 //判断一个函数中是否含有 'native code' 字符串 比如 // function code(){ // var native='native code' // } // 或者 // function code(){ // var native='native codeasdfsda' // } return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) } //判断是否支持Symbol 数据类型 var hasSymbol = //Symbol es6新出来的一种数据类型,类似于string类型,声明唯一的数据值 typeof Symbol !== 'undefined' && isNative(Symbol) && // Reflect.ownKeys // Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。 typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); var _Set; /* istanbul ignore if */ // $flow-disable-line //ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 // Set 本身是一个构造函数,用来生成 Set 数据结构。 //判断是否有set这个方法 if (typeof Set !== 'undefined' && isNative(Set)) { // use native Set when available. _Set = Set; } else { // a non-standard Set polyfill that only works with primitive keys. //如果没有他自己写一个 _Set = (function () { function Set() { this.set = Object.create(null); } Set.prototype.has = function has(key) { return this.set[key] === true }; Set.prototype.add = function add(key) { this.set[key] = true; }; Set.prototype.clear = function clear() { this.set = Object.create(null); }; return Set; }()); } var warn = noop; var tip = noop; var generateComponentTrace = (noop); // work around flow check 绕流检查 var formatComponentName = (noop); { //判断是否有console 打印输出属性 var hasConsole = typeof console !== 'undefined'; var classifyRE = /(?:^|[-_])(\w)/g; //非捕获 匹配不分组 。 就是可以包含,但是不匹配上 //过滤掉class中的 -_ 符号 并且把字母开头的改成大写 var classify = function (str) { return str.replace(classifyRE, function (c) { return c.toUpperCase(); }).replace(/[-_]/g, ''); }; /*********************************************************************************************** *函数名 :warn *函数功能描述 : 警告信息提示 *函数参数 : msg: 警告信息, vm:vue对象 *函数返回值 : void *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ warn = function (msg, vm) { //vm 如果没有传进来就给空, 不然给执行generateComponentTrace 收集 vue错误码 var trace = vm ? generateComponentTrace(vm) : ''; //warnHandler 如果存在 则调用他 if (config.warnHandler) { config.warnHandler.call(null, msg, vm, trace); } else if (hasConsole && (!config.silent)) { //如果config.warnHandler 不存在则 console 内置方法打印 console.error(("[Vue warn]: " + msg + trace)); } }; //也是个警告输出方法 tip = function (msg, vm) { if (hasConsole && (!config.silent)) { // console.warn("[Vue tip]: " + msg + ( vm ? generateComponentTrace(vm) : '' )); } }; /*********************************************************************************************** *函数名 :formatComponentName *函数功能描述 : 格式组件名 *函数参数 : msg: 警告信息, vm:vue对象 *函数返回值 : void *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ formatComponentName = function (vm, includeFile) { if (vm.$root === vm) { return '' } /* * 如果 vm === 'function' && vm.cid != null 条件成立 则options等于vm.options * 当vm === 'function' && vm.cid != null 条件不成立的时候 vm._isVue ? vm.$options || vm.constructor.options : vm || {}; * vm._isVue为真的时候 vm.$options || vm.constructor.options ,vm._isVue为假的时候 vm || {} * */ var options = typeof vm === 'function' && vm.cid != null ? vm.options : vm._isVue ? vm.$options || vm.constructor.options : vm || {}; var name = options.name || options._componentTag; console.log('name=' + name); var file = options.__file; if (!name && file) { //匹配.vue 后缀的文件名 //如果文件名中含有vue的文件将会被匹配出来 但是会多虑掉 \符号 var match = file.match(/([^/\\]+)\.vue$/); name = match && match[1]; } //可能返回 classify(name) //name 组件名称或者是文件名称 /* * classify 去掉-_连接 大些字母连接起来 * 如果name存在则返回name * 如果name不存在那么返回‘’+ 如果file存在并且includeFile!==false的时候 返回" at " + file 否则为空 * * */ return ( (name ? ("<" + (classify(name)) + ">") : "") + (file && includeFile !== false ? (" at " + file) : '') ) }; /* *重复 递归 除2次 方法+ str * */ var repeat = function (str, n) { var res = ''; while (n) { if (n % 2 === 1) { res += str; } if (n > 1) { str += str; } n >>= 1; //16 8 //15 7 相当于除2 向下取整2的倍数 //console.log( a >>= 1) } return res }; /*********************************************************************************************** *函数名 :generateComponentTrace *函数功能描述 : 生成组建跟踪 vm=vm.$parent递归收集到msg出处。 *函数参数 : vm 组建 *函数返回值 : *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ generateComponentTrace = function (vm) { if (vm._isVue && vm.$parent) { //如果_isVue 等于真,并且有父亲节点的 var tree = []; //记录父节点 var currentRecursiveSequence = 0; while (vm) { //循环 vm 节点 if (tree.length > 0) {//tree如果已经有父节点的 var last = tree[tree.length - 1]; if (last.constructor === vm.constructor) { //上一个节点等于父节点 个人感觉这里用户不会成立 currentRecursiveSequence++; vm = vm.$parent; continue } else if (currentRecursiveSequence > 0) { //这里也不会成立 tree[tree.length - 1] = [last, currentRecursiveSequence]; currentRecursiveSequence = 0; } } tree.push(vm); //把vm添加到队列中 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') } else { //如果没有父组件则输出一个组件名称 return ("\n\n(found in " + (formatComponentName(vm)) + ")") } }; } /* */ /* */ var uid = 0; /** * A dep is an observable that can have multiple dep是可观察到的,可以有多个 * directives subscribing to it.订阅它的指令。 * */ //主题对象Dep构造函数 主要用于添加发布事件后,用户更新数据的 响应式原理之一函数 var Dep = function Dep() { //uid 初始化为0 this.id = uid++; /* 用来存放Watcher对象的数组 */ this.subs = []; }; Dep.prototype.addSub = function addSub(sub) { /* 在subs中添加一个Watcher对象 */ this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub(sub) { /*删除 在subs中添加一个Watcher对象 */ remove(this.subs, sub); }; //this$1.deps[i].depend(); //为Watcher 添加 为Watcher.newDeps.push(dep); 一个dep对象 Dep.prototype.depend = function depend() { //添加一个dep target 是Watcher dep就是dep对象 if (Dep.target) { //像指令添加依赖项 Dep.target.addDep(this); } }; /* 通知所有Watcher对象更新视图 */ Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { //更新数据 subs[i].update(); } }; // 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 = []; function pushTarget(_target) { //target 是Watcher dep就是dep对象 if (Dep.target) { //静态标志 Dep当前是否有添加了target //添加一个pushTarget targetStack.push(Dep.target); } Dep.target = _target; } // function popTarget() { // 出盏一个pushTarget Dep.target = targetStack.pop(); } /* * 创建标准的vue vnode * * */ var VNode = function VNode( tag, /*当前节点的标签名*/ data, /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ children, //子节点 text, //文本 elm, /*当前节点的dom */ context, /*编译作用域*/ componentOptions, /*组件的option选项*/ asyncFactory/*异步工厂*/) { /*当前节点的标签名*/ this.tag = tag; /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ this.data = data; /*当前节点的子节点,是一个数组*/ this.children = children; /*当前节点的文本*/ this.text = text; /*当前虚拟节点对应的真实dom节点*/ this.elm = elm; /*当前节点的名字空间*/ this.ns = undefined; /*编译作用域 vm*/ this.context = context; this.fnContext = undefined; this.fnOptions = undefined; this.fnScopeId = undefined; /*节点的key属性,被当作节点的标志,用以优化*/ this.key = data && data.key; /*组件的option选项*/ this.componentOptions = componentOptions; /*当前节点对应的组件的实例*/ this.componentInstance = undefined; /*当前节点的父节点*/ this.parent = undefined; /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/ this.raw = false; /*静态节点标志*/ this.isStatic = false; /*是否作为跟节点插入*/ this.isRootInsert = true; /*是否为注释节点*/ this.isComment = false; /*是否为克隆节点*/ this.isCloned = false; /*是否有v-once指令*/ this.isOnce = false; /*异步工厂*/ this.asyncFactory = asyncFactory; this.asyncMeta = undefined; this.isAsyncPlaceholder = false; }; //当且仅当该属性描述符的类型可以被改变并且该属性可以从对应对象中删除。默认为 false var prototypeAccessors = { child: { configurable: true } }; // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ prototypeAccessors.child.get = function () { return this.componentInstance }; /*设置所有VNode.prototype 属性方法 都为 { 'child':{ configurable: true, get:function(){ return this.componentInstance } } } */ Object.defineProperties(VNode.prototype, prototypeAccessors); //创建一个节点 空的vnode var createEmptyVNode = function (text) { if (text === void 0) text = ''; var node = new VNode(); node.text = text; node.isComment = true; return node }; //创建一个文本节点 function createTextVNode(val) { return new VNode( undefined, undefined, undefined, String(val) ) } // 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) { // var componentOptions = vnode.componentOptions; /*组件的option选项*/ var cloned = new VNode( vnode.tag, vnode.data, vnode.children, vnode.text, vnode.elm, vnode.context, componentOptions, vnode.asyncFactory ); cloned.ns = vnode.ns;/*当前节点的名字空间*/ cloned.isStatic = vnode.isStatic;/*静态节点标志*/ cloned.key = vnode.key;/*节点的key属性,被当作节点的标志,用以优化*/ cloned.isComment = vnode.isComment;/*是否为注释节点*/ cloned.fnContext = vnode.fnContext; //函数上下文 cloned.fnOptions = vnode.fnOptions; //函数Options选项 cloned.fnScopeId = vnode.fnScopeId; //函数范围id cloned.isCloned = true; /*是否为克隆节点*/ if (deep) { //如果deep存在 if (vnode.children) { //如果有子节点 //深度拷贝子节点 cloned.children = cloneVNodes(vnode.children, true); } if (componentOptions && componentOptions.children) { //深度拷贝子节点 componentOptions.children = cloneVNodes(componentOptions.children, true); } } return cloned } //克隆多个节点 为数组的 function cloneVNodes(vnodes, deep) { var len = vnodes.length; var res = new Array(len); for (var i = 0; i < len; i++) { res[i] = cloneVNode(vnodes[i], deep); } return res } /* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]; /** * Intercept mutating methods and emit events */ /*********************************************************************************************** *函数名 :methodsToPatch *函数功能描述 : 更新数据时候如果是数组拦截方法,如果在数据中更新用的是'push','pop','shift','unshift','splice','sort','reverse' 方法则会调用这里 *函数参数 : *函数返回值 : *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ methodsToPatch.forEach(function (method) { console.log('methodsToPatch') // cache original method var original = arrayProto[method]; console.log('==method==') console.log(method) console.log('==original==') console.log(original) def(arrayMethods, method, function mutator() { console.log('==def_original==') console.log(original) var args = [], len = arguments.length; while (len--) args[len] = arguments[len]; var result = original.apply(this, args); var ob = this.__ob__; console.log('this.__ob__') console.log(this.__ob__) var inserted; switch (method) { case 'push': case 'unshift': inserted = args; break case 'splice': inserted = args.slice(2); break } if (inserted) { //观察数组数据 ob.observeArray(inserted); } // notify change //更新通知 ob.dep.notify(); console.log('====result====') console.log(result) return result }); }); /* */ // 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组,只包括实例化的属性和方法,不包括原型上的。 var arrayKeys = Object.getOwnPropertyNames(arrayMethods); /** * In some cases we may want to disable observation inside a component's * update computation. *在某些情况下,我们可能希望禁用组件内部的观察。 *更新计算。 */ var shouldObserve = true; //标志是否禁止还是添加到观察者模式 function toggleObserving(value) { shouldObserve = value; } /** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. * *每个观察到的观察者类 *对象。一旦被连接,观察者就转换目标。 *对象的属性键为吸收器/设置器 *收集依赖关系并发送更新。 * * 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性 */ var Observer = function Observer(value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; //设置监听 value 必须是对象 def(value, '__ob__', this); if (Array.isArray(value)) { //判断是不是数组 var augment = hasProto //__proto__ 存在么 高级浏览器都会有这个 ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } }; /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. * *遍历每个属性并将其转换为 * getter / setter。此方法只应在调用时调用 *值类型是Object。 */ Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } }; /** * Observe a list of Array items. * 观察数组项的列表。 * 把数组拆分一个个 添加到观察者 上面去 */ Observer.prototype.observeArray = function observeArray(items) { for (var i = 0, l = items.length; i < l; i++) { console.log('items[i]') console.log(items[i]) observe(items[i]); } }; // helpers /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ * 通过拦截来增强目标对象或数组 * 使用原型原型链 * target 目标对象 * src 原型 对象或者属性、 * keys key * */ function protoAugment(target, src, keys) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * Augment an target Object or Array by defining * hidden properties. * 复制扩充 * 定义添加属性 并且添加 监听 *target 目标对象 * src对象 * keys 数组keys */ /* istanbul ignore next */ function copyAugment(target, src, keys) { for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; def(target, key, src[key]); } } /** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. *尝试为值创建一个观察者实例, *如果成功观察,返回新的观察者; *或现有的观察员,如果值已经有一个。 * * 判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性 返回 new Observer 实例化的对象 */ function observe(value, asRootData) { if (!isObject(value) || value instanceof VNode) { //value 不是一个对象 或者 实例化 的VNode console.log(value) return } var ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { console.log('hasOwn value') console.log(value) ob = value.__ob__; } else if ( shouldObserve && //shouldObserve 为真 !isServerRendering() && //并且不是在服务器node环境下 (Array.isArray(value) || isPlainObject(value)) && //是数组或者是对象 Object.isExtensible(value) && //Object.preventExtensions(O) 方法用于锁住对象属性,使其不能够拓展,也就是不能增加新的属性,但是属性的值仍然可以更改,也可以把属性删除,Object.isExtensible用于判断对象是否可以被拓展 !value._isVue //_isVue为假 ) { console.log('new Observer value') console.log(value) //实例化 dep对象 为 value添加__ob__ 属性 ob = new Observer(value); } console.log(value) //如果是RootData,即咱们在新建Vue实例时,传到data里的值,只有RootData在每次observe的时候,会进行计数。 vmCount是用来记录此Vue实例被使用的次数的, 比如,我们有一个组件logo,页面头部和尾部都需要展示logo,都用了这个组件,那么这个时候vmCount就会计数,值为2 if (asRootData && ob) { //是根节点数据的话 并且 ob 存在 ob.vmCount++; //统计有几个vm } // * 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性 return ob } /** * Define a reactive property on an Object. * 在对象上定义一个无功属性。 * 更新数据 * 通过defineProperty的set方法去通知notify()订阅者subscribers有新的值修改 * 添加观察者 get set方法 */ function defineReactive(obj, //对象 key,//对象的key val, //监听的数据 返回的数据 customSetter, // 日志函数 shallow //是否要添加__ob__ 属性 ) { //实例化一个主题对象,对象中有空的观察者列表 var dep = new Dep(); //获取描述属性 var property = Object.getOwnPropertyDescriptor(obj, key); var _property = Object.getOwnPropertyNames(obj); //获取实力对象属性或者方法,包括定义的描述属性 console.log(property); console.log(_property); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; console.log('arguments.length=' + arguments.length) if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; console.log(val) //判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性递归把val添加到观察者中 返回 new Observer 实例化的对象 var childOb = !shallow && observe(val); //定义描述 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if (Dep.target) { //Dep.target 静态标志 标志了Dep添加了Watcher 实例化的对象 //添加一个dep dep.depend(); if (childOb) { //如果子节点存在也添加一个dep childOb.dep.depend(); if (Array.isArray(value)) { //判断是否是数组 如果是数组 dependArray(value); //则数组也添加dep } } } return value }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare 新旧值比较 如果是一样则不执行了*/ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare * 不是生产环境的情况下 * */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { //set 方法 设置新的值 setter.call(obj, newVal); } else { //新的值直接给他 val = newVal; } console.log(newVal) //observe 添加 观察者 childOb = !shallow && observe(newVal); //更新数据 dep.notify(); } }); } /** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. **在对象上设置属性。添加新属性和 *触发器更改通知,如果该属性不 *已经存在。 */ //如果是数组 并且key是数字 就更新数组 //如果是对象则重新赋值 //如果 (target).__ob__ 存在则表明该数据以前添加过观察者对象中 //通知订阅者ob.value更新数据 添加观察者 define set get 方法 function set(target, key, val) { if ("development" !== 'production' && //判断数据 是否是undefined或者null (isUndef(target) || isPrimitive(target)) //判断数据类型是否是string,number,symbol,boolean ) { //必须是对象数组才可以 否则发出警告 warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); } //如果是数组 并且key是数字 if (Array.isArray(target) && isValidArrayIndex(key)) { //设置数组的长度 target.length = Math.max(target.length, key); //像数组尾部添加一个新数据,相当于push target.splice(key, 1, val); return val } //判断key是否在target 上,并且不是在Object.prototype 原型上,而不是通过父层原型链查找的 if (key in target && !(key in Object.prototype)) { target[key] = val; //赋值 return val } var ob = (target).__ob__; //声明一个对象ob 值为该target对象中的原型上面的所有方法和属性 ,表明该数据加入过观察者中 //vmCount 记录vue被实例化的次数 //是不是vue if (target._isVue || (ob && ob.vmCount)) { //如果不是生产环境,发出警告 "development" !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ); return val } //如果ob不存在 说明他没有添加观察者 则直接赋值 if (!ob) { target[key] = val; return val } //通知订阅者ob.value更新数据 添加观察者 define set get 方法 defineReactive(ob.value, key, val); //通知订阅者ob.value更新数据 ob.dep.notify(); return val } /** * Delete a property and trigger change if necessary. * 删除属性并在必要时触发更改数据。 */ function del(target, key) { //如果不是生产环境 if ("development" !== 'production' && (isUndef(target) || isPrimitive(target)) ) { //无法删除未定义的、空的或原始值的无功属性: warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); } //如果是数据则用splice方法删除 if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return } var ob = (target).__ob__; //vmCount 记录vue被实例化的次数 //是不是vue if (target._isVue || (ob && ob.vmCount)) { //如果是开发环境就警告 "development" !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ); return } //如果不是target 实例化不删除原型方法 if (!hasOwn(target, key)) { return } //删除对象中的属性或者方法 delete target[key]; if (!ob) { return } //更新数据 ob.dep.notify(); } /** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. * 在数组被触摸时收集数组元素的依赖关系,因为 * 我们不能拦截数组元素访问,如属性吸收器。 * 参数是数组 */ function dependArray(value) { for (var e = (void 0), i = 0, l = value.length; i < l; i++) { e = value[i]; //添加一个dep e && e.__ob__ && e.__ob__.dep.depend(); //递归 if (Array.isArray(e)) { dependArray(e); } } } /* */ /** * Option overwriting strategies are functions that handle * how to merge a parent option value and a child option * value into the final value. * *选项重写策略是处理的函数 *如何合并父选项值和子选项 *值为最终值。 */ //选择策略 var strats = config.optionMergeStrategies; /** * Options with restrictions * 选择与限制 */ { strats.el = strats.propsData = function (parent, child, vm, key) { if (!vm) { warn( "option \"" + key + "\" can only be used during instance " + 'creation with the `new` keyword.' ); } //默认开始 return defaultStrat(parent, child) }; } /** * Helper that recursively merges two data objects together. * 递归合并数据 深度拷贝 */ function mergeData(to, from) { if (!from) { return to } var key, toVal, fromVal; var keys = Object.keys(from); //获取对象的keys 变成数组 for (var i = 0; i < keys.length; i++) { key = keys[i]; //获取对象的key toVal = to[key]; // fromVal = from[key]; //获取对象的值 if (!hasOwn(to, key)) { //如果from对象的key在to对象中没有 set(to, key, fromVal); } else if (isPlainObject(toVal) && isPlainObject(fromVal)) { //深层递归 mergeData(toVal, fromVal); } } return to } /** * Data * 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 } } } } 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) } return mergeDataOrFn(parentVal, childVal, vm) }; /** * Hooks and props are merged as arrays. * 钩子和道具被合并成数组。 * 判断childVal存在么?如果不存在 则返回parentVal * 如果childVal存在 则判断parentVal存在么。如果parentVal存在则返回 parentVal.concat(childVal),如果不存在,则判断childVal是不是数组如果是数组直接返回去, * 如果不是数组把childVal变成数组在返回出去 */ function mergeHook( parentVal, childVal ) { return childVal ? (parentVal ? parentVal.concat(childVal) : (Array.isArray(childVal) ? childVal : [childVal] ) ) : parentVal } LIFECYCLE_HOOKS.forEach(function (hook) { strats[hook] = mergeHook; }); /** * Assets * * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. * *资产 *当存在虚拟机(实例创建)时,我们需要做 *构造函数选项之间的三路合并,实例 *选项和父选项。 * 创建一个res对象,获取parentVal对象中的数据。如果parentVal存在则获取parentVal对象 的数据存在res中的 __props__ 中,如果没有则创建一个空的对象。 * 如果childVal 存在,则用浅拷贝吧 childVal 合并到res中,返回res对象 */ function mergeAssets( parentVal, childVal, vm, key ) { var res = Object.create(parentVal || null); if (childVal) { "development" !== 'production' && assertObjectType(key, childVal, vm); return extend(res, childVal) } else { return res } } //为每一个组件指令添加一个 ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets; }); /** * Watchers. * * Watchers hashes should not overwrite one * another, so we merge them as arrays. * *观察者散列不应该覆盖一个 *另一个,所以我们将它们合并为数组。 * * 循环childVal。获取到子节点childVal的key如果在父亲节点上面有,则先获取到父亲节点的值,如果父亲节点的上没有值得获取子节点的值。 变成数组存在ret对象中。 */ strats.watch = function ( 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; if (parentVal === nativeWatch) { parentVal = undefined; } if (childVal === nativeWatch) { childVal = undefined; } /* istanbul ignore if */ if (!childVal) { //如果子节点不存在 则创建一个 对象 return Object.create(parentVal || null) } { //检测childVal是不是对象 assertObjectType(key, childVal, vm); } if (!parentVal) { //如果父节点不存在 则返回子节点 return childVal } var ret = {}; extend(ret, parentVal); //合并对象 一个新的对象 for (var key$1 in childVal) { //循环子节点 var parent = ret[key$1]; // 把子节点的kye放到父节点中 var child = childVal[key$1]; //获取子节点的值 if (parent && !Array.isArray(parent)) { //如果子节点的key放到父节点中能获取到子节点 ,并且子节点不是一个数组 parent = [parent]; // } ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]; } return ret }; /** * Other object hashes. */ 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.provide = mergeDataOrFn; /** * Default strategy. * 如果没有子节点就返回父节点,如果有子节点就返回子节点 */ var defaultStrat = function (parentVal, childVal) { return childVal === undefined ? parentVal : childVal }; /** * Validate component names *验证组件名称 */ function checkComponents(options) { for (var key in options.components) { // 验证组件名称 必须是大小写,并且是-横杆 validateComponentName(key); } } //验证组件名称 必须是大小写,并且是-横杆 function validateComponentName(name) { if (!/^[a-zA-Z][\w-]*$/.test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + 'can only contain alphanumeric characters and the hyphen, ' + 'and must start with a letter.' ); } if (isBuiltInTag(name) || config.isReservedTag(name)) { warn( 'Do not use built-in or reserved HTML elements as component ' + 'id: ' + name ); } } /** * Ensure all props option syntax are normalized into the * 确保所有props选项语法都规范化为 * Object-based format. * 基于对象格式 * * 检查 props 数据类型 * normalizeProps 检查 props 数据类型,并把type标志打上。如果是数组循环props属性数组,如果val是string则把它变成驼峰写法 res[name] = {type: null}; 。如果是对象也循环props把key变成驼峰,并且判断val是不是对象如果是对象则 res[name] 是{type: val}否则 res[name] 是val。 * */ function normalizeProps(options, vm) { //参数中有没有props var props = options.props; if (!props) { return } var res = {}; var i, val, name; //如果props 是一个数组 if (Array.isArray(props)) { i = props.length; while (i--) { val = props[i]; if (typeof val === 'string') { //把含有横岗的字符串 变成驼峰写法 name = camelize(val); res[name] = { type: null }; } else { //当使用数组语法时,道具必须是字符串。 如果是props 是数组必须是字符串 warn('props must be strings when using array syntax.'); } } } else if (isPlainObject(props)) { //如果是对象 for (var key in props) { //for in 提取值 val = props[key]; name = camelize(key); //把含有横岗的字符串 变成驼峰写法 res[name] = isPlainObject(val) //判断值是不是对象 ? val : { type: val }; } } else { //如果不是对象和数组则警告 warn( "Invalid value for option \"props\": expected an Array or an Object, " + "but got " + (toRawType(props)) + ".", vm ); } options.props = res; } /** * Normalize all injections into Object-based format * 将所有注入规范化为基于对象的格式 * * * 将数组转化成对象 比如 [1,2,3]转化成 * normalized[1]={from: 1} * normalized[2]={from: 2} * normalized[3]={from: 3} * * * * */ function normalizeInject(options, vm) { // provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。 // 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。 var inject = options.inject; if (!inject) { return } 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] }; } } else if (isPlainObject(inject)) { //如果是对象 for (var key in inject) { var val = inject[key]; normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val }; } } else { warn( "Invalid value for option \"inject\": expected an Array or an Object, " + "but got " + (toRawType(inject)) + ".", vm ); } } /** * Normalize raw function directives into object format. * * 将原始函数指令归一化为对象格式。 * * * normalizeDirectives获取到指令对象值。循环对象指令的值,如果是函数则把它变成dirs[key] = {bind: def, update: def} 这种形式 */ function normalizeDirectives(options) { //获取参数中的指令 var dirs = options.directives; console.log(options) if (dirs) { //如果指令存在 for (var key in dirs) { //循环该指令 var def = dirs[key]; //获取到指令的值 console.log(def) if (typeof def === 'function') { //如果是函数 //为该函数添加一个对象和值 dirs[key] = { bind: def, update: def }; } } } } //判断是否是对象 function assertObjectType(name, value, vm) { if (!isPlainObject(value)) { warn( "Invalid value for option \"" + name + "\": expected an Object, " + "but got " + (toRawType(value)) + ".", vm ); } } /** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. * 将两个对象合成一个对象 将父值对象和子值对象合并在一起,并且优先取值子值,如果没有则取子值 * * 用于实例化和继承的核心实用程序。 */ function mergeOptions(parent, //父值 child, //子值 优选取子值 vm) { { //检验子组件 checkComponents(child); } if (typeof child === 'function') { //如果child 是函数则获取他的参数 child = child.options; } //检查 props 数据类型 normalizeProps(child, vm); // 将数组转化成对象 比如 [1,2,3]转化成 normalizeInject(child, vm); // * normalizeDirectives获取到指令对象值。循环对象指令的值,如果是函数则把它变成dirs[key] = {bind: def, update: def} 这种形式 normalizeDirectives(child); //子组件是否有需要合并的对象继承 方式 var extendsFrom = child.extends; if (extendsFrom) { //如果有则递归 parent = mergeOptions(parent, extendsFrom, vm); } //如果 子组件有mixins 数组 则也递归合并,继承 方式 mixins 必须是数组 if (child.mixins) { for (var i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); } } var options = {}; var key; for (key in parent) { //循环合并后的key mergeField(key); } for (key in child) { //循环子组件的 if (!hasOwn(parent, key)) { mergeField(key); } } //获取到key 去读取strats类的方法 // strats类 有方法 el,propsData,data,provide,watch,props,methods,inject,computed,components,directives,filters 。 // strats类里面的方法都是 合并数据 如果没有子节点childVal, // 就返回父节点parentVal,如果有子节点childVal就返回子节点childVal。 function mergeField(key) { //defaultStrat 获取子值还是父组的值 var strat = strats[key] || // defaultStrat; //* 如果没有子节点就返回父节点,如果有子节点就返回子节点 //获取子值还是父组的值 options[key] = strat(parent[key], child[key], vm, key); } //返回参数 return options } /** * Resolve an asset. * This function is used because child instances need access * to assets defined in its ancestor chain. * 检测指令是否在 组件对象上面 ,返回注册指令或者组建的对象, 包括检查directives , filters ,components * */ function resolveAsset(options, //参数 type, // 类型:directives , filters ,components id, // 指令的key 属性 warnMissing //警告的信息 true ) { console.log('==resolveAsset==') console.log(options) console.log(type) console.log(id) console.log(warnMissing) /* istanbul ignore if 如果id不是字符串 */ if (typeof id !== 'string') { return } var assets = options[type]; // console.log('==assets==') console.log(assets) // check local registration variations first //首先检查本地注册的变化 检查id是否是assets 实例化的属性或者方法 if (hasOwn(assets, id)) { return assets[id] } // 可以让这样的的属性 v-model 变成 vModel 变成驼峰 var camelizedId = camelize(id); console.log('==camelizedId==') console.log(camelizedId) // 检查camelizedId是否是assets 实例化的属性或者方法 if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } console.log('==assets==') console.log(assets) // 将首字母变成大写 变成 VModel var PascalCaseId = capitalize(camelizedId); console.log('==PascalCaseId==') console.log(PascalCaseId) // 检查PascalCaseId是否是assets 实例化的属性或者方法 if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } console.log('==assets==') console.log(assets) console.log('==id-camelizedId-PascalCaseId==') console.log(assets) console.log(assets[id]) console.log(assets[camelizedId]) console.log(assets[PascalCaseId]) // fallback to prototype chain 回到原型链 var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; //如果检查不到id 实例化则如果是开发环境则警告 if ("development" !== 'production' && warnMissing && !res) { warn( 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, options ); } console.log('==res==') console.log(res) //返回注册指令或者组建的对象 return res } /* *验证支柱 验证 prosp 是否是规范数据 并且为props 添加 value.__ob__ 属性,把prosp添加到观察者中 * 校验 props 参数 就是组建 定义的props 类型数据,校验类型 * * 判断prop.type的类型是不是Boolean或者String,如果不是他们两类型,调用getPropDefaultValue获取默认值并且把value添加到观察者模式中 * * */ 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 } /** * Get the default value of a prop. *获取prop 属性默认的vue值 */ function getPropDefaultValue(vm, prop, key) { // no default, return undefined //判断该对象prop 中的default 是否是prop 实例化的 if (!hasOwn(prop, 'default')) { return undefined } var def = prop.default; // warn against non-factory defaults for Object & Array //警告对象和数组的非工厂默认值 if ("development" !== 'production' && isObject(def)) { warn( 'Invalid default value for prop "' + key + '": ' + 'Props with type Object/Array must use a factory function ' + 'to return the default value.', vm ); } // the raw prop value was also undefined from previous render, //原始PROP值也未从先前的渲染中定义, // return previous default value to avoid unnecessary watcher trigger //返回先前的默认值以避免不必要的监视触发器 if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined ) { return vm._props[key] } // call factory function for non-Function types //非功能类型调用工厂函数 // a value is Function if its prototype is function even across different execution context //一个值是函数,即使它的原型在不同的执行上下文中也是函数。 //getType检查函数是否是函数声明 如果是函数表达式或者匿名函数是匹配不上的 //判断def 是不是函数 如果是则执行,如果不是则返回props的PropDefaultValue return typeof def === 'function' && getType(prop.type) !== 'Function' ? def.call(vm) : def } /** * Assert whether a prop is valid. * 断言一个属性是否有效。 * * * * prop, //属性的type值 key, //props属性中的key value, //view 属性的值 vm, //组件构造函数 absent //false */ 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 ); } } } //检测数据类型 是否是String|Number|Boolean|Function|Symbol 其中的一个数据类型 var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; //获取type类型 function assertType(value, type) { var valid; //getType检查函数是否是函数声明 如果是函数表达式或者匿名函数是匹配不上的 //type 必须是String|Number|Boolean|Function|Symbol 构造函数 var expectedType = getType(type); //检测改函数是什么类型 if (simpleCheckRE.test(expectedType)) { //type 必须是String|Number|Boolean|Function|Symbol 构造函数 这里才为真 (String|Number|Boolean|Function|Symbol) var t = typeof value; //转换成小写 valid = t === expectedType.toLowerCase(); //布尔值 // for primitive wrapper objects 对于原始包装对象 if (!valid && t === 'object') { valid = value instanceof type; } } else if (expectedType === 'Object') { //检测是否是真正的对象 valid = isPlainObject(value); } else if (expectedType === 'Array') { //检测是否是真正的数组 valid = Array.isArray(value); } else { //判断 value 是否是type中的实例化对象 valid = value instanceof type; } //返回出去值 return { valid: valid, expectedType: expectedType } } /** * Use function string name to check built-in types, * because a simple equality check will fail when running * across different vms / iframes. * 检查函数是否是函数声明 如果是函数表达式或者匿名函数是匹配不上的 * * */ function getType(fn) { var match = fn && fn.toString().match(/^\s*function (\w+)/); return match ? match[1] : '' } //判断两个函数声明是否是相等 function isSameType(a, b) { return getType(a) === getType(b) } //判断expectedTypes 中的函数和 type 函数是否有相等的如有有则返回索引index 如果没有则返回-1 function getTypeIndex(type, expectedTypes) { //如果不是数组直接比较 如果真则返回0 if (!Array.isArray(expectedTypes)) { return isSameType(expectedTypes, type) ? 0 : -1 } for (var i = 0, len = expectedTypes.length; i < len; i++) { //如果是数组则寻找索引 if (isSameType(expectedTypes[i], type)) { return i } } return -1 } /* 向外暴露了一个 handleError 方法,在需要捕获异常的地方调用。 handleError 方法中首先获取到报错的组件,之后递归查找当前组件的父组件, 依次调用 errorCaptured 方法。在遍历调用完所有 errorCaptured 方法、或 errorCaptured 方法有报错时, 会调用 globalHandleError 方法。 globalHandleError 方法调用了全局的 errorHandler 方法。 如果 errorHandler 方法自己又报错了呢?生产环境下会使用 console.error 在控制台中输出。 可以看到 errorCaptured 和 errorHandler 的触发时机都是相同的,不同的是 errorCaptured 发生在前, 且如果某个组件的 errorCaptured 方法返回了 false,那么这个异常信息不会再向上冒泡也不会再调用 errorHandler 方法。 */ function handleError(err, vm, info) { if (vm) { var cur = vm; //循环父组件 while ((cur = cur.$parent)) { //如果hooks 存在 则循环 所有的hooks var hooks = cur.$options.errorCaptured; if (hooks) { for (var i = 0; i < hooks.length; i++) { try { //调用hooks 中函数,如果发生错误则调用globalHandleError var capture = hooks[i].call(cur, err, vm, info) === false; if (capture) { return } } catch (e) { //调用全局日志输出 globalHandleError(e, cur, 'errorCaptured hook'); } } } } } //调用全局日志输出 globalHandleError(err, vm, info); } function globalHandleError(err, vm, info) { //如果errorHandler 存在 则调用 errorHandler函数 if (config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info) } catch (e) { //错误日志信息输出 logError(e, null, 'config.errorHandler'); } } logError(err, vm, info); } //错误日志信息输出 function logError(err, vm, info) { { warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); } /* istanbul ignore else 如果是浏览器或者是 微信端,输出console */ if ((inBrowser || inWeex) && typeof console !== 'undefined') { console.error(err); } else { //如果是服务器端 则抛出错误 throw err } } /* */ /* globals MessageChannel 全局消息通道 */ //回调函数队列 var callbacks = []; var pending = false; // 触发 callbacks 队列中的函数 function flushCallbacks() { pending = false; //.slice(0) 浅拷贝 var copies = callbacks.slice(0); callbacks.length = 0; console.log(copies) for (var i = 0; i < copies.length; i++) { //执行回调函数 copies[i](); } } // 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. 在同一循环中触发的事件是通过使用消息通道。 /* istanbul ignore if */ //判断setImmediate 是否存在,如果存在则判断下是是否是系统内置函数 if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { //函数表达式赋值给macroTimerFunc macroTimerFunc = function () { setImmediate(flushCallbacks); }; } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]' )) { //如果有 消息体 内置函数则实例化 var channel = new MessageChannel(); //获取端口2 var port = channel.port2; //设置端口1 的接受函数为flushCallbacks channel.port1.onmessage = flushCallbacks; //端口2推送信息给端口1 macroTimerFunc = function () { port.postMessage(1); }; } else { /* istanbul ignore next */ // 异步执行 macroTimerFunc = function () { setTimeout(flushCallbacks, 0); }; } // Determine microtask defer implementation. //确定微任务延迟执行。 /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { // 声明一个成功的 Promise var p = Promise.resolve(); //microTimerFunc 一个异步 队列函数 microTimerFunc = function () { p.then(flushCallbacks); // in problematic UIWebViews, Promise.then doesn't completely break, but 在有问题的UIWebVIEW中,Promise.then并没有完全崩溃,而是 // it can get stuck in a weird state where callbacks are pushed into the 它可能会陷入一种怪异的状态,其中回调被推到 // microtask queue but the queue isn't being flushed, until the browser 微任务队列,但队列没有刷新,直到浏览器 // needs to do some other work, e.g. handle a timer. Therefore we can 需要做一些其他的工作,例如处理计时器。因此我们可以 // "force" the microtask queue to be flushed by adding an empty timer. [强制]通过添加空计时器来刷新微任务队列。 //如果是ios 执行下 noop 空函数 if (isIOS) { setTimeout(noop); } }; } else { // fallback to macro //回归宏 microTimerFunc = macroTimerFunc; } /** * Wrap a function so that if any code inside triggers state change, 包装一个函数,如果内部的任何代码触发状态改变, * the changes are queued using a (macro) task instead of a microtask. 使用宏(宏)任务而不是微任务对这些队列进行排队 */ function withMacroTask(fn) { //宏任务 return fn._withTask || (fn._withTask = function () { useMacroTask = true; var res = fn.apply(null, arguments); useMacroTask = false; return res }) } //为callbacks 收集队列cb 函数 并且根据 pending 状态是否要触发callbacks 队列函数 function nextTick(cb, ctx) { //cb 回调函数 //ctx this的指向 var _resolve; //添加一个回调函数到队列里面去 callbacks.push(function () { if (cb) { //如果cb存在 并且是一个函数就执行 try { cb.call(ctx); } catch (e) { //如果不是函数则报错 handleError(e, ctx, 'nextTick'); } } else if (_resolve) { //_resolve 如果存在则执行 _resolve(ctx); } }); console.log('==callbacks==') console.log(callbacks) console.log(pending) if (!pending) { pending = true; //执行异步宏任务 if (useMacroTask) { macroTimerFunc(); //异步触发 或者 实现观察者 触发 callbacks 队列中的函数 } else { microTimerFunc(); //异步触发 或者 实现观察者 触发 callbacks 队列中的函数 } } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { //如果回调函数不存在 则声明一个Promise 函数 return new Promise(function (resolve) { _resolve = resolve; }) } } /* */ var mark; var measure; { //浏览器性能监控 var perf = inBrowser && window.performance; /* istanbul ignore if */ if ( perf && perf.mark && perf.measure && perf.clearMarks && perf.clearMeasures ) { mark = function (tag) { return perf.mark(tag); }; measure = function (name, startTag, endTag) { perf.measure(name, startTag, endTag); perf.clearMarks(startTag); perf.clearMarks(endTag); perf.clearMeasures(name); }; } } /* not type checking this file because flow doesn't play well with Proxy 不检查此文件,因为流不能很好地使用代理 * */ var initProxy; { //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true} /*全局api 匹配'Infinity,undefined,NaN,isFinite,isNaN,' + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 'require' */ var allowedGlobals = makeMap( 'Infinity,undefined,NaN,isFinite,isNaN,' + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 'require' // for Webpack/Browserify ); //不存在的key 发出警告 var warnNonPresent = function (target, key) { warn( "Property or method \"" + key + "\" is not defined on the instance but " + 'referenced during render. Make sure that this property is reactive, ' + 'either in the data option, or for class-based components, by ' + 'initializing the property. ' + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', target ); }; //判断 系统内置 函数有没有 es6的Proxy 代理对象api var hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy); if (hasProxy) { //这些修改键就是 Shift、Ctrl、Alt和 Meta(在 Windows键盘中是 Windows键,在苹果机中 是 Cmd 键)它们经常被用来修改鼠标事件的行为。 var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); //声明代理拦截对象 config.keyCodes = new Proxy(config.keyCodes, { set: function set(target, key, value) { if (isBuiltInModifier(key)) { //匹配键盘上的快捷键 'stop,prevent,self,ctrl,shift,alt,meta,exact' //避免在配置键代码中重写内置修改器: 在一些快捷键中不需要加vue事件修饰器 warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); return false } else { //记录不是快捷键的键盘码 target[key] = value; return true } } }); } var hasHandler = { has: function has(target, key) { var has = key in target; //是否含有全局api 就是window 的内置函数 //全局api // var allowedGlobals = makeMap( // 'Infinity,undefined,NaN,isFinite,isNaN,' + // 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + // 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + // 'require' // for Webpack/Browserify // ); var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; //如果 key 在target对象中 不存在 或者 isAllowed 不是全局api 并且 第一个字符不是_的时候 发出警告 if (!has && !isAllowed) { //不存在key发出警告 warnNonPresent(target, key); } //返回true return has || !isAllowed } }; var getHandler = { get: function get(target, key) { //key必须是等于string 并且 key在target中含有属性或者方法 if (typeof key === 'string' && !(key in target)) { //如果没有则发出警告 warnNonPresent(target, key); } //返回target值 return target[key] } }; //初始化 代理 监听 initProxy = function initProxy(vm) { if (hasProxy) { // determine which proxy handler to use 确定使用哪个代理处理程序 var options = vm.$options; //获取vm中的参数 //render 渲染 如果是渲染 并且含有_withStripped var handlers = options.render && options.render._withStripped ? getHandler //获取值 : hasHandler; //判断内部函数,这样vue中模板就可以使用内置函数 //实例化 代理对象,只是这里添加了 警告的日志而已 vm._renderProxy = new Proxy(vm, handlers); } else { //如果不能代理直接赋值 vm._renderProxy = vm; } }; } /* * 实例化set对象 * */ var seenObjects = new _Set(); /** * Recursively traverse an object to evoke all converted 递归遍历对象以唤起所有转换 * getters, so that every nested property inside the object 吸收器,以便对象内的每个嵌套属性 * is collected as a "deep" dependency. 被收集为一个“深度”依赖。 * 为 seenObjects 深度收集val 中的key */ function traverse(val) { // 搜索seen 为seen添加depId //seenObjects set对象 // 为 seenObjects 深度收集val 中的key _traverse(val, seenObjects); //清除对象 给对象置空 seenObjects.clear(); } //搜集依赖 /* * 搜索seen 为seen添加depId * 为 seenObjects 深度收集val 中的key * * */ //看到这里 function _traverse(val, seen) { console.log(val) console.log(seen.add) var i, keys; //判断是否是数组 var isA = Array.isArray(val); //isFrozen 方法判断一个对象是否被冻结。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen //val 是否是被VNode 实例化 if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } console.log(val.__ob__) //如果val 有__ob__ 属性 if (val.__ob__) { var depId = val.__ob__.dep.id; // seen 中是否含有depId 属性或者方法 if (seen.has(depId)) { return } console.log(seen.add) // seen 是 seenObjects = new _Set(); add 就是set对象中的add方法,添加为一的值得key //如果没有则添加进去 seen.add(depId); } //如果是数组 if (isA) { i = val.length; //则循环检查 回调递归 while (i--) { _traverse(val[i], seen); } } else { keys = Object.keys(val); i = keys.length; //如果是对象也循环递归检查 while (i--) { _traverse(val[keys[i]], seen); } } } /* * * // normalizeEvent函数主要用于将传入的带有特殊前缀的事件修饰符分解为具有特定值的事件对象 * cachedFn * function cached(fn) { var cache = Object.create(null); return (function cachedFn(str) { var hit = cache[str]; return hit || (cache[str] = fn(str)) }) } * normalizeEvent 得到的是一个函数 如果传入的 name 中 在cache 对象中有值 则返回这个值 * 如果该对象没有值则 调用该函数 并且用返回值 记录 当前执行函数返回值记录起来 * */ //该函数是过滤 vue 事件中的修饰符 var normalizeEvent = cached(function (name) { //判断第一个字符是否是'& var passive = name.charAt(0) === '&'; //slice(),返回一个新的字符串,该方法可从已有的数组中,或者字符串中返回选定的元素。 name = passive ? name.slice(1) : name; //判断第一个字符串是否是~ var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first //slice(),返回一个新的字符串,该方法可从已有的数组中,或者字符串中返回选定的元素。 name = once$$1 ? name.slice(1) : name; //判断第一个位是否是 ! var capture = name.charAt(0) === '!'; //slice(),返回一个新的字符串,该方法可从已有的数组中,或者字符串中返回选定的元素。 name = capture ? name.slice(1) : name; return { name: name, once: once$$1, capture: capture, passive: passive } }); //createFnInvoker 创建一个调用程序 创建一个钩子函数 //createFnInvoker,如果事件只是个函数就为为事件添加多一个静态类, invoker.fns = fns; 把真正的事件放在fns。而 invoker 则是转义fns然后再运行fns function createFnInvoker(fns) { function invoker() { //获取传进来的参数,是一个数组 var arguments$1 = arguments; //静态方法传进来的函数 赋值给fns var fns = invoker.fns; //判断fns 是否是一个数组 if (Array.isArray(fns)) { //如果是数组 浅拷贝 var cloned = fns.slice(); //执行fns 数组中的函数 并且把 invoker arguments$1参数一个个传给fns 函数中 for (var i = 0; i < cloned.length; i++) { cloned[i].apply(null, arguments$1); } } else { // return handler return value for single handlers //如果fns 不是数组函数,而是一个函数 则执行arguments$1参数一个个传给fns 函数中 return fns.apply(null, arguments) } } invoker.fns = fns; return invoker //静态类 } //更新事件 并且为新的值 添加函数 旧的值删除函数等功能 function updateListeners( on, //新的事件 oldOn, //旧的事件 add, //添加事件函数 remove$$1, //删除事件函数 vm//vue 实例化对象 ) { var name, def, cur, old, event; for (name in on) { // 遍历on def = cur = on[name]; //on 新的事件值 old = oldOn[name]; //oldOn 对象中的 与 name 匹配 并且赋值 old 估计这个是旧的值 event = normalizeEvent(name); //normalizeEvent 如果是事件,则过滤 事件修饰符 /* istanbul ignore if */ // isUndef 判断值存在 并且是空的 return v === undefined || v === null if (isUndef(cur)) { //如果不是生产环境 "development" !== 'production' && warn( "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), vm ); } else if (isUndef(old)) { //判断旧的值是否存在 为空的时候 没有定义旧的事件 if (isUndef(cur.fns)) { //如果函数不存在 则绑定函数 //函数 获取钩子函数 // 创建函数调用器并重新复制给cur和on[name] cur = on[name] = createFnInvoker(cur); //这个时候cur.fns就存在了 } name = '&' + name; // mark the event as passive 将事件标记为被动的 //添加事件 add( event.name, //事件名称 cur, // 转义过的事件 执行静态类 event.once, //是否只触发一次的状态 event.capture, // 事件俘获或是冒泡行为 event.passive, // 检测事件修饰符 是否是 '&' event.params //事件参数 ); } else if (cur !== old) { //如果新的值不等于旧的值 //则更新新旧值 old.fns = cur; on[name] = old; } } for (name in oldOn) { //循环旧的值 为空的时候 if (isUndef(on[name])) { //获取事件 event = normalizeEvent(name); //删除旧的值的事件 remove$$1(event.name, oldOn[name], event.capture); } } } /* * * 合并vue vnode 钩子函数, * def[hookKey] = invoker; //把钩子函数用对象存起来 * */ function mergeVNodeHook(def, hookKey, hook) { //判断def 是否 是vnode 实例化的对象 if (def instanceof VNode) { // 重新赋值def 把def.data.hook 赋值给def def = def.data.hook || (def.data.hook = {}); } var invoker; //获取旧的oldHook 钩子 var oldHook = def[hookKey]; function wrappedHook() { //执行钩子函数 hook.apply(this, arguments); // important: remove merged hook to ensure it's called only once // and prevent memory leak //重要:删除合并钩子以确保只调用一次 //和防止内存泄漏 remove(invoker.fns, wrappedHook); } if (isUndef(oldHook)) { //如果旧的钩子函数没有 为空的时候 // no existing hook 无现有钩 则创建一个钩子函数 invoker = createFnInvoker([wrappedHook]); } else { /* istanbul ignore if 如果有老的钩子函数,并且fns钩子函数存在 并且已经合并过*/ if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { // already a merged invoker 已合并的调用程序 invoker = oldHook; //直接老的钩子函数直接覆盖新的钩子函数 //为钩子函数的fns 添加一个函数 invoker.fns.push(wrappedHook); } else { // existing plain hook invoker = createFnInvoker([oldHook, wrappedHook]); } } invoker.merged = true; //把钩子函数用对象存起来 def[hookKey] = invoker; } /* extractPropsFromVNodeData 从 props属性中获取vnode数据 extractPropsFromVNodeData循环propOptions对象,把驼峰的key转换成横杠的key。校验props属性的key是否和attrs属性值相同,如果相同删除掉attrs属性的同样key的值。获取props属性的值添加搞res对象中,返回出去 * * */ 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 } //检查 属性 检查key和altKey 在hash属性对象中有没有,如果有则赋值给res对象 function checkProp( res, //需要添加值的对象 hash, // 属性对象 key, // 原始key altKey, //转换后的 横杆key preserve //是否要删除hash 对象中的属性或者方法 状态 布尔值 ) { //hash 值存在 if (isDef(hash)) { //如果是hash对象中含有key 属性或者方法 if (hasOwn(hash, key)) { //添加res值 res[key] = hash[key]; //preserve 不存在的时候则在hash对象中删除该key 属性或者方法 if (!preserve) { delete hash[key]; } return true } else if (hasOwn(hash, altKey)) { //如果是hash对象中含有altKey 属性或者方法 //添加res值 res[key] = hash[altKey]; //preserve 不存在的时候则在hash对象中删除该key 属性或者方法 if (!preserve) { delete hash[altKey]; } return true } } return false } /* */ // 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. 因为功能组件已经规范了他们自己的孩子。 //循环子节点children,把他连在一起,其实就是把伪数组变成真正的数组 function simpleNormalizeChildren(children) { for (var i = 0; i < children.length; i++) { if (Array.isArray(children[i])) { return Array.prototype.concat.apply([], children) } } return children } // 2. When the children contains constructs that always generated nested Arrays, 2。当子类包含总是生成嵌套数组的结构时, // e.g.