我才知道每天都在使用Object.defineProperty
之前写过一篇文章,让a==1&&a==2&&a==3成立中,有提到过访问器属性来解决该面试题的解法, 下面深入谈一谈。
一、Object.defineProperty
和Object.definpropertise
介绍
先来看看下面的代码:
let obj = {
name: '橘子哥',
age: '22'
}
想要控制obj
对对象中的属性进行配置或者操作,可以通过Object的defineProperty
和definpropertise
两种方法:
- 定义:它们是函数,可以修改一个已知对象的属性,修改包括修改已有的对象属性以及给该对象创建新的属性。
- 差别:从名字就可以知道,前者可以修改对象的单个属性,后者可以修改对象的多个属性。
- 语法:
- 前者的语法为
Object.defineProperty(obj, prop, descriptor)
- 后者的是
Object.defineProperties(obj, props)
- 前者的语法为

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。 ———— MDN
一般讨论的就是这两种两种描述符。上面展示的是数据描述符,下面是存取描述符的式图:
可以从上图看到,存取描述符是不具有值的,所以说,涉及到值的value
和wriable
是没有的。不然需要的取值的时候,是应该拿value
的还是调用get()
的呢?这就是数据描述符和存取描述符不能同存。你不能既是男的,又是女的吧?
其实我更加愿意把存取描述符称之为钩子属性。它本身并不存储值,但是我们使用obj.name
的时候,会触发get
这个函数,就像钩子一样。
数据描述符和存取描述符的属性的关系如下:
、
具体这6个属性的用法,推荐直接去看MDN的文档,已经足够的简单直白。
简单的了解了一些它们的用法,那么它们出现在我工作中的什么地方呢?
我一贯不理解为什么翻译为访问器属性这个字的意思,脑中没有画面感。后来看了MDN对于将其翻译为存取描述符,这就恰如其名了。
二、两种描述符对象出现在我们工作中哪里?
讨论两种描述符对象出现在我们工作中哪里,其实就是在讨论它们存在的意义。而我觉得这一点比它们本身的定义更加的重要。
只有了解了他们存在的意义,才可以直接感受到它们的重要性!自然会提高对它们的重视程度。它们早已经融入了我们的业务开发当中,以至于,我们时常忘记了它们的存在。
就拿Object.defineProperty
来说,它存在于我们将ES6转化为ES5当中,它存在于我们在使用vue2的时候需要响应式的时候。
2.1 实现es6的class static修饰符 - 数据描述符的使用
这是我之前在面试富途时候的一个面试题,面试官叫我将es6 class中static、construct等等用ES5实现一遍。我平时确实没有思考过怎么实现,只知道是protptye那些的语法糖。面试评分估计也不甚理想,所以记忆犹新。
其中,defineProperty
的存取描述符是关键点之一。我们以static修饰符的实现为例,下面代码片段摘抄于babel转换器:
"use strict";
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
class Test {}
_defineProperty(Test, "name", 1);
可以看到其中对于变量的配置也有Object.defineProperty
的影子。
2.2 Vue的响应式 - 存取描述符的使用
vue的响应式简单来说,就是监听变量的变化,当该变量改变之后,就改变视图。也就是我们要监听变量的变化,那么存取描述符岂不是正好合适。
核心代码如下:
data: {
name: "橘子哥哥",
}
for (let key in this.data) {
Object.defineProperty(this, key, {
get() {
return this.data[key]
},
set(newValue) {
this.data[key] = newValue
渲染界面({ [key]: newValue })
},
})
}
更详细的解析在我写的《手把手代你写MVC》一文,感兴趣的可以自行查阅。
2.3 让(a===1&&a===2&&a===3)为真 - 数据描述符和存取描述符的合作
Object.defineProperties(window, {
_a: {
value: 0,
writable: true
},
a: {
get: function() {
return ++_a
}
}
})
2.4 在屎山代码上面修改
想想这么一个场景,当我们有修改一个在多处使用的对象的属性的时候,我们需要找到所以使用到该对象的地方,让逐个去改。
或者说,又重新添加一个对象属性。
如果这时候我们使用存取描述符来监听这个对象的属性的变化,然后根据变化的进行相应的逻辑操作。这样是不是就简单很多了。
转载自:https://juejin.cn/post/7199279293755342905