• 理解defineProperty以及getter、setter


    我们常听说vue是用getter与setter实现数据监控的,那么getter与setter到底是什么东西,它与defineProperty是什么关系,平时有哪些用处呢?本文将为大家一一道来。

    对象的属性

    按照一贯的“由浅到深”行文原则,我们先温习一下对象的属性。我们知道对象有自身的属性以及原型上的属性,它们都可以通过obj.key这样的方式访问到。

    要设置/修改对象的属性也是很简单的,只需obj.key='value'即可。要注意的是,如果key位于原型上,那么此时会在对象自身设置该值,而不是修改原型上的。

    另外需要注意的是,原型上的属性有时候会被for in给“不小心”遍历出来,例如下面的代码:

    var arr = [1,2,3];
    arr.__proto__.test = 4;
    for(i in arr){
        console.log(arr[i]);
    }
    //输出:1234

    所以我们一般在用for in的时候都要加上hasOwnProperty判断,或者是抛弃for in,用forEach.

    认识defineProperty

    defineProperty是挂载在Object上的一个方法,作用是:为对象定义一个属性,或是修改已有属性的值,并设置该属性的描述符。该方法返回修改后的对象。

    如果没有后半句作用的话,那它与obj.key = 'value'这种赋值语句没什么两样。他的完整语法是这样:Object.defineProperty(obj, prop, descriptor)

    obj: 目标对象
    prop: 属性名称
    descriptor: 属性描述符

    前两个就不必讲了,需要重点理解的是第三参数。属性描述符用于定义该属性的一些特性。具体来讲分了两类:数据描述符(data descriptor)、访问描述符(accessor descriptor).

    这两类描述符有两个必选项:

    1. configurable
      从字面意思看它表示“可配置”,含义是:当它为true时,该属性的描述符可被修改,并且该属性可被delete删除。同理,当它为false时,我们无法再次调用defineProperty去修改描述符,也不可通过delete删除。

    2. enumerable
      从字面意思看它表示“可枚举”,含义是:当它为true时,该属性可被迭代器枚举出来。比如使用for in或者是Object.keys。

    接下来就是数据描述符(data descriptor)了,有两个:

    1. value
      这个就是该属性的值啦,即通过obj.key访问时返回。任何js数据类型都可以使用(number,string,object,function等)。

    2. writable
      这个也很好理解,表示该属性是否可写。当它为false时,属性不可被任何赋值语句重写。然而,此时还可以调用defineProperty来修改value,当然前提是configurable为true啦。

    剩下的就是访问描述符啦,先卖个关子讲两个注意事项。

    描述符的原型与默认值

    一般情况,我们会创建一个descriptor对象,然后传给defineProperty方法。如下:

    var descriptor = {
        writable: false
    }
    Object.defineProperty(obj, 'key', descriptor);

    这种情况是有风险的,如果descriptor的原型上面有相关特性,也会通过原型链被访问到,算入在对key的定义中。比如:

    descriptor.__proto__.enumerable = true;
    Object.defineProperty(obj, 'key', descriptor);
    Object.getOwnPropertyDescriptor(obj,'key'); //返回的enumerable为true

    为了避免发生这样的意外情况,官方建议使用Object.freeze冻结对象,或者是使用Object.create(null)创建一个纯净的对象(不含原型)来使用。

    接下来的注意点是默认值,首先我们会想普通的赋值语句会生成怎样的描述符,如obj.key="value"

    可以使用Object.getOwnPropertyDescriptor来返回一个属性的描述符:

    obj = {};
    obj.key = "value";
    Object.getOwnPropertyDescriptor(obj, 'key');
    /*输出
    {
        configurable:true,
        enumerable:true,
        value:"value",
        writable:true,
    }
    */

    这也是复合我们预期的,通过赋值语句添加的属性,相关描述符都为true,可写可配置可枚举。但是使用defineProperty定义的属性,默认值就不是这样了,其规则是这样的:
    configurable: false
    enumerable: false
    writable: false
    value: undefined

    所以这里还是要注意下的,使用的时候把描述符写全,免得默认都成false了。

    getter与setter

    所谓getter与setter其实是两个概念,并没有这样的属性。与之对应的是两个访问描述符(access descriptor):

    1. get
      它是一个函数,访问该属性时会自动调用,函数的返回值即为该属性的value。默认为undefined。

    你可能会想,既有value又有get函数,那么属性的值是什么呢?那你就想多了,这种情况在定义的时候就直接报错了,本身逻辑就矛盾嘛。

    1. set
      它是一个函数,为该属性赋值时会自动调用,并且新值会被当做参数传入。

    看到这里你可能就眼前一亮了,为属性赋值的时候会自动执行一个函数,那岂不是就能监控到数据的变化,从而实现mvvm的双向绑定?其实vue的数据监控用到的核心原理也就是这个啦。如果你用过knockout可能感受会更深,knockout能做到在IE6都支持双向绑定,就是强制让属性值为函数类型,必须手动执行函数才能拿到值。

    还好现在有了浏览器的默认支持,ES5开始就支持gettter、setter了,现在移动端基本完全可用,pc端需要IE9+。

    实际应用

    这么好用的方法,我们平时好像也不怎么用呀?写业务代码可能用到的确实少,但是当你要写一个公共模块乃至写一个框架时,就可能用到啦。

    比如你写一个公共模块,会往window上挂一些全局属性,并且你不希望别人在其他地方不小心覆盖这个属性,那就可以用defineProperty让该属性不可写、不可配置。贴一个我们项目中的代码:

    //向全局挂载通用方法
    for(let key in methods){
        if(methods.hasOwnProperty(key)){
            Object.defineProperty(WIN, key, {
                value        : methods[key]
            });
        }
    }

    另外一个用途呢,就是你自己想干坏事。覆盖别人写的代码,比如写chrome插件刷页面。或者说是想篡改浏览器的一些信息。

    比如你想把浏览器的userAgent给改了,直接写navigator.userAgent = 'iPhoneX'.你再输出一下userAgent,发现并没有修改。这是为什么呢?我们用这行代码看一下:

    Object.getOwnPropertyDescriptor(window, 'navigator');
    //输出
    {
        configurable:true,
        enumerable:true,
        get:ƒ (),
        set:undefined
    }

    原因就找到了,navigator是有setter的,每次取值总会执行这个set函数来做返回。但是好消息是什么呢?configurable为true,那就意味这我们可以通过defineProperty来修改这个属性,代码就相当简单了:

    Object.defineProperty(navigator, 'userAgent', {get: function(){return 'iphoneX'}})
    console.log(navigator.userAgent); //输出iphoneX

    喏,篡改浏览器userAgent的方法我教给你了。

  • 相关阅读:
    [置顶] 宏途_LCD调试流程.
    字典树的数据结构及基本算法的实现
    uva 10714 Ants(贪心)
    paip.输入法编程---增加码表类型
    chomp方法
    ios 限制输入长度
    我所理解的设计模式(C++实现)——策略模式(Strategy Pattern)
    Android用户界面 UI组件--AdapterView及其子类(一) ListView及各种Adapter详解
    C#系列教程——switch定义及使用
    局域网内linux由ip反解析主机名
  • 原文地址:https://www.cnblogs.com/bydzhangxiaowei/p/8089127.html
Copyright © 2020-2023  润新知