zoukankan      html  css  js  c++  java
  • 深入浅出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会将自身从父组件中删除,取消实例上所有依赖的追踪并且移除所有的事件监听器。

    未完待续...

  • 相关阅读:
    python面向对象(一)
    ls和cd命令详解
    SHELL 中的变量
    Shell基础
    Python版飞机大战
    Python模块制作
    Linux的cut命令
    Linux中的wc命令
    Ubuntu系统下adb devices 不能显示手机设备
    app耗电量测试工具--PowerTutor
  • 原文地址:https://www.cnblogs.com/zhenjianyu/p/13175996.html
Copyright © 2011-2022 走看看