精读《你不知道的JavaScript》上卷-II-第3章 对象

158 阅读3分钟

II-第3章 对象

对象是JavaScript的基础。

ECMAScript 标准定义了 8 种数据类型:

7 种原始类型: Undefined Null Number Boolean BigInt String Symbol 和 Object

记忆口诀:欧呦(O)你(U)俩(2个)牛(N)逼(B)啥(S)

定义对象两种形式:声明形式和构造形式。

// 声明
var myObj = {
    key: value
    // ...
};

// 构造
var myObj = new Object();
myObj.key = value;

可计算属性

ES6 增加了可计算属性名,可以在文字形式中使用 [] 包裹一个表达式来当作属性名:

var prefix = "foo";
var myObject = {
    [prefix + "bar"]:"hello",
    [prefix + "baz"]: "world"
};
myObject["foobar"]; // hello

属性与方法

js中函数不会属于一个对象,因为this是在运行时根据调用位置动态绑定的。 所以方法这个称呼不太成立。但这只是个称呼,不必太纠结。

复制对象

浅复制

对于JSON安全(可以被序列化为一个 JSON 字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象可以用JSON.parse复制

var newObj = JSON.parse(JSON.stringify(someObj));

ES6中可以用Object.assign({}, obj)复制

属性描述符

属性描述符writable(可写), configurable(可配置), enumerable(可枚举)

可以使用Object.defineProperty定义属性


Object.defineProperty(obj, 'KEY', {
    value: 12,
    writable: false,
    configurable: false
});

如果把 enumerable 设置成 false,这个属性不会出现在枚举中(比如for..in)。但可以正常访问这个属性。

结合writable: falseconfigurable: false可以创建一个真正的常量属性(不可修改、重定义或删除)

禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions(obj)

密封:Object.seal(..)调用Object.preventExtensions(obj)把所有现有属性标记为configurable: false。密封后不能添加,重新配置和删除现有属性。可以修改属性的值。

冻结:Object.freeze(..)调用Object.seal(..)把所有数据访问属性标记为writable: false。这样就无法修改属性值。

get, set

get: 属性访问先在对象中查找是否有名称相同的属性,没找到就遍历可能存在的原型链,找不到的话返回undefined。

set:

  1. 先判断属性是否是访问描述符,存在setter就调用setter。
  2. writable是否为false。是,非严格模式下静默失败,严格模式抛出TypeError异常。
  3. 都不是,设置属性值。
var myObject = {
// 给 a 定义一个 getter
    get a() {
        return this._a_;
    },
// 给 a 定义一个 setter
    set a(val) {
        this._a_ = val * 2;
    }
};
myObject.a = 2; myObject.a; // 4

存在性

判断对象是否有某个属性

var myObject = {
    a:2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false

in操作符会检查属性是否在对象及其原型链中

hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查链。

所有的普通对象都可以通过对于 Object.prototype 的委托来访问 hasOwnProperty(..), 但是有的对象可能没有连接到 Object.prototype(通过 Object. create(null) 来创建)。 在这种情况下,形如myObejct.hasOwnProperty(..)就会失败。

可以采用Object.prototype.hasOwnProperty.call(myObject,"a")判断

在数组上应用 for..in 循环有时会产生出人意料的结果,因为这种枚举不仅会包含所有数值索引,还会包含所有可枚举属性。最好只在对象上应用 for..in 循环,如果要遍历数组就使用传统的 for 循环来遍历数值索引。

// 判断是否可枚举
myObject.propertyIsEnumerable( "a" ); // true
myObject.propertyIsEnumerable( "b" ); // false
// 所有可枚举属性的数组
Object.keys( myObject ); // ["a"]
// 所有属性,无论它们是否可枚举
Object.getOwnPropertyNames( myObject ); // ["a", "b"]

遍历

for..in无法直接获取属性值,因为它遍历的是对象中的所有可枚举属性,需要手动获取属性值。 for..of循环每次调用 myObject 迭代器对象的 next() 方法时,内部的指针都会向前移动并返回对象属性列表的下一个值。

小结 第3章对象

了解对象是什么,怎么定义,一些常用方法和特性,遍历对象。

工作中对象的使用是非常频繁的,熟练掌握各种API是提升技术的必备条件。

最佳实践

// good
const newObj = {
    a: 12,
    b: 23
};
// bad
const newObj = {};
newObj.a = 12;
newObj.b = 23;
newObj['a'] = 12;

// good
this.setState({
    ...newObj
});

// bad
this.setState({
    a: newObj.a,
    b: newObj.b
});