可变对象和不可变对象

一、可变对象

js有7种基本数据类型:number、string、boolean、null、undefine、object、symbol(ES6新增),除了object为引用类型,其他均为基本类型
基本类型将值存在栈中

引用类型将地址存在栈中,值存在栈中地址指向的堆中
因此,我们如果copy基本类型,拷贝的是它的值,如果再单独赋值也不会影响被拷贝的值。如果我们copy一个引用类型,其实拷贝的是它在堆中的地址,如果改变其中一方的值,就是改变了堆中的值,另一方因为指向同一个堆,所以它的值也被同步修改了。
所以对于引用类型(array、function、obj等)在拷贝时为了使两个引用类型互不影响,一般我们不直接将旧的array赋值给新的array,而是生成新的引用类型变量,将旧的数组中的值一个一个的分别拷贝到新的array中,这就是所谓的深拷贝,但是如果数组中的值又是引用类型的话,还要把这个引用类型中的值再分别取出付给又新生成的引用对象中,如果嵌套层次太多的话,处理起来就容易出错。

二、不可变对象

用来引用类型深拷贝问题,有弱到强主要分为三种:
Object.preventExtensions(不可扩展)、Object.seal(密封)、Object.freeze(冻结)
1.Object.preventExtensions()

Object.preventExtensions()可以使一个对象不可再添加新的属性,参数为目标对象,返回修改后的对象。

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不可扩展。

var obj=new object();
object.isExtensible(obj);//true
object.preventExtensions(obj)
object.isExtensible(obj);//false

2.Object.seal()
Object.seal()可以使一个对象无法添加新属性的同时也无法删除旧属性,参数是目标对象,返回修改后的对象。
其本质是通过修改对象的configurable(可设置)为false来实现的。在属性描述对象里讲到,configurable为false时,其他配置不可改变,writable只能true变false,且属性无法被删除。只要writable或configurable其中之一为true时,value是可变的,所以密封之后的对象还是可以改属性值的。

var obj={content:'hello'};
object.getOwnPropertyDescriptor(obj,'content');
//obj{
//configurable: true
//enumerable: true
//value: "hello"
//writable: true
//}
object.seal(obj);
object.getOwnPropertyDescriptor(obj,'content');//seal后configurable变为false
//obj{
//configurable: false
//enumerable: true
//value: "hello"
//writable: true
//}

对应的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。当属性值修改时,触发执行该方法。该方法将接收唯一参数,即该属性新的参数值
描述符可同时具有的键值

image.png

如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
记住,这些选项不一定是自身属性,如果是继承来的也要考虑。为了确认保留这些默认值,你可能要在这之前冻结 Object.prototype,明确指定所有的选项,或者通过 Object.create(null)__proto__属性指向null

浏览器兼容问题
数组的length属性重定义是可能的,但会受到一般的重定义限制。(length属性初始值为non-configurable,non-enumerable 以及 writable。对于内容不变的数组改变其length属性的值或者改变它为non-writable是可能的。但是改变其可枚举性和可配置性或者当它是non-writable时尝试改变它的值或是可写性,这两者是不允许的。)然而并不是所有的浏览器都允许Array.length的重定义。
在 Firefox 4 至 22 版本中尝试去重定义数组的 length 属性都会抛出一个 TypeError 异常。
有些版本的Chrome中,Object.defineProperty() 在某些情况下会忽略不同于数组当前length属性的length值。有些情况下改变可写性并不起作用(也不抛出异常)。同时,比如Array.prototype.push的一些数组操作方法也不会考虑不可读的length属性。

有些版本的Safari中,Object.defineProperty() 在某些情况下会忽略不同于数组当前length属性的length值。尝试改变可写性的操作会正常执行而不抛出错误,但事实上并未改变属性的可写性。

只在Internet Explorer 9及以后版本和Firefox 23及以后版本中,才完整地正确地支持数组length属性的重新定义。目前不要依赖于重定义数组length 属性能够起作用,或在特定情形下起作用。与此同时,即使你能够依赖于它,你也没有合适的理由这样做

Internet Explorer 8具体案例
Internet Explorer 8 实现了 Object.defineProperty() 方法,但 只能在 DOM 对象上使用。 需要注意的一些事情:

  • 尝试在原生对象上使用 Object.defineProperty()会报错。
  • 属性特性必须设置一些特定的值。对于数据属性描述符,configurable, enumerablewritable特性必须全部设置为 true;对于访问器属性描述符,configurable必须设置为 trueenumerable必须设置为 false。(?) 任何试图提供其他值(?)将导致一个错误抛出。
  • 重新配置一个属性首先需要删除该属性。如果属性没有删除,就如同重新配置前的尝试。
    链接:github
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容