vue实例 初始化 完成之后,接下来就要进行 挂载。 html
vue实例挂载,即为将vue实例对应的 template模板,渲染成 Dom节点。vue
经过原型方法 $mount方法 来挂载vue实例。 node
挂载vue实例时,经历一下几个重要步骤:express
// 挂载Vue实例 Vue$3.prototype.$mount = function(el, hydrating) { // el为dom元素对应的选择器表达式,根据选择器表达式,获取dom元素 el = el && query(el); ... // this->Vue实例,或者是组件实例对象 var options = this.$options; // 解析模板,将模板转换为render渲染函数 if(!options.render) { // 通常是组件实例的构造函数的options中会直接有template这个属性 var template = options.template; if(template) { if(typeof template === 'string') { if(template.charAt(0) === '#') { template = idToTemplate(template); /* istanbul ignore if */ if("development" !== 'production' && !template) { warn( ("Template element not found or is empty: " + (options.template)), this ); } } } else if(template.nodeType) { template = template.innerHTML; } else { { warn('invalid template option:' + template, this); } return this } } else if(el) { // 获取dom节点的outerHTML template = getOuterHTML(el); } // template,模板字符串 if(template) { /* istanbul ignore if */ if("development" !== 'production' && config.performance && mark) { mark('compile'); } // 将html模板字符串编译为渲染函数 var ref = compileToFunctions(template, { // 换行符 shouldDecodeNewlines : shouldDecodeNewlines, // 分割符 delimiters : options.delimiters, // 注释 comments : options.comments }, this); // 获取渲染函数 var render = ref.render; // 获取静态渲染函数 var staticRenderFns = ref.staticRenderFns; // 将渲染函数添加到Vue实例对象的配置项options中 options.render = render; // 将静态渲染函数添加到Vue实例对象的配置项options中 options.staticRenderFns = staticRenderFns; /* istanbul ignore if */ if("development" !== 'production' && config.performance && mark) { mark('compile end'); measure(((this._name) + " compile"), 'compile', 'compile end'); } } } return mountComponent.call(this, el, hydrating) }; // 挂载vue实例 function mountComponent(vm, el, hydrating) { vm.$el = el; ... updateComponent = function() { vm._update(vm._render(), hydrating); }; // 给Vue实例或者是组件实例建立一个监听器, 监听updateComponent方法 vm._watcher = new Watcher(vm, updateComponent, noop); ... return vm; }
watcher 对象在构建过程当中,会做为观察者模式中的 Observer,会被添加到 响应式属性的dep对象的依赖列表 中。 app
当响应式属性发生变化时,会通知依赖列表中的watcher对象进行更新。 dom
此时,watcher 对象执行 updateComponent 方法,从新渲染 dom节点。函数
在 挂载vue实例 时, 会为 vue实例 构建一个 监听者watcher。oop
// vm => 当前监听者对应的vue实例 // expOfFn => 须要监听的表达式 // cb => 监听回调方法 // options => 选项,好比deep、immediate // vm.$watch('message', function(newValue) {...}) var Watcher = function Watcher(vm, expOrFn, cb, options) { this.vm = vm; vm._watchers.push(this); ... // 监听器订阅的Dep对象实例 this.deps = []; this.newDeps = []; // 存储监听器已经订阅的Dep对象实例的id,防止重复订阅。 // 每个Dep对象实例都有一个ID this.depIds = new _Set(); this.newDepIds = new _Set(); // 监听器监听的表达式 this.expression = expOrFn.toString(); // 初始化getter,getter用于收集依赖关系 if(typeof expOrFn === 'function') { // expOfFn 为 函数 // 对应:给vue实例创建watcher this.getter = expOrFn; } else { // epOfFn 为 字符串 // 对应:在vue实例中创建监听 // 好比 vm.$watch('message', function() {...}) this.getter = parsePath(expOrFn); } this.value = this.lazy ? undefined : this.get(); }; // 执行watcher的getter方法,用于收集响应式属性dep对象 和 watcher的依赖关系 // 在vue实例挂载过程当中, getter = updateComponent Watcher.prototype.get = function get() { // 将Dep.target设置为当前Watcher对象实例 pushTarget(this); var value; var vm = this.vm; ... value = this.getter.call(vm, vm); ... return value }; // watcher 更新 Watcher.prototype.update = function update() { ... this.get() ... }; // 将watcher添加到dep属性的依赖列表中 Watcher.prototype.addDep = function addDep(dep) { var id = dep.id; if(!this.newDepIds.has(id)) { this.newDepIds.add(id); this.newDeps.push(dep); if(!this.depIds.has(id)) { dep.addSub(this); } } };
在挂载vue实例时,watcher对象会在构建过程当中会执行 updateComponent 方法。this
执行 updateComponent 方法分为两个过程:spa
执行 render 方法时,会触发响应式属性的 getter 方法,将 watcher 添加到 dep对象的依赖列表中。
render 方法是 vue实例 在挂载时由 template 编译成的一个 渲染函数。
tempalte: <div id="app"> {{message}} </div> render: // 执行render, 须要读取响应式属性message,触发message的getter方法 (function anonymous() { with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(message))])} }) // _s, 将this.message转化为字符串 // _v, 生成文本节点对应的VNode // _c, 生成'div'元素节点对应的Vnode
render 函数返回 VNode节点 , 用于 渲染Dom节点。
在执行过程当中,若是须要读取 响应式属性,则会触发 响应式属性 的 getter。
在 getter 方法中, 将 watcher 对象添加到 响应式属性dep对象的依赖列表 中。
修改 响应式属性时,会触发响应式属性的 setter 方法。此时,响应式属性的 dep对象执行 notify 方法,遍历本身的 依赖列表subs, 逐个通知subs中的 watcher 去更新。
watcher 对象执行 update 方法,从新调用 render 方法生成 Vnode节点树,而后 对比新旧Vnode节点树 的不一样,更新dom树。
响应式属性 的原理: