diff --git a/README.md b/README.md index a8a2edd..30a926b 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,1296 @@ + ***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发展有十几年历史,但是这十几年历史发展,并没有多大的改变,思想还是那些,模块还是分为几大块: - vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了,差diff那部分,因为考虑到自己要换工作了,所以暂缓下来先,diff那块后期我会补上去。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。 - - 说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。 +## 1.模板转换: - 1.vue源码解读流程 1.new Vue 调用的是 Vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initLifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。 + 就是我们写的 vue 模板 或者是 react jsx 我们都可以理解是模板,然后他会经过 模板编译转换,像vue的话是进过一个方法paseHTML方法转换成ast树,里面的paseHTML用while 循环模板,然后经过正则 匹配到vue指令,还有vue的属性,事件方法等,收集到一个ast树中。 - 2.双数据绑定原理是:obersve()方法判断value没有没有__ob___属性并且是不是Obersve实例化的, - value是不是Vonde实例化的,如果不是则调用Obersve 去把数据添加到观察者中,为数据添加__ob__属性, Obersve 则调用defineReactive方法,该方法是连接Dep和wacther方法的一个通道,利用Object.definpropty() 中的get和set方法 监听数据。get方法中是new Dep调用depend()。为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 然后set方法就是调用dep中的notify 方法调用wacther中的run 更新视图 - 3.vue从字符串模板怎么到真实的dom呢?是通过$mount挂载模板,就是获取到html,然后通过paseHTML这个方法转义成ast模板,他大概算法是 while(html) 如果匹配到开始标签,结束标签,或者是属性,都会截取掉html,然后收集到一个对象中,知道循环结束 html被截取完。最后变成一个ast对象,ast对象好了之后,在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数,编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过diff算法变成正真正的dom元素。 +## 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: -具体看我源码和流程图,这里文字就不描述这么多了,流程图是下面这中的网盘,源码是vue.js,基本每一行都有注释,然后diff待更新中 + 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: + //匹配开头必需是 后面可以忽略是任何字符串 ^<\\/((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*)[^>]*> + 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) && //匹配开头必需是 后面可以忽略是任何字符串 + !startTagOpen.test(rest) && // 匹配开头必需是< 后面可以忽略是任何字符串 + !comment.test(rest) && // 匹配 开始字符串为/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 //上一个标签和现在标签相同
, or missing
. Bailing hydration and performing ' + + 'full client-side render.' + ); + } + } + // either not server-rendered, or hydration failed. + // create an empty node and replace it + oldVnode = emptyNodeAt(oldVnode); + } + + // replacing existing element + var oldElm = oldVnode.elm; + var parentElm$1 = nodeOps.parentNode(oldElm); - var isInitialPatch = false; - var insertedVnodeQueue = []; //vonde队列 如果vnode上有insert钩子,那么就将这个vnode放入insertedVnodeQueue中作记录,到时再在全局批量调用insert钩子回调 - - if (isUndef(oldVnode)) { //如果没有定义旧的vonde - // empty mount (likely as component), create new root element 空挂载(可能作为组件),创建新的根元素 - isInitialPatch = true; - createElm( //创建节点 - vnode, //虚拟dom - insertedVnodeQueue, //vonde队列空数组 - parentElm, //真实的 父节点 - refElm //当前节点 - ); - } else { - var isRealElement = isDef(oldVnode.nodeType); //获取 真实的dom 类型 - if (!isRealElement && //如果获取不到真实的dom 类型 - sameVnode(oldVnode, vnode) //sameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。 - ) { - // patch existing root node - //修补现有根节点 - patchVnode( - oldVnode, - vnode, - insertedVnodeQueue, //vonde队列 - removeOnly //是否要全部删除标志 - ); - } else { - if (isRealElement) { - // mounting to a real element - // check if this is server-rendered content and if we can perform - // a successful hydration. - if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { - oldVnode.removeAttribute(SSR_ATTR); - hydrating = true; - } - if (isTrue(hydrating)) { - if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { - invokeInsertHook(vnode, insertedVnodeQueue, true); - return oldVnode - } else { - warn( - 'The client-side rendered virtual DOM tree is not matching ' + - 'server-rendered content. This is likely caused by incorrect ' + - 'HTML markup, for example nesting block-level elements inside ' + - ', or missing
. Bailing hydration and performing ' + - 'full client-side render.' - ); - } - } - // either not server-rendered, or hydration failed. - // create an empty node and replace it - oldVnode = emptyNodeAt(oldVnode); - } - - // replacing existing element - var oldElm = oldVnode.elm; - var parentElm$1 = nodeOps.parentNode(oldElm); - - // create new node - createElm( - vnode, - insertedVnodeQueue, - // extremely rare edge case: do not insert if old element is in a - // leaving transition. Only happens when combining transition + - // keep-alive + HOCs. (#4590) - oldElm._leaveCb ? null : parentElm$1, - nodeOps.nextSibling(oldElm) - ); - - // update parent placeholder node element, recursively - if (isDef(vnode.parent)) { - var ancestor = vnode.parent; - var patchable = isPatchable(vnode); - while (ancestor) { - for (var i = 0; i < cbs.destroy.length; ++i) { - cbs.destroy[i](ancestor); - } - ancestor.elm = vnode.elm; - if (patchable) { - for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { - cbs.create[i$1](emptyNode, ancestor); - } - // #6513 - // invoke insert hooks that may have been merged by create hooks. - // e.g. for directives that uses the "inserted" hook. - var insert = ancestor.data.hook.insert; - if (insert.merged) { - // start at index 1 to avoid re-invoking component mounted hook - for (var i$2 = 1; i$2 < insert.fns.length; i$2++) { - insert.fns[i$2](); - } - } - } else { - registerRef(ancestor); - } - ancestor = ancestor.parent; - } - } - - // destroy old node - if (isDef(parentElm$1)) { - removeVnodes(parentElm$1, [oldVnode], 0, 0); - } else if (isDef(oldVnode.tag)) { - invokeDestroyHook(oldVnode); - } - } + // create new node + createElm( + vnode, + insertedVnodeQueue, + // extremely rare edge case: do not insert if old element is in a + // leaving transition. Only happens when combining transition + + // keep-alive + HOCs. (#4590) + oldElm._leaveCb ? null : parentElm$1, + nodeOps.nextSibling(oldElm) + ); + + // update parent placeholder node element, recursively + if (isDef(vnode.parent)) { + var ancestor = vnode.parent; + var patchable = isPatchable(vnode); + while (ancestor) { + for (var i = 0; i < cbs.destroy.length; ++i) { + cbs.destroy[i](ancestor); + } + ancestor.elm = vnode.elm; + if (patchable) { + for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { + cbs.create[i$1](emptyNode, ancestor); + } + // #6513 + // invoke insert hooks that may have been merged by create hooks. + // e.g. for directives that uses the "inserted" hook. + var insert = ancestor.data.hook.insert; + if (insert.merged) { + // start at index 1 to avoid re-invoking component mounted hook + for (var i$2 = 1; i$2 < insert.fns.length; i$2++) { + insert.fns[i$2](); } + } + } else { + registerRef(ancestor); + } + ancestor = ancestor.parent; + } + } + + // destroy old node + if (isDef(parentElm$1)) { + removeVnodes(parentElm$1, [oldVnode], 0, 0); + } else if (isDef(oldVnode.tag)) { + invokeDestroyHook(oldVnode); + } + } + } - invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); - return vnode.elm + invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); + return vnode.elm } @@ -9022,7 +9097,7 @@ if (!oldDir) { //如果旧的不存在了 // new directive, bind 新指令,绑定 callHook$1( - dir, //新的指令值 + dir, //新的指令值 'bind', //触发bind钩子函数 vnode,//新的vonde oldVnode //旧的vonde @@ -9071,13 +9146,13 @@ mergeVNodeHook(vnode, 'postpatch', function () { - for (var i = 0; i < dirsWithPostpatch.length; i++) { - callHook$1( - dirsWithPostpatch[i], - 'componentUpdated', - vnode, oldVnode); - } - }); + for (var i = 0; i < dirsWithPostpatch.length; i++) { + callHook$1( + dirsWithPostpatch[i], + 'componentUpdated', + vnode, oldVnode); + } + }); } if (!isCreate) { @@ -9100,9 +9175,9 @@ //规范化的指令,为指令属性修正变成规范的指令数据。返回指令数据集合 function normalizeDirectives$1( - dirs, //vonde 指令集合 - vm //vm vne实例化对象,或者是组件实例化的对象 - ) { + dirs, //vonde 指令集合 + vm //vm vne实例化对象,或者是组件实例化的对象 + ) { //创建一个空的对象 var res = Object.create(null); //如果 指令 名称dirs 不存在 则返回一个空的对象 @@ -9127,7 +9202,7 @@ return res } - //返回指令名称 或者属性name名称+修饰符 + //返回指令名称 或者属性name名称+修饰符 function getRawDirName(dir) { //rawName 视图中的 指令如 就是v-hello //name 视图中的 指令如 就是hello @@ -9137,28 +9212,28 @@ return dir.rawName || ((dir.name) + "." + (Object.keys(dir.modifiers || {}).join('.'))) } - //触发指令钩子函数 + //触发指令钩子函数 function callHook$1( - dir, //新的指令值 - hook, //钩子函数 - vnode, //新的vnode - oldVnode, //旧的vnode - isDestroy - ) { - var fn = dir.def && dir.def[hook]; //获取属性上面的钩子函数 - if (fn) { - try { - fn( - vnode.elm, //真实dom - dir, //指令的参数 - vnode, //新的vond - oldVnode, //旧的vonde - isDestroy //是否要销毁标记 - ); - } catch (e) { - handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook")); - } - } + dir, //新的指令值 + hook, //钩子函数 + vnode, //新的vnode + oldVnode, //旧的vnode + isDestroy + ) { + var fn = dir.def && dir.def[hook]; //获取属性上面的钩子函数 + if (fn) { + try { + fn( + vnode.elm, //真实dom + dir, //指令的参数 + vnode, //新的vond + oldVnode, //旧的vonde + isDestroy //是否要销毁标记 + ); + } catch (e) { + handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook")); + } + } } var baseModules = [ @@ -9261,8 +9336,8 @@ // 设置基本的属性 //设置属性,并且判断是否是ie浏览器 如果是 并且不是ie九的时候 更新input事件 function baseSetAttr(el, //dom节点 - key, //属性的 key - value //属性的值 + key, //属性的 key + value //属性的值 ) { if (isFalsyAttrValue(value)) { // 判断value 是否是 null 或者 false @@ -9383,10 +9458,10 @@ console.log('exp.charCodeAt(i - 1) !== 0x7C=' + (exp.charCodeAt(i - 1) !== 0x7C)) console.log('curly=' + (curly)) console.log('!curly=' + (!curly)) - console.log('square=' + (square )) - console.log('!square=' + (!square )) + console.log('square=' + (square)) + console.log('!square=' + (!square)) console.log('!paren=' + (!paren)) - console.log('最后一个=' + ( c === 0x7C && // pipe + console.log('最后一个=' + (c === 0x7C && // pipe exp.charCodeAt(i + 1) !== 0x7C && exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren)) @@ -9414,16 +9489,16 @@ } } else if ( // 如果在 之前不在 ' " ` / 即字符串 或者正则中 - // 那么就判断 当前字符是否是 | - // 如果当前 字符为 | - // 且 不在 { } 对象中 - // 且 不在 [] 数组中 - // 且不在 () 中 - // 那么说明此时是过滤器的一个 分界点 - - c === 0x7C && // | - exp.charCodeAt(i + 1) !== 0x7C && - exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren + // 那么就判断 当前字符是否是 | + // 如果当前 字符为 | + // 且 不在 { } 对象中 + // 且 不在 [] 数组中 + // 且不在 () 中 + // 那么说明此时是过滤器的一个 分界点 + + c === 0x7C && // | + exp.charCodeAt(i + 1) !== 0x7C && + exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren ) { @@ -9476,7 +9551,7 @@ case 0x2f: break; // 匹配 / - case 0x7C: // 匹配 | + case 0x7C: // 匹配 | break; } @@ -9561,7 +9636,7 @@ "_f(\"" + name + "\")(" + exp + ( args !== ')' ? - ',' + args + ',' + args : args ) ) @@ -9581,29 +9656,29 @@ key //key ) { return modules ? - modules.map(function (m) { - return m[key]; // 获取modules[key] 值 - }).filter(function (_) { - return _; //过滤modules[key] 值,如果不存在则丢弃 - }):[] + modules.map(function (m) { + return m[key]; // 获取modules[key] 值 + }).filter(function (_) { + return _; //过滤modules[key] 值,如果不存在则丢弃 + }) : [] } //在虚拟dom中添加prop属性 function addProp(el, name, value) { - (el.props || (el.props = [])).push({name: name, value: value}); + (el.props || (el.props = [])).push({ name: name, value: value }); el.plain = false; } //添加attrs属性 function addAttr(el, name, value) { - (el.attrs || (el.attrs = [])).push({name: name, value: value}); + (el.attrs || (el.attrs = [])).push({ name: name, value: value }); el.plain = false; } -// add a raw attr (use this in preTransforms) + // add a raw attr (use this in preTransforms) //添加原始attr(在预转换中使用) function addRawAttr(el, name, value) { el.attrsMap[name] = value; - el.attrsList.push({name: name, value: value}); + el.attrsList.push({ name: name, value: value }); } @@ -9611,13 +9686,13 @@ //为虚拟dom 添加一个 指令directives属性 对象 function addDirective( - el, //虚拟dom - name, //获取 view 原始属性的名称 不包含 v- : @的 - rawName, // 获取 view 原始属性的名称 包含 v- : @的 - value, //属性view 属性上的值 - arg, // efg:hig 属性名称冒号后面多出来的标签 - modifiers - ) { + el, //虚拟dom + name, //获取 view 原始属性的名称 不包含 v- : @的 + rawName, // 获取 view 原始属性的名称 包含 v- : @的 + value, //属性view 属性上的值 + arg, // efg:hig 属性名称冒号后面多出来的标签 + modifiers + ) { (el.directives || (el.directives = [])).push({ name: name, rawName: rawName, @@ -9632,13 +9707,13 @@ //为虚拟dom添加events 事件对象属性,如果添加@click='clickEvent' 则此时 虚拟dom为el.events.click.value="clickEvent" //或者虚拟dom添加nativeEvents 事件对象属性,如果添加@click.native='clickEvent' 则此时 虚拟dom为el.nativeEvents.click.value="clickEvent" function addHandler( - el, //虚拟dom - name, //name 事件名称 事件类型 - value, //事件函数 - modifiers, //事件类型状态状态 - important, // 根据important为true 把事件添加在前面 假就添加在尾部 - warn //警告日志 - ) { + el, //虚拟dom + name, //name 事件名称 事件类型 + value, //事件函数 + modifiers, //事件类型状态状态 + important, // 根据important为true 把事件添加在前面 假就添加在尾部 + warn //警告日志 + ) { modifiers = modifiers || emptyObject; // warn prevent and passive modifier @@ -9685,17 +9760,17 @@ var events; if (modifiers.native) { // 判断是有原生事件修饰符 通俗点讲:就是在父组件中给子组件绑定一个原生的事件,就将子组件变成了普通的HTML标签,不加'. native'事件是无法触 发的。 - /* - * 比如