[[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对象如下:
这时候显式定义的方法会覆盖原型链的对应方法而被执行。
如果我们不显式定义属性的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对象中仅定义了属性a的get方法,并且返回的是常量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