likes
comments
collection
share

源码共读,我又来了——Vue2你到底做了什么

作者站长头像
站长
· 阅读数 15

前言

本文参加了由公众号@若川视野 发起的每周源码共读活动点击了解详情一起参与。

本篇是源码共读第23期 | 为什么Vue2 this 能够直接获取到 data 和 methods,点击了解本期详情

准备

首先看下想要的实现结果:

<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
    const vm = new Vue({
        data: {
            name: '1122',
        },
        methods: {
            sayName() {
                console.log(this.name);
            }
        },
    });
console.log(vm.name); // 1122
console.log(vm.sayName()); // 1122

看源码前的思考及自我实现

这是怎么实现的?如果让我完成这个需求,我该如何编写?

首先这个打印结果'1122'哪里来的,换句话说数据源是谁?从上面代码不难发现是name,而vm.name打印了这个数值,说明vm添加了name这个属性并且值是data中的name。同样也添加了sayName这个属性,值是一个方法,这个方法中打印了this.namevm.sayName()显式绑定了里面的this,此时this也就是vm对象。好了,听上去这猜测还挺是那么回事的,自己动手试试:

function Vue({ data, methods }) {
    for (key in data) {
        this[key] = data[key]
    }
    for (fn in methods) {
        this[fn] = methods[fn]
    }
}
const vm = new Vue({
    data: {
        name: '我是小李',
    },
    methods: {
        sayName() {
            console.log(this.name);
            this.showMessage()
        },
        showMessage() {
            console.log('我' + this.age + '岁了')
        }
    },
});
console.log(vm.name) // 我是小李
vm.sayName() // 我是小李 我22岁了

咦~不光听起来有的可行,写起来也还蛮像那么回事的嘛。不过人家可是Vue源码,还能这么简单?但是我实在想不到了,就简单需求而言,确实可以做到。那么接下来看下源码吧

查看源码

注:我没有从官方拷贝过来代码,是直接从若川文章里Copy过来的

实不相瞒我开始都不晓得咋进入Vue构造函数内部😅在构造函数处打断点F11进不去,后来发现点击⬇️就可以,由此可以看出我不习惯打断点的坏习惯了😂

function Vue(options) {
    if (!(this instanceof Vue)
    ) {
        warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
}

嗯?咋还用了instanceof?这个方法是判断一个对象是否属于特定的类或其继承链中的某个类(我个人理解就是这个类是否在对象的原型链上)在此处就是判断是否使用了new关键词调用构造函数,因为构造函数也是函数,只要是函数那就有被调用的可能(个人是这么认为的,如有不对请指正)

_init初始化函数

// 代码有删减
function initMixin(Vue) {
    Vue.prototype._init = function (options) {
        var vm = this;
        vm._uid = uid$3++;

        vm._isVue = true;
        if (options && options._isComponent) {
            initInternalComponent(vm, options);
        } else {
            vm.$options = mergeOptions(
                resolveConstructorOptions(vm.constructor),
                options || {},
                vm
            );
        }
        vm._self = vm;
        initLifecycle(vm);
        initEvents(vm);
        initRender(vm);
        callHook(vm, 'beforeCreate');
        initInjections(vm); // resolve injections before data/props
        //  初始化状态
        initState(vm);
        initProvide(vm); // resolve provide after data/props
        callHook(vm, 'created');
    };
}

initState初始化状态

function initState(vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    // 有传入 methods,初始化方法
    if (opts.methods) { initMethods(vm, opts.methods); }
    // 有传入 data,初始化 data
    if (opts.data) {
        initData(vm);
    } else {
        observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) { initComputed(vm, opts.computed); }
        if (opts.watch && opts.watch !== nativeWatch) {
            initWatch(vm, opts.watch);
        }
    }

initMethods

function initMethods(vm, methods) {
    var props = vm.$options.props;
    for (var key in methods) { // 遍历methods
        {
            if (typeof methods[key] !== 'function') { 如果每一项的value不是函数,警告
                warn(
                    "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
                    "Did you reference the function correctly?",
                    vm
                );
            }
            if (props && hasOwn(props, key)) { 
            // 如果存在props,并且props已经存在了这个key, 警告
                warn(
                    ("Method \"" + key + "\" has already been defined as a prop."),
                    vm
                );
            }
            if ((key in vm) && isReserved(key)) {
            // 如果每一项已经在Vue的实例对象vm上存在,且方法名是保留的 _ $,警告
                warn(
                    "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
                    "Avoid defining component methods that start with _ or $."
                );
            }
        }
        vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
    }
}

initData 初始化 data

function initData(vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {};
    // 是函数时调用函数,执行获取到对象 ,不是函数时正常将对象赋值过去
    if (!isPlainObject(data)) { // 判断data是否是纯粹的普通对象, 如果不是,警告
        data = {};
        warn(
            'data functions should return an object:\n' +
            'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
            vm
        );
    }
    // proxy data on instance
    var keys = Object.keys(data);
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) { // 遍历 data ,获取其中每一项:
        var key = keys[i];
        {
            if (methods && hasOwn(methods, key)) {
            // 如果和 methods 冲突了,警告
                warn(
                    ("Method \"" + key + "\" has already been defined as a data property."),
                    vm
                );
            }
        }
        if (props && hasOwn(props, key)) {
        // 如果和 props 冲突了,警告。
            warn(
                "The data property \"" + key + "\" is already declared as a prop. " +
                "Use prop default value instead.",
                 vm
            );
        } else if (!isReserved(key)) {
        // 如果不是内部私有的保留属性,做一层代理,代理到 _data 上
            proxy(vm, "_data", key);
        }
    }
 
    observe(data, true /* asRootData */); // 监测 data,使其成为响应式的数据
}

提取出的简化版实现

function noop(a, b, c) { }
var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
};
function proxy(target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

function initData(vm) {
    const data = vm._data = vm.$options.data;
    const keys = Object.keys(data);
    var i = keys.length;
    while (i--) {
        var key = keys[i];
        proxy(vm, '_data', key);
    }
}
function initMethods(vm, methods) {
    for (var key in methods) {
        vm[key] = typeof methods[key] !== 'function' ? noop : methods[key].bind(vm);
    }
}
function simpVue(options) {
    let vm = this;
    vm.$options = options;
    var opts = vm.$options;
    if (opts.data) {
        initData(vm);
    }
    if (opts.methods) {
        initMethods(vm, opts.methods)
    }
}

😭可以看出差的不是一星半点啊

总结

我在考虑实现时没有特殊情况,而是直接设定了Vue构造函数是使用了new关键词调用的,data传递的是对象,methods传递的是方法......

同样也只是将给出的示例实现而不是将Vue的该项功能实现,像代理,props等问题我就没有进行考虑😄

总体下来这篇源码阅读起来并没有那么难(没有那么难只是我想不到罢了😥)

转载自:https://juejin.cn/post/7252951745013825596
评论
请登录