使用 defineProperty 劫持数据属性的改变

使用defineProperty劫持数据属性的变化

例子一:有一个全局变量a,有一个全局函数b,实现一个`bindData`,执行后,a的任何赋值都会触发b的执行
// var a = 1;
a = 1
// console.log(Object.getOwnPropertyDescriptor(window,'a')); // 自定义的全局变量是可配置的
// console: { value: 1, writeable: true, enumerable: true, configurable: true }
function b () {
    alert('a的值发生改变');
}
bindData(window, b); // Uncaught TypeError: Cannot redefine property: a;(如果a是通过var声明的)

function bindData(target, event{
    for (var k in target) { // 给全局对象的任意属性都进行变化检测
        if (target.hasOwnProperty(k)) {
            let descriptor = Object.getOwnPropertyDescriptor(window, k);
            // Object.getOwnPropertyDescriptor(window, "TEMPORARY") => undefined
            // 找出对象可配置的属性
            if (descriptor && descriptor.configurable) { // configurable 为 true, 才可以使用 defineProperty()方法来修改属性描述符

                (function () {
                    var v = target[k]; // 闭包
                    Object.defineProperty(target, k, {
                        getfunction () {
                            return v;
                        },
                        setfunction (newValue{
                            v = newValue;
                            event.call(this);
                        }
                    })
                })();

            }
        }
    }
}

a = 2// alert: a的值发生改变

另外,注意

可配置性决定是否可以使用delete删除属性,以及是否可以修改属性描述符的特性

  1. 设置 configurable:false 后,无法使用 delete 删除属性
  2. 一般地,设置 configurable:false 后,将无法再使用 defineProperty() 方法来修改属性描述符
// 使用var声明全局变量时,变量的configurable为false
var a = 1;
console.log(Object.getOwnPropertyDescriptor(window,'a'));
// console: { value: 1, writable: true, enumerable: true, configurable: false }

delete window.a // false // 在严格模式下删除为configurable为false的属性,会提示类型错误TypeError
Object.defineProperty(window,'a',{
    configurable:true
});
// Uncaught TypeError: Cannot redefine property: a

但有一个例外,设置configurable:false后,只允许writable的状态从true变为false,但不允许将writable的状态从false变为true

如果在定义属性描述符特性时,未指定configurable,writable,enumerable的值,那么它的值默认是 false

// configurable, writable 均未设置,默认为false;由于configurable为false,因此不可 redefine property
var obj = {}
Object.defineProperty(obj, 'name', {
    value'gg'
})

Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "gg", writable: false, enumerable: false, configurable: false}

obj.name = 'hello'
console.log(obj.name) // 'gg'; 由于writable:false生效,对象name属性无法修改值,所以obj.name = 'hello'的赋值语句静默失败

// 在 configurable: false 的情况下,试图将writable从false改为true,会报错
Object.defineProperty(obj, 'name', {
    writabletrue
})
// Uncaught TypeError: Cannot redefine property: name
// configurable设置为true, writable 未设置,默认为false;configurable为true,因此可以配置 writable为true
var obj = {}
Object.defineProperty(obj, 'name', {
    configurabletrue
    value'gg'
})

Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "gg", writable: false, enumerable: false, configurable: true}

obj.name = 'hello'
console.log(obj.name) // 'gg'; 由于writable:false生效,对象name属性无法修改值,所以obj.name = 'hello'的赋值语句静默失败

// 在 configurable: true 的情况下,可以将writable从false改为true
Object.defineProperty(obj, 'name', {
    writabletrue
})

obj.name = 'hello'
console.log(obj.name) // 'hello'; // 说明 writable 修改成功
Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "hello", writable: true, enumerable: false, configurable: true}
// writable设置为true, configurable 未设置,默认为false; 不可修改属性描述符的特性,
// 但有一个例外,设置configurable:false后,允许writable的状态从true变为false !!!
var obj = {}
Object.defineProperty(obj, 'name', {
    writabletrue
    value'gg'
})

Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "gg", writable: true, enumerable: false, configurable: false}
obj.name = 'hello'
console.log(obj.name) // 'hello'; // writable 为true,可以修改name属性

// 在configurable为false的情况下,只允许writable的状态从true变为false !!!
Object.defineProperty(obj, 'name', {
    writablefalse
})
obj.name = 'good job'
console.log(obj.name) // 'hello'; // 由于writable:false修改生效,对象name属性无法修改值,obj.name = 'good job'的赋值语句静默失败

Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "hello", writable: false, enumerable: false, configurable: true}
例子二:快速定位不小心暴露到全局的变量
// 函数泄漏全局变量的场景
/*
function A () {
    // 在一个函数中多次用到了 for 循环, 为了节省变量, 都是用了 i
    for (var i=0; i<5; i++) {
        // ...
    }
    for (i=0; i<5; i++) {
        // ...
    }
    for (i=0; i<5; i++) {
        // ...
    }
}

// 在某次分拆函数的时候, 忘记在新函数中对抽取出来的 for 循环中的变量进行重新定义
// 从而导致该变量成为泄漏的全局变量
function A () {
    for (var i=0; i<5; i++) {
        // ...
    }
    for (i=0; i<5; i++) {
        // ...
    }
}

function B () {
    for (i=0; i<5; i++) { // 这里 i 成了泄漏的全局变量
        console.log(i);
    }
}
*/


// 通过数据劫持对泄漏的全局变量进行检测
(function () {
    var value = window["i"];
    Object.defineProperty(window"i", {
        get () {
            return value;
        },
        set (newValue) {
            debugger;
            value = newValue;
        },
        enumerabletrue,
        configurabletrue,
    });
})();

// 更快的解决方式
// window.__defineSetter__('i', function(){ debugger })

// 'use strict' 使用严格模式可以避免出现未定义的变量
function B () {
    for (i=0; i<5; i++) {
        console.log(i);
    }
}

B(); // 运行 B 函数, i 变为全局变量; 可以被我们的 debugger 调用栈检测到

/*
// 如果在函数里对 i 进行声明, 那么就不会被检测
function B () {
    for (var i=0; i<5; i++) { // 这里 i 被声明了
        console.log(i);
    }
}
*/

posted @ 2019-12-04 18:17  rencoo  阅读(865)  评论(0编辑  收藏  举报