从日常应用例子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 小记