面向对象之属性

# 面向对象之属性

[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

# 二、属性描述符

  • 所有的对象都具备
    • value
    • writable(可写,决定是否可以修改属性的值)
    • enumerable(可枚举)
      • 符控制的是属性是否会出现在对象的属性枚举中
        • 比如说for..in 循环
          1. enumerable设置成 false,这个属性就不会出现在枚举中,但可以正常访问它。
          2. 设置成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
  • 如果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
  • 【小例外】

    • 即便属性是configurable:false, 我们还是可以 writable的状态由true改为 false,但是无法由false改为true
  • 创建一个真正的常量属性

    • 结合writable:falseconfigurable:false就可以不可修改、重定义或者删除。
  • 使用Object.getOwnPropertyDescriptor(obj, 'name')来查看obj.name的对象属性描述符。

# 三、对象的不可变性

# 3.1 扩展

# 3.1.1 判断是否扩展Object.isExtensible(obj)

  1. 是否可以在它上面添加新的属性
  2. 是否可以配置无关
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

# 3.1.2 禁止扩展Object.preventExtensions(obj)

  1. 禁 止 一 个 对 象 添 加 新 属 性 并 且 保 留 已 有 属 性
  2. 只能阻止一个对象不能再添加新的自身属性,仍然可以为该对象的原型添加属性。
  3. 把所有现有属性标记为 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

# 3.2 密封

# 3.2.1 判断是否密封Object.isSealed(obj)

  1. 密封指不可扩展 且所有自身属性都不可配置的non-configurable

# 3.2.1 密封对象Object.seal(obj)

  1. 不会影响从原型链上继承的属性,但 __proto__ ( )。 属性的值也会不能修改。
  2. 把所有“数据访问”属性标记为 writable:false
  3. 尝试删除一个密封对象的属性或者将某个密封对象的属性从数据属性转换成访问器属性,结果会静默失败或抛出TypeError 异常(严格模式)。

# 3.3 冻结

# 3.3.1 判断是否密封Object.isFrozen(obj)

  1. 一个对象是冻结的(frozen)是指它不可扩展,所有属性都是不可配置的(non-configurable),且所有数据属性(data properties)都是不可写的(non-writable)。
    1. 数据属性的值不可更改,访问器属性(有getter和setter)也同样(但由于是函数调用,给人的错觉是还是可以修改这个属性)。
    2. 这个对象永远是不可变的。

# 3.3.2 冻结对象Object.freeze(obj)

  1. 任何尝试修改该对象的操作都会失败,可能是静默失败,也可能会抛出异常(严格模式中)。
  2. 如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它也是个冻结对象。
    1. 浅冻结:一个对象的属性是一个对象,那么对这个外部对象进行冻结,内部对象的属性是依旧可以改变。
    2. 深冻结:把外部对象冻结的同时把其所有内部对象甚至是内部的内部无限延伸的对象属性也冻结。
  3. 深度冻结
    1. 先在这个对象上调用 Object.freeze(obj)
    2. 遍历它引用的所有对象并在这些对象上调用 Object.freeze(obj)
      1. 但是一定要小心,因为这样做有可能会在无意中冻结其他(共享)对象。
//浅冻结与深冻结
(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

# 四、属性的存在性

# 4.1 in 操作符

  • 检查属性是否在对象及其 [[Prototype]] 原型链中
  • 看起来 in 操作符可以检查容器内是否有某个值,但是它实际上检查的是某个属性名是否存在。
    1. 对于数组来说, 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

# 五、枚举(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

如何直接遍历值而不是数组下标(或者对象属性)呢?

  • 可以使用 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
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