【面试题】Vue2为什么能通过this访问到data、methods的属性或方法

简介: 【面试题】Vue2为什么能通过this访问到data、methods的属性或方法

在我没接触vue之前我不着调this是啥压根就没有接触过,在我学过了vue之后我知道了this,那时候理解的this就是你要使用data中的属性或调用methods中的方法等其他东西都要用this去调用,那时候其实我还是不知道this是啥,后面慢慢的才知道,当然我知道应该就是八股文背出来的,通过今天读这个源码,让我理解的更加深刻了,原来还可以这么用。

一、vue的使用

看这一段代码我们能知道有个Vue的构造函数 ,然后我们使用new Vue创建了它的实例,并给它传了一个对象参数,里面有data和methods,那么在这个Vue构造函数做了什么才能让我使用this可以直接访问里面的属性或者方法呢?

    //创建vue的实例
    const vm = new Vue({
        data: {
            desc: '为什么this能够直接访问data中的属性',
        },
        methods: {
            sayName() {
                console.log(this.desc);
            }
        },
    });
    console.log(vm.name);
    console.log(vm.sayName());

二、Vue的构造函数

接收一个options参数

  • 使用 instanceof 判断 this对象上是否出现了Vue的prototype,我们都知道this的指向是取决于谁调用
  • this._init(options) 证明在这调用要么我们创建的实例上有_init方法要么方法在Vue的prototype上,但是我们可以看到实例上并没有_init方法 ,那么肯定在一个地方给Vue的prototype上加上了_init方法 继续往下看
 function Vue(options) {
        if (!(this instanceof Vue)
        ) {
            warn('Vue是一个构造函数,应使用“new”关键字调用');
        }
        this._init(options);
}
//Vue() //错误的调用方式 进入警告判断 此时this指向window 然后window的 window.__proto__的指向的Window构造函数的prototype

三 初始化initMixin(Vue)

在源码中会看到很多初始化的函数在这我们initMixin()

这个函数就是在Vue的原型上增加了_init方法,方法接收一个参数,然后定义了vm变量,在我看的时候就想看看这个函数的this指向谁,其实也不难函数挂在Vue构造函数的原型上,调用还是在构造函数里面使用this调用,构造函数的this指向Vue实例,根据this的指向规则 此时的vm就指向了Vue构造函数的实例。

使用this的访问规则如果实例上没有就去原型上找

然后执行 initState(vm)

initMixin(Vue)
function initMixin(Vue) {
        //prototype上增加init方法
        Vue.prototype._init = function (options) {
            var vm = this; //Vue实例
            vm.age = 30
          //代码进行了删减
            initState(vm);
        }
}
//这里只是举例测试
const vm = new Vue({})
console.log(vm.age) //30

四 initState(vm)

这里就是对我们传入的data 或者methods进行不同的处理

 //initState方法代码进行了删减
    function initState(vm) {
        vm._watchers = [];
        var opts = vm.$options; //这里是我们在创建实例的时候传的参数
        //如果传了methods 则去调用
        if (opts.methods) { initMethods(vm, opts.methods); }
        if (opts.data) {
            initData(vm);
        } else {
            observe(vm._data = {}, true /* asRootData */);
        }
    }

五 initMethods(vm, opts.methods)

如果有methods则取调用initMethods方法

前面主要是判断 methods中的值是不是函数,key有没有跟props冲突等

最后一段代码就是在vm的实例上增加方法vm[key]=methods[key],在读的时候我有这样一个以为为什么还要用bind改变this指向呢不本来就是写在vm实例上的方法吗 只能使用vm调用 那么方法的this不就指向vm吗?

/*
    vm:构造函数实例
    methods:我们传的methods对象
    */
    function initMethods(vm, methods) {
        var props = vm.$options.props;
        //循环methods对象
        for (var key in methods) {
            {
                //判断是否是函数 不是的化则作出警告
                if (typeof methods[key] !== 'function') {
                    warn(
                        "Method "" + key + "" has type "" + (typeof methods[key]) + "" in the component definition. " +
                        "Did you reference the function correctly?",
                        vm
                    );
                }
                //判断 methods 中的每一项是不是和 props 冲突了,如果是,警告。
                if (props && hasOwn(props, key)) {
                    warn(
                        ("Method "" + key + "" has already been defined as a prop."),
                        vm
                    );
                }
                //判断 methods 中的每一项是不是已经在 new Vue实例 vm 上存在,而且是方法名是保留的 _ $ (在JS中一般指内部变量标识)开头,如果是警告。
                if ((key in vm) && isReserved(key)) {
                    warn(
                        "Method "" + key + "" conflicts with an existing Vue instance method. " +
                        "Avoid defining component methods that start with _ or $."
                    );
                }
            }
            //给实例增加methods中的方法 这样其实我们就已经可以用vm访问 到methods中的方法了
            vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
        }
    }

问了群里大佬之后原来这步操作时为了防止用户改变this指向,专门做了个例子

在这我有定义了对象a里面有个age属性和fn,fn我赋值vm实例上的sayHi,然后a.fn()调用很明显this的指向已经被改变了,使用bind之后则不会

  const vm = new Vue({
        methods: {
            sayHi() {
                console.log(this.age, 'hello-this')
            }
        }
    });
    let a = {
        age: 15,
        fn: vm.sayHi
    }
    console.log(a.fn(), 'vm') //打印15

六 initData(vm)

data是如何做到的使用this可以直接访问的,其实原理都一样,

首先在vm实例上增加了_data,里面存的我们传入的data参数

 function initData(vm) {
        var data = vm.$options.data;
        data = vm._data = typeof data === 'function'
            ? getData(data, vm)
            : data || {};
        //如果不是对象则警告
        if (!isPlainObject(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--) {
            var key = keys[i];
            //判断key值有没有跟methods中的key重名
            {
                if (methods && hasOwn(methods, key)) {
                    warn(
                        ("Method "" + key + "" has already been defined as a data property."),
                        vm
                    );
                }
            }
            //判断key值有没有跟props中的key重名
            if (props && hasOwn(props, key)) {
                warn(
                    "The data property "" + key + "" is already declared as a prop. " +
                    "Use prop default value instead.",
                    vm
                );
                //是否是内部私有保留的字符串$ 和 _ 开头
            } else if (!isReserved(key)) {
              //代理
                proxy(vm, "_data", key);
            }
        }
        // observe data
        observe(data, true /* asRootData */);
    }

七 proxy(vm, "_data", key)

get 和 set 方法 注意里面的this 指向vm实例对象,上面已经在vm实例对象上增加了_data 所有在获取或者设置属性值的时候 都是this._data[key] 也就是vm._data[key],

然后通过Object.defineProperty往实例对象上添加属性,所以当我们访问vm[key] 也就是 vm._data[key]

  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);
  }
//创建vue构造函数
    function Vue(options) {
        if (!(this instanceof Vue)
        ) {
            warn('Vue是一个构造函数,应使用“new”关键字调用');
        }
        this._init(options);
    }
    //初始化
    initMixin(Vue);
    function initMixin(Vue) {
        //prototype上增加init方法
        Vue.prototype._init = function (options) {
            var vm = this; //Vue实例
            let methods = options.methods
            initState(vm);
        }
    }
    //initState方法代码进行了删减
    function initState(vm) {
        vm._watchers = [];
        var opts = vm.$options; //这里是我们在创建实例的时候传的参数
        //如果传了methods 则去调用
        if (opts.methods) { initMethods(vm, opts.methods); }
        if (opts.data) {
            initData(vm);
        } else {
            observe(vm._data = {}, true /* asRootData */);
        }
    }
    /*
    vm:构造函数实例
    methods:我们传的methods对象
    */
    function initMethods(vm, methods) {
        var props = vm.$options.props;
        //循环methods对象
        for (var key in methods) {
            {
                //一些条件判断
            }
            //给实例增加methods中的方法 这样其实我们就已经可以用vm访问 到methods中的方法了
            vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
        }
    }
    const vm = new Vue({
        methods: {
            sayHi() {
                console.log('hello-this')
            }
        }
    });
    vm.sayHi() //hello-this

总结

其实看明白了Methods是怎么做到直接用this可以直接访问的后面的都是差不多的,主要就是一个构造函数,然后创建一个实例,在实例上增加属性或者方法,这样我们就可以用实例对象直接访问了。原理就是那么简单。

 总结给大家一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关文章
|
1月前
|
缓存 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,Java中的引用类型在内存管理和垃圾回收中的作用,Java中的finalize方法及其在垃圾回收中的作用,哪种策略能够提高垃圾回收的效率
Java面试题:深入探究Java内存模型与垃圾回收机制,Java中的引用类型在内存管理和垃圾回收中的作用,Java中的finalize方法及其在垃圾回收中的作用,哪种策略能够提高垃圾回收的效率
25 1
|
5天前
|
JavaScript 开发工具 git
Vue学习之--------脚手架的分析、Ref属性、Props配置(2022/7/28)
这篇文章分析了Vue脚手架的结构,并详细讲解了`ref`属性和`Props`配置的基础知识、代码实现和测试效果,展示了如何在Vue组件中使用`ref`获取DOM元素或组件实例,以及如何通过`Props`传递和接收外部数据。
Vue学习之--------脚手架的分析、Ref属性、Props配置(2022/7/28)
|
5天前
|
缓存 JavaScript
Vue学习之--------计算属性(2022/7/9)
这篇文章通过代码实例和效果测试,详细讲解了Vue计算属性的概念、实现方式、优势以及与插值语法和methods的区别。
Vue学习之--------计算属性(2022/7/9)
|
12天前
|
SQL 安全 测试技术
[go 面试] 接口测试的方法与技巧
[go 面试] 接口测试的方法与技巧
|
13天前
|
机器学习/深度学习 算法 Python
【机器学习】面试问答:决策树如何进行剪枝?剪枝的方法有哪些?
文章讨论了决策树的剪枝技术,包括预剪枝和后剪枝的概念、方法以及各自的优缺点。
29 2
|
13天前
|
机器学习/深度学习
【机器学习】面试题:LSTM长短期记忆网络的理解?LSTM是怎么解决梯度消失的问题的?还有哪些其它的解决梯度消失或梯度爆炸的方法?
长短时记忆网络(LSTM)的基本概念、解决梯度消失问题的机制,以及介绍了包括梯度裁剪、改变激活函数、残差结构和Batch Normalization在内的其他方法来解决梯度消失或梯度爆炸问题。
27 2
|
13天前
|
存储 机器学习/深度学习 缓存
【数据挖掘】XGBoost面试题:与GBDT的区别?为什么使用泰勒二阶展开?为什么可以并行训练?为什么快?防止过拟合的方法?如何处理缺失值?
XGBoost与GBDT的区别、XGBoost使用泰勒二阶展开的原因、并行训练的原理、速度优势、防止过拟合的策略以及处理缺失值的方法,突出了XGBoost在提升模型性能和训练效率方面的一系列优化。
17 1
|
14天前
|
机器学习/深度学习
|
5天前
|
JavaScript
Vue学习之--------监视属性(2022/7/10)
这篇博客文章详细介绍了Vue框架中的监视属性(watch)功能,通过天气案例展示了监视属性的基础知识、代码实例、测试效果、深度监视、监视属性简写以及在项目中的实际应用。
|
7天前
|
API
VUE3——如何挂载全局属性和方法
VUE3——如何挂载全局属性和方法
22 0