面向对象之属性
# 面向对象之属性
[TOC]
# 一、可计算属性名
- ES6 增加了可计算属性名,可以在文字形式中使用 [] 包裹一个表达式来当作属性名:
var prefix = "foo";
var myObject = {
[prefix + "bar"]:"hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 二、属性描述符
- 所有的对象都具备
value
writable
(可写,决定是否可以修改属性的值)enumerable
(可枚举)- 符控制的是属性是否会出现在对象的属性枚举中
- 比如说
for..in
循环- 把
enumerable
设置成false
,这个属性就不会出现在枚举中,但可以正常访问它。 - 设置成
true
就会让它出现在枚举中。
- 把
- 比如说
- 符控制的是属性是否会出现在对象的属性枚举中
configurable
(可配置)
- 在创建普通属性时属性描述符会使用默认值。
- 使用
Object.defineProperty(..)
来添加一个新属性或者修改一个已有属性(如果它是configurable
)并对特性进行设置。
var myObject = {};
Object.defineProperty(myObject,'a',{
value:2,
writable:true,
configurable:true,
enumerable:true
});
myObject.a;//2
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 如果
configurable:false
,则无法修改,还会禁止删除属性。
var myObject = {
a:2
};
myObject.a;//2
delete myObject.a;
myObject.a;//undefined
Object.defineProperty(myObject,'a',{
value:2,
writable:true,
configurable:false,
enumerable:true
});
myObject.a;//2
delete myObject.a;
myObject.a;//2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
【小例外】
- 即便属性是
configurable:false
, 我们还是可以writable
的状态由true
改为false
,但是无法由false
改为true
。
- 即便属性是
创建一个真正的常量属性
- 结合
writable:false
和configurable:false
就可以不可修改、重定义或者删除。
- 结合
使用
Object.getOwnPropertyDescriptor(obj, 'name')
来查看obj.name的对象属性描述符。
# 三、对象的不可变性
# 3.1 扩展
# 3.1.1 判断是否扩展Object.isExtensible(obj)
- 是否可以在它上面添加新的属性
- 是否可以配置无关
var myObject = {};
Object.defineProperty(myObject, 'a', {
value: 2,
writable: true,
configurable: false,
enumerable: true
});
alert(Object.isExtensible(myObject) === true); //true
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 3.1.2 禁止扩展Object.preventExtensions(obj)
- 禁 止 一 个 对 象 添 加 新 属 性 并 且 保 留 已 有 属 性
- 只能阻止一个对象不能再添加新的自身属性,仍然可以为该对象的原型添加属性。
- 把所有现有属性标记为
configurable:false
。
var myObject = {
a:2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
//非严格模式下:创建属性 b 会静默失败。
//"use strict"; 下,抛出 TypeError 错误。
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 3.2 密封
# 3.2.1 判断是否密封Object.isSealed(obj)
- 密封指不可扩展 且所有自身属性都不可配置的
non-configurable
。
# 3.2.1 密封对象Object.seal(obj)
- 不会影响从原型链上继承的属性,但
__proto__ ( )
。 属性的值也会不能修改。 - 把所有“数据访问”属性标记为
writable:false
。 - 尝试删除一个密封对象的属性或者将某个密封对象的属性从数据属性转换成访问器属性,结果会静默失败或抛出TypeError 异常(严格模式)。
# 3.3 冻结
# 3.3.1 判断是否密封Object.isFrozen(obj)
- 一个对象是冻结的(frozen)是指它不可扩展,所有属性都是不可配置的(non-configurable),且所有数据属性(data properties)都是不可写的(non-writable)。
- 数据属性的值不可更改,访问器属性(有getter和setter)也同样(但由于是函数调用,给人的错觉是还是可以修改这个属性)。
- 这个对象永远是不可变的。
# 3.3.2 冻结对象Object.freeze(obj)
- 任何尝试修改该对象的操作都会失败,可能是静默失败,也可能会抛出异常(严格模式中)。
- 如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它也是个冻结对象。
- 浅冻结:一个对象的属性是一个对象,那么对这个外部对象进行冻结,内部对象的属性是依旧可以改变。
- 深冻结:把外部对象冻结的同时把其所有内部对象甚至是内部的内部无限延伸的对象属性也冻结。
- 深度冻结
- 先在这个对象上调用
Object.freeze(obj)
。 - 遍历它引用的所有对象并在这些对象上调用
Object.freeze(obj)
。- 但是一定要小心,因为这样做有可能会在无意中冻结其他(共享)对象。
- 先在这个对象上调用
//浅冻结与深冻结
(function () {
obj = {
internal :{}
};
Object.freeze(obj);//浅冻结
obj.internal.a = "aValue";
console.log(obj.internal.a);//"aValue"
//想让一个对象变得完全冻结,冻结所有对象中的对象
function deepFreeze(o){
var prop,propKey;
Object.freeze(o);//首先冻结第一层对象
for(propKey in o){
prop = o[propKey];
if(!o.hasOwnProperty(propKey) || !(typeof prop === "object") || Object.isFrozen(prop)){
continue;
}
deepFreeze(prop);//递归
}
}
deepFreeze(obj);
obj.internal.b = "bValue";//静默失败
console.log(obj.internal.b);//undefined
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 四、属性的存在性
# 4.1 in
操作符
- 检查属性是否在对象及其
[[Prototype]]
原型链中 - 看起来
in
操作符可以检查容器内是否有某个值,但是它实际上检查的是某个属性名是否存在。- 对于数组来说,
4 in [2, 4, 6]
的结果并不是你期待的True
,因为[2, 4, 6]
这个数组中包含的属性名是0、 1、2
, 没有 4。
- 对于数组来说,
# 4.2 hasOwnProperty(..)
- 只会检查属性是否在
myObject
对象中,不会检查[[Prototype]]
链。可用来区分直接属性和从原型链继承的属性。
o = new Object();
o.prop = 'exists';
function changeO() {
o.newprop = o.prop;
delete o.prop;
}
o.hasOwnProperty('prop'); // 回傳 true
changeO();
o.hasOwnProperty('prop'); // 回傳 false
function changeO() {
o.newprop = o.prop;
delete o.prop;
}
changeO();
o.hasOwnProperty('prop'); // 回傳 false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 五、枚举(enumeration)
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合。
# 5.1 for..in
循环
- 在数组上应用 有时会产生出人意料的结果,因为这种枚举不仅会包含所有数值索引,还会包含所有可枚举属性。
- 如果要遍历数组就使用传统的 for 循环来遍历数值索引。
- 最好只在对象上应用。
- 属性可以是否可枚举或者不可枚举的,这决定了它们是否会出现在 for..in 循环中。
# 5.2 propertyIsEnumerable(..)
- 检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足 enumerable:true。
# 5.3 Object.keys(..)
- 返回一个数组,包含所有可枚举属性,
# 5.4 Object.getOwnPropertyNames(..)
- 返回一个数组,包含所有属性,无论它们是否可枚举。
# 六、遍历(traversal)
树形结构的一种重要运算,指的是按照一定的规则访问树形结构中的每个节点,而且每个节点都只访问一次。
# 6.1 for..in
循环
- 可以用来遍历对象的可枚举属性列表(包括
[[Prototype]]
链)。 - 遍历对象属性时的顺序是不确定的。
- 在不同的 JavaScript 引擎中可能不一样。
- 在不同的环境中需要保证一致性时,一定不要相信任何观察到的顺序,它们是不可靠的。
Object.keys()
只遍历自身属性,也可以用hasOwnProperty()
过滤原型链上的值。
# 6.1.1 遍历属性的值
属性不一定包含值——它们可能是具备getter/setter
的“访问描述符”。
对于数值索引的数组来说,可以使用标准的for
循环来遍历值:
var myArray = [1, 2, 3];
for (var i = 0; i < myArray.length; i++) {
console.log( myArray[i] );
}
//实际不是在遍历值,而是遍历下标来指向值,如 myArray[i]
1
2
3
4
5
2
3
4
5
如何直接遍历值而不是数组下标(或者对象属性)呢?
- 可以使用 ES6 的
for..of
语法来遍历数据结构(数组、对象,等等)中的值for..of
循环首先会向被访问对象请求一个迭代器对象(内置或者自定义的@@iterator
对象),然后通过调用迭代器对象的next()
方法来遍历所有返回值。- 和数组不同,普通的对象没有内置的
@@iterator
(为了避免影响未来的对象 类型),所以无法自动完成for..of
遍历。
var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
console.log( v );
}
// 1
// 2
// 3
1
2
3
4
5
6
7
2
3
4
5
6
7
var myObject = {
a: 2,
b: 3
};
//可以给任何想遍历的对象定义@@iterator
Object.defineProperty( myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys( o );
return {
next: function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
};
}
};
}
});
// 手动遍历 myObject
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefned, done:true }
// 用 for..of 遍历 myObject
for (var v of myObject) {
console.log( v );
}
// 2
// 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37