对象
写在前面
在上一篇笔记中,我们介绍了不同的调用位置this会绑定不同的对象,那么,什么是对象呢?
在这里我们介绍一下JS的对象是个什么
创建一个对象
// 字面量创建方式
var obj1 = {
key : value ,
...
}
//构造函数创建
var obj2 = new Object();
obj2.key = value;
这两种方式创建的对象基本一样,他的不同点是字面量声明可以一次声明多个健值对,但是构造方式每次质嫩个设置一个。
value可以是任意js支持的类型,关于类型的介绍在这篇文章里边哦。
存取对象的值
obj1[key]
obj1.key
我们可以通过以上两种方式获取对象的值,他们基本上一样,但是在细节上还是有一定的区别。
采用obj.key的方式获取属性值时,必须时合法的标识符,比如obj.a-123!! ,由于a-123!!不是一个合法的标识符,因此需要采用key[‘a-123!!’]来获取属性的值。
在方括号获取值时,这里的key永远会转成一个字符串来使用。此外采用括号的形式还可以用表达式的方式来动态计算属性名。例如:obj[name+‘str’]
对象属性的Get和Put
在获取对象的属性值时,其实是调用了底层的get方法,获取对象的属性,如果找不到,就会沿着prototype继续查找,如果找到了顶层依然没有找到,那么get的值会返回undefined。
这种规则与获取一个变量有细微的区别,如果变量找到顶层依然没有找到,会会抛出一个reference异常。
如果属性值本身被赋值为了undefined,怎么区分呢?参考本文最后一节,属性的存在性~
再来说说put。也就是我们给对象的属性设置一个值时,不仅仅是一个setter这么简单。他的底层还会判断是否是访问描述符、他的writable属性是否是可写。如果是否,要判断是否是严格模式再进一步判断,最后才考虑修改属性的值。
数组存取的小细节
数组作为对象的一种,但是他的存取与普通对象的存取还是有一些差别。
差别一:尽管我们可以把数组当成普通的对象去使用,但是如果数组的键不是数字,他的length不会增加。
var arr = ['a','b','c'];
arr.d = 'd'
arr.length; // 3
arr.d // d
差别二:如果我们设置值时的键没有使用数字,数组底层会优先转成数字进行存储,这个时候length就会发生变化~
var arr = ['a','b','c'];
arr['3'] = 'd'
arr.length; // 4
arr[3] // d
复制对象
由于对象的底层是指针,他是存在栈中的地址指向了真正的对象。
根据对象的复制的程度,也就是你究竟想要的是他的值还是他的地址,可以将对象的复制分为深复制和浅复制,也称为深拷贝和浅拷贝。
当然这样表述是不完全准确的。
属性描述符
属性描述符包括:值、可写、可枚举、可配置~
- value:值
- writable:决定是否可以修改value,如果为false,则表示不可以修改属性的值,强行修改的话,普通模式修改失败,严格模式,会报错TypeError
- configurable:可配置的,即是否可以修改属性描述符。defineProperty(…)可以修改属性的这几种配置项。当然你要注意,configurable一旦配置成false,就无法修改属性描述,也不能删除当前属性了~当你要修改时,无论什么模式都会报错TypeError。有一个小小的例外,即便configurable配置成false,也可以将writable的true改成false。
- enumerable:控制属性是否会出现在对象的属性枚举中。
对象的不变性
有时候我们需要一个对象的属性值是不可变的,就算是属性值为对象的引用,也要控制他的不变性。
这个时候我们可以参考以下的方法:
对象常量控制不变性
结合writable和configurable就可以创建一个真正的常量属性,这意味着它不可以被更改、重定义、删除。
var myobj = {};
Object.defineProperty(myobj,'MAX_COUNT',{
value:10,
writable:false,
configurable:false
})
通过禁止扩展创建常量属性
这种方式可以禁止一个对象添加新属性,保留之前的属性。
var myobj = {a:2};
Object.preventExtensions(myobj)
myobj.b = 3
myobj.b //undefined
非严格模式:静默失败;严格模式:TypeError
通过密封创建常量属性
var myobj = {a:2};
Object.seal(myobj)//密封对象
//等价于下面的代码 先禁止扩展后设置可配置属性为false
Object.preventExtensions(myobj)
Object.defineProperty(myobj,'a',{
configurable:false
})
这种方式不能添加新属性,不能重新配置或者删除现有的属性。
这种方式可以修改属性的值~
冻结属性值
var myobj = {a:2};
Object.freeze(myobj)//冻结对象
//等价于下面的代码 先冻结后设置可写属性为false
Object.seal(myobj)
Object.defineProperty(myobj,'a',{
writable:false
})
//也等价于下面的代码,先禁止扩展后设置可配置和可写属性为false
Object.preventExtensions(myobj)
Object.defineProperty(myobj,'a',{
configurable:false,
writable:false
})
如果你想深度冻结,就需要遍历咯~
属性的存在性
考虑一个这样的问题。如果一个对象的属性值被我们手动赋值成undefined,当通过属性访问时,就是undefined,但是如果一个属性值从未被定义和赋值。访问时依然是undefined,这两个要怎么区分呢?
这个时候我们往往用key in 来判断,这个时候如果自有属性没有的话就要去原型链上找。
如果我们不希望去原型链上找,可以采用obj.hasOwnProperty(key)来判断。
这两种方式都可以。
// 这里是一些注意点
4 in [2,3,4] //false
var obj = Object.create(null);
obj.hasOwnProperty(key) //报错
Object.prototype.hasOwnProperty.call(obj,key) //正确
这里的in操作符要与for in 属性便利区分开来,in 操作符是判断存在性, 而for in 是判断枚举性。
枚举
关于枚举的几个方法:
obj.propertyIsEnumerable(key) //判断一个属性是否可枚举且直接存在对象中,而不是原型链
Object.keys(obj) //返回一个可枚举的属性数组
Object.getOwnPropertyNames() //返回所有的属性值,不论是否可以枚举