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


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

    一、vue的使用

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

    1. //创建vue的实例
    2. const vm = new Vue({
    3. data: {
    4. desc: '为什么this能够直接访问data中的属性',
    5. },
    6. methods: {
    7. sayName() {
    8. console.log(this.desc);
    9. }
    10. },
    11. });
    12. console.log(vm.name);
    13. console.log(vm.sayName());

    二、Vue的构造函数

    接收一个options参数

    • 使用 instanceof 判断 this对象上是否出现了Vue的prototype,我们都知道this的指向是取决于谁调用
    • this._init(options) 证明在这调用要么我们创建的实例上有_init方法要么方法在Vue的prototype上,但是我们可以看到实例上并没有_init方法 ,那么肯定在一个地方给Vue的prototype上加上了_init方法 继续往下看
    1. function Vue(options) {
    2. if (!(this instanceof Vue)
    3. ) {
    4. warn('Vue是一个构造函数,应使用“new”关键字调用');
    5. }
    6. this._init(options);
    7. }
    8. //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)

    1. initMixin(Vue)
    2. function initMixin(Vue) {
    3. //prototype上增加init方法
    4. Vue.prototype._init = function (options) {
    5. var vm = this; //Vue实例
    6. vm.age = 30
    7. //代码进行了删减
    8. initState(vm);
    9. }
    10. }
    11. //这里只是举例测试
    12. const vm = new Vue({})
    13. console.log(vm.age) //30

    四 initState(vm)

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

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

    五 initMethods(vm, opts.methods)

    如果有methods则取调用initMethods方法

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

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

    1. /*
    2. vm:构造函数实例
    3. methods:我们传的methods对象
    4. */
    5. function initMethods(vm, methods) {
    6. var props = vm.$options.props;
    7. //循环methods对象
    8. for (var key in methods) {
    9. {
    10. //判断是否是函数 不是的化则作出警告
    11. if (typeof methods[key] !== 'function') {
    12. warn(
    13. "Method "" + key + "" has type "" + (typeof methods[key]) + "" in the component definition. " +
    14. "Did you reference the function correctly?",
    15. vm
    16. );
    17. }
    18. //判断 methods 中的每一项是不是和 props 冲突了,如果是,警告。
    19. if (props && hasOwn(props, key)) {
    20. warn(
    21. ("Method "" + key + "" has already been defined as a prop."),
    22. vm
    23. );
    24. }
    25. //判断 methods 中的每一项是不是已经在 new Vue实例 vm 上存在,而且是方法名是保留的 _ $ (在JS中一般指内部变量标识)开头,如果是警告。
    26. if ((key in vm) && isReserved(key)) {
    27. warn(
    28. "Method "" + key + "" conflicts with an existing Vue instance method. " +
    29. "Avoid defining component methods that start with _ or $."
    30. );
    31. }
    32. }
    33. //给实例增加methods中的方法 这样其实我们就已经可以用vm访问 到methods中的方法了
    34. vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
    35. }
    36. }

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

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

    1. const vm = new Vue({
    2. methods: {
    3. sayHi() {
    4. console.log(this.age, 'hello-this')
    5. }
    6. }
    7. });
    8. let a = {
    9. age: 15,
    10. fn: vm.sayHi
    11. }
    12. console.log(a.fn(), 'vm') //打印15

    六 initData(vm)

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

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

    1. function initData(vm) {
    2. var data = vm.$options.data;
    3. data = vm._data = typeof data === 'function'
    4. ? getData(data, vm)
    5. : data || {};
    6. //如果不是对象则警告
    7. if (!isPlainObject(data)) {
    8. data = {};
    9. warn(
    10. 'data functions should return an object:\n' +
    11. 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
    12. vm
    13. );
    14. }
    15. // proxy data on instance
    16. var keys = Object.keys(data);
    17. var props = vm.$options.props;
    18. var methods = vm.$options.methods;
    19. var i = keys.length;
    20. while (i--) {
    21. var key = keys[i];
    22. //判断key值有没有跟methods中的key重名
    23. {
    24. if (methods && hasOwn(methods, key)) {
    25. warn(
    26. ("Method "" + key + "" has already been defined as a data property."),
    27. vm
    28. );
    29. }
    30. }
    31. //判断key值有没有跟props中的key重名
    32. if (props && hasOwn(props, key)) {
    33. warn(
    34. "The data property "" + key + "" is already declared as a prop. " +
    35. "Use prop default value instead.",
    36. vm
    37. );
    38. //是否是内部私有保留的字符串$ 和 _ 开头
    39. } else if (!isReserved(key)) {
    40. //代理
    41. proxy(vm, "_data", key);
    42. }
    43. }
    44. // observe data
    45. observe(data, true /* asRootData */);
    46. }

    七 proxy(vm, "_data", key)

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

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

    1. function proxy (target, sourceKey, key) {
    2. sharedPropertyDefinition.get = function proxyGetter () {
    3. return this[sourceKey][key]
    4. };
    5. sharedPropertyDefinition.set = function proxySetter (val) {
    6. this[sourceKey][key] = val;
    7. };
    8. Object.defineProperty(target, key, sharedPropertyDefinition);
    9. }
    1. //创建vue构造函数
    2. function Vue(options) {
    3. if (!(this instanceof Vue)
    4. ) {
    5. warn('Vue是一个构造函数,应使用“new”关键字调用');
    6. }
    7. this._init(options);
    8. }
    9. //初始化
    10. initMixin(Vue);
    11. function initMixin(Vue) {
    12. //prototype上增加init方法
    13. Vue.prototype._init = function (options) {
    14. var vm = this; //Vue实例
    15. let methods = options.methods
    16. initState(vm);
    17. }
    18. }
    19. //initState方法代码进行了删减
    20. function initState(vm) {
    21. vm._watchers = [];
    22. var opts = vm.$options; //这里是我们在创建实例的时候传的参数
    23. //如果传了methods 则去调用
    24. if (opts.methods) { initMethods(vm, opts.methods); }
    25. if (opts.data) {
    26. initData(vm);
    27. } else {
    28. observe(vm._data = {}, true /* asRootData */);
    29. }
    30. }
    31. /*
    32. vm:构造函数实例
    33. methods:我们传的methods对象
    34. */
    35. function initMethods(vm, methods) {
    36. var props = vm.$options.props;
    37. //循环methods对象
    38. for (var key in methods) {
    39. {
    40. //一些条件判断
    41. }
    42. //给实例增加methods中的方法 这样其实我们就已经可以用vm访问 到methods中的方法了
    43. vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
    44. }
    45. }
    46. const vm = new Vue({
    47. methods: {
    48. sayHi() {
    49. console.log('hello-this')
    50. }
    51. }
    52. });
    53. vm.sayHi() //hello-this

    总结

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

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

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

    地址:前端面试题库

  • 相关阅读:
    在iis上配置think php上传文件时报错“is not within the allowed path“
    1.7.C++项目:仿muduo库实现并发服务器之Poller模块的设计
    【C++】Windows端VS code中运行CMake工程(手把手教学)
    不会吧,都2023年了你还不会JavaStream?
    Redis教程(二十二):Redis的过期删除和缓存淘汰策略
    Spring注解开发
    vue3总结
    Gromacs分析处理-模拟前后蛋白结构差异对比图的制作
    如何搬运视频赚钱?
    华为设备VRRP配置命令
  • 原文地址:https://blog.csdn.net/weixin_42981560/article/details/126707872