深入浅出Vue.js(四) 整体流程
整体流程
vm.$set(target,key,val)
function set(target,key,val){ //有效的下标 if(Array.isArray(target) && isValidArrayIndex(key)){ target.length = Math.max(target.length,key) target.splice(key,1,val) return val } if(key in target && !(key in Object.prototype)){ target[key] = val return val } const ob = target.__ob__ if(target.isVue || (ob && ob.vmCount)){ return val } if(!ob){ target[key] = val return val } defineReactive(ob.value,key,val) ob.dep.notify() return val }
vm.$delete(target,key)
function del(target,key){ if(Array.isArray(target) && isValidArrayIndex(key)){ target.splice(key,1) return } const ob = target.__ob__ if(target.isVue || (ob && ob.vmCount)){ return } // 如果key不是target自身的属性,则终止程序继续执行 if(!hasOwn(target,key)){ return } delete target[key] // 如果ob不存在(判断target是不是一个响应式数据),则直接终止程序 if(!ob){ return } ob.dep.notify() }
vm.$on(event,fn)
Vue.prototype.$on = function(event,fn){ const vm = this if(Array.isArray(event)){ for(let i = 0,len = event.length;i < len;i++){ this.$on(event[i],fn) } }else{ (vm._events[event] || (vm._events[event] == [])).push(fn) } return vm }
vm._events是一个对象,在执行new Vue()时,Vue会执行this._init()方法进行一系列的初始化操作,其中就会在Vue.js的实例上创建一个_events属性,用来存储事件。vm._events = Object.create(null)。
vm.$off(event,fn)
移除自定义事件监听器
- 如果没有提供参数,则移除所有的事件监听器。
- 如果只提供了事件,则移除该事件所有的监听器。
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
Vue.prototype.off = function(event,fn){ const vm = this if(!arguments.length){ vm._events = Object.create(null) return vm } if(Array.isArray(event)){ for(let i = 0,len = event.length;i < len;i++){ this.$off(event[i],fn) } return vm } const cbs = vm._events[event] if(!cbs){ return vm } if(arguments.length == 1){ vm._events[event] = null return vm } if(fn){ const cbs = vm._events[event] let cb; let i = cbs.length while(i--){ cb = cbs[i] if(cb == fn || cb.fn == fn){ cbs.splice(i,1) break } } } return vm }
vm.$once(event,fn)
Vue.prototype.$once = function(event,fn){ const vm = this function on(){ vm.off(event,on) fn.apply(vm,arguments) } on.fn = fn vm.$on(event,on) return vm }
vm.$emit(event[,...args])
Vue.prototype.$emit = function(event,...args){ const vm = this let cbs = tis._events[event] if(cbs){ for(let i = 0;i < cbs.length;i++){ try{ cbs[i].apply(vm,args) }catch(err){ console.log(err) } } } }
vm..$forceUpdate()
Vue.prototype.$forceUpdate = function(){ const vm = this if(vm._watcher){ wm._watcher.update() } }
vm.$destory()
function remove(arr,item){ if(arr.length){ const index = arr.indexOf(item) if(index > -1){ arr.splice(index,1) } } } Vue.prototype.$destroy = function(){ const vm = this if(vm._isBeingDestroyed){ return } callHook(vm,'beforeDestroy') vm._isBeingDestroyed = true const parent = vm.$parent if(parent && !parent._isBeingDestroyed && !vm.$options.abstract){ remove(parent.$children,vm) } if(vm._watcher){ vm._watcher.teardown() } let i = vm._watchers.length while(i--){ vm._watchers[i].teardown() } vm._isDestroyed = true vm.__patch__(vm.vnode,null) callHook(vm,'destroyed') vm.$off() }
vm.$nextTick([fn])
const callbacks = [] let pending = false function flushCallbacks(){ pending = false const copies = callbacks.splice(0) callbacks.length = 0 for(let i = 0;i < copies.length;i++){ copies[i]() } } let microTimerFunc let macroTimerFunc let useMacroTask = false if(typeof setImmediate !=='undefined' && isNative(setImmediate)){ macroTimerFunc = () => { setImmediate(flushCallbacks) } }else if(typeof MessageChannel !=='undefined' && (isNative(MessageChannel) || MessageChannel.toString() === '[object MessageChannelConstructor]')){ const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } }else{ macroTimerFunc = () => { setTimeout(flushCallbacks, 0); } } if(typeof Promise !== 'undefined' && isNative(Promise)){ const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } }else{ microTimerFunc = macroTimerFunc } function withMacroTask(fn){ return fn._withTask || (fn._withTask = function(){ useMacroTask = true const res = fn.apply(null,arguments) useMacroTask = false return res }) } function nextTick(cb,ctx){//参数:回调函数,上下文 let _resolve callbacks.push(()=>{ if(cb){ cb.call(ctx) }else if(_resolve){ _resolve(ctx) } }) if(!pending){ pending = true if(useMacroTask){ macroTimerFunc() }else{ microTimerFunc() } } if(!cb && typeof Promise !=='undefined'){ return new Promise(resolve => { _resolve = resolve }) } } Vue.prototype.$nextTick = function(fn){ return nextTick(fn,this) }
vm.$mount()
如果vue.js实例在实例化时没有收到el选项,则它处于未挂载状态,没有关联的DOM元素。我们可以使用vm.$mount手动挂载一个未挂载的实例。如果没有提供elementOrSelector参数,模板将被渲染为文档之外的元素,并且必须使用原生的DOM的API把它插入文档中。这个方法返回实例自身,因而可以链式调用其他实例方法。
完整版和只包含运行时版本之间的差异在于是否有编译器,而是否有编译器的差异主要在于vm.$mount方法的表现形式。在只包含运行时的构建版本中,vm.$mount的作用如前面介绍的那样。而在完整的构建版本中,vm.$mount的作用会稍有不同,它会首先检查template或el选项所提供的模板是否已经转换成渲染函数(render函数)。如果没有,则立即进入编译过程,将模板编译为渲染函数,完成之后再进入挂载与渲染的流程中。
只包含运行时的vm.$mount没有编译步骤,它会默认实例上已经存在的渲染函数,如果不存在,则会设置一个。并且,这个渲染函数在执行时会返回一个空节点VNode,以保证执行时不会因为函数不存在而报错。同时如果在开发环境下运行,vue.js会触发警告,提示当前使用的是只包含运行时版本,会让我们提供渲染函数,或者去使用完整的构建版本。
完整版vm.$mount的实现原理
function query(el){ if(typeof el === 'string'){ const selected = document.querySelector(el) if(!selected){ return document.createElement('div') } return selected }else{ return el } } function getOuterHTML(el){ if(el.outerHTML){ return el.outerHTML }else{ const container = document.createElement('div') container.appendChild(el.cloneNode(true)) return container.innerHTML } } function idToTemplate(id){ const el = query(id) return el && el.innerHTML } function createFunction(render){ return new Function(render) } function compileToFunctions(tempalte,options,vm){ options = extend({},options) const key = options.delimiters ? String(options.delimiters) + tempalte : tempalte if(cache[key]){ return cache[key] } const compiled = compile(tempalte,options) const res = {} res.render = createFunction(compiled.render) return (cache[key] = res) } const mount = Vue.prototype.$mount Vue.prototype.$mount = function(el){ //函数劫持 el = el && query(el) const options = this.options if(!options.render){ let template = options.tempalte if(template){ if(typeof template == 'string'){ if(template.charAt(0) == '#'){ template = idToTemplate(template) } }else if(template.nodeType){ template = template.innerHTML }else{ return this } }else if(el){ template = getOuterHTML(el) } if(template){ const {render} = compileToFunctions(template,{...},this) options.render = render } } return mount.call(this,el) }
只包含运行时版本的vm.$mount的实现原理
function mountComponent(vm,el){ if(!vm.$options.render){ vm.$options.render = createEmptyVNode() } callHook(vm,'beforeMount') vm._watcher = new Watcher(vm,() => { vm._update(vm._render()) },noop) callHook(vm,'mounted') return vm } Vue.prototype.$mount = function(el){ el = el && inBrowser ? query(el) : undefined return mountComponent(this,el) }
Vue.directive方法接受两个参数id和definition,它可以注册或获取指令,这取决于definition参数是否存在。如果definition参数不存在,则使用id从this.options['directives']中读出指令并将其返回;如果definition参数存在,则说明是注册操作,那么近而判断definition参数的类型是否是函数。
如果是函数,则默认监听bind和update两个事件,所以代码中将definition函数分别赋值给对象中的bind和update这两个方法,并使用这个对象覆盖definition;如果definition不是函数则说明它是用户自定义的指令对象,此时不需要做任何操作。直接将用户提供的指令对象保存在this.options['directives']上即可。
Vue.options = Object.create(null) Vue.options['directives'] = Object.create(null) Vue.directive = function(id,definition){ if(!definition){ return this.options['directives'][id] }else{ if(typeof definition == 'function'){ definition = { bind:definition, update:definition } } this.options['directives'][id] = definition return definition } }
生命周期
- new Vue()到created之间的阶段叫做初始化阶段。这个阶段的主要目的是在vue.js实例上初始化一些属性、事件以及响应式数据。如:props、methods、data、computed、watch、provide和inject等
- 在created钩子函数与beforeMount钩子函数之间的阶段是模板编译阶段。这个阶段的主要目的是将模板编译为渲染函数。
- beforeMount钩子函数到mounted钩子函数之间是挂载阶段。在这个阶段,vue.js会将其实例挂载到DOM元素上,在挂载的过程中,vue.js会开启watcher来持续追踪依赖的变化。
- 应用调用vm.$destroy方法后,vue.js的生命周期会进入卸载阶段。在这个阶段,vue.js会将自身从父组件中删除,取消实例上所有依赖的追踪并且移除所有的事件监听器。
未完待续...
以自己现在的努力程度,还没有资格和别人拼天赋