从日常应用例子defineProperty到vue.js computed实现

Posted hello,是翠花呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从日常应用例子defineProperty到vue.js computed实现相关的知识,希望对你有一定的参考价值。

相信很多人知道 defineProperty ,或者在面试的时候被问过“vue(2)实现双向数据绑定的原理”呀“vue3 与 vue2 实现双向绑定的区别”呀等类似问题了。

官方描述:

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

大白话就是说可以通过这个方法在 a 对象上定义一些属性更改 a 对象的可读/写和值的操作。

懒得写了,复制的官方示例 🌰:
往 object 上添加了个值为 42 不可赋值的 property1 属性

const object = {};

Object.defineProperty(object, "property1", {
  value: 42,
  writable: false,
});

object.property1 = 77;
// throws an error in strict mode 严格模式下报错,普通模式不能改值也不报错

console.log(object.property1);
// expected output: 42
configurable、enumerable、writable,writable

属性的很多描述值都为 false:
configurable、enumerable、writable,writable 上述有例子,enumerable 是设置是否可枚举:

默认值为 false,添加的属性 property1 默认不会被枚举出来:

const object = { a: 1 };

Object.defineProperty(object, "property1", {
  value: 42,
  enumerable: false,
});

for (let key in object) {
  console.log(key); // a
}

改为 true 即可被枚举:

const object = { a: 1 };

Object.defineProperty(object, "property1", {
  value: 42,
  enumerable: true,
});

for (let key in object) {
  console.log(key); // a property1
}

configurable 可控制属性的描述符和属性本性本身的读写:

const object = { a: 1 };

Object.defineProperty(object, "property1", {
  value: 42,
  configurable: false,
});

delete object.property1;
console.log(object); // {a: 1, property1: 42}

设为 true

const object = { a: 1 };

Object.defineProperty(object, "property1", {
  value: 42,
  configurable: true,
});

delete object.property1;
console.log(object); // {a: 1}

configurable 描述符的读写接下来后面的例子中会有穿插。

get、 set

一个在属性读取时进行某些操作,一个在属性赋值时进行某些操作。
官方例子 🌰:

默认给 b 属性写入值 38,当给 b 赋值 newValue 时 b 就等于那个值:

var bValue = 38;
Object.defineProperty(o, "b", {
  get() {
    return bValue;
  },
  set(newValue) {
    bValue = newValue;
  },
  enumerable: true,
  configurable: true,
});

去找个get\\set 其实际应用的例子 🌰:
我们结合element-ui 中日历 calendar 组件来分析:
realSelectedDay变量用于告知组件当前选择的日期,当点击calendar组件中的日期格子时,会触发v-module绑定值的更新。
realSelectedDay值的经历历程是:当用户点击日历单元格,会调用date-table.vue文件中的pickDay方法,触发main.vue文件中pickDay方法。

element/blob/dev/packages/calendar/src/date-table.vue中:

pickDay({ text, type }) {
  const date = this.getFormateDate(text, type);
  this.$emit('pick', date);
},

element/blob/dev/packages/calendar/src/main.vue中:

pickDay(day) {
    this.realSelectedDay = day;
  }

而main.vue中通过计算属性computed对realSelectedDay值进行了额外操作,具体如下:

realSelectedDay: {
    get() {
      if (!this.value) return this.selectedDay;
      return this.formatedDate;
    },
    set(val) {
      this.selectedDay = val;
      const date = new Date(val);
      this.$emit('input', date);
    }
  }

在获取realSelectedDay值时将它格式化了一下,在赋值时“通知”calendar组件v-model绑定的值变更。那么computed里的get/set与defineProperty中的get/set有什么区别呢,我们来看一下:

去GitHub找vue.js的仓库,打开地址:https://github.com/vuejs/vue/blob/dev/dist/vue.js 文件,直接搜索computed找到initComputed方法,这里判断了一些临界条件,我们直接把目光转移到defineComputed方法上:
defineComputed方法首先也是判断了一些临界,将用户手写的get/set方法赋值到sharedPropertyDefinition对象上默认computed值以方法return出去为get,然后眼前又一亮看到一句关键性代码:

Object.defineProperty(target, key, sharedPropertyDefinition);

诶~我们就知道computed实际上走的是defineProperty了。

让我们来跑个例子🌰

去GitHub把vue2的仓库clone下来,随便找个文件夹或者examples文件夹里新建test.html文件:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue.js example</title>

    <script src="../dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{msg}}
    </div>
    <script>
      var app2 = new Vue({
        el: '#app',
        data: {
          message: '页面加载于 ' + new Date().toLocaleString()
        },
        computed: {
          msg: {
            get () {
              return this.message + ' computed'
            }
          }
        }
      })
    </script>

  </body>
</html>

因为只写了一个get属性描述符,所以我们直接在createComputedGetter方法处打上断点,在控制台查看:

我们可以看到程序直接走了sharedPropertyDefinition.get赋值操作,因为我们的msg不是一个function所以跳过了第一步function的判断。
在赋值get之前,调用了createComputedGetter方法:

function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value
    }
  }
}

进入createComputedGetter方法后,该方法会调用depend方法进行依赖收集:
depend方法实际走了addDep

 Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};
Watcher.prototype.addDep = function addDep (dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
};

addDep给我们的msg做了个标记,可以在这里看到我们test.html文件里写的get:

根据id标记调用 createTextVNode方法创建text,调用createElement、createElm、createComponent创建节点,更新虚拟dom,
当我们更新都完成时,替换掉老的app id下面的节点:

此时可以看到界面上数据以及更新了

回到defineProperty

至此,calendar组件通过computed的get\\set做了一些事情,那当我们想要禁止点击日期操作时修改它的computed操作就能拦截日历点击:

// ...
// let calendar = this.$refs.calendar
Object.defineProperty(calendar, 'realSelectedDay', {
  configurable: true,
  get() {
    if (!calendar.value) return calendar.selectedDay
    return calendar.formatedDate
  },
  set(val) {
    return val
  }
})

是的,这里只需要添加configurable: true就能修改。

以上是关于从日常应用例子defineProperty到vue.js computed实现的主要内容,如果未能解决你的问题,请参考以下文章

从日常应用例子defineProperty到vue.js computed实现

Vue双向绑定原理 从vue2的Object.defineProperty到vue3的proxy

学习 vue2.0/3.0 中的proxy和Object.defineProperty 小记

vue项目创建和部署使用

Vue源码Object.defineProperty与Proxy

深入理解 Object.defineProperty