存在性
之前談到過的一個問題
如果對象中有一個屬性的值是undefined
那麼我無法簡單的通過對這個屬性訪問的返回值來判斷對象是否有這個屬性
那我們要用什麼辦法來判斷一個對象是否有一個屬性呢?
這裏有兩種辦法
var obj = {
a: 1
}
console.log("a" in obj); //true
console.log(obj.hasOwnProperty("a")); //true
in操作符會檢查屬性是否在對象及原型鏈中而hasOwnProperty(..)方法只會檢查對象中的直接屬性而不會檢查原型鏈
值得注意的是,in操作符是通過檢查屬性名是否存在來判斷的
也就是說,4 in [2,4,6]返回的是false,因爲包含的屬性名只有0,1,2
所有的普通對象都可以通過對Object.prototype的委託來訪問hasOwnProperty()方法
但是有的函數可能沒有連接到Object.prototype,比如通過Object.create(null)來創建的對象
這時候可以通過顯示綁定Object.prototype.hasOwnProperty(..).cal(..)來訪問
枚舉性
我們之前討論過關於屬性描述符特效中關於enumerable(可枚舉性)的意義
儘管enumerable:false的屬性可以被in操作符檢測到
但是在for .. in循環中卻不會出現
原因就是enumerable(可枚舉性)就相當於"可以出現在對象屬性的遍歷中"
for .. in 遍歷的就是所有可枚舉的對象屬性,在對數組遍歷時尤其要注意
因爲這種枚舉不僅會包括所有數組索引,還會包括所有可枚舉的屬性
區分屬性是否可枚舉的方法
除了上述的方法以外,還有另一種方法可以用來區分屬性是否可枚舉
- propertyIsEnumerable(..) 會檢查給定的屬性名是否直接存在於對象中(而不是在原型鏈上),並且滿足enumerable:true
- Object.keys(..)會返回一個數組,包含所有可枚舉屬性(不查找原型鏈)
- Object.getOwnPropertyNames(..)則返回的是一個包括所有屬性的數組,無論是否可枚舉(不查找原型鏈)
var obj = {
a: 1,
b:2
}
Object.defineProperty(obj,"c",{
enumerable:false
})
console.log(
obj.propertyIsEnumerable("a"), //true
obj.propertyIsEnumerable("c"), //false
Object.keys(obj), //[a,b]
Object.getOwnPropertyNames(obj) //[a,b,c]
)
遍歷
for .. in 循環可以遍歷包括原型鏈中的屬性在內的對象的所有可枚舉屬性列表
但是for .. in無法直接獲取屬性值,因爲他遍歷的是所有可枚舉屬性,需要手動獲取屬性值
var arr = [1, 2, 3, 4];
for(let idx in arr) { //0 1 2 3
console.log(idx);
}
除了for..in以外,ES5還增加了一些數組的輔助迭代器(接收一個回調函數作爲參數,回調函數接收三參數,值,索引,還有數組本身)
- forEach() 遍歷數組中所有值並忽略回調函數的返回值
- every() 遍歷到回調函數返回false時停止 (回調函數無返回值時也算)
- some() 遍歷到回調函數返回true時停止
var arr = [1, 2, 3, 4];
arr.forEach(test); //1 2 3 4 忽略返回值
arr.every(test); //1 遇到false返回值則停止
arr.some(test); //1 2 3 4 遇到true返回值則停止
function test(value, index, array) {
console.log(value);
}
ES6中還添加了一種可以直接遍歷屬性值的for .. of 語法(如果對象本身定義了迭代器的話也可以遍歷對象)
var arr = [1, 2, 3, 4];
for(let val of arr) { //1 2 3 4
console.log(val);
}
for .. of循環首先會向被訪問對象請求一個迭代器對象,然後通過調用迭代器對象的next()方法來遍歷所有的返回值
數組有內置的@@iterator,因此for .. of可以直接用在數組上
我們先來試試用內置的@@iterator手動遍歷數組(@@iterator並不是一個迭代器對象,而是一個返回迭代器對象的函數)
var arr = [1, 2, 3, 4];
var it = arr[Symbol.iterator]();
console.log(it.next()); //{value: 1, done: false}
console.log(it.next()); //{value: 2, done: false}
console.log(it.next()); //{value: 3, done: false}
console.log(it.next()); //{value: 4, done: false}
console.log(it.next()); //{value: undefined, done: true}
迭代器(generator)的next()方法會返回一個{value:..,done:..}的值
value是當前遍歷值,done表示是否還有可以遍歷的值
值得注意的是,在遍歷到最後一個值的時候,done依舊是false
再遍歷一次纔會變爲true
當然,我們也可以給普通對象定義一個@@iterator
var obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, Symbol.iterator, {
enumerable: false,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys(o);
return { //返回一個迭代器對象
next: function() { //每調用一次.next(),返回一個{value:..,done:..}對象
return {
value: o[ks[idx++]],
done: (idx > ks.length)
}
}
}
}
});
var it = obj[Symbol.iterator]();
console.log(it.next()); //{value: 1, done: false}
console.log(it.next()); //{value: 2, done: false}
console.log(it.next()); //{value: undefined, done: true}
for(let val of obj) { //1 2
console.log(val);
}
用這種看起來似乎很麻煩的defineProperty方法是爲了讓他不可枚舉
當然,也可以直接在聲明對象時聲明,但是要記得,我們把Symbol當做可計算屬性名(也就是[]內放一個表達式的形式來進行聲明)
var obj = {
a: 1,
b: 2,
[Symbol.iterator]:function(){
//..
}
}
還有一個需要注意的地方
遍歷數組時採用的是數字順序(for或者其他迭代器)
但是遍歷對象屬性時順序是不確定的,在不同的JavaScript引擎中可能不一樣