0%

学习笔记 2020 10 19

学习笔记 2020-10-19

JavaScript 高级程序设计(第4版) 阅读记录

对象、类与面向对象编程

原型模式

每个函数都有一个 prototype 属性。这个属性是一个包含应该由特定引用类型的实例共享的属性和方法的对象。这个对象就是通过调用构造函数创建的对象的原型。在原型对象上定义的属性和方法可以被对象实例共享。

  1. 理解原型

    创建一个函数,按照特定的规则为这个函数创建一个 prototype 的属性。默认情况下,所有原型对象自动获得一个 constructor 属性,指回与之关联的构造函数。如,Person.prototype.constructor 指向 Person

    自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自 Object 。调用构造函数创建的新实例,这个实例的内部 [[Prototype]] 指针就会被赋值为构造函数的原型对象。FirefoxSafariChrome 会在对象上暴露 __proto__ 属性来访问原型对象。

    实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有。

    /**
     * 构造函数可以是函数表达式
     * 也可以是函数声明,因此以下两种形式都可以:
     * function Person() {}
     * let Person = function() {}
     */
    function Person() {}
    /**
     * 声明之后,构造函数就有了一个
     * 与之关联的原型对象:
     */
    console.log(typeof Person.prototype); // object
    console.log(Person.prototype);
    // {
    // constructor: f Person(),
    // __proto__: Object
    // }
    /**
     * 如前所述,构造函数有一个 prototype 属性
     * 引用其原型对象,而这个原型对象也有一个
     * constructor 属性,引用这个构造函数
     * 换句话说,两者循环引用:
     */
    console.log(Person.prototype.constructor === Person); // true
    /**
     * 正常的原型链都会终止于 Object 的原型对象
     * Object 原型的原型是 null
     */
    console.log(Person.prototype.__proto__ === Object.prototype); // true
    console.log(Person.prototype.__proto__.constructor === Object); // true
    console.log(Person.prototype.__proto__.__proto__ === null); // true
    console.log(Person.prototype.__proto__);
    // {
    // constructor: f Object(),
    // toString: ...
    // hasOwnProperty: ...
    // isPrototypeOf: ...
    // ...
    // }
    let person1 = new Person(),
      person2 = new Person();
    /**
     * 构造函数、原型对象和实例
     * 是 3 个完全不同的对象:
     */
    console.log(person1 !== Person); // true
    console.log(person1 !== Person.prototype); // true
    console.log(Person.prototype !== Person); // true
    /**
     * 实例通过__proto__链接到原型对象,
     * 它实际上指向隐藏特性[[Prototype]]
     *
     * 构造函数通过 prototype 属性链接到原型对象
     *
     * 实例与构造函数没有直接联系,与原型对象有直接联系
     */
    console.log(person1.__proto__ === Person.prototype); // true
    console.log(person1.__proto__.constructor === Person); // true
    /**
     * 同一个构造函数创建的两个实例
     * 共享同一个原型对象:
     */
    console.log(person1.__proto__ === person2.__proto__); // true
    /**
     * instanceof 检查实例的原型链中
     * 是否包含指定构造函数的原型:
     */
    console.log(person1 instanceof Person); // true
    console.log(person1 instanceof Object); // true
    console.log(Person.prototype instanceof Object); // true

    可以使用 isPrototypeOf() 方法检查是否是该构造函数的实例对象。

    可以使用 Object.getPrototypeOf() 方法返回参数的内部特性 [[Prototype]] 的值。

    可以使用 setPrototypeOf() 方法来写入原型对象。但该方法可能会严重影响代码性能。可以选择使用 Object.create() 来创建一个新对象,为其指定原型。

  2. 原型层级

    在通过对象访问属性时,会按照这个属性的名称开始搜索,首先开始于对象实例本身。如果没有找到,则沿着指针进入原型对象搜索。

    通过实例无法重写原型的值。在实例上添加了同名的属性,会在实例自身上创建这个属性,掩盖住原型对象上对应的属性。

    当实例上遮蔽了原型对象上的同名属性时,除非使用 delete 删除这个实例上的属性,否则无法再访问到原型对象上的同名属性。

    可以使用 hasOwnProperty 来确定某个属性存在于实例还是原型对象。这个方法继承于 Object 。该方法只对实例属性有效,要取得原型属性的描述符,就必须直接在原型对象上调用 Object.getOwnPropertyDescriptor()

  3. 原型和 in 操作符

    in 操作符可以单独使用或是在 for-in 循环中使用。

    单独使用时, in 操作符会在可以通过对象访问指定属性时返回 true ,无论该属性是在实例上还是在原型上。

    for-in 循环中使用 in 操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性。

    可以使用 Object.keys() 方法来获得对象上所有可枚举的实例属性。接收一个对象作为参数,返回包含该对象所有可枚举属性名称的字符串数组。

    可以使用 Object.getOwnPropertyNames() 来获得所有实例属性,无论是否可以枚举。

    ES6 新增了一个 Object.getOwnPropertySymbols() 方法来获取符号为键的属性。

  4. 属性枚举顺序

    for-in 循环和 Object.keys() 的枚举顺序是不确定的。

    Object.getOwnpropertyNames()Object.getOwnPropertySymbols()Object.assign() 的枚举顺序是确定性的。先以升序枚举数值键,再以插入顺序枚举字符串和符号键。

对象迭代

ES8 新增了两个静态方法,用于将对象内容转换为序列化的、可迭代的格式。分别是 Object.values()Object.entries() ,都接收一个对象,返回相应内容的数组。

Object.values() 返回对象值的数组, Object.entries() 返回键值对的数组。

非字符串属性会被转换为字符串输出。两个方法都执行对象的浅复制。符号属性会被忽略。