在前面我们看到过关于函数调用的位置不同this所绑定的对象也不同,但是对象是何物呢?
-
语法
对象的定义可以通过声明形式和构造形式。
//对象字面量形式
var obj = {};
//构造形式
var obj = new Object();
obj.name = "bob";
构造形式和文字形式生成的对象是一样的。唯一的区别是,在字面量形式中我们可以任意添加多个属性,但是在构造形式中我们必须逐个添加属性。
-
类型
在JavaScript中主要有6中数据类型:
- string
- number
- boolean
- null
- undefined
- object
注意,简单的基本类型并不是对象(string,number,boolean,null,undefined)。null有时会被当做是一种对象类型。对null进行typeof null时会返回字符串"object"。实际上null的本身是基本类型。
-
内置对象
JavaScript中还有一些对象的子类型,通常被称为内置对象。
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Math
- Error
这些内置对象可以理解像是其他语言中的type或者class。但是在JavaScript中,他们只是一些内置函数,这些内置函数可以当做构造函数来使用。
-
内容
对象的内容是由一些存储在特定命名位置的,任意类型的值组成的,我们称之为属性。
在引擎内部,对象的值得存储方式是多样的,一般并不会存在对象容器内部。存储在对象容器内部的是这些属性的名称,它们类似于指针(引用)一样,指向这些值真正的存储位置。
访问对象属性的值有两种方式,“.”和“[]”,其访问并没有什么区别。
var obj = {a: 2};
obj.a; //称为属性访问
obj['a']; //称为键访问
-
数组
在数组中也支持[]访问,不过数组有一套更加结构化的值存储机制。数组期望的是数值下标,也就是说值存储的位置是非负整数。
数组也是对象,所以每个下标都是整数,你任然可以给数组添加属性。
var arr = [1, 2, 3];
arr.baz = 'baz';
arr.length; //3
arr.baz; //'baz'
可以看到给数组添加了属性,但是并未对数组的长度进行改变。单数如果你给数组添加一个“数字”属性,那么它会将整个“数字”属性转换为数组的下标。
var arr = [1, 2, 3];
aa['3'] = 4;
arr.length = 4;
arr[3]; // 4
-
属性描述符
在ES5之前,JavaScript没有提供直接检测属性特性的方法,但是在此之后,所有属性都具备了属性描述符。
var myObj = {
a: 2
}
myObj.getOwnPropertyDescriptor(myObj, 'a');
// {
// configurable: true,
// enumerable: true,
// writable: true,
// value: 2
// }
如上,该对象对应的属性描述符包含configurable,enumerable,writable,value四个特性。分别代表可配置,可枚举,可写,数据值。
在创建普通属性时属性描述符会使用默认值,我们还可以通过Object.defineProperty(...)来添加一个新属性或者修改一个已有的属性(如果它的configuable为true)。
var obj = {
}
Object.defineProperty(obj, 'a', {
configurable: true,
enumerable: true,
writable: true,
value: 4
});
obj.a; //4
一. Writable
writable决定是否可以修改属性的值。
var myObject = {
}
Object.defineProperty(myObject, 'a', {
value: 2,
writable: false, //不可写
enumerable: true,
configurable: true
});
myObject.a; //2
myObject.a = 4;
myObject.a; //2
如上,我们将writable设置为false,所以后来我们对该属性值的修改失败,如果在严格模式下,这种方法会出错。
二. Configurable
只要属性是可以配置的,就可以使用defineProperty(...)方法来修改属性描述符。
var myObj = {
a: 2
};
myObj.a = 3;
myObj.a; //3
Object.defineProperty(myObj, 'a', {
value: 4,
writable: true,
configurable: false, //不可配置
enumerable: true
});
myObj.a; //4
myObj.a = 5;
myObj.a; //5
myObj.defineProperty(myObj, 'a', {
value: 6,
writable: true,
configurable: true,
enumerable: true
}); //TypeError
如上操作,最后使用defineProperty(...)会产生一个报错,不管是否处于严格模式下,尝试修改一个不可配置的描述符属性都会出错。注意,将configurable设置为FALSE是一个单向操作。(configurable为false的情况下,我们还是可以将writable设置为false,但是不可以将writable从false设置为true。)
除了无法设置该属性,configuable为false的情况下,也是禁止删除掉该属性。
三. Enumerable
该属性是用来控制属性时否会出现在对象的属性枚举中,比如for-in循环,如果将enumerable设置为false,那么这个属性就不会出现在枚举当中。
四. [[GET]]
当访问某个对象的属性的时候,例如查看obj对象的a属性,看起来只是查找到名字为a的属性。其实是在obj上实现了[[Get]]操作。对象默认的内置[[Get]]操作首先会在对象中查找是否有名称相同的属性。如果找到则返回该值。
然而如果没有找到,按照[[Get]]的算法的定义,遍历可能存在的[[Prototype]]链,就是原型链。如果最终都没有找到则会返回undefined。
五. [[Put]]
[[Put]]和[[Get]]是相对应的,但是[[Put]]被触发时,会取决于多种因素,包括对象中是否已经存在该属性。
如果对象中已经存在该属性那么会进行如下操作:
属性是否是访问描述符,如果是并且存在setter就调用setter。
属性的描述符中writable是否为false。如果是则在非严格模式下失败,在严格模式下会提示异常。
如果都不是则将该值设置为属性的值。
-
Getter和Setter
对象默认的[[Put]]和[[Get]]操作分别可以控制属性值的设置和获取。
在ES5中可以通过getter和setter部分改写默认操作。getter是一个隐藏函数,会在获取属性值时调用,setter也是一个隐藏函数,会在设置属性值时调用。
var myobj = {
//给a定义一个getter
get a() {
return 2;
}
}
Object.defineProperty(myobj, 'b', {
get: function() {
return this.a*2
},
enumerable: true
});
myobj.a; //2
myobj.b; //4
不管是对象文字语法中的get a(){...}还是defineProperty(...)中显示的定义,两者都会在对象中创建一个不包含值得属性,对于这个属性的访问会自动调用一个隐藏函数,隐藏函数的返回值会被当做属性访问的返回值。
-
存在性
我们如何判断某个属性是否在该对象中?
var myobj = {
a: 2
};
('a' in myobj); //true
('b' in myobj); //false
myobj.hasOwnProperty('a'); //true
myobj.hasOwnProperty('b'); //false
如上,in操作符会检查属性是否在对象中以及该对象的[[Prototype]]原型链中,hasOwnProperty只会检查该属性是否在该对象中。
所有的普通对象都可以通过Object.prototype的委托来访问hasOwnProperty方法。但是例如Object.create(null)来创建的对象并不会委托到。
-
枚举
关于对象属性是否可枚举,这个关系到了对象属性的数据描述符中的enumerable属性。
var obj = {};
Object.defineProperty(obj, 'a', {
enumerable: true,
value: 2
});
Object.defineProperty(obj, 'b', {
enumerable: false,
value: 3
});
for( var i in obj){
console.log(i, obj[i])
} // 'a' 2
如上,属性b并没有出现在for-in循环中,所以可枚举可以理解为可以出现在对象属性的遍历中。
我们还可以通过propertyIsEnumerable(...)检查,该方法判断属性是否属于该对象而不是出现在原型链中,并且enumerable为true。
在ES6中Object.keys()会返回一个数组,包括该对象所有可以枚举的属性的数组。Object.getOwnPropertyNames(...)会返回一个数组,包括所有的属性,无论是否可以枚举。