2026-10-25 21:44:07
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()可以使一个对象不可再添加新的属性,参数为目标对象,返回修改后的对象。
Object.isExtensible()可以判断一个对象是否可扩展,即是否可以添加新的属性。参数是目标对象,返回布尔值,true代表可扩展,false不可扩展。
2.Object.seal()
Object.seal()可以使一个对象无法添加新属性的同时也无法删除旧属性,参数是目标对象,返回修改后的对象。
其本质是通过修改对象的configurable(可设置)为false来实现的。在属性描述对象里讲到,configurable为false时,其他配置不可改变,writable只能true变false,且属性无法被删除。只要writable或configurable其中之一为true时,value是可变的,所以密封之后的对象还是可以改属性值的。
对应的Object.isSealed()可以检测一个对象是否密封,参数是目标对象,返回布尔值,true代表被密封,不可增删属性,false代表没被密封可增删属性。
Object.freeze()可以使一个对象不能再添加新属性,也不可以删除旧属性,且不能修改属性的值。参数是目标对象,返回修改后的对象。
对应的Object.isFrozen()可以检测一个对象是否冻结,即是否可以增删改。参数是目标对象,返回布尔值,true表示已经冻结不可再增删改,false表示未冻结。
无论是不可扩展,密封,还是冻结,都是浅层控制的,即只对对象本身属性的增删改。如果对象属性是一个引用类型,比如数组subArr或对象subObj等,虽然subArr、subObj的不可被删改,但他们的属性可以增删改。
由于每个对象都有一个 proto 该属性的值是该对象的原型对象,也是引用类型,由于冻结是浅层的,所以原型对象并不会被连着冻结,仍然可以通过给对象的原型对象加属性达到给当前对象新增属性的效果。所以如果想进一步冻结还需要把原型对象也冻结上。
object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
语法
参数
obj 要在其上定义属性的对象
prop 要定义或修改的属性的名称
descriptor 将被定义或修改的属性描述符
返回值
被传递给函数的对象
该方法允许添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举间呈现出来(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。当属性值修改时,触发执行该方法。该方法将接收唯一参数,即该属性新的参数值
描述符可同时具有的键值
浏览器兼容问题
数组的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 对象上使用 。 需要注意的一些事情: