• vue2.x源码刨析-new Vue的时候做了什么(手写简易版01)


    本篇文章大致的介绍一下new Vue的过程,
    首先我们在生成一个Vue实例化对象的时候,一般会这样写:

    <div id="app" style="color: red">{{name}} dep {{age}} dep {{name}}</div>
    const vm = new Vue({
        data() {
            return {
                // 代理数据
                name: "zf",
                age: 20,
                address: {
                    num: 30,
                    content: "回龙观",
                },
                hobby: ["eat", "drink", { a: 1 }],
            };
        },
        created() {
            // console.log(this.xxx); // 数据来源不明确
            console.log("created");
        },
        el: "#app", // 我们要将数据  解析到el元素上
        // template: "
    111
    ",
    }); // vue.mixin 混合 可以混入一些公共方法
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    那么在new Vue的过程中,都做了些什么,是怎么把数据做到响应式的呢?
    其实,在实例化Vue的时候,主要执行的是,_init方法,

    function Vue(options) {
        // options就是用户的选项
        this._init(options);
    }
    
    • 1
    • 2
    • 3
    • 4

    在实例化的时候,会对数据进行初始化,在初始化的过程中,会在Vue原型上面挂载上_init方法,

    initMixin(Vue); // 扩展了init方法
    initLifeCycle(Vue); // vm._update vm._render
    initGlobalAPI(Vue); // 全局 api 的实现
    initStateMixin(Vue); // 实现了 nextTick $watch
    
    • 1
    • 2
    • 3
    • 4

    initMixin_init方法挂载到了原型上面,

    export function initMixin(Vue) {
        Vue.prototype._init = function (options) {
            // 用于初始化操作
            const vm = this;
            // Vue vm.$options 就是获取用户的配置
            // 我们定义的全局指令和过滤器.... 都会挂载到实例上
            // this.constructor 不能写成 Vue,可能是子组件
            vm.$options = mergeOptions(this.constructor.options, options);
            callHook(vm, "beforeCreate");
            // 初始化状态 初始化计算属性 watch
            initState(vm);
            callHook(vm, "created");
            if (options.el) {
                vm.$mount(options.el); // 实现数据的挂载
            }
        };
    
        Vue.prototype.$mount = function (el) {
            const vm = this;
            el = document.querySelector(el);
            let ops = vm.$options;
            if (!ops.render) {
                // 先进行查找有没有render函数
                let template; // 没有render看一下是否写了template, 没写template采用外部的template
                if (!ops.template && el) {
                    // 没有写模板,但是写了el
                    template = el.outerHTML;
                } else {
                    // if (el) {
                    //     template = ops.template;
                    // }
                    template = ops.template;
                }
                // 写了template就用写了的template
                if (template) {
                    // 这里需要对模板进行编译
                    const render = compileToFunction(template);
                    ops.render = render;
                }
            }
            // console.log(ops.render); // 最终就可以获取render方法
            mountComponent(vm, el); // 组件的挂载
        };
    }
    
    • 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

    其中,mergeOptions方法主要是用来合并两个对象

    const strats = {};
    const LIFECYCLE = ["beforeCreate", "created"];
    LIFECYCLE.forEach((hook) => {
        strats[hook] = function (p, c) {
            // {} {created:function(){}} => {created:[fn]}
            // {created:[fn]} {created: function(){}} => {created: [fn,fn]}
            if (c) {
                // 如果儿子有  父亲有  让父亲和儿子拼在一起
                if (p) {
                    return p.concat(c);
                } else {
                    return [c]; // 儿子有父亲没有,则将儿子包装成数组
                }
            } else {
                return p; // 如果儿子没有,则用父亲即可
            }
        };
    });
    
    strats.components = function(parentVal, childVal) {
        const res = Object.create(parentVal)
        if(childVal) {
            for(let key in childVal) {
                res[key] = childVal[key];  // 返回的是构造的对象  可以拿到父亲原型上的属性,并且将儿子的都拷贝到自己身上
            }
        }
        return res;
    }
    
    
    
    export function mergeOptions(parent, child) {
        const options = {};
        for (let key in parent) {
            // 循环老的 {a:1}
            mergeField(key);
        }
        for (let key in child) {
            // 循环新的 {}
            if (!parent.hasOwnProperty(key)) {
                mergeField(key);
            }
        }
        function mergeField(key) {
            // 策略模式,用策略模式减少 if/else
            if (strats[key]) {
                options[key] = strats[key](parent[key], child[key]);
            } else {
                options[key] = child[key] || parent[key]; // 优先采用儿子,再采用父亲
            }
        }
        return options;
    }
    
    • 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

    在这里首先用child的属性替换掉parent的属性,其次使用策略模式,整合各个生命周期,这里将生命周期整合成一个数组,是因为如果我们使用了mixin方法,可能会存在一个生命周期被调用两次的情况。在这里我们可以看出components其实就是父数据的一个复制版,先使用Object.create实现父数据的继承,然后循环子数据,替换掉父数据的数据。
    callhook方法就是调用生命周期

    export function callHook(vm, hook) {
        const handlers = vm.$options[hook];
        if (handlers) {
            handlers.forEach((handler) => handler.call(vm));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在前面我们已经知道,生命周期通过mergeOptions方法策略模式,已经是一个数组的形式,所以在这里对数组进行循环,调用数组的方法。
    当然这里面主要还有$mount方法,这个方法可以解析模板中的数据,将响应式数据进行渲染,这个方法我在后面会主要进行分析。
    _init方法中,其实还有对状态进行初始化(data,computed,watch),这个后面谈到响应式数据的时候,可以再说。
    在初始化的时候还有几个初始化的方法,主要还是在Vue的原型上面挂载方法。
    initLifeCycle方法,其中涉及到数据的更新引起dom的变化,模板的解析,主要用来处理这些东西

    export function initLifeCycle(Vue) {
        Vue.prototype._update = function (vnode) {
            const vm = this;
            const el = vm.$el;
    
            const prevVnode = vm._vnode;
            vm._vnode = vnode; // 把组件第一次产生的虚拟节点保存到 _vnode 上
            if (prevVnode) {
                // 之前渲染过了
                vm.$el = patch(prevVnode, vnode)
            } else {
                // patch既有初始化的功能,又有更新的逻辑
                vm.$el = patch(el, vnode);
            }
        };
        // _c('div', {}, ...children)
        Vue.prototype._c = function () {
            return createElementVNode(this, ...arguments);
        };
        // _v(text)
        Vue.prototype._v = function () {
            return cretaeTextNode(this, ...arguments);
        };
        Vue.prototype._s = function (value) {
            if (typeof value !== "object") return value;
            return JSON.stringify(value);
        };
        Vue.prototype._render = function () {
            const vm = this;
            // 让 with 中的this指向vm
            // 当渲染的时候会去实例中取值,我们就可以将属性和视图绑定在一起
            return vm.$options.render.call(vm); // 通过ast语法树转义后生成的render方法
        };
    }
    
    • 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

    initGlobalAPI主要是用来配置一些全局属性

    function initGlobalAPI(Vue) {
        // 静态方法
        Vue.options = {
            _base: Vue
        };
        Vue.mixin = function (mixin) {
            // 我们期望将用户的选项和全局的 options 进行合并
            // {} {created:function(){}} => {created:[fn]}
            // {created:[fn]} {created: function(){}} => {created: [fn,fn]}
            this.options = mergeOptions(this.options, mixin);
            return this;
        };
        Vue.extend = function(options) {
            // 就是实现根据用户的参数 返回一个构造函数而已
            function Sub(options = {}) { // 最终使用一个组件 就是 new 一个实例
                this._init(options); // 就是默认对子类进行初始化操作
            }  
    
            Sub.prototype = Object.create(Vue.prototype);  // Sub.prototype.__proto__ === Vue.prototype
            Sub.prototype.constructor = Sub;  // 组合式继承要重新连接
            // 希望将用户传递的参数 和全局的 Vue.options 来合并
            Sub.options = mergeOptions(Vue.options, options); // 保存用户传递的选项;
            return Sub;
        }
    
        Vue.options.components = {} // 全局的指令  Vue.options.directives
        Vue.component = function(id, definition) {
    
            // 如果 definition 已经是一个函数了,说明用户自己调用了 Vue.extend
    
            definition = typeof definition === 'function' ? definition : Vue.extend(definition)
            Vue.options.components[id] = definition;
            console.log(Vue.options.components);
        }
    }
    
    • 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

    initStateMixin主要实现$nextTick$watch方法,其中$watch方法极为重要,是整个响应式系统的核心

    function initStateMixin(Vue) {
        Vue.prototype.$nextTick = nextTick;
        Vue.prototype.$watch = function (exprOrFn, cb) {
            // firstname 的值变化了,直接执行 cb 函数即可
            new Watcher(this, exprOrFn, { user: true }, cb);
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    【开源的串口可视化工具——Serial Studio】
    zipkin2.24.2源码install遇见的问题
    【图像处理】使用各向异性滤波器和分割图像处理从MRI图像检测脑肿瘤(Matlab代码实现)
    肖sir__面试就业课___数据库
    【JUC】并发编程学习笔记(三)
    贪心算法(四) | 加油站、单调递增的数字、监控二叉树 | leecode刷题笔记
    linux——主从同步
    【Python零基础入门篇 · 15】:内置函数二【min()和max函数、zip()拉链函数、map()映射函数、reduce()】
    【前端实例代码】用HTML、CSS和JavaScript创建一个简易图片编辑器(实现图片的亮度、饱和度、灰度、颜色反转、图片旋转镜面翻转等滤镜效果)
    Qt之使用QTreeView实现QQ登录好友列表
  • 原文地址:https://blog.csdn.net/m0_47531829/article/details/134538586