在红宝书上看对象属性的时候,讲到属性分为数据属性和访问器属性,其中访问器属性可以拦截取值和赋值操作。
于是,很自然地写出了一个访问器属性的例子。
const person = {};
Object.defineProperty(person, "name", {
get() {
return this.name;
},
set(v) {
this.name = v;
},
});
但是无论执行 person.name
还是 person.name = 'zhaji'
操作,都会报错 Uncaught RangeError: Maximum call stack size exceeded
,大意就是无限循环。
根据经验,最后这应该会是一个很弱智的错误。
确认了this === person
为true
后,忽然意识到了。get函数的目的就是获取 person.name
,在这里执行了 this.name
,这句话本身就是一个取值操作,那就又会调用get函数,所以陷入无限循环了;set也是同样的道理,在函数体里执行了赋值操作。
那么,应该如何取值赋值呢?看到一个例子。
const peroson = {};
Object.defineProperty(peroson, "name", {
get() {
// return this.name;
// why 'Maximum call stack size exceeded'? this.name就是get操作。
console.log("get ", name);
return name
},
set(v) {
// this.name = v;
console.log("set ", name, v);
name = v;
},
});
这样就可以正常访问和赋值了。在get、set函数中使用 name
变量即可。哪儿来的?当我们定义get、set时,js底层给的。()
还有一种等价的写法,就是在对象字面量中声明。
const person = {
get name(){
console.log('get', name);
return name;
},
set name(v){
console.log('set', v);
name = v;
}
}
两者的区别?
效果上没有区别,只是使用get关键字只能在对象初始化时定义访问器属性, Object.defineProperty
可以随时添加。
最重要的区别体现在class时,使用get关键字时,属性被定义在实例的原型上,使用Object.defineProperty
定义在实例本身上。
class Person{
get name(){
return 'zhaji'
}
}
const p1 = new Person()
p1.name // zhaji
Object.getOwnPropertyNames(p1) // []
Object.getOwnPropertyNames(Object.getPrototypeOf(p1)) // ["constructor", "name"]
都有proxy了,怎么还在看getter/setter呢?
了解一个知识点的演变历史未尝不是一件坏事。
参考资料:
[1] 《getter》https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/get
本文使用 mdnice 排版