最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue2核心原理(简易) - 视图更新(初次渲染)笔记

    正文概述 掘金(小桂summer)   2021-01-06   638

    前言

    • 当数据发生变化 视图也在发生变化
    • 每个组件都有一个渲染watchernew Watch()(观察者模式)
    • new Watche()组件开始渲染组件,在render函数生成vnode时,调用了劫持数据getter
    • 这个时候每个属性可以订阅一个渲染watcher, 在数据发生变化时就会调用劫持数据的setter
    • setter里去通过一个notify去调用渲染watcher中的updateComponent方法去更新视图

    Vue2核心原理(简易) - 视图更新(初次渲染)笔记

    html 和 javascript模板

    <div id="app">{{ message }} - {{ arr }}</div>
    
    <script>
    var vm = new Vue({
        data: {
            message: 'Hello Vue!',
            arr: [[0]]
        }
    })
    
    vm.$mount('#app')
    
    setTimeout(() => {
        vm.message = 'vue updater'
        vm.arr.push(100)
    }, 2000)
    </script>
    

    Vue2核心原理(简易) - 视图更新(初次渲染)笔记

    Vue2核心原理(简易) - 视图更新(初次渲染)笔记

    render函数生成vnode 方法

    /**
     * @description 创建标签vnode
     */
    export function createElement(vm, tag, data = {}, ...children) {
        return vnode(vm, tag, data, data.key, children, undefined);
    }
    
    /**
     * @description 创建文本的vnode
     */
    export function createTextElement(vm, text) {
        return vnode(vm, undefined, undefined, undefined, undefined, text);
    }
    
    /** 核心方法 */
    /**
     * @description 套装vnode
     */
    function vnode(vm, tag, data, key, children, text) {
        return {
            vm,
            tag,
            data,
            key,
            children,
            text,
            // .....
        }
    }
    
    export function renderMixin(Vue){
        Vue.prototype._c = function() {
            return createElement(this,...arguments)
        }
    
        Vue.prototype._v = function(text) {
            return createTextElement(this,text)
        }
    
        Vue.prototype._s = function(val) {
            if(typeof val == 'object') return JSON.stringify(val)
            return val;
        }
    
        Vue.prototype._render = function(){
           const vm = this
           let render = vm.$options.render
           let vnode = render.call(vm)
           return vnode
        }
    }
    

    初始化

    function Vue (options) {
        /** 初始化 */
        this._init(options)
    }
    /**
     * @description 扩展原型
     */
    initMixin(Vue)
    renderMixin(Vue)
    
    export function initMixin (Vue) {    
        Vue.prototype._init = function (options) {
            vm.$options = options
    
            /** 数据初始化 */
            initState(vm)
    
            /** compile */
            if(vm.$options.el){
                vm.$mount(vm.$options.el);
            } 
        }
    
        Vue.prototype.$mount = function (el) {
            const vm = this;
            const options = vm.$options
            el = document.querySelector(el);
            vm.$el = el;
    
            if(!options.render) {
                let template = options.template;
                if(!template && el) {
                    template = el.outerHTML;
                }
                // 生成的render函数
                let render = compileToFunction(template);
                options.render = render;
            }
    
            /** 组件挂载 */
            mountComponent(vm,el)
        }
    }
    

    组件挂载,初始化渲染watcher

    mountComponent文件

    import { patch } from './vdom/patch'
    import Watcher from './observer/watcher'
    
    /** 生命周期 */
    export function lifecycleMixin(Vue) {
      Vue.prototype._update = function(vnode) {
            const vm = this
            // patch是将老的vnode和新的vnode做比对 然后生成真实的dom
            vm.$el = patch(vm.$el, vnode)
     	 }
    }
    
    
    export function mountComponent(vm, el) {
        // 更新函数 数据变化后 会再次调用此函数
        let updateComponent = () => {
            vm._update(vm._render())
        }
    
        new Watcher(vm, updateComponent, () => {
            console.log('视图更新了')
        }, true)
    }
    

    patch文件 将vnode生成真实dom

    /** patch 文件 这里只是简单处理 直接生成真实的dom */
    export function patch(oldVnode, vnode) {
        if (oldVnode.nodeType == 1) {
            const parentElm = oldVnode.parentNode
            let elm = createElm(vnode)
    
            // 在第一次渲染后 删除掉节点
            parentElm.insertBefore(elm, oldVnode.nextSibling)
            parentElm.removeChild(oldVnode)
    
            return elm
        }
    }
    
    function createElm(vnode) {
        let { tag, data, children, text, vm } = vnode
        if (typeof tag === 'string') {
            vnode.el = document.createElement(tag)
            children.forEach(child => {
                vnode.el.appendChild(createElm(child))
            })
        } else {
            vnode.el = document.createTextNode(text)
        }
        return vnode.el
    }
    

    Watcher 和 Dep (重点)

    Watcher 类

    import { popTarget, pushTarget } from './dep'
    import { queueWatcher } from './scheduler'
    
    let id = 0
    class Watcher {
        constructor(vm,exprOrFn,cb,options){
            this.vm = vm
            this.exprOrFn = exprOrFn
            this.cb = cb
            this.options = options
            this.id = id++
    		
            // 视图更新 就是上面的updateComponent方法
            this.getter = exprOrFn 
            this.deps = [] 
            this.depsId = new Set()
    
            this.get()
        }
    
        get(){
            // 将渲染wather指向给dep 在数据getter时 依赖起来 将dep与watcher关联起来(神来之笔)
            pushTarget(this)
            this.getter()
            // 页面渲染后将 Dep.target = null
            popTarget()
        }
    
        update(){
          // 异步更新操作 就是将更新缓存起来(做一些去重, 防抖)然后一起调用,最后还是调用下方run方法
           queueWatcher(this)
        }
        run(){
            this.get()
        }
        
        /**
         * @description 将watcher 存储dep dep也存储watcher实现双向双向存储, 并做去重处理
         * @description 给每个属性都加了个dep属性,用于存储这个渲染watcher (同一个watcher会对应多个dep)
         * @description 每个属性可能对应多个视图(多个视图肯定是多个watcher) 一个属性也要对应多个watcher
         */
        addDep(dep){
            let id = dep.id;
            if(!this.depsId.has(id)){
                this.depsId.add(id);
                this.deps.push(dep);
                dep.addSub(this)
            }
        }
    }
    
    export default Watcher
    

    Dep 类

    /** 每个劫持的属性 加上唯一的标识 */
    let id = 0
    
    /**
     * @description 每个劫持的属性 new Dep
     */
    class Dep {
        constructor() {
            this.id = id++
            this.subs = []
        }
    
        /** dep传给watcher */
        depend() {
            if (Dep.target) {
                Dep.target.addDep(this)
            }
        }
    
        addSub(watcher) {
            this.subs.push(watcher)
        }
    
        notify() {
            this.subs.forEach(watcher => watcher.update())
        }
    
    }
    
    Dep.target = null
    let stack = []
    
    export function pushTarget(watcher) {
        Dep.target = watcher
        stack.push(watcher)
    }
    
    export function popTarget() {
        stack.pop()
        Dep.target = stack[stack.length - 1]
    }
    
    export default Dep
    

    数据劫持

    Observer 类(主文件)

    import { isObject } from '../utils'
    import { arrayMethods } from './array'
    import Dep from './dep'
    
    class Observer {
        constructor(data) {
    		
            // 看这里 数据 加了个Dep
            this.dep = new Dep()
    
            Object.defineProperty(data, '__ob__', {
                value: this,
                enumerable: false
            })
            
            /** 数据是数组 */
            if (Array.isArray(data)) {
                // 针对数组中使用的方法 如push splice... 修改原数组增加的元素(是对象)进行劫持
                data.__proto__ = arrayMethods
    
                // 初始化 劫持数组中的每个元素 如果是对象进行劫持
                this.observeArray(data)
                return
            }
    
            /** 数据是对象 */
            this.walk(data)
        }
    
        walk(data) {
            Object.keys(data).forEach(key => {
                defineReactive(data, key, data[key])
            })
        }
    
        observeArray(data) {
            data.forEach(item => observe(item))
        }
    }
    
    /**
     * @description 看这里 劫持数据只劫持对象 不劫持数组 通过current.__ob__.dep依赖watcehr
     * @description 多层数组 依赖收集 watcher
     */
    function dependArray(value) {
        for (let i = 0; i < value.length; i++) {
            let current = value[i]
            current.__ob__ && current.__ob__.dep.depend()
            if (Array.isArray(current)) {
                dependArray(current)
            }
        }
    }
    
    /** 核心方法 */
    /**
     * @description 劫持对象数据
     */
    function defineReactive(data, key, value) {
        let childOb = observe(value)
    
        let dep = new Dep()
    
        Object.defineProperty(data, key, {
            get() {
                
                if (Dep.target) {
                    dep.depend()
    
                    // 看这里 数组进行依赖收集watcher
                    if (childOb) {
                        childOb.dep.depend()
    
                        // 看这里 多层数组[[[]]] 
                        if (Array.isArray(value)) { dependArray(value) }
    
                    }
    
                }
    
    
                return value
            },
            set(newValue) {
                if (newValue !== value) {
                    observe(newValue)
                    value = newValue
                    dep.notify()
                }
            }
        })
    }
    
    export function observe(data) {
        if (!isObject(data)) return
        
        if (data.__ob__) return data.__ob__
    
        return new Observer(data)
    }
    
    

    arrayMethods调用数组的七个方法时 直接notify()

    const oldArrayPrototype = Array.prototype
    
    export let arrayMethods = Object.create(oldArrayPrototype)
    
    /**
     * @description 改变原数组的方法
     */
    const methods = [
        'push',
        'pop',
        'unshift',
        'shift',
        'reverse',
        'sort',
        'splice'
    ]
    
    methods.forEach(method => {
        arrayMethods[method] = function (...args) {
            oldArrayPrototype[method].call(this, ...args)
            
            let ob = this.__ob__
            let inserted
            switch (method) {
                case 'push':
                case 'unshift':
                    inserted = args
                    break;
                case 'splice':
                    inserted = args.slice(2)
                    break;
                default:
                    break;
            }
    
            if (inserted) ob.observeArray(inserted)
    		
            // 看这里
            ob.dep.notify()
    
        }
    })
    
    


    起源地下载网 » Vue2核心原理(简易) - 视图更新(初次渲染)笔记

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元