JavaScript高级程序设计-原型

理解原型

无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。
在定义构造函数时,原型对象默认只会获得constructor属性,其他的所有方法都继承自Object。每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为构造函数的原型对象。脚本中没有访问这个[[Prototype]]特性的标准方式,但Firefox/Safari/Chrome会在每个对象暴露__proto__属性,通过这个属性可以访问对象的原型。

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

这种关系不好可视化,但可以通过下面的代码来理解原型的行为

    function Person() {}
    // 声明之后,构造函数就有了一个与之关联的原型对象
    console.log(typeof Person.prototype);
    console.log(Person.prototype)
    // 如前所述,构造函数有一个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

    let person1 = new Person()
    let 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

JavaScript高级程序设计-原型_第1张图片
上面的图片展示了Person构造函数、Person的原型对象和Person现有两个实例之间的关系。
注意:Person.prototype指向原型对象,而Person.prototype.constructor指回Person构造函数。
原型对象包含constructor属性和其他后来添加的属性。
Person的两个实例person1和person2都只有一个内部属性指回Person.prototype,而且两者都与构造函数没有直接联系。
另外要注意,虽然这两个实例都没有属性和方法,但person1.sayName()都可以正常调用,这是由于对象属性查找机制的原因。
虽然不是所有实现都对外暴漏了[[Prototype]],但可以使用isPrototypeOf()方法确定两个对象之间的这种关系,本质上,isPrototypeOf()会在传入参数的[[Prototype]]指向调用它的对象时返回true,如下所示

    console.log(Person.prototype.isPrototypeOf(person1) // true
    console.log(Person.prototype.isPrototypeOf(person2)) // true

这里通过原型对象调用isPrototypeOf()方法检查了person1和person2.因为这两个例子内部都有链接指向Person.prototype,所以结果都返回true。
ESMAScript的Object类型有一个方法叫Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值。例如:

    console.log(Object.getPrototypeOf(person1) === Peron.prototype // true
    console.log(Object.getPrototypeOf(person1).name // '66'

第一行代码简单确认了Object.getPrototypeOf()返回的对象就是传入对象的原型对象。第二行代码则取得了原型对象上name属性的值,即'66'。使用Object.getPrototypeOf()可以方便地取得一个对象的原型,而这在通过原型实现继承时显得尤为重要。
可以通过Object.create()来创建一个新对象,同时为其指定原型:

    let biped = {
        numLegs: 2
    }
    let person = Object.create(biped)
    person.name = '66'
    console.log(person.name) // '66'
    console.log(person.numLegs) // 2
    console.log(Object.getPrototypeOf(person) === biped) // true

原型层级

TODO...

你可能感兴趣的:(javascript)