存在性
之前谈到过的一个问题
如果对象中有一个属性的值是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引擎中可能不一样