对象
语法
对象可以通过两种形式定义:声明形式和构造形式。
//声明形式
var obj = {
key: value
/...
};
//构造形式,很少使用
var obj1 = new Object();
obj1.key = value;
类型
六种主要类型:
- string
- number
- boolean
- null
- undefined
- object
基本类型(string、boolean、number、null和undefined)本身不是对象。
null有时候会被当做一种对象类型,typeof null会返回“object”。但是实际上null本身是基本类型。
内置对象
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
它们 实际上只是一些内置函数,也称为对象子类型。这些内置对象可以当做构造函数来使用,从而构造一个对应子类型的新对象。
var strPrimitive = 'I am a string';
typeof strPrimitive;//"string"
strPrimitive instanceof String;//false
var strObject = new String("I am a string");
typeof strObject;//object
strObject instanceof String;//true
//检查sub-type对象
Object.prototype.toString.call(strObject);//[object String]
子类型在内部借用了Object中的toString()方法。
strObject是由String构造函数创建的一个对象 。
内容
对象的内容是由一些存储在特定命名位置的值组成的,称之为属性。
在引擎内部,值得存储方式是多样的,一般不会在对象容器里内部。存储在对象容器内部的是这些属性的名称。像指针一样指向真正的存储位置。
在对象中,属性名永远都是字符串。
复制对象
对于JSON安全(也就是说可以被序列化称为一个JSON字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种复制方法:
var newObj = JSON.parse(JSON.stringfy(someobj));
这种方法需要保证对象是JSON安全的,所以只适用于部分情况。
ES6定义了Object.assign()来实现浅复制。
var newObj = Object.assign({},myObject);
属性描述符
var myObject = {
a:2
};
Object.getOwnPropertyDescriptor(myObject, "a");
//{
// value:2,
// writable:true,
// enumberable:true,
// configurable:true
//}
可以使用Object.defineProperty()方法来添加一个新属性或者修改一个已有属性。
var myObject = {};
Object.defineProperty(myObject, "a" , {
value:2,
writable:true,
enumberable:true,
configurable:true
});
myObject.a;//2
一般来说不会使用这种方式来添加属性,除非想要修改属性描述符。
writable
writable决定是否可以修改属性的值。
configurable
只要属性是可配置的,就可以用defineProperty()方法来修改属性描述符。
把configurable设置成false是单向操作,无法撤销。
ps:configurable是false的时候也能修改writable的值,可以从true改成false,但是不能从false改成true。
configurable设为false的时候,会阻止delete操作。
Enumberable
这个属性描述符控制的是属性是否会出现在对象的属性枚举中。
不变性
所有的方法创建的都是浅不变性,它们只会影响目标对象和它的直接属性。如果目标对象引用了其他对象,其他对象的内容是不受影响的。
对象常量
将writable和configurable设置为false就可以创建一个真正的常量属性(不可修改、重定义或者删除)
禁止扩展
禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions():
var obj = { a:2 }; Object.preventExtensions(obj); obj.b = 3; obj.b;//undefined
密封
Object.seal()方法创建一个密封对象。
Object.seal() = Object.preventExtensions() + configurable:false
冻结
Object.freeze()方法创建一个冻结对象。
Object.freeze() = Object.seal() + writable:false
这个方法是对象上级别最高的不可变性。
[[Get]]和[[Put]]
属性在访问时有一个微妙重要的细节。
var obj = {
a: 2
}
obj.a;//2
obj.a是一次属性访问。
obj.a在obj上实际上实现了[[Get]]操作(有点像函数调用:[[ Get ]] ())。对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。没有找到返回undefined。
var obj = {
a:undefind
}
obj.a;//undefind
obj.b;//undefind
虽然都是undefined,但是[[Get]]操作的处理完全不一样。
那么相对应的[[Put]]操作怎么触发的?
[[Put]]被触发时,取决于很多因素,包括对象中是否已经存在这个属性(最重要因素)。
如果这个属性存在,[[Put]]算法会大致检查一下内容:
- 属性是否是访问描述符?如果是并且存在setter就调用setter。
- 属性的数据描述符中writable是否为false?是的话,在非严格模式下静默失败,在严格模式下抛出TypeError异常。
- 如果都不是的话,将该值设为属性的值。
如果对象中不存在这个属性,[[Put]]的操作更加复杂。
getter和setter
对象默认的[[Get]]和[[Put]]操作分别可以控制属性值的设置和获取。
在ES5中可以使用getter和setter部分改写默认操作,但是只能应用到单个属性上,无法应用到整个对象上。
getter是一个隐藏函数,会在获取属性值时调用。setter也是一个隐藏属性,会在设置属性时调用。
当给 一个属性定义getter、setter或者两者都有时,这个属性会被定义为 “访问描述符” 。
对于访问描述符来说,JS会忽略它们的value和writable特性,取而代之关心的是get和set特性(还有configurable和enumberable特性)。
var myObject = {
//给a定义一个getter
get a(){
return 2;
}
};
Object.defineProperty(myObject, "b", {
//给B设置一个getter
get:function(){
return this.a * 2;
},
//确保b会出现在对象的属性列表中
enumberable:true;
});
myObject.a;//2
myObject.b;//4
上面两种方式都会在对象中创建一个不包含值得属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当做属性访问的返回值。
var myObject = {
//给a定义一个getter
get a(){
return 2;
}
};
myObject.a = 3;
myObject.a;//2
由于只定义了a的getter,所以在对a的值进行设置时,set操作会忽略赋值操作,不会抛出错误。
并且及即使有合法的setter,由于自定义的getter只会返回2,所以set操作没有意义。
var myObject = {
//给a定义一个getter
get a(){
return this._a_;
},
//给a定义一个setter
set a(val){
this._a_ = val * 2;
}
};
myObject.a = 2;
myObject.a;//4
this._a_
变量只是一个惯例名称,没有特殊不同,和其它普通属性一样。
存在性
var obj = {
a:undefind
}
obj.a;//undefind
obj.b;//undefind
上面这个例子前面提到过,这两种情况如何区分?
我们可以在不访问属性值的情况下判断对象中是否存在这个属性:
var myObject = {
a: 2
};
("a" in myObject);//true
("b" in myObject);//false
myObject.hasOwnProperty("a");//true
myObject.hasOwnProperty("b");//false
in操作符会检查属性是否存在对象及其[[prototype]]原型链中。
hasOwnProperty()只会检查属性是否在myObject属性中,不会检查[[prototype]]链。
ps:in操作符实际上是检测某个属性名是否存在,而不是检查容器中是否有个某个值。
例如在数组中,4 in [2, 4, 6]
的结果 是false,因为这个数组中包含的属性名是0,1,2。
枚举
var myObject = {};
Object.defineProperty(myObject, "a", {
//让a像普通属性一样可以枚举
enumberable: true,
value: 2
});
Object.defineProperty(myObject, "b", {
//让a像普通属性一样可以枚举
enumberable: false,
value: 3
});
myObject.b;//3
(b in myObject);//true
myObject.hasOwnProperty("b");//true
//..
for(var k in myObject) {
console.log(k, myObject[k]);
}
//"a" 2
myObject.b确实存在并且有访问值,但是不出现在for…in循环中。
“可枚举” = “可以出现在对象属性的遍历中”。
另外一种方法来区分属性是否可枚举:
var myObject = {};
Object.defineProperty(myObject, "a", {
//让a像普通属性一样可以枚举
enumberable: true,
value: 2
});
Object.defineProperty(myObject, "b", {
//让a像普通属性一样可以枚举
enumberable: false,
value: 3
});
myObject.propertyIsEnumberable("a");//true
myObject.propertyIsEnumberable("b");//false
Object.keys(myObject);//["a"]
Object.getOwnPropertyNames(myObject);//["a","b"]
propertyIsEnumberable()会检查给定的属性名是否直接存在于对象中(而不是原型链中),并且满足enumberable:true。
Object.keys()会返回一个数组,包含所有可枚举属性。
Object.getOwnPropertyNames()会返回一个数组,包含所有属性,无论它们是否可以枚举。
in和hasOwnProperty()的区别在于是否查找[[prototype]]链;Object.keys()和Object.getOwnPropertyNames()都只会查找对象直接包含的属性。
遍历
for…in循环可以用来遍历对象的可枚举属性列表(包括[[prototype]]链)。
如何遍历属性的值?
对于数值索引的数组来说,可以用标准的for循环来遍历值:
var myArray = [1, 2, 3];
for(var i = 0;i < myArray.length; i++){
console.log(myArray[i]);
}
//1 2 3
这是实际上并不是在遍历值,是遍历下标来指向值,如myArray[i].
ES5中增加了一些数组的辅助迭代器,包括forEach(),every()和some()。每个迭代器都可以接受一个回调函数并把它应用到数组的每个元素上,唯一的区别是它们对于回调函数返回值的处理方式不同。
forEach()会遍历数组中所有的值并忽略回电函数的返回值。
every()会一直运行直到回调函数返回false。
some()会一直运行直到回调函数返回true。
而使用for…in遍历对象是无法直接获取属性值的,因为它实际上遍历的是对象中的所有可枚举属性,需要手动获取属性值。
ES6中新增for…of遍历数组。