复制对象
复制对象是平时比较常遇到的需求之一
可惜的是,这里并没有什么一个内置的copy函数能够简单的完成这一步
事实上JavaScrip中复制一个对象比我们想象的要复杂的多
复制一个对象首先要决定复制方式是深复制还是浅复制
对于浅复制来说,复制出来的新对象的属性的值会直接复制旧对象中的值
但是由于对象中的方法并不是对象的值,对象存储的只是对这个方法函数的引用而已
浅复制出来的对象中的方法其实跟旧对象中的方法是一样的,都是指向同一个函数
而深复制,不仅要复制属性值,还要复制属性引用的方法,那么如果数个方法中出现了引用嵌套呢?
那么又要继续复制引用的函数,这样就会由于出现循环引用而导致死循环
对于JSON安全的对象来说有个巧妙的方法
(JSON安全是指可以被序列化为一个JSON对象并且可以根据这个字符串解析出一个结构和值完全一样的对象)
var newObj = JSON.parse(JSON.stringify(oldObj));
ES6中的对象复制
相比深复制,浅复制问题要少很多,所以ES6定义了Object.assign(..)方法来实现浅复制
Object.assign()方法接受的第一个对象是目标对象,之后跟一个或多个源对象
它会遍历所有源对象的所有可枚举(接下来会讲到)的属性,并把它们复制到目标对象然后返回这个目标对象
var oldObj1 = {
a: 1,
foo: foo
}
var oldObj2 = {
b: 2
}
function foo() {
console.log(this.a, this.b);
}
var newObj = Object.assign({}, oldObj1, oldObj2);
newObj.foo(); //1,2
属性描述符
ES5开始,所有的属性都具备了属性描述符(用来描述属性特性,比如是否可写,是否可枚举等)
var obj={
a:1
}
console.log(
Object.getOwnPropertyDescriptor(obj,"a")
)
//{
// value: 1, 属性的值
// writable: true, 是否可写
// enumerable: true, 是否可枚举
// configurable: true 是否可修改属性配置符
// }
创建属性的时候,属性描述符会使用默认值
我们也可以自己设置或者修改一个属性的属性描述符
var obj={
a:1
}
Object.defineProperty(obj,"a",{
value: 2,
writable: true,
enumerable: true,
configurable: true
})
console.log(
Object.getOwnPropertyDescriptor(obj,"a")
)
//{
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }
-
Writable 决定是否可以修改属性的值,在严格模式下修改一个writable为false的属性还会出现TypeError错误
-
Configurable 决定是否可以修改属性描述符,不管是不是严格模式都会出现一个TypeError错误,而且如果设定configurable为false,这将是一个单向操作,无法被撤销(不过有一个例外,即便设定了false,我们还是可以将writable的状态由true改为false,但是无法由false改为true)除了无法修改,状态为false时甚至无法删除这个属性
-
Enumerable 决定这个属性是否可枚举 这个描述符控制的是属性是否会出现在对象的属性枚举中,比如for..in循环,你可以正常的访问到这个属性,但是无法被for..in遍历到(因此数组使用for..in时要小心,因为for..in不仅会包含所有数值索引,还会包含所有的可枚举属性)
属性描述符的应用
不变性
有的时候你可能会希望你你所创建的某个属性或者对象是不变的
这时候需要引入一个浅不变性和深不变性的概念
ES5中有很多方法来实现不变性,但是这些方法都是浅不变性的,也就是只会更改对象的直接属性
对象的引用属性,比如存储了对其他对象,函数,数组的引用,这些对象是不受影响的
1.对象常量
结合writable:false和configurable:false就可以创建一个真正的常量属性(不可修改,重定义或删除),修改方法见上
2.禁止拓展
Object.preventExtensions(..)可以禁止一个对象添加新属性,但是目前已有的属性不受影响,在严格模式下强行添加会出现TypeError错误
3.密封
Object.seal(..)会创建一个密封的对象,实际上等同于调用了一个preventExtensions(..)并把所有属性标记为configurable:false
因此密封之后的对象不能添加新属性也不能删除或重新配置,但是可以修改
4.冻结
注意,这是你能引用在对象上最高级别的不可变性
Object.freeze(..)会创建一个冻结对象,实际上等同于调用了preventExtensions(..)并把所有属性标记为configurable:false和writable:false(不过引用的对象依然不受影响)
你可以深度冻结一个对象,也就是把他调用的所有对象都一起冻结了,但是这样可能会出现一些问题,比如冻结了一些共享对象之类的