Javascript高级程序设计 读书笔记

Javascript 面向对象的程序设计(原型链与继承)

2018-03-08  本文已影响36人  Sue1024

继承

原型链

讲原型的时候提到过继承,设计原型的初衷就是为了继承,原型链是实现继承的主要方法。
那什么是原型链,还记得之前提到过的作用域链吗,它表示标识符在环境中的查找顺序,原型链与作用域链相似,它表示属性或方法在实例和构造函数间的追溯顺序,看一个例子:

function Father() {
}
Father.prototype.familyName = "Zhao";
function Child() {
}
var father = new Father();
Child.prototype = father;
var me = new Child();
me.familyName;
// "Zhao"

当我们在me实例上访问familyName属性时,搜索过程会从原型链的末端开始逐步向上,即:
me实例 → Child原型 → Father原型
默认的原型

由于所有引用类型都继承了Object 因此所有引用类型原型链的顶端都是Object.prototype,因此所有自定义类型都会继承toString() valueOf()等默认方法。

确定原型和实例的关系
  1. instanceof
    用这个操作符测试实例和原型链上出现的构造函数,即返回true
  2. isPropertyOf
    用这个方法测试原型链中出现过的原型,即返回true
谨慎地定义方法

在上面的例子中,我们有一步替换原型对象的操作:

Child.prototype = father;

很多时候,我们想要在子类型中添加一些超类型没有的方法,应该放在替换原型对象之后。

不能使用对象字面量

通过原型链实现继承时,不能使用对象字面量来重写原型,因此这样做会切断子类型与超类型之间的联系。

原型链的问题

在上述例子中,Child构造函数的原型是Father的实例father,那么father实例的属性就变成Child的原型属性,接下来在Child的所有实例,均会共享father实例的属性,我们修改一下上述例子:

function Father() {
    this.hobbies = [];
}
function Child() {
}
Child.prototype = new Father();
var me = new Child();
var sister = new Child();
me.hobbies.push("dancing");
sister.hobbies;
// ["dancing"]

me修改Child原型上的hobbies属性会影响到sister访问该属性时获得的值,注意,当我们直接在实例对象上对某个属性赋值时,我们相当于修改或添加实例中的某个属性(同访问不一样,不遵循原型链),比如:

me.hobbies = [];
sister.hobbies;
//["dancing"]

上述例子中,直接在me实例中添加属性hobbies,因此没有影响sister

借用构造函数

即在子类型的构造函数调用超类型的构造函数,举一个例子:

function Father(givenName) {
    this.givenName = givenName;
}
function Child(givenName) {
    Father.call(this, givenName)
}
var child = new Child("Xianshu");
child.givenName
// "Xianshu"
  1. 私有属性
    使用这种方式,可以使每个实例从超类型中继承的属性私有化,一个实例更改属性值不会影响到其他属性,举一个例子:
var child1 = new Child("Sue")
var child2 = new Child("Jane")
child1.givenName
// "Sue"
child2.givenName
// "Jane"
  1. 传递参数
    通过这种方式,子类型可以在调用超类型构造函数时向其传参。

  2. 结构构造函数的问题
    首先没办法在实例间复用函数,比如:

function Father(givenName) {
    this.givenName = givenName;
    this.cook = function() {
        return "delicious food";
    }
}
function Child(givenName) {
    Father.call(this, givenName)
}
var child1 = new Child("Sue")
var child2 = new Child("Jane")
child1.cook === child2.cook
// false

上述例子中,可以看出,两个实例的cook方法引用的不是同一块内存地址,说明cook被实例化了多次,这显然是冗余的。
其次,超类型的原型中定义的方法对于子类型来说也是不可见的。

组合继承

使用原型链来实现方法的继承,使用构造函数实现属性的继承。比如:

function Father(givenName) {
    this.givenName = givenName;
}
Father.prototype.cook = function() {
    return "delicious food";
}
function Child(givenName) {
    Father.call(this, givenName)
}
Child.prototype = new Father();
Child.prototype.constructor = Child;
var child = new Child("Sue")
child.cook();
// "delicious food"

在创建Child构造函数时,首先继承超类型Father中的属性,然后创建一个Father实例,将其赋值给Child的原型,使其继承Father定义在原型中的方法,这里需要注意两点:

  1. new Father()中的givenName属性不会覆盖child中的givenName属性,这是由标识符在实例及其原型链上的搜索顺序决定的
  2. 需要修正Child.prototype.constructor属性

原型式继承

无需构造函数的一种继承方式,在一个对象的基础上创建出一个新对象:

function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
}
var person = { age : 18, hobbies: [] };
var anotherPerson = object(person);
anotherPerson.age;
// 18

上述例子中,person不是构造函数,只是一个普通的对象,anotherPerson继承了它的属性,需要注意的是,继承到的属性是在实例间共享的。

anotherPerson.hobbies.push("dancing");
anotherPerson.age = 19;
var anotherPerson2 = object(person);
anotherPerson2.age; // 18
anotherPerson2.hobbies; // ["dancing"]

寄生式继承

寄生式继承封装了如下过程:

  1. 调用一个能够返回新对象的函数
  2. 以某种方式来增强返回的对象
  3. 返回对象
function printBook(original) {
    var book = object(original);
    book.auther = "Sue";
    return book;
}
var book = {
    name: "travel notes",
    type: "travel"
}
var travelBook = printBook(book);
travelBook.name
// "travel notes"
travelBook.auther
// "Sue"

上述例子中,travelBook不继承了book中的属性,而且还具有自己的属性。需要注意的是,使用寄生式继承,在第二步中添加的属性或方法不能在实例间复用。

寄生组合式继承

还记得我们在组合继承中提到的注意事项第一条吗...为什么Child原型中以及child实例中都包含givenName属性,这是因为我们调用了两次Father构造函数,第一次将属性赋予child实例,第二次是将Father的实例赋值给Child的原型,虽然第二次调用添加的givenName属性并没有影响到child.givenName,但两次调用毕竟是冗余的,况且Child只需要继承Father的原型,而Father的实例包含了额外的属性。寄生组合方式就是为了解决上述的问题:

function Father(givenName) {
    this.givenName = givenName;
}
Father.prototype.cook = function() {
    return "delicious food";
}
function Child(givenName) {
    Father.call(this, givenName)
}
function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); 
    prototype.constructor = subType;
    subType.prototype = prototype; 
}
inheritPrototype(Child, Father);
var child = new Child("Sue")
undefined
child.givenName
// "Sue"
"givenName" in Child.prototype
// false
child.cook()
// "delicious food"
child instanceof Father
// true
Father.prototype.isPrototypeOf(child)
// true

Child使用寄生组合的方式继承了Father,可以调用Father原型中的方法,同时,没有留下Father“私有”属性的痕迹,instanceof()isPrototypeOf()可以正常使用

上一篇下一篇

猜你喜欢

热点阅读