记一道实用的面试题:闭包的一个漏洞

309 阅读2分钟

闭包的一个漏洞

alarm.JPG

非原创:记录+自己的理解

这道题目需要掌握闭包以及原型和原型链的相关知识

题目

const o = (function () {
    let obj = { a: 1, b: 2 };
    return {
        get(k) {
            return obj[k];
        },
    };
})();
// 如何在不改变上面代码的情况下 修改obj对象

题目分析

题目主要利用了闭包创建私有空间,可以看出其目的旨在让外界只能通过提供的get方法来读取obj对象里的属性

o.get('a'); // 1
o.get('b'); // 2

但是这道题的漏洞就在于,obj既然是一个对象,我们通过get方法除了可以访问到obj的自身属性之外,还可以通过原型链访问到其原型中的属性Object.prototype.xxx

于是我们自然而然想到了在Object.prototype中有没有一个属性让我们可以获取到obj

尝试1

const a = { hello: "world" };
console.log(a.valueOf()); // {hello: 'world'}

没错,就是Object.prototype.valueOf属性

console.log(o.get('valueOf')); // ƒ valueOf() { [native code] }
console.log(o.get('valueOf')()); // Uncaught TypeError: Cannot convert undefined or null to object  at valueOf (<anonymous>)

为什么会报错?

分析可知是由于this指向出现了问题

const v = Object.prototype.valueOf;
console.log(v); // ƒ valueOf() { [native code] }
v(); // Uncaught TypeError: Cannot convert undefined or null to object  at valueOf (<anonymous>)

如果题目改为

const o = (function () {
    let obj = { a: 1, b: 2 };
    return {
        get(k) {
            return obj[k](); // 此处改动为方法调用
        },
    };
})();

则上述方法可行,我们可以拿到obj对象

console.log(o.get("valueOf")); // {a: 1, b: 2}

尝试2

既然Object.prototype中的既有属性不能满足我们的需求,那么我们就考虑给它添加一个属性来另辟蹊径
通过上面的尝试我们想到可以通过函数调用的this来获取

Object.defineProperty(Object.prototype, "xxx", {
    get() {
        return this;
    },
});
// 其中 xxx 可以任意设置
console.log(o.get("xxx")); // {a: 1, b: 2}

结果

Object.defineProperty(Object.prototype, "xxx", {
    get() {
        return this;
    },
});

let obj2 = o.get("xxx");
obj2.a = "hello world";
obj2.c = 3;
console.log(o.get("a")); // hello world
console.log(o.get("b")); // 2
console.log(o.get("c")); // 3

优化

const o = (function () {
    let obj = { a: 1, b: 2 };
    return {
        get(k) {
            if (obj.hasOwnProperty(k)) {
                return obj[k];
            }
        },
    };
})();

Object.defineProperty(Object.prototype, "xxx", {
    get() {
        return this;
    },
});

let obj2 = o.get("xxx");
obj2.a = "hello world"; // Uncaught TypeError: Cannot set properties of undefined (setting 'a')

or

const o = (function () {
    let obj = { a: 1, b: 2 };
    Object.setPrototypeOf(obj, null);
    return {
        get(k) {
            return obj[k];
        },
    };
})();

Object.defineProperty(Object.prototype, "xxx", {
    get() {
        return this;
    },
});

let obj2 = o.get("xxx");
obj2.a = "hello world"; // Uncaught TypeError: Cannot set properties of undefined (setting 'a')