开始
有如下一道面试题
const o = (function () {
const obj = {
a: 1,
b: 2,
}
return {
get: function (k) {
return obj[k]
}
}
})()
要求是如何不修改这部分代码的情况下,做到可修改obj的属性,使得o.get('b')结果为3
思路
如果是用结果导向的思维方式,从题目的描述,期望达到的目的应该是大概可以如下伪代码所示
情况一
foo(o).b = 3
function foo (obj) {
// 通过某些方法获取到题目中o对象中的obj属性(代理)
// 允许修改到o的obj属性
// 使得o.get方法可以正确访问到上述的修改
}
o.get('b') // 3
情况二
const newObj = o.get(某个代理属性)
newObj.b // 2 访问得到
newObj.b = 3 // 允许修改
o.get('b') // 3
这里主要的思路还是通过某些间接的方式(代理)使得能够从外部访问到闭包内的obj的属性,但如何做到是个大问题。闭包内部的obj是个js对象,js的对象有一个特点,访问当前实例化的对象上不存在的属性的时候,会自动的向指向上层的原型链上寻找,直到尽头(null)。
这里摘抄一段MDN的描述
const o = {
a: 1,
b: 2,
// __proto__ 设置了 [[Prototype]]。它在这里被指定为另一个对象字面量。
__proto__: {
b: 3,
c: 4,
},
};
// o.[[Prototype]] 具有属性 b 和 c。
// o.[[Prototype]].[[Prototype]] 是 Object.prototype(我们会在下文解释其含义)。
// 最后,o.[[Prototype]].[[Prototype]].[[Prototype]] 是 null。
// 这是原型链的末尾,值为 null,
// 根据定义,其没有 [[Prototype]]。
// 因此,完整的原型链看起来像这样:
// { a: 1, b: 2 } ---> { b: 3, c: 4 } ---> Object.prototype ---> null
console.log(o.a); // 1
// o 上有自有属性“a”吗?有,且其值为 1。
console.log(o.b); // 2
// o 上有自有属性“b”吗?有,且其值为 2。
// 原型也有“b”属性,但其没有被访问。
// 这被称为属性遮蔽(Property Shadowing)
console.log(o.c); // 4
// o 上有自有属性“c”吗?没有,检查其原型。
// o.[[Prototype]] 上有自有属性“c”吗?有,其值为 4。
console.log(o.d); // undefined
// o 上有自有属性“d”吗?没有,检查其原型。
// o.[[Prototype]] 上有自有属性“d”吗?没有,检查其原型。
// o.[[Prototype]].[[Prototype]] 是 Object.prototype 且
// 其默认没有“d”属性,检查其原型。
// o.[[Prototype]].[[Prototype]].[[Prototype]] 为 null,停止搜索,
// 未找到该属性,返回 undefined。
结合一个关于this的知识点:this指向调用它的上下文对象,这里先不讨论关于this和箭头函数的情况,当o.get被调用的时候,我们如果可以构造一个不在于obj的属性,且由于访问的时候上下文对象已经指向了要被访问的obj,那么就可以在不改变obj自由属性的情况下,获得对obj的读写能力。
可能这么描述有点绕,直接上代码:
const o = (function () {
const obj = {
a: 1,
b: 2,
}
return {
get: function (k) {
return obj[k]
}
}
})()
Object.defineProperty(Object.prototype, 'proxy_key', {
get () {
return this
}
})
const _obj = o.get('proxy_key')
_obj.b = 3
o.get('b') // 3
这里由于第一次执行o.get('proxy_key')的时候属性并没有存在于obj内,于是自发向上层原型链,也就是Object.prototype中寻找,此刻触发了访问getter方法,此刻访问的上下文对象指向obj,如果此刻将this return,则等同于拿到了obj的访问方式。
关于这里为什么this会指向obj,可以看下面这个例子
Object.defineProperty(Object.prototype, 'proxy_key', {
get () {
return this
}
})
function Person () {
}
const p = new Person()
const p_k = p.proxy_key // 向上搜寻,直到访问了Object.prototype,存在该值
// 这里访问的上下文对象是p
p === p_k // true
总结
- this指向执行上下文环境的对象
- 对象的属性访问会向原型链层层向上搜寻,直至null