美文网首页Web前端之路让前端飞前端开发那些事
复制对象 | 属性描述符 | 不变性 | 存在性

复制对象 | 属性描述符 | 不变性 | 存在性

作者: 姚屹晨 | 来源:发表于2017-09-13 09:02 被阅读32次

    一.对象

    1.对象如何复制?

    ①方法一:巧妙地使用JSON

    function test(){
        return 'yyc';
    }
    var obj1 = {
        age: 21
    };
    var arr = ['G','e','r','g'];
    var anotherArray = [];
    var obj = {
        a: 2,
        b: obj1,
        c: arr,
        d: test
    };
    JSON.parse(JSON.stringify(obj));
    >>>{a: 2, b: {…}, c: Array(4)}
    

    ②方法二:ES6新定义了一种方法来实现浅复制,它的名字叫Object.assign()方法

    var newObj = Object.assign({},obj);
    newObj;
    >>>{a: 2, b: {…}, c: Array(4), d: ƒ}
    

    ③浅复制是什么?

    浅复制是对对象地址的复制。通过上面的Object.assign()方法的返回对象可知,复制得到的新对象中a的值会直接复制obj对象中相应的值,也就是2,但是新对象中bcd三个属性其实只是三个引用,它们和obj对象中bcd引用的对象是一样的。

    ④既然有浅复制,那应该也有深复制把?它是什么?

    深复制是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

    json100.png age21.png
    2.属性描述符

    Object.getOwnPropertyDescriptor()

    descriptor.png

    var person = {
        age: 21
    };
    Object.defineProperty(person,'age',{
        configurable: false,
        writable: true,
        enumerable: true,
        value: 100
    });
    person.age;
    >>>100
    

    configurableWritable.png
    var person = {
        age: 21
    };
    Object.defineProperty(person,'age',{
        configurable: false,
        writable: true,
        enumerable: true,
        value: 100
    });
    person.age = 66;
    person.age;
    >>>66
    
    Object.defineProperty(person,'age',{
        configurable: true,
        writable: true,
        enumerable: true,
        value: 100
    });
    >>>Uncaught TypeError: Cannot redefine property: age
    
    • configurable特性修改成false是单向操作,无法撤销!

    ④一个细节:即使属性是configurable: false,我们还是可以把writable的状态由true改为false,但无法由false改为true

    var person = {
        age: 21
    };
    Object.defineProperty(person,'age',{
        configurable: false,
        writable: true,
        enumerable: true,
        value: 100
    });
    person.age;
    >>>100
    
    Object.defineProperty(person,'age',{
        configurable: false,
        writable: false,
        enumerable: true,
        value: 100
    });
    person.age;
    >>>100
    
    person.age = 66;
    person.age;
    >>>100
    
    var person = {
        age: 21
    };
    Object.defineProperty(person,'age',{
        configurable: false,
        writable: false,
        enumerable: true,
        value: 100
    });
    person.age;
    >>>100
    
    Object.defineProperty(person,'age',{
        configurable: false,
        writable: true,
        enumerable: true,
        value: 100
    });
    person.age;
    >>>Uncaught TypeError: Cannot redefine property: age
    
    writableTrueFalse.png

    ⑤除了无法更改配置,configurable: false还会禁止删除delete这个属性。

    function run(){
        return 'l like running';
    }
    var person = {
        age: 21,
        sport: run
    };
    person.sport();
    >>>"l like running"
    
    Object.defineProperty(person,'sport',{
        configurable: false,
        enumerable: true,
        writable: true
    });
    delete person.sport;
    person.sport();
    >>>"l like running"
    
    • delete语句静默失败(silently failed)了,因为属性是不可配置的,连删都不能删

    • delete语句只用来删除对象的(可删除)属性。如果对象的某个属性是某个对象或函数的最后一个引用者,对这个属性进行delete操作后,这个未引用的对象或函数就可以被垃圾回收了。但是,delete只是一个删除对象属性的操作,仅此而已,并不是一个释放内存的工具。

    3.不变性

    ①有时候你会希望属性或对象是不可改变的,在ES5中有很多方法可以做到这一点。但是这些方法创建的都是浅不变性

    ②什么是浅不变性?

    • 它们只会影响目标对象和它的直接属性。如果目标对象还引用了其他对象(数组、对象、函数等),其他对象的内容并不受到影响,仍是可变的。

    ③方法一:对象常量

    var person = {};
    Object.defineProperty(person,'favorite_color',{
        value: 'green',
        configurable: false,
        writable: false
    });
    

    ④方法二:禁止扩展

    如果你想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions()

    function run(){
        return 'l like running';
    }
    var person = {
        sport: run,
        age: 21
    };
    Object.preventExtensions(person);
    person.name = 'Gerg';
    person.name;
    >>>undefined
    
    密封冻结.png

    ⑤密封:Object.seal()

    • 密封之后不能添加新属性,也不能重新配置或删除任何现有属性,不过可以修改现有属性的值。
    //不能添加新属性
    function run(){
        return 'l like running';
    }
    var person = {
        sport: run,
        age: 21
    };
    Object.seal(person);
    person.name = 'Gerg';
    person.name;
    >>>undefined
    
    //不能重新配置现有属性
    function run(){
        return 'l like running';
    }
    var person = {
        sport: run,
        age: 21
    };
    Object.seal(person);
    Object.defineProperty(person,'age',{
        configurable: true
    });
    Object.defineProperty(person,'age',{
        configurable: false
    });
    >>>Uncaught TypeError: Cannot redefine property: age
    
    //不能删除任何现有属性
    function run(){
        return 'l like running';
    }
    var person = {
        sport: run,
        age: 21
    };
    Object.seal(person);
    delete person.age;
    >>>false
    
    person.age;
    >>>21
    
    //不过还能修改现有属性的值喔
    function run(){
        return 'l like running';
    }
    var person = {
        sport: run,
        age: 21
    };
    Object.seal(person);
    person.age = 100;
    person.age;
    >>>100
    

    ⑥冻结Object.freeze()

    • 完犊子,这回连修改现有属性都不行了!
    function run(){
        return 'l like running';
    }
    var person = {
        sport: run,
        age: 21
    };
    Object.freeze(person);
    person.age = 100;
    person.age;
    >>>21
    
    4.[[Get]]
    var person = {
        name: 'Gerg'
    };
    person.name;
    >>>"Gerg"
    

    person.name是一次属性访问,在person对象中实际是使用了[[Get]]操作。

    ②对象默认的内置[[Get]]操作首先在对象中查找是否拥有同名的属性,如果找到就返回该属性的值。

    ③如果没有找到同名的属性,按照[[Get]]算法的定义会执行另一种非常重要的行为:遍历可能存在的[[Prototype]]链,也就是原型链。

    ④最后如果在原型链上也没找到同名属性,那么[[Get]]]操作会返回undefined

    ⑤细节:访问属性和访问变量是不同滴!(前提:当前词法作用域中不存在的属性和变量)。

    //访问属性
    var person = {
        name: 'Gerg'
    };
    person.city;
    >>>undefined
    
    //访问变量
    z;
    >>>Uncaught ReferenceError: z is not defined
    
    5.[[Put]]

    ①既然有获取属性值的[[Get]],那么也应该有对属性值的设置呀,也就是[[Put]]

    ②如果已经存在了这个属性,[[Put]]算法大致会检查以下内容:

    • 属性是否是访问描述符(请听后文分解)? 如果是并且存在setter就调用setter
    • 属性的属性(数据)描述符中writable是否为false?如果是,在非严格模式下静默失败(silently failed);在严格模式下抛出TypeError异常。
    • 如果都不是,将该值设置为属性的值。
    6.GetterSetter

    ①对象默认的[[Put]][[Get]]操作分别可以控制属性值的设置和获取。

    getter是什么?

    • getter是一个隐藏函数,会在获取属性值时调用。

    ③那setter又是什么?

    • setter也是一个隐藏函数,会在设置属性值时调用。

    ④那这两个玩意有什么用?

    • gettersetter可以部分改写默认操作,但只能应用在单个属性上,无法应用在整个对象上。

    ⑤前面提到的访问描述符是什么东西?

    • 当你为一个属性定义gettersetter或两者兼有时,这个属性就被定义为"访问描述符"。
    属性描述符访问描述符.png

    var person = {
        get name(){
            return 'Gerg';
        }
    };
    person.name;
    >>>"Gerg"
    
    Object.defineProperty(person,'sayHi',{
        get: function(){
            return 'Hi ' + this.name;
        }
    });
    person.sayHi;
    >>>"Hi Gerg"
    
    • 不管是对面字面量中的get name() {...},还是defineProperty(...)中的显式定义,二者都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当做属性访问的返回值。

    ⑦若只定义了属性的getter,则会忽略对该属性的赋值操作。

    var person = {
        get name(){
            return 'Gerg';
        }
    };
    person.name = 'yyc';
    person.name;
    >>>"Gerg"
    

    ⑧getter和setter一起使用

    var obj = {
        get a(){
            return this._a_;
        },
        set a(val){
            this._a_ = val * 2;
        }
    };
    obj.a = 2;
    obj.a;
    >>>4
    
    7.存在性

    ①如何区分属性值为undefined,还是属性压根就不存在?

    var person = {
        name: undefined
    };
    person.name;
    >>>undefined
    
    person.age;
    >>>undefined
    

    ②方法一:in操作符

    'name' in person;
    >>>true
    
    'age' in person;
    >>>false
    

    ③方法二:hasOwnProperty()方法

    person.hasOwnProperty('name');
    >>>true
    
    person.hasOwnProperty('age');
    >>>false
    

    ④方法三:Object.keys()

    Object.keys(person);
    >>>["name"]
    

    ⑤方法四:Object.getOwnPropertyNames()

    Object.getOwnPropertyNames(person);
    >>>["name"]
    

    ⑥细节又来了!

    • in操作符实际上检查的是某个属性名是否存在。对于数组来说这个区别非常重要。
    var arr = [2,4,6];
    4 in arr;
    >>>false
    
    for(var index in arr){
        console.log(index);
    }
    >>>
    0
    1
    2
    

    ⑦枚举详解

    • 方法一:for...in循环
    var person = {};
    Object.defineProperty(person,'name',{
        enumerable: true,
        value: 'Gerg'
    });
    Object.defineProperty(person,'age',{
        enumerable: false,
        value: 21
    });
    person.age;
    >>>21
    
    'age' in person;
    >>>true
    
    person.hasOwnProperty('age');
    >>>true
    
    for(var i in person){
        console.log(i,person[i]);
    }
    >>>name Gerg
    
    • 可见"可枚举"就相当于"可以出现在对象属性的遍历中"。其他一切正常,该属性确实存在,并且有访问值。

    • 细节!

    在数组上应用for...in循环有时会产生出人意料的结果,因为这种枚举不仅会包含所有数值索引,还会包含所有可枚举属性。因此,最好只在对象上引用for...in循环,使用传统的for循环遍历数组。

    方法二:propertyIsEnumerable()方法

    var person = {};
    Object.defineProperty(person,'name',{
        enumerable: true,
        value: 'Gerg'
    });
    Object.defineProperty(person,'age',{
        enumerable: false,
        value: 21
    });
    person.propertyIsEnumerable('name');
    >>>true
    
    person.propertyIsEnumerable('age');
    >>>false
    
    Object.keys(person);
    >>>["name"]
    
    Object.getOwnPropertyNames(person);
    >>>(2) ["name", "age"]
    
    • propertyIsEnumerable()方法会检查给定的属性名是否直接存在于对象中(而不是原型链上)。

    • Object.keys()Object.getOwnPropertyNames()区别?

    ObjectKeys.png
    • in操作符和hasOwnProperty()方法的区别在于是否查找 [[Prototype]]链。

    相关文章

      网友评论

        本文标题:复制对象 | 属性描述符 | 不变性 | 存在性

        本文链接:https://www.haomeiwen.com/subject/nrcksxtx.html