likes
comments
collection
share

const obj = {name: ‘yoran’};console.log(obj.name);打印什么?

作者站长头像
站长
· 阅读数 14

访问器拦截

有这么一个对象:

const obj = {
	name: '什么橘子好次'
}

console.log(obj.name)的时候,一定打印的是「什么橘子好次」吗?

答案是否定的。

我们可以通过它的改变它的存取属性「get」来改变它的行为。

Object.defineProperty(obj, 'name', {
  get: function() {
    return '砂糖橘很甜'
  }
})
console.log(obj.name) // 砂糖橘很甜

我也可以通过对象的数据属性来限制它的行为:

Object.defineProperty(obj, 'name', {
  value: '砂糖橘加盐',
})

但是如果我的对象是基于某个原型生成的呢?当我们在进行obj.name的时候发生了什么?

属性设置和屏蔽

如果name属性不是之际存在obj上面的,而是在它的原型上面的,当我们obj.name的时候会出现什么样过程呢?

function MyObj() {}
MyObj.prototype = {
  name: 'yoran'
}
const obj = new MyObj()

假设我们对MyObj.prototype进行一些配置如下:

Object.defineProperty(MyObj.prototype, 'name', {
  value: 'organ',
  writable: false
})

那么我们从obj.name中[put]的结果是organ。而我们给它添加setter(存取属性)呢?首次启发,我们可以总结如下(内容源于黄皮书上):

  1. 如果[[prototype]]链上层不存name,会给obj添加name属性
  2. 如果在[[Prototype]]链上层存在名为name的普通数据访问属性,并且没有被标记为只读(writable:false),那就会直接在obj中添加一个名为foo的新属性,它是屏蔽属性
  3. 如果在[[Prototype]]链上层存在name,但是它被标记为只读(writable:false),那么无法修改已有属性或者在obj上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
  4. 如果在[[Prototype]]链上层存在name并且它是一个setter,那就一定会调用这个setter。foo不会被添加到(或者说屏蔽于)obj,也不会重新定义name这个setter。

写不如画,综上可以得到下面这样一张图:

obj.name
__prototype__的链上是否存在name
给obj对象直接添加上name
name是否是数据访问属性?
writable: false?
是否严格模式
屏蔽属性
报错!
name=?setter
非屏蔽属性
忽略

但是也是有例外的情况:

ES6 原生提供了 Proxy构造函数,const proxy = new Proxy (target, handler);

其中new Proxy用来生成Proxy 实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。

Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。

我们可以在业务中怎么使用

别使用!

当你真的需要在业务项目中要用到这些属性拦截的话。我建议是对一部分进行重构。

补充

内容主要来源: 《你不知道的JavaScript》上册, 即黄皮书。

如果你需要了解对象的原型和原型链可以看这篇文中:重学JS(4) - 原型和原型链

如果你需要了解访问器属性和数据属性可以看这篇文章:我才知道每天都在使用Object.defineProperty