本文探讨了Javascript对象的创建,以及对象的属性及其特性,同时还说明了属性的设置与屏蔽.
一、创建对象
javascript中有三种方法可以创建一个对象:
对象字面量
1
2
3
4var obj = {
name: 'jack',
age: 12
}new 构造函数
1
2
3
4
5var obj = new Object()
var obj1 = new Object({
name: 'jack',
age: 12
})Object.create()
1
2
3
4var obj = Object.create({
name: 'jack',
age: 12
})
需要注意的是通过Object.create()
创建的对象实际上等于将该对象的__proto__
指向Object.create()
里面的参数对象,而obj
本身是个空对象。
1 | var obj = Object.create({ |
如果往Object.create()
里面传入的是null
,则创建的对象不继承Object
的任何方法及属性。
1 | var obj = Object.create(null) |
如果想创建一个空对象,需要传入Object.prototype
1 | var obj = Object.create(Object.prototype) |
二、对象的属性
我们知道,对象的属性是由名字、值和一组特性组成(属性的特性待会介绍)。在ES5中属性值可以用一个或两个方法代替,这两个方法就是getter
和setter
。由getter
和setter
定义的属性称为“存储器属性”,它不同于数据类型的属性,数据属性只有一个简单的值。我们重点讲解存储器属性。
当我们查询存储器属性时会调用getter
方法(无参数)。这个方法返回值就是属性存取表达式返回的值。
当我们设置存储器属性时会调用setter
方法(有参数)。这个方法修改存储器属性的值。
1 | var obj = { |
存储器属性定义为一个或者两个和属性同名的函数,这个函数定义没有使用function
关键字而是使用get
和set
。
可以看出如果该属性只有getter
方法则只能读取该属性不能设置该属性,同样如果只有setter
方法就只能设置该属性,不能读取该属性,只有当两者都有时才能正常读取和设置属性。
三、对象属性的特性
每个对象的数据属性都有四个特性(也可以说是属性描述符),分别为:
value
属性的值writable
可写性,如果为false
该值将不能被修改enumerable
可枚举性,如果为false
将不能被枚举configurable
可配置性,如果为false
将不能被配置,即不能被delete
操作符删除,不能更改这四个特性,一旦设为false
则无法再设为true
,也就是一个不可逆过程
前面我们讲过存储器属性,每隔对象的存储器属性同样也有四个特性,分别为:
get
set
enumerable
configurable
如果想要获得一个对象某个属性的这四个特性,可以调用 Object.getOwnPropertyDescriptor() 方法,该方法接受两个参数,第一个为对象,第二个为对象的属性
1 | var obj = { |
从上面可以看出,Object.getOwnPropertyDescriptor()
只能得到自有属性的描述符,要想获得继承属性的特性,我们可以把该对象的原型传进去。
1 | function Person () { |
以上可以看出,我们通过对象字面量和new
运算符创建的对象的属性它们的writable
,enumerable
,configurable
都有true
,默认都是可写、可枚举、可配置。如果要修改属性的特性可以调用Object.defineProperty()
。
1 | var obj = { |
需要注意的是通过Object.defineProperty()
创建的属性其writable
, enumerable
, configurable
都为false
。尝试修改不写的属性不会报错,但也不会修改,只有在严格模式下才会报错。
如果需要同时修改和创建多个属性,可以使用Object.defineProperties()
。
1 | var obj = Object.defineProperties({},{ |
四、属性的设置和屏蔽
我们知道当我们书写以下代码时
obj.foo = 'bar'
如果obj
存在一个名为foo
的普通数据访问属性,这条赋值语句只会修改已有的属性值。
如果foo
不是直接存在于obj中,[[prototype]]
链就会被遍历,如果原型链上找不到foo
,foo
就直接被添加到obj
上。
然而,如果原型链上找到了foo
属性,情况就有些不一样了。
如果属性foo
既出现在obj
中也在其原型链中,那么obj
中包含的foo
属性就会屏蔽原型链里面的foo
属性,这就是属性屏蔽,原理就是属性的查找规则。
下面我们看一下如果foo
不直接存在于obj
中,而是在其原型链中时,obj.foo = 'bar'
会出现的三种情况:
- 如果原型链中存在名为
foo
的普通数据访问属性并且其writable
为true
,那么就会直接在obj
中添加foo
属性,它是属性屏蔽。 - 如果原型链中存在
foo
,但其writabla
为false
,那么无法修改已有属性或者在obj
中创建屏蔽属性。如果运行在严格模式下,会抛出一个错误。否则这条赋值语句会被忽略,不会发生属性屏蔽。 - 如果原型链上存在
foo
并且它是一个setter
,那就一定会调用这个setter
。foo
不会被添加到obj
中,也不会重新定义这个setter
。
大多数人认为,如果向原型链中已存在的属性赋值,就一定会发生属性屏蔽,但以上三种情况只有一种是如此。
如果希望在任何情况下都屏蔽foo
,那就不能使用=
操作符来赋值,而是使用Object.defineProperty()
来向obj
中添加foo
。
情况一:
1 | function Person() { } |
情况二:
1 | function Person() {} |
情况三:
1 | function Person() {} |
有些情况下会隐式产生屏蔽,一定要注意,思考一下代码:
1 | var obj = { |
尽管myObj.a ++
看起来是查找并增加obj.a
的属性,但是别忘了++
操作符相当于myObj.a = myObj.a + 1
;因此++
操作首先会通过原型链查找到obj.a
,并读取其值为2,然后加1赋值给myObj.a
。