访问器属性中为什么不能用this调用本属性?

233 阅读2分钟

在红宝书上看对象属性的时候,讲到属性分为数据属性和访问器属性,其中访问器属性可以拦截取值和赋值操作。

于是,很自然地写出了一个访问器属性的例子。

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 === persontrue后,忽然意识到了。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 排版