一、可变对象
js有7种基本数据类型:number、string、boolean、null、undefine、object、symbol(ES6新增),除了object为引用类型,其他均为基本类型
基本类型将值存在栈中
引用类型将地址存在栈中,值存在栈中地址指向的堆中
因此,我们如果copy基本类型,拷贝的是它的值,如果再单独赋值也不会影响被拷贝的值。如果我们copy一个引用类型,其实拷贝的是它在堆中的地址,如果改变其中一方的值,就是改变了堆中的值,另一方因为指向同一个堆,所以它的值也被同步修改了。
所以对于引用类型(array、function、obj等)在拷贝时为了使两个引用类型互不影响,一般我们不直接将旧的array赋值给新的array,而是生成新的引用类型变量,将旧的数组中的值一个一个的分别拷贝到新的array中,这就是所谓的深拷贝,但是如果数组中的值又是引用类型的话,还要把这个引用类型中的值再分别取出付给又新生成的引用对象中,如果嵌套层次太多的话,处理起来就容易出错。
二、不可变对象
var obj=object.preventExtensions({}); //直接定义新的属性会报错 object.defineProperty(obj,'content',{ value:'hello' });//TypeError: Cannot define property:p, object is not extensible. //非严格模式下通过点符号添加不会报错,但会静默失败,原对象仍然没有content属性 object.content='Hello'; object.content://undefine
Object.isExtensible()可以判断一个对象是否可扩展,即是否可以添加新的属性。参数是目标对象,返回布尔值,true代表可扩展,false不可扩展。
2.Object.seal()
Object.seal()可以使一个对象无法添加新属性的同时也无法删除旧属性,参数是目标对象,返回修改后的对象。
其本质是通过修改对象的configurable(可设置)为false来实现的。在属性描述对象里讲到,configurable为false时,其他配置不可改变,writable只能true变false,且属性无法被删除。只要writable或configurable其中之一为true时,value是可变的,所以密封之后的对象还是可以改属性值的。
1 var obj={content:'hello'}; 2 object.getOwnPropertyDescriptor(obj,'content'); 3 //obj{ 4 //configurable: true 5 //enumerable: true 6 //value: "hello" 7 //writable: true 8 //} 9 object.seal(obj); 10 object.getOwnPropertyDescriptor(obj,'content');//seal后configurable变为false 11 //obj{ 12 //configurable: false 13 //enumerable: true 14 //value: "hello" 15 //writable: true 16 //}
对应的Object.isSealed()可以检测一个对象是否密封,参数是目标对象,返回布尔值,true代表被密封,不可增删属性,false代表没被密封可增删属性。
var obj=new object(); object.isExtensible(obj);//true object.isSealed(obj)//false object.seal(obj); object.isExtensible(obj)//false,seal后对象isExtensible()也随之改变 object.isSealed(obj);//true
3.Object.freeze()
Object.freeze()可以使一个对象不能再添加新属性,也不可以删除旧属性,且不能修改属性的值。参数是目标对象,返回修改后的对象。
var obj=object.freeze({name:'example'}); //直接定义新的属性会报错 object.defineProperty(obj,'content',{ value:'hello' })//TypeError:cannot define property:p,object is not extensible. //非严格模式下通过点符号添加不会报错,但会静默失败,原对象仍然没有content属性 obj.content='hello'; obj.content;//undefined; delect obj.name;//删除失败,返回false obj.name='hello'; obj.name;//仍然是‘example’
对应的Object.isFrozen()可以检测一个对象是否冻结,即是否可以增删改。参数是目标对象,返回布尔值,true表示已经冻结不可再增删改,false表示未冻结。
var obj=new object() object.isExtensible(obj);//true object.isSealed(obj);//false object.isFrozen(obj);//false object.freeze(obj); object.isExtensible(obj);//false,freeze后对象的isExtensible()也随之改变 object.isSealed(obj);//true,freeze后对象的isSealed()也随之改变 object.isFrozen(obj);//true
注意
无论是不可扩展,密封,还是冻结,都是浅层控制的,即只对对象本身属性的增删改。如果对象属性是一个引用类型,比如数组subArr或对象subObj等,虽然subArr、subObj的不可被删改,但他们的属性可以增删改。
var obj=object.freeze({ content:{name:'example'} }); obj.content=new object(); obj.content;//{name:'example'},content本身不可修改 obj.content.name='test'; obj.content;//{name:'test'}但content的属性仍可改,因为冻结的是obj不是它的属性
由于每个对象都有一个_proto_该属性的值是该对象的原型对象,也是引用类型,由于冻结是浅层的,所以原型对象并不会被连着冻结,仍然可以通过给对象的原型对象加属性达到给当前对象新增属性的效果。所以如果想进一步冻结还需要把原型对象也冻结上。
var obj=object.freeze({}); obj.content='hello'; obj.content;//undefine,增加失败 var proto=object.getPrototypeOf(obj); proto.content='hello'//obj的圆形属性为hello obj.content;//obj本身没有content这个属性的话,向它的原型找这个属性,‘hello’增加成功 object.freeze(proto); proto.name="example"; obj.name//undefine,冻结原型之后增加失败
object.defineProperty()
object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
语法 object.definePoperty(obj,prop,descriptor)
参数
obj 要在其上定义属性的对象
prop 要定义或修改的属性的名称
descriptor 将被定义或修改的属性描述符
返回值
被传递给函数的对象
在ES6中,由于Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而object.defineProperty是定义key为Symbol的属性的方法之一。
描述
该方法允许添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举间呈现出来(for...in 或 object.keys方法),这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。
属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
数据描述符和存取描述符**均具有以下可选键值:
configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
数据描述符同时具有以下可选键值:
value
该属性对相应的值。可以是有效的JavaScript值(数值、对象、函数)默认undefine
writable
当且仅当属性的writable为true时,value才能被赋值运算符改变,默认为false
存取描述符同时具有以下可选键值:
get(默认为undefined)
一个属性提供getter方法,如果没有getter则为undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
set(默认为undefined)
一个属性提供setter的方法,如果没有setter则为undefined。当属性值修改时,触发执行该方法。该方法将接收唯一参数,即该属性新的参数值