对象属性的获取和设置

156 阅读3分钟

[[Get]]操作

思考如下代码:

var myObject = {
  a: 1
}
// 访问myObject对象属性a
console.log(myObject.a) // 1

在这个过程中实现了[[Get]]操作:第一步会在当前对象查找该属性;如果不存在,会继续向上沿着原型链继续查找,都没找到的话,则返回undefined

get函数

思考如下代码:

var myObject = {
  get a () {
    return 2
  }
}
console.log(myObject.a) // 2

给对象属性a新增get函数,在获取属性值的时候会调用这个函数。打印出的myObject对象如下:

image.png

这时候显式定义的方法会覆盖原型链的对应方法而被执行。

如果我们不显式定义属性的get方法,而获取属性值仍需要调用get方法才可以获取到,这时候会在原型链中来查找并调用。

特殊情况

var myObject = {
  a: undefined
}
console.log(myObject.a) // undefined
console.log(myObject.b) // undefined

这2种情况虽然返回的结果都一样,也调用了[[Get]]操作,但查找的过程并不一样,只是我们暂时还无法区分,是值本身是undefined,还是确实没有找到才返回的undefined

当我们访问一个词法作用域中不存在的变量的时候,会报错ReferenceError,说明在这种情况下,并没有调用[[Get]]操作来取值。

[[Put]]操作

[[Get]]操作是获取对象的属性值,[[Put]]操作是它对应的操作,是给对象属性赋值,或属性不存在时,先创建这个属性。其实在实际情况下,可能会比这复杂的多。

[[Put]]被触发取决于很多因素,其中最重要的因素是对象是否已经包含这个属性。

如果已经存在这个属性的情况下:

  • 属性是否有属性描述符相关的权限,如果都有的话并且存在setter函数,则会调用setter函数执行赋值操作
  • 如果属性描述符中writable为false,在严格模式下会静默失败并抛出错误TypeError,非严格模式下赋值也不会成功
  • 如果都不是,就会给属性赋值

如果对象中并不存在这个属性,[[Put]]操作将会非常复杂。首先它不仅要检查当前对象,而且要检查整个作用域链是否存在这个属性,如果是作用域链中的某个作用域存在该属性,那整个作用域链的属性该如何赋值且如何关联呢。后面在说[[Prototype]]时再仔细讨论。

var myObject = {
  a: 1
}
myObject.b = 2
console.log(myObject.b) // 2

set函数

属性描述符中有个writable属性,如果我们配置为false,能否通过setter函数进行操作了呢?

var myObject = {
  a: 1
}
Object.defineProperty(myObject, 'b', {
  writable: false,
  set: function (x) {
    this.a = x
  }
})
myObject.b = 100
// Error: Invalid property descriptor. Cannot both specify accessors and a value 
// or writable attribute, #<Object>
console.log(myObject.a)

执行报错了:无效的属性描述符。也就是说writable属性描述符和set函数不能同时存在于属性描述对象中,不管writable属性描述符的值是true还是false,删掉之后,输出的值100是对的。

思考如下代码:

var myObject = {
  get a() {
    return 2
  }
}
myObject.a = 3
console.log(myObject.a) // 2

myObject对象中仅定义了属性aget方法,并且返回的是常量2,所以即使给属性a赋值了3,返回的结果仍然是2,这里即使定义了set函数,也会被忽略。

一般来说,getter和setter是成对出现的,如下我们设置变量a的值,需要定义出一个变量出来,如果在setter函数中直接设置this.a的值,会死循环,因为在setter函数本身就是设置属性值,再执行赋值操作,就会不断循环执行下去。

var aVal = 0
var myObject = {
  get a() {
    return aVal
  },
  set a(val){
    aVal = val * 2
  }
}
myObject.a = 100
console.log(myObject.a) // 200