使用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, {
                        get: function () {
                            return v;
                        },
                        set: function (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', {
    writable: true
})
// Uncaught TypeError: Cannot redefine property: name
// configurable设置为true, writable 未设置,默认为false;configurable为true,因此可以配置 writable为true
var obj = {}
Object.defineProperty(obj, 'name', {
    configurable: true, 
    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', {
    writable: true
}) 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', {
    writable: true, 
    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', {
    writable: false, 
})
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;
        },
        enumerable: true,
        configurable: true,
    });
})(); // 更快的解决方式
// 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);
    }
}
*/

使用 defineProperty 劫持数据属性的改变的更多相关文章

  1. 实现双向绑定Proxy比defineproperty优劣如何?

    前言 双向绑定其实已经是一个老掉牙的问题了,只要涉及到MVVM框架就不得不谈的知识点,但它毕竟是Vue的三要素之一. Vue三要素 响应式: 例如如何监听数据变化,其中的实现方法就是我们提到的双向绑定 ...

  2. vue双向绑定、Proxy、defineproperty

    本文原链接:https://www.jianshu.com/p/2df6dcddb0d7 前言 双向绑定其实已经是一个老掉牙的问题了,只要涉及到MVVM框架就不得不谈的知识点,但它毕竟是Vue的三要素 ...

  3. js篇之对象数据属性与存取器属性

    在ECMAScript中,对象属性值可以用一个或两个方法代替,这两个方法就是getter和setter.由getter与与setter定义的属性叫做‘存取器属性’.当程序查询存取器属性的值时,js调用 ...

  4. vue2.x版本中Object.defineProperty对象属性监听和关联

    前言 在vue2.x版本官方文档中 深入响应式原理 https://cn.vuejs.org/v2/guide/reactivity.html一文的解释当中,Object.defineProperty ...

  5. avalon与双缓冲技术

    avalon与双缓冲技术 avalon1.5一个重要技术升级是引进异步渲染.异步渲染在游戏界有一个更专业的名字,叫双缓冲.游戏界要刷新界面与我们刷新浏览器视图,面临的问题是一致的.视图是由许多存在套嵌 ...

  6. JS复习:第二十二章

    一.高级函数 1.在任何值上调用Object原声的toString( )方法,都会返回一个[object NativeConstructorName]格式d字符串.每个类在内部都有一个[[Class] ...

  7. 浅析vue数据绑定

    前言:最近团队需要做一个分享,脚进脑子,不知如何分享.最后想着之前一直想研究一下 vue 源码,今天刚好 "借此机会" 研究一下. 网上研究vue数据绑定的文章已经非常多了,但是自 ...

  8. javascript基础修炼(9)——MVVM中双向数据绑定的基本原理

    开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 概述 1.1 MVVM模型 MVVM模型是前端单页面应用中非常重要的模型之一,也是Single Page Appl ...

  9. 一个极其简易版的vue.js实现

    前言 之前项目中一直在用vue,也边做边学摸滚打爬了近一年.对一些基础原理性的东西有过了解,但是不深入,例如面试经常问的vue的响应式原理,可能大多数人都能答出来Object.defineProper ...

随机推荐

  1. 基于Windows下永久破解jetbrains公司的系列产品(Idea, pycharm,clion,phpstorm)

    基于Windows下永久破解jetbrains公司的系列产品(Idea, pycharm,clion,phpstorm): PS : 有能力的建议购买正版,好吧. PS:均针对其对应的2018.3.1 ...

  2. docker中部署项目时遇到的问题

    容器和宿主机时间不同步问题? 将本地时间复制到docker容器内的etc文件夹下即可 docker cp /etc/localtime scrapy_8:/etc/ 启动crontab错误? 报错: ...

  3. [学习笔记] 在Windows 10上安装 WebLogic 12.2.1.3

    本文适用于学习目的而撰写.截止目前WebLogic已经有12.2.1.4.0了. 在安装WebLogic 12.2.1.3.0 首先要在Windows10之上Oracle JDK 1.8.  当前认证 ...

  4. 《Java基础教程》第一章学习笔记

    Java 是什么呀! 计算机语言总的来说分成机器语言,汇编语言,高级语言.其中Java一种高级计算机语言,它是一种可以编写跨平台应用软件,完全面向对象的程序设计语言. Java划分为三个技术平台,Ja ...

  5. vim python extension

    1. 检查vim 版本,需高于7.3. 2. Install extension manager : Vundle git clone https://github.com/gmarik/Vundle ...

  6. 什么是PHP Socket?

    什么是 Socket? Socket 的中文翻译过来就是“套接字”.套接字是什么,我们先来看看它的英文含义:插座. Socket 就像一个电话插座,负责连通两端的电话,进行点对点通信,让电话可以进行通 ...

  7. PHP安全之道学习笔记2:编码安全指南

    编码安全指南 编程本身就应该是一门艺术,而安全编程更是一种在刀尖上舞蹈的艺术,不仅要小心脚下的锋利寒刃,更要小心来自网络黑客或攻击者的狂轰乱炸. - by code artist 1.hash比较的缺 ...

  8. webpack-dev-middleware 和 webpack-hot-middleware 配置开发环境和生产环境;webpack脚手架;仿vue-cli

    webpack-dev-server更新后自带express服务器,已经不需要自己搭建.vue-cli从17年底左右也换成了最新的webpack-dev-server,而不是用webpack-dev- ...

  9. 快速入门函数式编程——以Javascript为例

    函数式编程是在不改变状态和数据的情况下使用表达式和函数来编写程序的一种编程范式.通过遵守这种范式,我们能够编写更清晰易懂.更能抵御bug的代码.这是通过避免使用流控制语句(for.while.brea ...

  10. python数据分析pandas中的DataFrame数据清洗

    pandas中的DataFrame中的空数据处理方法: 方法一:直接删除 1.查看行或列是否有空格(以下的df为DataFrame类型,axis=0,代表列,axis=1代表行,以下的返回值都是行或列 ...