某人

此前素未谋面、此后遥遥无期

0%

vue2.0双向绑定

MVVM

MVVM是Model-View-ViewModel 的简写,是MVC的改进版.

在双向绑定中,Model和View之间没有耦合,通过操作Model,利用ViewModel提供的机制,自动实现ViewModel的更新。

Object.defineProperty

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

1
Object.defineProperty(obj, prop, descriptor)
  • obj:要在其上定义属性的对象
  • prop:要定义或修改的属性的名称
  • descriptor:将被定义或修改的属性描述符。
  1. get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined
  2. set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Log() {
var val = null;
var arr = [];
var id = 0;

Object.defineProperty(this, 'name', {
get: function() {
return val;
},
set: function(newVal) {
arr.push({id:id++,oldVal:val,newVal:newVal});
val = newVal;
}
});
this.getLog = function() { return arr; };
}

var lg = new Log();
lg.name="小明";
lg.name="小华";
lg.name="小刚";
lg.getLog();

发布/订阅者模式

订阅者把自己想订阅的事件注册到调度中心,

当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

1
2
            发布                  订阅
发布者 -> 调度中心 <- 订阅者(多个)

vue双向绑定的实现Dep、Observer、Watcher:

  • Dep(调度中心):存放收集Watcher,Watcher的实例就是订阅者。
  • Observer(发布者):是一个类(class),每个Observer都附有被观察的对象,观察的对象,一旦连接,观察者就转换目标对象的属性键成为getter/setters,收集依赖关系并发送更新
  • Watcher(订阅者):观察者分析表达式,收集依赖关系,并在表达式值更改时触发回调。

Watcher(订阅者)

解析对象路径函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* [解析对象路径 obj = {a:b:{c:1}}]
* a.b.c
*/
function parsePath (path) {
var bailRE = /[^\w.$]/;
if (bailRE.test(path)) {
return
}
var segments = path.split('.');
return function (obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
obj = obj[segments[i]];
}
return obj
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* [订阅者]
*/
var Watcher = function Watcher(vm,expOrFn,cb){
this.vm = vm;
this.cb = cb;
this.expression = expOrFn;
this.getter = parsePath(expOrFn);
this.value = this.get()//更新前的值
}

//获取表达式的值
Watcher.prototype.get = function(){
Dep.target = this;
var data = this.vm.data;
var value = this.getter.call(data, data);
//表明是watcher调用了getter
return value;
}

//订阅者的更新方法
Watcher.prototype.update = function (){
//获取更新后的值
var value = this.get()
var oldVal = this.value;
if(value!==oldVal){
this.value = value //更新新值
this.cb.call(this.vm,value,oldVal);
}
}

/**
* 添加订阅者
*/
Watcher.prototype.addDep = function addDep (dep) {
dep.addSub(this);
};

Dep(调度中心)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* [调度中心]
*/
var Dep = function Dep () {
this.subs = []//订阅者队列
};

//定义一个全局变量,用来判断是否是watcher调用了getter
Dep.target = null;

/**
* [添加订阅者]
*/
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};

/**
* [移除订阅者]
*/
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};

/**
* [订阅者,注册事件到事件中心]
* set触发notify,并触发相应的update方法
*/
Dep.prototype.notify = function notify () {
// 复制一个新的订阅者数组
var subs = this.subs.slice();
console.log('notify',subs)
for (var i = 0, l = subs.length; i < l; i++) {
//调度中心统一调度订阅者注册到调度中心的处理代码
subs[i].update();
}
};

/**
* [增加订阅者watcher到对象Dep]
*/
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};

Observer(发布者)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* [发布者 Observer 对data做监听]
*/
var Observer = function Observer (data) {
this.data = data;
this.dep = new Dep();
this.walk(data);
};

/**
* 遍历对象所有属性,包括子属性,使其拥有getter/setter方法
* 仅限值类型是对象,这个方法才能被调用
*/
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
this.defineReactive(obj, keys[i], obj[keys[i]]);
}
};

/**
* [在对象上定义一个响应式属性]
* @return {[type]} [description]
*/
Observer.prototype.defineReactive = function( obj, key,val){
//实例化一个Dep
var _this = this;
var dep = new Dep();
observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
//Dep.target指针指向watcher,增加订阅者watcher到对象Dep
if(Dep.target){
//console.log('Dep.target')
// dep.depend();
dep.addSub(Dep.target)
}
return val;
},
set: function reactiveSetter (newVal) {
// 如果新设置的值和原来相等,则不重新赋值
// console.log('set之前',val,newVal)
if (newVal === val) {
return
}
val = newVal;
observe(newVal);
//调度中心发出通知
dep.notify();
// console.log('set之后',val,newVal)
}
});
}

Observer.prototype.$watcher = function(expOrFn,cb){
new Watcher(this,expOrFn,cb);
}

//如果是对象,则再实例化一个Observer
function observe (val,vm){
if(Object.prototype.toString.call(val) ==="[object Object]"){
new Observer(val);
}
}

return Observer;

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {name:'小明',other:20,power:{
p1:'有钱',
},
};
var app = new Observer(obj);
app.$watcher('name', function(newVal,oldVal) {
console.log('打印name',newVal,oldVal)
});
app.$watcher('power.p1', function(newVal,oldVal) {
console.log('power.p1',newVal,oldVal)
});

setTimeout(function(){
obj.name = "小刚";
obj.power.p1 = '会打王者';
},1500);

抽象语法树AST(abstract syntax tree)

AST是指抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式。

Vue.js v2.5.13

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* `createCompilerCreator`允许创建使用替代的编译器
* 解析器/优化器/ codegen,例如SSR优化编译器。
* 这里我们只使用默认的部分导出一个默认的编译器。
*/
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
var ast = parse(template.trim(), options);
if (options.optimize !== false) {
optimize(ast, options);
}
var code = generate(ast, options);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});

/**
* 将HTML字符串转换为AST
*/
function parse (
template,
options
) {
var root;
/*todo....*/
parseHTML(template, {
warn: warn$2,
/*...*/
start: function start (tag, attrs, unary) {}
end: function end () {/*...*/},
chars: function chars (text) {/*...*/},
comment: function comment (text){/*...*/}
})
return root
}

vue将模板字符串给parse函数,最终返回一个抽象语法树(AST)

parse函数内部会调用一个parseHTML,来完成对模板字符串解析,最终返回个js语法的树形结构。

执行generate函数返回code中有个render函数和staticRenderFns函数

虚拟DOM(VNode)

文档对象模型(Document Object Model,简称DOM)。用jquery在网页中直接操作dom结构,相对于原生API无疑方便了很多。

为了追求性能、和更方便的对DOM维护,于是就引入了虚拟DOM。

用JS表示DOM结构,操作DOM慢,而javascript很快,用javascript对象可以很容易地表示DOM节点。DOM节点包括标签、属性和子节点.

image

Vue.js v2.5.13(VNode.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* @flow */
export default class VNode {
tag: string | void;/* 标签类型 */
data: VNodeData | void; /* 节点上的class,attribute,style 事件 */
children: ?Array<VNode>;/* 存放的是子虚拟节点 */
text: string | void;/* 表明文本节点内容 */
elm: Node | void;/* 虚拟节点对应的真实DOM节点 */
ns: string | void;/* html命名空间 */
context: Component | void; /* 在此组件的作用域 */
key: string | number | void; /* 是vnode的标记 */
componentOptions: VNodeComponentOptions | void;/* 组件参数 */
componentInstance: Component | void; /* 组件实例 */
parent: VNode | void; /* */

/* 严格内部 */
raw: boolean; /* 包含原始HTML? (仅限服务器) */
isStatic: boolean; /* 悬挂静态节点 */
isRootInsert: boolean; /* 进入转换检查所必需的 */
isComment: boolean; /* 空注释占位符? */
isCloned: boolean; /*是否是一个克隆节点? */
isOnce: boolean; /* 是否是一次性节点? */
asyncFactory: Function | void; /* 异步组件 factory function */
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; /* 真实的上下文虚拟功能节点 */
fnOptions: ?ComponentOptions; /* 用于SSR缓存 */
fnScopeId: ?string; /* 功能范围id支持 */

constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}

/**
* 弃用: 别名 为组件实例 向后兼容.
* istanbul ignore next
*/
get child (): Component | void {
return this.componentInstance
}
}

/* 创建一个空的vnode实例 */
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}

/* 创建一个文本vnode */
export function createTextVNode (val: string | number) {
return new VNode(undefined, undefined, undefined, String(val))
}

/**
* 优化的浅层克隆
* 用于静态节点和插槽节点,因为它们可能会被重复使用
* 多重渲染,克隆它们可以避免DOM操作依赖时的错误
* 克隆一个vnode
*/
export function cloneVNode (vnode: VNode): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
vnode.children,
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
)
cloned.ns = vnode.ns
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key
cloned.isComment = vnode.isComment
cloned.fnContext = vnode.fnContext
cloned.fnOptions = vnode.fnOptions
cloned.fnScopeId = vnode.fnScopeId
cloned.isCloned = true
return cloned
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function mountComponent ( vm, el, hydrating) {
vm.$el = el;
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
/* .... */
}
callHook(vm, 'beforeMount');

var updateComponent;

if ("development" !== 'production' && config.performance && mark) {
updateComponent = function () {
vm._update(vnode, hydrating);
/* .... */
};
} else {
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
}
//我们在观察者的构造函数中将其设置为vm._watcher
//因为观察者的初始补丁可能会调用$ forceUpdate(例如在子内部)
//组件的挂载钩子),这依赖于已经定义的vm._watcher
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
hydrating = false;
/* .... */
return vm
}

在初始化的时候执行mountComponent函数方法,其中的vm._render()函数返回虚拟Dom(在模板编译后生成的render方法挂载了实例上)

其中的updateComponent传给了Watcher,而updateComponent中有vm._update(vnode, hydrating)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var prevActiveInstance = activeInstance;
activeInstance = vm;
vm._vnode = vnode;
// Vue.prototype.__patch__方法已经在入口点注入
// based on the rendering backend used.
if (!prevVnode) {
// 初始渲染
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
);
//在初始补丁之后不需要ref节点
// 这可以防止在内存中保留分离的DOM树(#5851)
vm.$options._parentElm = vm.$options._refElm = null;
} else {
// 更新
vm.$el = vm.__patch__(prevVnode, vnode);
}
/* ... */
};

传入新的vnode与老的vnode进行diff,来进行dom更新操作,其中的vm.__patch__方法在入口处已经注入

定义patch补丁函数:

1
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });

注入patch方法:

1
2
// 安装平台补丁功能
Vue.prototype.__patch__ = inBrowser ? patch : noop;

函数createPatchFunction返回patch方法,其中的patch方法之后来完成vnode的diff过程和打补丁pacth过程,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* sameVnode会对传入的2个vnode进行基本属性的比较 */
function sameVnode (a, b) {
/* .... */
}
function createPatchFunction (backend) {
function emptyNodeAt (elm) {return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)}
function createRmCb (childElm, listeners)}
function removeNode (el) {}
/* ... */
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly){}
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {}
function invokeInsertHook (vnode, queue, initial) {}
/* ... */
return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
//销毁钩子
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
}
/* ... */
if (isUndef(oldVnode)) {
// oldVnode不存在,创建一个root节点
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue, parentElm, refElm);
} else {
var isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 是同一个节点的时候直接修改现有的节点
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
} else {
/* .... */
}
}
/*调用insert钩子*/
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
}

vue生命周期

生命周期钩子(Vue.js v2.5.13)

1
2
3
4
5
6
7
8
9
10
11
12
13
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured'
];

image

版本:Vue.js v2.5.13

  1. beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  2. created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
  3. beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。(该钩子在服务器端渲染期间不被调用)
  4. mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。(该钩子在服务器端渲染期间不被调用。)
    • 注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted
  5. beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。(该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。)
  6. updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子(该钩子在服务器端渲染期间不被调用)
  7. activated:keep-alive 组件激活时调用(该钩子在服务器端渲染期间不被调用)
  8. deactivated:keep-alive 组件停用时调用(该钩子在服务器端渲染期间不被调用)
  9. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  10. destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁(该钩子在服务器端渲染期间不被调用)
  11. errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

new Vue()调用构造函数,执行this._init(options)

构造函数Vue$3:

1
2
3
4
5
6
7
8
9
function Vue$3 (options) {
if ("development" !== 'production' &&
!(this instanceof Vue$3)
) {
warn(`Vue是一个构造函数,应该用new关键字`);
}
this._init(options);
}
return Vue$3;

原型对象上的_init方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Vue.prototype._init = function (options) {
var vm = this;
/* todo... */
{
initProxy(vm);
}
/*初始化生命周期*/
initLifecycle(vm);
/* 初始化事件 */
initEvents(vm);
initRender(vm);
/*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/
callHook(vm, 'beforeCreate');
initInjections(vm); // 在data/props之前初始化注入
/*初始化props、methods、data、computed、watch */
initState(vm);
initProvide(vm); // resolve provide after data/props
/*调用created钩子函数并且触发created钩子事件*/
callHook(vm, 'created');
/* todo... */
if (vm.$options.el) {
/*挂载组件*/
vm.$mount(vm.$options.el);
}
}

初始化props、methods、data、computed、watch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
/*初始化props*/
if (opts.props) { initProps(vm, opts.props); }
/*初始化方法*/
if (opts.methods) { initMethods(vm, opts.methods); }
/*初始化data*/
if (opts.data) {
initData(vm);
} else {
/*该组件没有data的时候绑定一个空对象*/
observe(vm._data = {}, true /* asRootData */);
}
/*初始化computed*/
if (opts.computed) { initComputed(vm, opts.computed); }
/*初始化watchers*/
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}

初始化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function'? getData(data, vm): data || {};
/* todo.... */

var keys = Object.keys(data);
var i = keys.length;
/* todo... */
while (i--) {
var key = keys[i];
/* todo... */
//代理数据到实例上
proxy(vm, "_data", key);
}
// 开始观察数据,递归进行数据绑定
observe(data, true /* 作为根数据 */);
}

代理数据到实例上:

1
2
3
4
5
6
7
8
9
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}

尝试创建一个Observer实例,如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。

1
2
3
4
5
6
7
8
9
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
/* todo... */
ob = new Observer(value);
return ob
}

挂载组件,并编译模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
var ast = parse(template.trim(), options);
if (options.optimize !== false) {
optimize(ast, options);
}
var code = generate(ast, options);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});
var ref$1 = createCompiler(baseOptions);
var compileToFunctions = ref$1.compileToFunctions;
/*将$mount方法保存下来,此时没编译模板,在最后边调用。*/
var mount = Vue$3.prototype.$mount;

Vue$3.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
/* todo... */
var options = this.$options;
// resolve template/el and convert to render function
// 处理模板templete,编译成render函数,render不存在的时候编译template,否则使用render
if (!options.render) {
var template = options.template;
if (template) {
/* todo... */
} else if (el) {
/*获取el的outerHTML*/
template = getOuterHTML(el);
}
if (template) {
/* todo... */
/* 编译模板,最后返回两个函数render和staticRenderFns */
var ref = compileToFunctions(template, {
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
/* todo... */
}
}
//调用保存下来的mount方法
return mount.call(this, el, hydrating)
};

当模板编译了之后调用公共挂载方法:

1
2
3
4
5
6
7
8
// 公共挂载方法
Vue$3.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};

挂载组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
/* todo */
}
callHook(vm, 'beforeMount');
var updateComponent;
if ("development" !== 'production' && config.performance && mark) {
updateComponent = function () {
/* todo... */
vm._update(vnode, hydrating);
/* todo... */
};
} else {
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
}

/**
* 我们在观察者的构造函数中将其设置为vm._watcher
   * 因为观察者的初始补丁可能会调用$forceUpdate(例如在子内部组件的挂载钩子),
   * 这依赖于已经定义的vm._watcher
*/
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
hydrating = false;

/**
* 手动挂载实例,调用自己挂载
  * 在其插入的钩子中调用渲染创建的子组件
*/
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}

强制更新:

1
2
3
4
5
6
Vue.prototype.$forceUpdate = function () {
var vm = this;
if (vm._watcher) {
vm._watcher.update();
}
};