Skip to content

Latest commit

 

History

History
481 lines (416 loc) · 14.6 KB

继承.md

File metadata and controls

481 lines (416 loc) · 14.6 KB

继承

extend

继承

原型链继承

利用原型链作为实现继承的主要方法,主要思想是利用原型让一个引用类型继承另一个引用类型的属性和方法

function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); // true

SubType继承了SuperType,继承是通过SubType.prototype = new SuperType();实现的。实现的本质是重写原型对象,换成一个新类型(super)的实例。这样,原来存在于SuperType的实例中的属性和方法,也存在与SubType.prototype中了。然后给SubType.prototype添加一个方法,这样就继承了SuperType的属性和方法的基础上又添加了一个方法。

alt

上面没有使用SubType默认提供的原型,而是给它换了一个新原型;这个新原型就是SuperType的实例。新原型内部还有一个指向SuperType的原型的指针。结果变成了instance指向SubType的原型,SubType的原型又指向SuperType的原型。getValue()方法仍然还在SuperType.prototype中,但prototype则位于SubType.prototype中。这是因为property是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype现在是SuperType的实例,那么property自然位于该实例(SubType.prototype)中。

注意:instance.constructor现在指向的是SuperType,当以读取模式访问一个属性时,首先在实例中搜索。如果没有找到。则会继续搜索实例的原型。通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。

1. 默认的原型 Object.prototype

alt

2. 确定原型和实例的关系

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

3. 谨慎地定义方法

  • 给原型添加方法的代码一定要放在替换原型的语句之后
function SuperType() {
  this.property = true;
}
SuperType.prototype.getSuperValue = function () {
  return this.property;
};
function SubType() {
  this.subproperty = false;
}
SuperType.prototype = new SuperType();
// 添加方法
SubType.prototype.getSubValue = function () {
  return this.subproperty;
};
// 覆盖超类中的方法
SubType.prototype.getSuperValue = function () {
  return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); // false
  • 通过原型链实现继承时,不能使用对象字面量创建原型方法,这样会重写原型链
function SuperType() {
  this.property = true;
}
SuperType.prototype.getSuperValue = function () {
  return this.property;
};
function SubType() {
  this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
// 使用字面量添加新方法,导致上一行代码无效
SubType.prototype = {
  getSubValue :function() {
    return this.subproperty;
  },
  someOtherMethod: function () {
    return false;
  }
};
var instance = new SubType();
alert(instance.getSuperValue()); // error

现在的原型包含一个Object的实例,而非SuperType的实例,SubType和SuperType之间已经没有关系了。

4. 原型链的问题

因此很少单独使用原型链实现继承

  • 包含引用类型的原型属性会被所有实例共享
function SuperType() {
  this.colors = ["red", "blue", "green"];
}
function SubType() {
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); // "red", "blue", "green", "black"
var instance2 = new SubType();
alert(instance2.colors); // "red", "blue", "green", "black"

SuperType 构造函数中定义了一个 colors 属性,该属性包含一个数组,SuperType 的每个实例都会有各自包含自己数组的 colors 属性。当SubType 通过原型链继承了 SuperType 之后,SubType.prototype 就变成了 SuperType 的一个实例,所以它也拥有了一个它自己的 colors 属性。但是,SubType的所有实例都会共享这一个 colors 属性。

  • 创建子类型时,不能向超类型的构造函数中传递参数

借用构造函数继承

可以在子类构造函数中给超类传递参数

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
    //继承了SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
 
 
function SuperType(name){
    this.name = name;
}
function SubType(){
    //继承了SuperType,同时还传递了参数
    SuperType.call(this, "Nicholas");
    //实例属性
    this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
  • 原理:在子类型构造函数的内部调用超类型构造函数
  • 优点:解决了superType中的私有属性变公有的问题,可以传递参数
  • 缺点:方法在函数中定义,无法得到复用

组合继承

将原型链和借用构造函数继承组合

alt

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);//借用构造函数继承属性,二次调用
    this.age = age;
}
SubType.prototype = new SuperType();//借用原型链继承方法,一次调用
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
  • 优点:既通过在原型上定义的方法实现了函数复用,又能够保证每个实例都有它的属性。 是JavaScript中最常用的继承模式。
  • 缺点:两次调用父构造器函数,浪费内存。

原型式继承

对传入的对象进行浅复制

function object(o){
	function F();
	F.prototype = o;
	return new F();
}

Object.create():实现了上面函数的功能

  • 缺点:只想让一个对象与另一个对象保持类似的情况下,原型式继承可以胜任, 但包含引用类型值的属性会被共享
  • 使用场合:没必要构建构造函数,仅仅是想模拟一个对象的时候

寄生式继承

在内部以某种方式增强对象,最后返回对象

function createAnother(original){
    var clone = Object(original); //通过调用函数创建一个新对象
    clone.sayHi = function(){ //以某种方式来增强这个对象
        alert("hi");
    };
    return clone; //返回这个对象
}
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
  • 缺点:做不到函数复用而降低效率

寄生组合式继承

组合继承的缺点:无论在什么情况下,都会调用两次超类构造函数

function inheritPrototype(subType, superType){
    var prototype = Object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //指定对象
}
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);//实现继承
SubType.prototype.sayAge = function(){
    alert(this.age);
};

寄生组合继承是引用类型最理想的继承范式

ES6中的继承

语法糖,本质是组合继承,原型链继承实现公有方法,借用构造函数继承实现私有属性

如何创建一个类

class Parent {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    speakSomething(){
        console.log("I can speek chinese");
    }
}

经过babel转码之后

"use strict";

var _createClass = function () {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;//默认不可枚举
            descriptor.configurable = true;//可配置修改属性
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);//给target添加属性
        }
    }

    return function (Constructor, protoProps, staticProps) {
        // 原型属性
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        // 静态属性
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
}();

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

var Parent = function () {
    function Parent(name, age) {
        _classCallCheck(this, Parent);

        this.name = name;
        this.age = age;
    }

    _createClass(Parent, [{
        key: "speakSomething",
        value: function speakSomething() {
            console.log("I can speek chinese");
        }
    }]);

    return Parent;
}();
  • class 可以看出声明一个 class 就是通过创建并执行一个匿名函数,在这个匿名函数中声明 function Parent,最后返回 Parent。

  • constructor 其中 _classCallCheck(this, Parent),是防止直接通过构造函数直接运行,必须通过 new

_createClass 方法,它调用 Object.defineProperty 方法去给新创建的 Parent 添加各种属性。defineProperties(Constructor.prototype, protoProps) 是给原型添加属性。如果你有静态属性,会直接添加到构造函数上 defineProperties(Constructor, staticProps)。

如何实现继承

给Parent添加静态属性,原型属性,内部属性

class Parent {
    static height = 12
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    speakSomething(){
        console.log("I can speek chinese");
    }
}
Parent.prototype.color = 'yellow'


//定义子类,继承父类
class Child extends Parent {
    static width = 18
    constructor(name,age){
        super(name,age);
    }
    coding(){
        console.log("I can code JS");
    }
}

var c = new Child("job",30);
c.coding()

转码之后的代码变成了这样

"use strict";

var _createClass = function () {
    // ...
}();

function _possibleConstructorReturn(self, call) {
    if (!self) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

function _classCallCheck(instance, Constructor) {
    // ...
}

var Parent = function () {
    function Parent(name, age) {
        _classCallCheck(this, Parent);

        this.name = name;
        this.age = age;
    }

    _createClass(Parent, [{
        key: "speakSomething",
        value: function speakSomething() {
            console.log("I can speek chinese");
        }
    }]);

    return Parent;
}();

Parent.height = 12;

Parent.prototype.color = 'yellow';

//定义子类,继承父类

var Child = function (_Parent) {
    _inherits(Child, _Parent);

    function Child(name, age) {
        _classCallCheck(this, Child);

        return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age));
    }

    _createClass(Child, [{
        key: "coding",
        value: function coding() {
            console.log("I can code JS");
        }
    }]);

    return Child;
}(Parent);

Child.width = 18;


var c = new Child("job", 30);
c.coding();
  • extend 构造类的方法都没变,只是添加了 _inherits 核心方法来实现继承,下面我们就看下这个方法做了什么?
subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
});

这段代码翻译下来就是

function F(){}
F.prototype = superClass.prototype
subClass.prototype = new F()  // new 的过程就是将实例链接到构造函数的原型
subClass.prototype.constructor = subClass

接下来就是 subClass.proto = superClass

_inherits 核心思想就是下面两句

subClass.prototype.__proto__ = superClass.prototype
subClass.__proto__ = superClass

alt

那为什么这样一倒腾,它就实现了继承了呢? 首先 subClass.prototype.proto = superClass.prototype保证了c instanceof Parent是true,Child的实例可以访问到父类的属性,包括内部属性,以及原型属性。其次,subClass.proto = superClass,保证了Child.height也能访问到,也就是静态方法。

  • super 其中 _possibleConstructorReturn 实现了super的原理