.'
- );
- }
- }
- //添加属性
- addAttr(
- el, //虚拟dom
- name, //view 属性名称
- JSON.stringify(value) //view 属性值
- );
- // #6887 firefox doesn't update muted state if set via attribute
- // even immediately after element creation
- // #6887如果通过属性设置,firefox不会更新静音状态
- //甚至在元素创建之后
- if (
- !el.component && //如果不是组件
- name === 'muted' && // Video 属性 muted 属性设置或返回视频是否应该被静音(关闭声音)。
- platformMustUseProp(el.tag, el.attrsMap.type, name) // 校验特定的属性方法
- ) {
- //添加音频属性
- addProp(el, name, 'true');
- }
- }
+ name = rawName = list[i].name; //获取 view 属性的名称
+ value = list[i].value; //获取属性的值
+
+ if (dirRE.test(name)) { // 判断是否是 v-或者@或者: 属性开头的
+ // mark element as dynamic
+ el.hasBindings = true; // 动态标记元素
+ // modifiers 编辑器 //把字符串中的对象拆分成 对象比如 data.object.info.age 变成对象{object:true,info:true,age:true} 返回出去
+ modifiers = parseModifiers(name);
+ if (modifiers) {
+ //把刚才后面的.+字符串去除掉 获取最后一位的key
+ name = name.replace(modifierRE, '');
+ }
+ if (bindRE.test(name)) { // v-bind 匹配开始匹配是 :或者是v-bind
+
+ name = name.replace(bindRE, ''); //去除 开始匹配是 :或者是v-bind
+ // 处理value 解析成正确的value,把过滤器 转换成vue 虚拟dom的解析方法函数 比如把过滤器 ' ab | c | d' 转换成 _f("d")(_f("c")(ab))
+ // 表达式中的过滤器解析方法
+ value = parseFilters(value);
+ isProp = false;
+ if (modifiers) { //匹配到对象点的时候
+ if (modifiers.prop) {//匹配到有prop属性的时候
+ isProp = true;
+ //属性 v-model 变成 vModel
+ name = camelize(name);
+ //如果是innerHtml属性变成innerHTML
+ if (name === 'innerHtml') {
+ name = 'innerHTML';
+ }
+ }
+ if (modifiers.camel) {
+ name = camelize(name);
+ }
+ if (modifiers.sync) { //同步属性
+
+ //为虚拟dom添加events 事件对象属性,如果添加@click='clickEvent' 则此时 虚拟dom为el.events.click.value="clickEvent"
+ //或者虚拟dom添加nativeEvents 事件对象属性,如果添加@click.native='clickEvent' 则此时 虚拟dom为el.nativeEvents.click.value="clickEvent"
+ addHandler(
+ el,
+ ("update:" + (camelize(name))), // //属性 v-model 变成 vModel
+ //创建赋值代码
+ // 创赋值代码,子功能转义字符串对象拆分字符串对象 把后一位key分离出来
+ genAssignmentCode( //返回值 函数
+ value, //对象
+ "$event" //key
+ )
+ );
+ }
+ }
+ if (
+ isProp //如果是prop属性
+ ||
+ (
+ !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) //校验特定的属性方法
+ )) {
+ //添加props属性
+ addProp(el, name, value);
+ } else {
+ //添加普通的属性 在attrs属性中
+ addAttr(el, name, value);
+ }
+ } else if (onRE.test(name)) { // v-on 判断是否是 @或者v-on:属性开头的
+
+ name = name.replace(onRE, '');
+ console.log(name)
+ console.log(value)
+ console.log(modifiers)
+ console.log(false)
+ console.log(warn$2)
+ console.log(el)
+
+ addHandler(
+ el, //虚拟dom
+ name, //name 事件名称 事件类型
+ value, // 事件名称的值
+ modifiers,
+ false,
+ warn$2 //警告的日志
+ );
+ console.log(el)
+
+ } else { // normal directives 正常的指令
+ //一般也不会进来这里 因为前面已经匹配了 :或者v-bind @或者v-on:属性 开头的,所以进来这里的就是自定义指令
+ name = name.replace(dirRE, ''); //判断是否是 v-或者@或者: 属性开头的 去除掉 值获取name
+ // parse arg
+ var argMatch = name.match(argRE); //匹配字符串是否含有: 只是匹配一个
+ var arg = argMatch && argMatch[1]; //获取字符串 比如原字符串是 abc:efg:hig 获取到efg:hig
+
+ if (arg) {
+ name = name.slice(0, -(arg.length + 1)); // 截取name 取得abc
+ }
+
+ /* 当然也可以这么写
+ var index = argMatch&&argMatch.index;
+ if (index) {
+ name = name.slice(0, index+ 1); // 截取name 取得abc
+ }
+ */
+ console.log(el)
+ console.log(name)
+ console.log(rawName)
+ console.log(value)
+ console.log(arg)
+ console.log(modifiers)
+
+ //为虚拟dom 添加一个 指令directives属性 对象
+ addDirective(
+ el, //虚拟dom vonde
+ name, //获取 view 原始属性的名称 不包含 v- : @的
+ rawName,// 获取 view 原始属性的名称 包含 v- : @的
+ value, // 属性view 属性上的值
+ arg, // efg:hig 属性名称冒号后面多出来的标签
+ modifiers
+ );
+ if ("development" !== 'production' && name === 'model') {
+ //检查指令的命名值 不能为for 或者 for中的遍历的item
+ checkForAliasModel(el, value);
+ }
+ }
+ } else {
+ // literal attribute文字属性
+
+ {
+ //匹配view 指令,并且把他转换成 虚拟dom vonde 需要渲染的函数,比如指令{{name}}转换成 _s(name)
+ //比如字符串 我叫{{name}},今年{{age}},数据{{data.number}}个手机 转换成 我叫+_s(name)+,今年+_s(age)+,数据+_s(data.number)+个手机
+ var res = parseText(value, delimiters); //校验是否含有{{}} 括号的属性 比如
则报错警告
+ if (res) {
+ warn$2(
+ name + "=\"" + value + "\": " +
+ 'Interpolation inside attributes has been removed. ' +
+ 'Use v-bind or the colon shorthand instead. For example, ' +
+ 'instead of
, use
.'
+ );
+ }
+ }
+ //添加属性
+ addAttr(
+ el, //虚拟dom
+ name, //view 属性名称
+ JSON.stringify(value) //view 属性值
+ );
+ // #6887 firefox doesn't update muted state if set via attribute
+ // even immediately after element creation
+ // #6887如果通过属性设置,firefox不会更新静音状态
+ //甚至在元素创建之后
+ if (
+ !el.component && //如果不是组件
+ name === 'muted' && // Video 属性 muted 属性设置或返回视频是否应该被静音(关闭声音)。
+ platformMustUseProp(el.tag, el.attrsMap.type, name) // 校验特定的属性方法
+ ) {
+ //添加音频属性
+ addProp(el, name, 'true');
+ }
+ }
}
}
@@ -13928,7 +14006,7 @@
return map
}
-// for script (e.g. type="x/template") or style, do not decode content
+ // for script (e.g. type="x/template") or style, do not decode content
//判断标签是否是script或者是style
function isTextTag(el) {
return el.tag === 'script' || el.tag === 'style'
@@ -13939,7 +14017,7 @@
return (
el.tag === 'style' || //如果标签是 style
(
- el.tag === 'script'&& //如果是script 标签
+ el.tag === 'script' && //如果是script 标签
(
!el.attrsMap.type || //如果type属性不存在
el.attrsMap.type === 'text/javascript' //或者如果type属性是javascript
@@ -13959,7 +14037,7 @@
var attr = attrs[i];
if (!ieNSBug.test(attr.name)) { //匹配 字符串 xmlns:NS+数字
attr.name = attr.name.replace(
- ieNSPrefix, //匹配 字符串 NS+数字
+ ieNSPrefix, //匹配 字符串 NS+数字
'');
res.push(attr);
}
@@ -14000,11 +14078,11 @@
*/
// preTransformNode把attrsMap与attrsList属性值转换添加到el ast虚拟dom中为虚拟dom添加for,alias,iterator1,iterator2,
-// addRawAttr ,type ,key, ref,slotName或者slotScope或者slot,component或者inlineTemplate , plain,if ,else,elseif 属性
+ // addRawAttr ,type ,key, ref,slotName或者slotScope或者slot,component或者inlineTemplate , plain,if ,else,elseif 属性
function preTransformNode(
- el, //虚拟dom vonde
- options
- ) {
+ el, //虚拟dom vonde
+ options
+ ) {
if (el.tag === 'input') { //如果是input标签
var map = el.attrsMap; //获取vonde 所有属性
@@ -14054,7 +14132,7 @@
//删除v-for 属性
getAndRemoveAttr(branch1, 'v-for', true);
- //添加type 属性
+ //添加type 属性
addRawAttr(branch1, 'type', 'radio');
//校验属性的值,为el 虚拟dom添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
@@ -14068,7 +14146,7 @@
var branch2 = cloneASTElement(el);
//删除v-for属性
getAndRemoveAttr(branch2, 'v-for', true);
- //添加:type 属性
+ //添加:type 属性
addRawAttr(branch2, ':type', typeBinding);
//校验属性的值,为el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
processElement(branch2, options);
@@ -14076,18 +14154,18 @@
addIfCondition(
branch0,
{
- exp: ifCondition, //v-if 属性值
- block: branch2 //ast元素 需要渲染的ast子组件
+ exp: ifCondition, //v-if 属性值
+ block: branch2 //ast元素 需要渲染的ast子组件
}
);
- //判断是else还是elseif
+ //判断是else还是elseif
if (hasElse) {
branch0.else = true;
} else if (elseIfCondition) {
branch0.elseif = elseIfCondition;
}
- //返回转换过虚拟dom的对象值
+ //返回转换过虚拟dom的对象值
return branch0
}
}
@@ -14243,52 +14321,52 @@
(keys ? ',' + keys : '')
)
}
- //循环递归虚拟node,标记不是静态节点
+ //循环递归虚拟node,标记不是静态节点
function markStatic$1(node) {
node.static = isStatic(node); //判断是否是静态的ast虚拟dom type必须不等于2和3,pre必须为真
if (node.type === 1) {
- // do not make component slot content static. this avoids
- // 1. components not able to mutate slot nodes
- // 2. static slot content fails for hot-reloading
- //不要将组件插槽内容设置为静态。这就避免了
- // 1。组件无法更改插槽节点
- // 2。静态插槽内容无法热加载
- if (
- !isPlatformReservedTag(node.tag) && //保留标签 判断是不是真的是 html 原有的标签 或者svg标签
- node.tag !== 'slot' && //当前标签不等于slot
- node.attrsMap['inline-template'] == null // 也不是inline-template 内联模板
- ) {
- return
- }
- //深递归循环
- for (var i = 0, l = node.children.length; i < l; i++) {
- var child = node.children[i];
- markStatic$1(child);
- if (!child.static) {
- node.static = false;
- }
- }
+ // do not make component slot content static. this avoids
+ // 1. components not able to mutate slot nodes
+ // 2. static slot content fails for hot-reloading
+ //不要将组件插槽内容设置为静态。这就避免了
+ // 1。组件无法更改插槽节点
+ // 2。静态插槽内容无法热加载
+ if (
+ !isPlatformReservedTag(node.tag) && //保留标签 判断是不是真的是 html 原有的标签 或者svg标签
+ node.tag !== 'slot' && //当前标签不等于slot
+ node.attrsMap['inline-template'] == null // 也不是inline-template 内联模板
+ ) {
+ return
+ }
+ //深递归循环
+ for (var i = 0, l = node.children.length; i < l; i++) {
+ var child = node.children[i];
+ markStatic$1(child);
+ if (!child.static) {
+ node.static = false;
+ }
+ }
- if (node.ifConditions) { //if标记
- for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
- var block = node.ifConditions[i$1].block; //虚拟dom
- markStatic$1(block);
- if (!block.static) {
- node.static = false;
- }
- }
- }
+ if (node.ifConditions) { //if标记
+ for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
+ var block = node.ifConditions[i$1].block; //虚拟dom
+ markStatic$1(block);
+ if (!block.static) {
+ node.static = false;
+ }
+ }
+ }
}
}
- //根据node.static或者 node.once 标记staticRoot的状态
+ //根据node.static或者 node.once 标记staticRoot的状态
function markStaticRoots(node, isInFor) {
if (node.type === 1) { //虚拟 dom 节点
if (
node.static || //静态节点
node.once // v-once 只渲染一次节点。
- ) {
- node.staticInFor = isInFor;
- }
+ ) {
+ node.staticInFor = isInFor;
+ }
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
@@ -14302,27 +14380,27 @@
node.children.length === 1 && //如果只有一个子节点
node.children[0].type === 3 //属性节点
)) {
- node.staticRoot = true; //标记静态根节点
- return
- } else {
- node.staticRoot = false;
- }
- if (node.children) {
- for (var i = 0, l = node.children.length; i < l; i++) {
- markStaticRoots(
- node.children[i],
- isInFor || !!node.for
- );
- }
+ node.staticRoot = true; //标记静态根节点
+ return
+ } else {
+ node.staticRoot = false;
+ }
+ if (node.children) {
+ for (var i = 0, l = node.children.length; i < l; i++) {
+ markStaticRoots(
+ node.children[i],
+ isInFor || !!node.for
+ );
}
- if (node.ifConditions) {
- for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
- markStaticRoots(
- node.ifConditions[i$1].block,
- isInFor
- );
- }
+ }
+ if (node.ifConditions) {
+ for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
+ markStaticRoots(
+ node.ifConditions[i$1].block,
+ isInFor
+ );
}
+ }
}
}
//判断是否是静态的ast虚拟dom type必须不等于2和3,pre必须为真
@@ -14336,14 +14414,14 @@
return !!(
// 跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。 遇到指令不需要编译成模板显示原始指令
node.pre || //标记 标签是否还有 v-pre 指令 ,如果有则为真
- (
- !node.hasBindings && // no dynamic bindings // 没有动态标记元素
- !node.if && !node.for && // not v-if or v-for or v-else 没有 v-if 或者 v-for 或者 v-else
- !isBuiltInTag(node.tag) && // not a built-in 没有 slot,component
- isPlatformReservedTag(node.tag) && // not a component 不是一个组件 保留标签 判断是不是真的是 html 原有的标签 或者svg标签
- !isDirectChildOfTemplateFor(node) && // 判断当前ast 虚拟dom 的父标签 如果不是template则返回false,如果含有v-for则返回true
- Object.keys(node).every(isStaticKey) //node的key必须每一项都符合 匹配type,tag,attrsList,attrsMap,plain,parent,children,attrs + staticKeys 的字符串
- )
+ (
+ !node.hasBindings && // no dynamic bindings // 没有动态标记元素
+ !node.if && !node.for && // not v-if or v-for or v-else 没有 v-if 或者 v-for 或者 v-else
+ !isBuiltInTag(node.tag) && // not a built-in 没有 slot,component
+ isPlatformReservedTag(node.tag) && // not a component 不是一个组件 保留标签 判断是不是真的是 html 原有的标签 或者svg标签
+ !isDirectChildOfTemplateFor(node) && // 判断当前ast 虚拟dom 的父标签 如果不是template则返回false,如果含有v-for则返回true
+ Object.keys(node).every(isStaticKey) //node的key必须每一项都符合 匹配type,tag,attrsList,attrsMap,plain,parent,children,attrs + staticKeys 的字符串
+ )
)
}
@@ -14366,7 +14444,7 @@
var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;
-// KeyboardEvent.keyCode aliases
+ // KeyboardEvent.keyCode aliases
var keyCodes = {
esc: 27,
tab: 9,
@@ -14379,7 +14457,7 @@
'delete': [8, 46]
};
-// KeyboardEvent.key aliases
+ // KeyboardEvent.key aliases
var keyNames = {
esc: 'Escape',
tab: 'Tab',
@@ -14393,9 +14471,9 @@
'delete': ['Backspace', 'Delete']
};
-// #4868: modifiers that prevent the execution of the listener
-// need to explicitly return null so that we can determine whether to remove
-// the listener for .once
+ // #4868: modifiers that prevent the execution of the listener
+ // need to explicitly return null so that we can determine whether to remove
+ // the listener for .once
var genGuard = function (condition) {
return ("if(" + condition + ")return null;");
};
@@ -14414,82 +14492,82 @@
};
function genHandlers(
- events,
- isNative,
- warn
- ) {
- var res = isNative ? 'nativeOn:{' : 'on:{';
- for (var name in events) {
- res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";
- }
- return res.slice(0, -1) + '}'
- }
+ events,
+ isNative,
+ warn
+ ) {
+ var res = isNative ? 'nativeOn:{' : 'on:{';
+ for (var name in events) {
+ res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";
+ }
+ return res.slice(0, -1) + '}'
+ }
function genHandler(
- name,
- handler
- ) {
- if (!handler) {
- return 'function(){}'
- }
+ name,
+ handler
+ ) {
+ if (!handler) {
+ return 'function(){}'
+ }
- if (Array.isArray(handler)) {
- return ("[" + (handler.map(function (handler) {
- return genHandler(name, handler);
- }).join(',')) + "]")
- }
+ if (Array.isArray(handler)) {
+ return ("[" + (handler.map(function (handler) {
+ return genHandler(name, handler);
+ }).join(',')) + "]")
+ }
- var isMethodPath = simplePathRE.test(handler.value);
- var isFunctionExpression = fnExpRE.test(handler.value);
+ var isMethodPath = simplePathRE.test(handler.value);
+ var isFunctionExpression = fnExpRE.test(handler.value);
- if (!handler.modifiers) {
- if (isMethodPath || isFunctionExpression) {
- return handler.value
- }
- /* istanbul ignore if */
- return ("function($event){" + (handler.value) + "}") // inline statement
- } else {
- var code = '';
- var genModifierCode = '';
- var keys = [];
- for (var key in handler.modifiers) {
- if (modifierCode[key]) {
- genModifierCode += modifierCode[key];
- // left/right
- if (keyCodes[key]) {
- keys.push(key);
- }
- } else if (key === 'exact') {
- var modifiers = (handler.modifiers);
- genModifierCode += genGuard(
- ['ctrl', 'shift', 'alt', 'meta']
- .filter(function (keyModifier) {
- return !modifiers[keyModifier];
- })
- .map(function (keyModifier) {
- return ("$event." + keyModifier + "Key");
- })
- .join('||')
- );
- } else {
- keys.push(key);
- }
- }
- if (keys.length) {
- code += genKeyFilter(keys);
- }
- // Make sure modifiers like prevent and stop get executed after key filtering
- if (genModifierCode) {
- code += genModifierCode;
- }
- var handlerCode = isMethodPath
- ? ("return " + (handler.value) + "($event)")
- : isFunctionExpression
- ? ("return (" + (handler.value) + ")($event)")
- : handler.value;
- /* istanbul ignore if */
- return ("function($event){" + code + handlerCode + "}")
- }
+ if (!handler.modifiers) {
+ if (isMethodPath || isFunctionExpression) {
+ return handler.value
+ }
+ /* istanbul ignore if */
+ return ("function($event){" + (handler.value) + "}") // inline statement
+ } else {
+ var code = '';
+ var genModifierCode = '';
+ var keys = [];
+ for (var key in handler.modifiers) {
+ if (modifierCode[key]) {
+ genModifierCode += modifierCode[key];
+ // left/right
+ if (keyCodes[key]) {
+ keys.push(key);
+ }
+ } else if (key === 'exact') {
+ var modifiers = (handler.modifiers);
+ genModifierCode += genGuard(
+ ['ctrl', 'shift', 'alt', 'meta']
+ .filter(function (keyModifier) {
+ return !modifiers[keyModifier];
+ })
+ .map(function (keyModifier) {
+ return ("$event." + keyModifier + "Key");
+ })
+ .join('||')
+ );
+ } else {
+ keys.push(key);
+ }
+ }
+ if (keys.length) {
+ code += genKeyFilter(keys);
+ }
+ // Make sure modifiers like prevent and stop get executed after key filtering
+ if (genModifierCode) {
+ code += genModifierCode;
+ }
+ var handlerCode = isMethodPath
+ ? ("return " + (handler.value) + "($event)")
+ : isFunctionExpression
+ ? ("return (" + (handler.value) + ")($event)")
+ : handler.value;
+ /* istanbul ignore if */
+ return ("function($event){" + code + handlerCode + "}")
+ }
}
function genKeyFilter(keys) {
@@ -14575,8 +14653,8 @@
//获取到一个数组,数组中有两个函数genData和genData$1
this.dataGenFns = pluckModuleFunction(options.modules, 'genData');
- console.log(this.transforms )
- console.log(this.dataGenFns )
+ console.log(this.transforms)
+ console.log(this.dataGenFns)
console.log(options)
@@ -14602,10 +14680,10 @@
// 扩展指令,on,bind,cloak,方法
this.directives = extend(
extend(
- {},
- baseDirectives
- ),
- options.directives
+ {},
+ baseDirectives
+ ),
+ options.directives
);
var isReservedTag = options.isReservedTag || no; //保留标签 判断是不是真的是 html 原有的标签 或者svg标签
//也许是组件
@@ -14617,12 +14695,12 @@
this.staticRenderFns = [];
};
- //初始化扩展指令,on,bind,cloak,方法, dataGenFns 获取到一个数组,数组中有两个函数genData和genData$1
+ //初始化扩展指令,on,bind,cloak,方法, dataGenFns 获取到一个数组,数组中有两个函数genData和genData$1
//genElement根据el判断是否是组件,或者是否含有v-once,v-if,v-for,是否有template属性,或者是slot插槽,转换style,css等转换成虚拟dom需要渲染的参数函数
function generate(
- ast, //ast 对象模板数据
- options
- ) {
+ ast, //ast 对象模板数据
+ options
+ ) {
// options 参数为
// 原型中有baseOptions方法
@@ -14632,25 +14710,25 @@
// delimiters: options.delimiters, //改变纯文本插入分隔符。修改指令的书写风格,比如默认是{{mgs}} delimiters: ['${', '}']之后变成这样 ${mgs}
// comments: options.comments //当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。
// },
- //生成状态
- // * 扩展指令,on,bind,cloak,方法,
- // * dataGenFns 获取到一个数组,数组中有两个函数genData和genData$1
- var state = new CodegenState(options);
+ //生成状态
+ // * 扩展指令,on,bind,cloak,方法,
+ // * dataGenFns 获取到一个数组,数组中有两个函数genData和genData$1
+ var state = new CodegenState(options);
//根据el判断是否是组件,或者是否含有v-once,v-if,v-for,是否有template属性,或者是slot插槽,转换style,css等转换成虚拟dom需要渲染的参数函数
var code = ast ? genElement(ast, state) : '_c("div")';
- console.log({
- render: ("with(this){return " + code + "}"),
- staticRenderFns: state.staticRenderFns
- })
+ console.log({
+ render: ("with(this){return " + code + "}"),
+ staticRenderFns: state.staticRenderFns
+ })
- return {
- //with 绑定js的this 缩写
- render: ("with(this){return " + code + "}"),
- staticRenderFns: state.staticRenderFns //空数组
- }
+ return {
+ //with 绑定js的this 缩写
+ render: ("with(this){return " + code + "}"),
+ staticRenderFns: state.staticRenderFns //空数组
+ }
}
- //根据el判断是否是组件,或者是否含有v-once,v-if,v-for,是否有template属性,或者是slot插槽,转换style,css等转换成虚拟dom需要渲染的参数函数
+ //根据el判断是否是组件,或者是否含有v-once,v-if,v-for,是否有template属性,或者是slot插槽,转换style,css等转换成虚拟dom需要渲染的参数函数
function genElement(
el, //ast对象或者虚拟dom
state //渲染虚拟dom的一些方法
@@ -14701,12 +14779,12 @@
} else {
var data = el.plain ? //如果标签中没有属性则这个标志为真
- undefined :
- genData$2(el, state);
+ undefined :
+ genData$2(el, state);
var children = el.inlineTemplate ? //是不是内联模板标签
- null :
- genChildren(el, state, true);
+ null :
+ genChildren(el, state, true);
code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
}
@@ -14719,7 +14797,7 @@
}
}
-// hoist static sub-trees out 将静态子树吊出
+ // hoist static sub-trees out 将静态子树吊出
//将子节点导出虚拟dom 渲染函数的参数形式
function genStatic(el, state) {
//标记已经处理过
@@ -14732,7 +14810,7 @@
return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
}
-// v-once
+ // v-once
//文档https://cn.vuejs.org/v2/api/#v-once
// v-once
// 不需要表达式
@@ -14771,10 +14849,10 @@
//判断标签是否含有if属性 解析 if指令中的参数 并且返回 虚拟dom需要的参数js渲染函数
function genIf(
- el, //dom节点
- state, //状态
- altGen, // 不知道干嘛的
- altEmpty // 不知道干嘛的
+ el, //dom节点
+ state, //状态
+ altGen, // 不知道干嘛的
+ altEmpty // 不知道干嘛的
) {
console.log('==el==')
console.log(el)
@@ -14787,45 +14865,45 @@
//解析 if指令中的参数 并且返回 虚拟dom需要的参数js渲染函数
function genIfConditions(
- conditions, //el 虚拟dom
- state, //状态
- altGen, //知道干嘛的
- altEmpty//知道干嘛的
- ) {
-
- if (!conditions.length) { //如果conditions 不存在 则返回一个空的虚拟dom参数
- return altEmpty || '_e()'
- }
+ conditions, //el 虚拟dom
+ state, //状态
+ altGen, //知道干嘛的
+ altEmpty//知道干嘛的
+ ) {
- var condition = conditions.shift(); //取第一个元素
- console.log('==condition==')
- console.log(condition)
- if (condition.exp) { //判断if指令参数是否存在 如果存在则递归condition.block 数据此时ifProcessed 变为true 下次不会再进来
+ if (!conditions.length) { //如果conditions 不存在 则返回一个空的虚拟dom参数
+ return altEmpty || '_e()'
+ }
- return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
- } else {
- return ("" + (genTernaryExp(condition.block)))
- }
+ var condition = conditions.shift(); //取第一个元素
+ console.log('==condition==')
+ console.log(condition)
+ if (condition.exp) { //判断if指令参数是否存在 如果存在则递归condition.block 数据此时ifProcessed 变为true 下次不会再进来
- // v-if with v-once should generate code like (a)?_m(0):_m(1)
- //如果用v-once生成像(a)?_m(0):_m(1)这样的代码
- function genTernaryExp(el) {
- console.log('==altGen==');
- console.log(altGen);
- //数据此时ifProcessed 变为true 下次不会再进来
- return altGen ?
- altGen(el, state) //altGen 一个自定义函数吧
- : el.once ? //静态标签标志 存在么 不存在
- genOnce(el, state) //导出一个静态标签的虚拟dom参数
- : genElement(el, state) //递归el 数据此时ifProcessed 变为true 下次不会再进来
- }
+ return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
+ } else {
+ return ("" + (genTernaryExp(condition.block)))
+ }
+
+ // v-if with v-once should generate code like (a)?_m(0):_m(1)
+ //如果用v-once生成像(a)?_m(0):_m(1)这样的代码
+ function genTernaryExp(el) {
+ console.log('==altGen==');
+ console.log(altGen);
+ //数据此时ifProcessed 变为true 下次不会再进来
+ return altGen ?
+ altGen(el, state) //altGen 一个自定义函数吧
+ : el.once ? //静态标签标志 存在么 不存在
+ genOnce(el, state) //导出一个静态标签的虚拟dom参数
+ : genElement(el, state) //递归el 数据此时ifProcessed 变为true 下次不会再进来
+ }
}
function genFor(
- el, //虚拟dom 节点
- state, //状态
- altGen, //函数不知道是什么
- altHelper //函数不知道是什么
+ el, //虚拟dom 节点
+ state, //状态
+ altGen, //函数不知道是什么
+ altHelper //函数不知道是什么
) {
var exp = el.for; //含有for的标签
@@ -14854,7 +14932,7 @@
'})'
}
- //根据判断el是否含有 指令属性,key,ref,refInFor,v-for,pre,component
+ //根据判断el是否含有 指令属性,key,ref,refInFor,v-for,pre,component
function genData$2(el, state) {
var data = '{';
@@ -14993,8 +15071,8 @@
function genInlineTemplate(el, state) {
var ast = el.children[0];
if ("development" !== 'production' && (
- el.children.length !== 1 || ast.type !== 1
- )) {
+ el.children.length !== 1 || ast.type !== 1
+ )) {
state.warn('Inline-template components must have exactly one child element.');
}
if (ast.type === 1) {
@@ -15006,30 +15084,30 @@
}
function genScopedSlots(slots,
- state) {
+ state) {
return ("scopedSlots:_u([" + (Object.keys(slots).map(function (key) {
return genScopedSlot(key, slots[key], state)
}).join(',')) + "])")
}
function genScopedSlot(key,
- el,
- state) {
+ el,
+ state) {
if (el.for && !el.forProcessed) {
return genForScopedSlot(key, el, state)
}
var fn = "function(" + (String(el.slotScope)) + "){" +
"return " + (el.tag === 'template'
? el.if
- ? ((el.if) + "?" + (genChildren(el, state) || 'undefined') + ":undefined")
- : genChildren(el, state) || 'undefined'
+ ? ((el.if) + "?" + (genChildren(el, state) || 'undefined') + ":undefined")
+ : genChildren(el, state) || 'undefined'
: genElement(el, state)) + "}"; //genElement根据el判断是否是组件,或者是否含有v-once,v-if,v-for,是否有template属性,或者是slot插槽,转换style,css等转换成虚拟dom需要渲染的参数函数
return ("{key:" + key + ",fn:" + fn + "}")
}
function genForScopedSlot(key,
- el,
- state) {
+ el,
+ state) {
var exp = el.for;
var alias = el.alias;
var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
@@ -15043,78 +15121,78 @@
//获取虚拟dom子节点
function genChildren(el, //dom
- state, //状态
- checkSkip, // 布尔值
- altGenElement,
- altGenNode
- ) {
- var children = el.children; //子节点
- if (children.length) {
- var el$1 = children[0];
- // optimize single v-for 优化单 v-for。
- if (
- children.length === 1 &&//如果只有一个子节点
- el$1.for &&
- el$1.tag !== 'template' && //节点不是template
- el$1.tag !== 'slot' //节点不是slot
- ) { //子节点如果只是一个
-
- //altGenElement和genElement是一个函数 传进来参数是el$1, state
- return (altGenElement || genElement)(el$1, state)
- }
-
- //确定子数组所需的标准化。
- // 0:不需要标准化
- // 1:需要简单的标准化(可能是1级深嵌套数组)
- // 2:需要完全标准化
- var normalizationType = checkSkip
- ? getNormalizationType( //如果children.length==0 就返回0,如果如果有for属性存在或者tag等于template或者是slot 则问真就返回1,如果是组件则返回2
- children, //子节点
- state.maybeComponent //判断是否是组件
- )
- : 0;
- var gen = altGenNode || genNode; //genNode根据node.type 属性不同调用不同的方法,得到不同的虚拟dom渲染方法
- return ("[" + (children.map(function (c) {
- return gen(c, state); //genNode根据node.type 属性不同调用不同的方法,得到不同的虚拟dom渲染方法
- }).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
- }
- }
-
-// determine the normalization needed for the children array.
-// 0: no normalization needed
-// 1: simple normalization needed (possible 1-level deep nested array)
-// 2: full normalization needed
-//确定子数组所需的标准化。
-// 0:不需要标准化
-// 1:需要简单的标准化(可能是1级深嵌套数组)
-// 2:需要完全标准化
+ state, //状态
+ checkSkip, // 布尔值
+ altGenElement,
+ altGenNode
+ ) {
+ var children = el.children; //子节点
+ if (children.length) {
+ var el$1 = children[0];
+ // optimize single v-for 优化单 v-for。
+ if (
+ children.length === 1 &&//如果只有一个子节点
+ el$1.for &&
+ el$1.tag !== 'template' && //节点不是template
+ el$1.tag !== 'slot' //节点不是slot
+ ) { //子节点如果只是一个
+
+ //altGenElement和genElement是一个函数 传进来参数是el$1, state
+ return (altGenElement || genElement)(el$1, state)
+ }
+
+ //确定子数组所需的标准化。
+ // 0:不需要标准化
+ // 1:需要简单的标准化(可能是1级深嵌套数组)
+ // 2:需要完全标准化
+ var normalizationType = checkSkip
+ ? getNormalizationType( //如果children.length==0 就返回0,如果如果有for属性存在或者tag等于template或者是slot 则问真就返回1,如果是组件则返回2
+ children, //子节点
+ state.maybeComponent //判断是否是组件
+ )
+ : 0;
+ var gen = altGenNode || genNode; //genNode根据node.type 属性不同调用不同的方法,得到不同的虚拟dom渲染方法
+ return ("[" + (children.map(function (c) {
+ return gen(c, state); //genNode根据node.type 属性不同调用不同的方法,得到不同的虚拟dom渲染方法
+ }).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
+ }
+ }
+
+ // determine the normalization needed for the children array.
+ // 0: no normalization needed
+ // 1: simple normalization needed (possible 1-level deep nested array)
+ // 2: full normalization needed
+ //确定子数组所需的标准化。
+ // 0:不需要标准化
+ // 1:需要简单的标准化(可能是1级深嵌套数组)
+ // 2:需要完全标准化
//如果children.length==0 就返回0,如果如果有for属性存在或者tag等于template或者是slot 则问真就返回1,如果是组件则返回2
function getNormalizationType(
- children,
- maybeComponent
- ) {
- var res = 0;
- for (var i = 0; i < children.length; i++) { //循环子节点
- var el = children[i];
- if (el.type !== 1) { //如果是真是dom则跳过循环
- continue
- }
- //如果有for属性存在或者tag等于template或者是slot 则问真
- if (needsNormalization(el) ||
- (el.ifConditions && el.ifConditions.some(function (c) { //判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
- return needsNormalization(c.block);
- }))) {
- res = 2;
- break
- }
- if (maybeComponent(el) || //判断是否是组件
- (el.ifConditions && el.ifConditions.some(function (c) {//判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
- return maybeComponent(c.block);
- }))) {
- res = 1;
- }
- }
- return res
+ children,
+ maybeComponent
+ ) {
+ var res = 0;
+ for (var i = 0; i < children.length; i++) { //循环子节点
+ var el = children[i];
+ if (el.type !== 1) { //如果是真是dom则跳过循环
+ continue
+ }
+ //如果有for属性存在或者tag等于template或者是slot 则问真
+ if (needsNormalization(el) ||
+ (el.ifConditions && el.ifConditions.some(function (c) { //判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
+ return needsNormalization(c.block);
+ }))) {
+ res = 2;
+ break
+ }
+ if (maybeComponent(el) || //判断是否是组件
+ (el.ifConditions && el.ifConditions.some(function (c) {//判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
+ return maybeComponent(c.block);
+ }))) {
+ res = 1;
+ }
+ }
+ return res
}
//如果for属性存在或者tag等于template或者是slot 则问真
@@ -15133,12 +15211,12 @@
//返回虚拟dom vonde渲染调用的函数
return genComment(node)
} else {
- //返回虚拟dom vonde渲染调用的函数
+ //返回虚拟dom vonde渲染调用的函数
return genText(node)
}
}
- //返回虚拟dom vonde渲染调用的函数
+ //返回虚拟dom vonde渲染调用的函数
function genText(text) {
return ("_v(" + (text.type === 2
? text.expression // no need for () because already wrapped in _s()
@@ -15150,14 +15228,14 @@
return ("_e(" + (JSON.stringify(comment.text)) + ")")
}
- //返回虚拟dom vonde渲染调用的函数
+ //返回虚拟dom vonde渲染调用的函数
function genSlot(el, state) {
var slotName = el.slotName || '"default"'; //获取slotName 插槽名称
var children = genChildren(el, state); //获取子节点的虚拟dom渲染 函数
var res = "_t(" + slotName + (children ? ("," + children) : '');
var attrs = el.attrs && ("{" + (el.attrs.map(function (a) { //属性
- return ((camelize(a.name)) + ":" + (a.value));
- }).join(',')) + "}");
+ return ((camelize(a.name)) + ":" + (a.value));
+ }).join(',')) + "}");
var bind$$1 = el.attrsMap['v-bind']; //v-bind属性
if ((attrs || bind$$1) && !children) {
res += ",null";
@@ -15171,13 +15249,13 @@
return res + ')'
}
-// componentName is el.component, take it as argument to shun flow's pessimistic refinement
+ // componentName is el.component, take it as argument to shun flow's pessimistic refinement
//返回虚拟dom vonde渲染调用的函数
function genComponent(
- componentName, //组件名称
- el,
- state
- ) {
+ componentName, //组件名称
+ el,
+ state
+ ) {
var children = el.inlineTemplate ? null : genChildren(el, state, true);
return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
}
@@ -15214,8 +15292,8 @@
/* */
-// these keywords should not appear inside expressions, but operators like 这些关键字不应该出现在表达式中,但是操作符喜欢
-// typeof, instanceof and in are allowed 允许使用类型of、instanceof和in
+ // these keywords should not appear inside expressions, but operators like 这些关键字不应该出现在表达式中,但是操作符喜欢
+ // typeof, instanceof and in are allowed 允许使用类型of、instanceof和in
//匹配 配有全局匹配 只会匹配到一个
// do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
// 'super,throw,while,yield,delete,export,import,return,switch,default,' +
@@ -15224,19 +15302,19 @@
// 'super,throw,while,yield,delete,export,import,return,switch,default,' +
// 'extends,finally,continue,debugger,function,arguments'
var prohibitedKeywordRE = new RegExp('\\b' + (
- 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
- 'super,throw,while,yield,delete,export,import,return,switch,default,' +
- 'extends,finally,continue,debugger,function,arguments'
- ).split(',').join('\\b|\\b') + '\\b');
+ 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
+ 'super,throw,while,yield,delete,export,import,return,switch,default,' +
+ 'extends,finally,continue,debugger,function,arguments'
+ ).split(',').join('\\b|\\b') + '\\b');
-// these unary operators should not be used as property/method names 这些一元运算符不应该用作属性/方法名
+ // these unary operators should not be used as property/method names 这些一元运算符不应该用作属性/方法名
// 匹配 delete (任何字符) 或 typeof (任何字符) 或 void (任何字符)
var unaryOperatorsRE = new RegExp('\\b' + (
- 'delete,typeof,void'
- ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
+ 'delete,typeof,void'
+ ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
-// strip strings in expressions 在表达式中剥离字符串
+ // strip strings in expressions 在表达式中剥离字符串
//判断是否是真正的字符串
var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
@@ -15249,7 +15327,7 @@
//`([^`\\]|\\.)*` `和`之间的若干字符
-// detect problematic expressions in a template
+ // detect problematic expressions in a template
//检测模板中有问题的表达式
function detectErrors(ast) {
var errors = [];
@@ -15313,8 +15391,8 @@
//检查 for
function checkFor(node, //节点
- text, //for的text "(itme,index) in list"
- errors //错误信息
+ text, //for的text "(itme,index) in list"
+ errors //错误信息
) {
//检查字符串 转成真正的js的时候是否会报错
@@ -15329,11 +15407,11 @@
}
-//检查var a ='_' 或者 检查var a =_ 是否会报错 new function 用来检测js错误 与eval差不多
+ //检查var a ='_' 或者 检查var a =_ 是否会报错 new function 用来检测js错误 与eval差不多
function checkIdentifier(ident, //识别
- type, //类型
- text, //为本
- errors //错误信息
+ type, //类型
+ text, //为本
+ errors //错误信息
) {
if (typeof ident === 'string') {
try {
@@ -15381,7 +15459,7 @@
try {
return new Function(code)
} catch (err) {
- errors.push({err: err, code: code});
+ errors.push({ err: err, code: code });
return noop
}
}
@@ -15389,12 +15467,12 @@
//创建编译函数
/*********************************************************************************
- *Function: createCompileToFunctionFn
- * Description: 函数科里化 创建一个对象,并且把字符串转换成 对象函数方式存在在对象中,导出去匿名函数
- *Calls:
- *Called By: //调用本函数的清单
- *Input: template 模板字符串 options参数 vm vnode节点
- *Return: function 返回一个匿名函数
+ *Function: createCompileToFunctionFn
+ * Description: 函数科里化 创建一个对象,并且把字符串转换成 对象函数方式存在在对象中,导出去匿名函数
+ *Calls:
+ *Called By: //调用本函数的清单
+ *Input: template 模板字符串 options参数 vm vnode节点
+ *Return: function 返回一个匿名函数
**********************************************************************************/
function createCompileToFunctionFn(compile) {
@@ -15403,17 +15481,17 @@
//函数科里化
// 把字符串 编译变成 真正的js 并且以对象函数方式导出去
/*********************************************************************************
- *Function: compileToFunctions
- * Description: 把字符串 编译变成 真正的js 并且以对象函数方式导出去
- *Calls:
- *Called By:
- *Input: template 模板字符串 options参数 vm vnode节点
- *Return: object 对象函数 //函数返回值的说明
+ *Function: compileToFunctions
+ * Description: 把字符串 编译变成 真正的js 并且以对象函数方式导出去
+ *Calls:
+ *Called By:
+ *Input: template 模板字符串 options参数 vm vnode节点
+ *Return: object 对象函数 //函数返回值的说明
**********************************************************************************/
return function compileToFunctions(
- template, //字符串模板
- options, //参数
- vm //vmnode
+ template, //字符串模板
+ options, //参数
+ vm //vmnode
) {
//浅拷贝参数
options = extend({}, options);
@@ -15530,105 +15608,105 @@
* */
/*********************************************************************************
- *Function: createCompilerCreator
- * Description: 函数科里化 创建一个对象,并且把字符串转换成 对象函数方式存在在对象中,导出去匿名函数
- *Input: baseCompile 基本编译函数
- *Return: function 返回一个函数
+ *Function: createCompilerCreator
+ * Description: 函数科里化 创建一个对象,并且把字符串转换成 对象函数方式存在在对象中,导出去匿名函数
+ *Input: baseCompile 基本编译函数
+ *Return: function 返回一个函数
**********************************************************************************/
function createCompilerCreator(
- baseCompile //基本的编译函数
+ baseCompile //基本的编译函数
) {
console.log(baseCompile)
return function createCompiler(baseOptions) {
- console.log(baseOptions)
+ console.log(baseOptions)
function compile(
- template, //字符串模板
- options //options 参数
- ) {
- console.log(options)
-
-
- //template 模板 options 参数
- // 创建一个对象 拷贝baseOptions 拷贝到 原型 protype 中
- var finalOptions = Object.create(baseOptions); //为虚拟dom添加基本需要的属性
- console.log(finalOptions)
- console.log(finalOptions.__proto__)
- console.log(finalOptions.property)
-
- var errors = [];
- var tips = [];
- //声明警告函数
- finalOptions.warn = function (msg, tip) {
- (tip ? tips : errors).push(msg);
- };
-
- if (options) {
- console.log(options)
-
- // merge custom modules
- //baseOptions中的modules参数为
- // modules=modules$1=[
- // { // class 转换函数
- // staticKeys: ['staticClass'],
- // transformNode: transformNode,
- // genData: genData
- // },
- // { //style 转换函数
- // staticKeys: ['staticStyle'],
- // transformNode: transformNode$1,
- // genData: genData$1
- // },
- // {
- // preTransformNode: preTransformNode
- // }
- // ]
-
-
-
- if (options.modules) { //
- finalOptions.modules = (baseOptions.modules || []).concat(options.modules);
- }
- // merge custom directives 合并定制指令
- if (options.directives) {
- finalOptions.directives = extend(Object.create(baseOptions.directives || null), options.directives);
- }
- console.log(options)
-
- // options 为:
-
- // comments: undefined
- // delimiters: undefined
- // shouldDecodeNewlines: false
- // shouldDecodeNewlinesForHref: true
-
- // copy other options 复制其他选项
- for (var key in options) {
- if (key !== 'modules' && key !== 'directives') {
- //浅拷贝
- finalOptions[key] = options[key];
- }
- }
- }
- //参数传进来的函数
- //template 模板
- //finalOptions 基本参数
- var compiled = baseCompile(
- template, //template 模板
- finalOptions //finalOptions 基本参数 为虚拟dom添加基本需要的属性
- );
+ template, //字符串模板
+ options //options 参数
+ ) {
+ console.log(options)
- {
- errors.push.apply(errors, detectErrors(compiled.ast));
- }
- compiled.errors = errors;
- compiled.tips = tips;
- return compiled
+ //template 模板 options 参数
+ // 创建一个对象 拷贝baseOptions 拷贝到 原型 protype 中
+ var finalOptions = Object.create(baseOptions); //为虚拟dom添加基本需要的属性
+ console.log(finalOptions)
+ console.log(finalOptions.__proto__)
+ console.log(finalOptions.property)
+
+ var errors = [];
+ var tips = [];
+ //声明警告函数
+ finalOptions.warn = function (msg, tip) {
+ (tip ? tips : errors).push(msg);
+ };
+
+ if (options) {
+ console.log(options)
+
+ // merge custom modules
+ //baseOptions中的modules参数为
+ // modules=modules$1=[
+ // { // class 转换函数
+ // staticKeys: ['staticClass'],
+ // transformNode: transformNode,
+ // genData: genData
+ // },
+ // { //style 转换函数
+ // staticKeys: ['staticStyle'],
+ // transformNode: transformNode$1,
+ // genData: genData$1
+ // },
+ // {
+ // preTransformNode: preTransformNode
+ // }
+ // ]
+
+
+
+ if (options.modules) { //
+ finalOptions.modules = (baseOptions.modules || []).concat(options.modules);
+ }
+ // merge custom directives 合并定制指令
+ if (options.directives) {
+ finalOptions.directives = extend(Object.create(baseOptions.directives || null), options.directives);
+ }
+ console.log(options)
+
+ // options 为:
+
+ // comments: undefined
+ // delimiters: undefined
+ // shouldDecodeNewlines: false
+ // shouldDecodeNewlinesForHref: true
+
+ // copy other options 复制其他选项
+ for (var key in options) {
+ if (key !== 'modules' && key !== 'directives') {
+ //浅拷贝
+ finalOptions[key] = options[key];
+ }
+ }
+ }
+ //参数传进来的函数
+ //template 模板
+ //finalOptions 基本参数
+ var compiled = baseCompile(
+ template, //template 模板
+ finalOptions //finalOptions 基本参数 为虚拟dom添加基本需要的属性
+ );
+
+
+ {
+ errors.push.apply(errors, detectErrors(compiled.ast));
+ }
+ compiled.errors = errors;
+ compiled.tips = tips;
+ return compiled
}
/*
@@ -15655,56 +15733,58 @@
/* */
-// `createCompilerCreator` allows creating compilers that use alternative 允许创建使用替代的编译器
-// parser/optimizer/codegen, e.g the SSR optimizing compiler. 解析器/优化/ codegen,e。SSR优化编译器。
-// Here we just export a default compiler using the default parts. 这里我们只是使用默认部分导出一个默认编译器。
+ // `createCompilerCreator` allows creating compilers that use alternative 允许创建使用替代的编译器
+ // parser/optimizer/codegen, e.g the SSR optimizing compiler. 解析器/优化/ codegen,e。SSR优化编译器。
+ // Here we just export a default compiler using the default parts. 这里我们只是使用默认部分导出一个默认编译器。
//编译器创建的创造者
var createCompiler = createCompilerCreator(
- //把html变成ast模板对象,然后再转换成 虚拟dom 渲染的函数参数形式。
- // 返回出去一个对象
- // {ast: ast, //ast 模板
- // render: code.render, //code 虚拟dom需要渲染的参数函数
- //staticRenderFns: code.staticRenderFns } //空数组
-
- function baseCompile(
- template, //string模板
- options //
- ) {
-
- /*
- template, //模板字符串
- options 参数为
- 原型中有baseOptions方法
- {
- shouldDecodeNewlines: shouldDecodeNewlines, //flase //IE在属性值中编码换行,而其他浏览器则不会
- shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, //true chrome在a[href]中编码内容
- delimiters: options.delimiters, //改变纯文本插入分隔符。修改指令的书写风格,比如默认是{{mgs}} delimiters: ['${', '}']之后变成这样 ${mgs}
- comments: options.comments //当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。
- },
- */
- console.log(options)
-
- //返回ast模板对象
- var ast = parse(template.trim(), options);
-
- if (options.optimize !== false) { //optimize 的主要作用是标记 static 静态节点,
- // * 循环递归虚拟node,标记是不是静态节点
- //* 根据node.static或者 node.once 标记staticRoot的状态
- optimize(ast, options);
- }
- //初始化扩展指令,on,bind,cloak,方法, dataGenFns 获取到一个数组,数组中有两个函数genData和genData$1
- //genElement根据el判断是否是组件,或者是否含有v-once,v-if,v-for,是否有template属性,或者是slot插槽,转换style,css等转换成虚拟dom需要渲染的参数函数
- //返回对象{ render: ("with(this){return " + code + "}"),staticRenderFns: state.staticRenderFns} //空数组
- var code = generate(ast, options);
-
- return {
- ast: ast, //ast 模板
- render: code.render, //code 虚拟dom需要渲染的参数函数
- staticRenderFns: code.staticRenderFns //空数组
- }
- });
+ //把html变成ast模板对象,然后再转换成 虚拟dom 渲染的函数参数形式。
+ // 返回出去一个对象
+ // {ast: ast, //ast 模板
+ // render: code.render, //code 虚拟dom需要渲染的参数函数
+ //staticRenderFns: code.staticRenderFns } //空数组
+
+ function baseCompile(
+ template, //string模板
+ options //
+ ) {
+
+ /*
+ template, //模板字符串
+ options 参数为
+ 原型中有baseOptions方法
+ {
+ shouldDecodeNewlines: shouldDecodeNewlines, //flase //IE在属性值中编码换行,而其他浏览器则不会
+ shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, //true chrome在a[href]中编码内容
+ delimiters: options.delimiters, //改变纯文本插入分隔符。修改指令的书写风格,比如默认是{{mgs}} delimiters: ['${', '}']之后变成这样 ${mgs}
+ comments: options.comments //当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。
+ },
+ */
+ console.log(options)
+
+ //返回ast模板对象
+ var ast = parse(template.trim(), options);
+
+ if (options.optimize !== false) { //optimize 的主要作用是标记 static 静态节点,
+ // * 循环递归虚拟node,标记是不是静态节点
+ //* 根据node.static或者 node.once 标记staticRoot的状态
+ optimize(ast, options);
+ }
+ //初始化扩展指令,on,bind,cloak,方法, dataGenFns 获取到一个数组,数组中有两个函数genData和genData$1
+ //genElement根据el判断是否是组件,或者是否含有v-once,v-if,v-for,是否有template属性,或者是slot插槽,转换style,css等转换成虚拟dom需要渲染的参数函数
+ //返回对象{ render: ("with(this){return " + code + "}"),staticRenderFns: state.staticRenderFns} //空数组
+ var code = generate(ast, options);
+
+ return {
+ ast: ast, //ast 模板
+ render: code.render, //code 虚拟dom需要渲染的参数函数
+ staticRenderFns: code.staticRenderFns //空数组
+ }
+ }
+
+ );
/*
*
@@ -15717,7 +15797,7 @@
/* */
-// check whether current browser encodes a char inside attribute values
+ // check whether current browser encodes a char inside attribute values
var div;
//检查a标签是否有href 地址,如果有则渲染a标签,如果没有则渲染div标签
@@ -15729,10 +15809,10 @@
return div.innerHTML.indexOf('
') > 0
}
-// #3663: IE encodes newlines inside attribute values while other browsers don't
+ // #3663: IE encodes newlines inside attribute values while other browsers don't
//IE在属性值中编码换行,而其他浏览器则不会
var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
-// #6828: chrome encodes content in a[href]
+ // #6828: chrome encodes content in a[href]
//chrome在a[href]中编码内容
var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;
@@ -15757,6 +15837,8 @@
Vue.prototype.$mount = function (el, hydrating) { //重写Vue.prototype.$mount
+ console.log('$mount 15835')
+ debugger
el = el && query(el); //获取dom
/* istanbul ignore if */
//如果el 是body 或者文档 则警告
@@ -15820,6 +15902,7 @@
console.log('==options.comments==')
console.log(options.comments)
+ // render 函数 也是 ast转换 方法
var ref = compileToFunctions(
template, //模板字符串
{
@@ -15852,12 +15935,12 @@
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
- /*
- render 是 虚拟dom,需要执行的编译函数 类似于这样的函数
- (function anonymous( ) {
- with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"info",rawName:"v-info"},{name:"data",rawName:"v-data"}],attrs:{"type":"text"}}),_v(" "),_m(0)])}
- })
- */
+ /*
+ render 是 虚拟dom,需要执行的编译函数 类似于这样的函数
+ (function anonymous( ) {
+ with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"info",rawName:"v-info"},{name:"data",rawName:"v-data"}],attrs:{"type":"text"}}),_v(" "),_m(0)])}
+ })
+ */
options.render = render;
options.staticRenderFns = staticRenderFns;
console.log(options);
@@ -15877,10 +15960,10 @@
//执行$mount方法 一共执行了两次 第一次是在9000多行那一个 用$mount的方法把扩展挂载到dom上
return mount.call(
- this,
- el, //真实的dom
- hydrating //undefined
- )
+ this,
+ el, //真实的dom
+ hydrating //undefined
+ )
};
/**
From 82d363c0628b93b30e5fbda46af9bedb75a679c5 Mon Sep 17 00:00:00 2001
From: yaoguanshou <281113270@qq.com>
Date: Mon, 2 Sep 2024 00:26:36 +0800
Subject: [PATCH 3/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9332d3d..206db68 100644
--- a/README.md
+++ b/README.md
@@ -624,7 +624,7 @@ parseHTML
-具体看我源码和流程图,这里文字就不描述这么多了,流程图是下面这中的网盘,源码是vue.js,基本每一行都有注释,然后diff待更新中
+具体看我源码和流程图,这里文字就不描述这么多了,流程图是下面这中的网盘,源码是vue.js,基本每一行都有注释
链接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng
提取码:1fnu
From 54bd86b308ad4ed0d96f9d1fc111f1c5f6dd7f55 Mon Sep 17 00:00:00 2001
From: yaoguanshou <281113270@qq.com>
Date: Tue, 3 Sep 2024 00:52:08 +0800
Subject: [PATCH 4/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
"README - \345\211\257\346\234\254.md" | 28 -
README.md | 681 ++++++++++++++++++++++++-
vue.js | 1 +
3 files changed, 670 insertions(+), 40 deletions(-)
delete mode 100644 "README - \345\211\257\346\234\254.md"
diff --git "a/README - \345\211\257\346\234\254.md" "b/README - \345\211\257\346\234\254.md"
deleted file mode 100644
index fc3d2b2..0000000
--- "a/README - \345\211\257\346\234\254.md"
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
- vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。
-
- 说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。
-
- 1.vue源码解读流程 1.new Vue 调用的是 Vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initLifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。
-
- 2.双数据绑定原理是:obersve()方法判断value没有没有__ob___属性并且是不是Obersve实例化的,
- value是不是Vonde实例化的,如果不是则调用Obersve 去把数据添加到观察者中,为数据添加__ob__属性, Obersve 则调用defineReactive方法,该方法是连接Dep和wacther方法的一个通道,利用Object.definpropty() 中的get和set方法 监听数据。get方法中是new Dep调用depend()。为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 然后set方法就是调用dep中的notify 方法调用wacther中的run 更新视图
- 3.vue从字符串模板怎么到真实的dom呢?是通过$mount挂载模板,就是获取到html,然后通过paseHTML这个方法转义成ast模板,他大概算法是 while(html) 如果匹配到开始标签,结束标签,或者是属性,都会截取掉html,然后收集到一个对象中,知道循环结束 html被截取完。最后变成一个ast对象,ast对象好了之后,在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数,编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过diff算法变成正真正的dom元素。
-
-
-
-具体看我源码和流程图,这里文字就不描述这么多了,流程图是下面这中的网盘,源码是vue.js,基本每一行都有注释,然后diff待更新中
-
-链接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng
-提取码:1fnu
-
-
-上面的vue.js 就是我基于vue源码中每行加有注释的vue.js, 其他文件就是我看vue.js源码的时候抽出来的vue.js 源码小demo
-
-
-
-
- 作者:姚观寿
diff --git a/README.md b/README.md
index 206db68..5f6794f 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,35 @@
- vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。
-
- 说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我QQ群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。
+# 开始
+ 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元素。
+## 1.模板转换:
- 4.diif算法,vue2 的diff 算法是深度优先算法遍历,然后对比算法是通过 新旧的vnode对比先对比他们的基本属性,比如key 标签等,如果是相同则通过diff算法对比然后diff算法是新旧的vnode对比,然后有四个指针索引,两个新的vnode开始指针和新的 vnode 结束指针,两个旧的vnode开始指针和旧的 vnode 结束指针。然后先判断vnode是否为空,如果为空就往中间靠拢 开始的指针++ 结束的指针 --。然后两头对比之后,在交叉对比,直到找不到相同的vnode之后如果多出的就删除,如果少的话就新增,然后对比完之后在更新到真实dom。
+ 就是我们写的 vue 模板 或者是 react jsx 我们都可以理解是模板,然后他会经过 模板编译转换,像vue的话是进过一个方法paseHTML方法转换成ast树,里面的paseHTML用while 循环模板,然后经过正则 匹配到vue指令,还有vue的属性,事件方法等,收集到一个ast树中。
+## 2.数据相应:
+ vue是一个双数据相应的框架,底层用的是Object.defineProperty 监听和挟持数据改变,然后调用回调方法更新视图更新。双数据绑定原理是:obersve()方法判断value没有没有__ob___属性并且是不是Obersve实例化的, value是不是Vonde实例化的,如果不是则调用Obersve 去把数据添加到观察者中,为数据添加__ob__属性, Obersve 则调用defineReactive方法,该方法是连接Dep和wacther方法的一个通道,利用Object.definpropty() 中的get和set方法 监听数据。get方法中是new Dep调用depend()。为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 然后set方法就是调用dep中的notify 方法调用wacther中的run 更新视图
+## 3.虚拟dom:
+ vnode,在vue用vnode是通过 ast对象,在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数,编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过diff算法变成正真正的dom元素。
+## 4.diif算法:
+
+ vue2 的diff 算法是深度优先算法遍历,然后对比算法是通过 新旧的vnode对比先对比他们的基本属性,比如key 标签等,如果是相同则通过diff算法对比然后diff算法是新旧的vnode对比,然后有四个指针索引,两个新的vnode开始指针和新的 vnode 结束指针,两个旧的vnode开始指针和旧的 vnode 结束指针。然后先判断vnode是否为空,如果为空就往中间靠拢 开始的指针++ 结束的指针 --。然后两头对比之后,在交叉对比,直到找不到相同的vnode之后如果多出的就删除,如果少的话就新增,然后对比完之后在更新到真实dom。
-源码入口流程 vue源码解读流程 1.new Vue 调用的是 Vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initLifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。
+源码入口流程 vue源码解读流程 1.new Vue 调用的是 Vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initLifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。
+
+# new Vue实例化程序入口
+
```
Vue.prototype._init = function (options) { //初始化函数
//... 省略code
@@ -45,6 +53,8 @@ vue 如何去看vue源码呢?其实mvvm源码并没有想象中那么神秘,
+# 查找和挂载模板
+
vm.$mount 进入这个挂载模板方法,判断是否有 render 函数 或者是template,如果没有则使用el.outerHTML , 实际上这里就是要拿到模板的html内容
```
@@ -90,7 +100,7 @@ vue 如何去看vue源码呢?其实mvvm源码并没有想象中那么神秘,
-
+# 编译AST和render函数
调用 Vue.prototype.$mount 方法之后 拿到模板之后 就会进入以下这几个方法,这几个方法用了很多函数式编程
@@ -614,15 +624,662 @@ parseHTML
+## 双数据响应
+ 双数据绑定 入口 方法在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,基本每一行都有注释
@@ -630,7 +1287,7 @@ parseHTML
提取码:1fnu
-上面的vue.js 就是我基于vue源码中每行加有注释的vue.js, 其他文件就是我看vue.js源码的时候抽出来的vue.js 源码小demo
+上面的vue.js 就是我基于vue源码中每行加有注释的vue.js, 其他文件就是我看vue.js源码的时候抽出来的vue.js 源码小demo。如果大家觉得不错请动动小手指,帮我点一个satr,你们的支持就是我的动力
diff --git a/vue.js b/vue.js
index 8b44868..3da71b9 100644
--- a/vue.js
+++ b/vue.js
@@ -6240,6 +6240,7 @@
console.log({ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children })
console.log(asyncFactory)
+ // 创建虚拟dom
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, // 标签 属性数据
From d32e7d985f22a7e09250f943f146daefaa6853f3 Mon Sep 17 00:00:00 2001
From: yaoguanshou <281113270@qq.com>
Date: Wed, 4 Sep 2024 22:24:51 +0800
Subject: [PATCH 5/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README_EN.md | 1296 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1296 insertions(+)
create mode 100644 README_EN.md
diff --git a/README_EN.md b/README_EN.md
new file mode 100644
index 0000000..a622c8e
--- /dev/null
+++ b/README_EN.md
@@ -0,0 +1,1296 @@
+# 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:
+ //匹配开头必需是 后面可以忽略是任何字符串 ^<\\/((?:[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 //上一个标签和现在标签相同 编译成 但是这种情况是不会出现的 因为浏览器解析的时候会自动补全如果是我是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
From 7636c6e496680ab4e4a576fc802fd5af7f39030c Mon Sep 17 00:00:00 2001
From: yaoguanshou <281113270@qq.com>
Date: Wed, 4 Sep 2024 22:27:45 +0800
Subject: [PATCH 6/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 5f6794f..30a926b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+ ***English document***: https://github.com/ygs-code/vue/blob/master/README_EN.md
# 开始
vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了 。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。
From 33a6165749d00a547702d01197d67035f912bd2c Mon Sep 17 00:00:00 2001
From: yaoguanshou <281113270@qq.com>
Date: Wed, 4 Sep 2024 22:41:09 +0800
Subject: [PATCH 7/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8D=95=E8=AF=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
vue.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/vue.js b/vue.js
index 3da71b9..a8f2eac 100644
--- a/vue.js
+++ b/vue.js
@@ -503,8 +503,8 @@
'created', //生命周期 结束实例化完 vue 指令
'beforeMount', //生命周期 开始渲染虚拟dom ,挂载event 事件 指令
'mounted', //生命周期 渲染虚拟dom ,挂载event 事件 完 指令
- 'beforeUpdate', //生命周期 开始更新wiew 数据指令
- 'updated', //生命周期 结束更新wiew 数据指令
+ 'beforeUpdate', //生命周期 开始更新view 数据指令
+ 'updated', //生命周期 结束更新view 数据指令
'beforeDestroy', //生命周期 开始销毁 new 实例 指令
'destroyed', //生命周期 结束销毁 new 实例 指令
'activated', //keep-alive组件激活时调用。