美文网首页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]]链。

相关文章

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

    一.对象 1.对象如何复制? ①方法一:巧妙地使用JSON ②方法二:ES6新定义了一种方法来实现浅复制,它的名字...

  • 你不知道的JavaScript(上卷):第三章:对象

    一:这一章到底在说什么? 详细介绍对象。 语法 类型 内容可计算属性属性和方法数组复制对象属性描述符不变性[Get...

  • js 对象属性描述符

    ECMAScript对象中⽬前存在的属性描述符主要有两种,数据描述符(数据属性)和存取描述符(访问器属性),数据描...

  • 如何让对象属性不可配置或枚举

    一、什么是属性描述符? MDN: 对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。 数据描述符是...

  • 如何让对象属性不可配置或枚举

    一、什么是属性描述符? MDN: 对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。 数据描述符是...

  • 2019-09-08 理解 数据描述符和存取描述符

    对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。 我们可以理解 属性描述符 为用来描述一个属性的...

  • js对象的属性探究

    js对象的属性有很多种,总结这一篇文章是为将来理解对象复制和对象继承打基础。 数据描述符 1. 可枚举性(Enum...

  • 那些年成为node攻城狮的路(六)

    对象 属性 数据属性存在四个描述符:configurable(是否可以通过delete删除)、emunerable...

  • defineProperty笔记

    【摘自MDN】对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值...

  • 定义一个变量

    属性描述符(Property Descriptors) 我们普通的对象属性a的属性描述符(称为“数据描述符”,因为...

网友评论

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

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