一篇就够-JS继承的多种方式和实现

原型链继承

方法:子构造函数的prototype指向为父构造函数的实例,因为原型链是proto的链表,父构造函数的实例的proto指向父构造函数实例的原型。

function Parent(){
    this.name = 'johe'
}

Parent.prototype.getName = function(){
    console.log(this.name)
}

function Child(){

}

//原型必须是对象,所以为Parent的实例
Child.prototype = new Parent()

var child1 = new Child()

child1.getName()

问题:

  1. 引用类型的属性被所有实例共享
  2. 创建Child的实例时,不能向parent传参
function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]

这是因为Parent在实例化之后,成为了Child的原型,原型上的属性和方法是共享的。

借用构造函数(经典继承)

调用父构造函数

function Parent(){
    this.names = ['kevin','daisy'];
}

function Child(){
    Parent.call(this);
}

var Child1 = new Child()

Child1.names.push('johe')

var Child2 = new Child()

//['kevin','daisy']
Child2.names

优点:

  1. 避免了引用类型的属性被所有实例共享
  2. 可以在Child中向Parent传参
    缺点:
  3. 方法都在构造函数中定义,每次创建实例都会创建一遍方法
  4. instanceof检验为false
function Parent(name){
    this.name = name;
}

function Child(name){
    Parent.call(this,name)
}

var Child1 = new Child('johe')

//johe 
Child1.name

组合继承(原型链继承和经典继承)

优点:借用构造函数继承解决了传参问题和实例属性被共享的问题,原型链继承能够满足共享方法不被重复创建。
缺点:调用了两次父构造函数

function Parent(name){
    this.name = name
    this.names = ['johe']
}

Parent.prototype.getName = function(){
    return this.name
}

function Child(name,age){
    //调用父级的构造方法,实现实例属性
    Parent.call(this,name)
    this.age = age
}

//这里不用参数是因为子构造函数调用父构造函数时已实现实例属性,有实例属性的情况下不会从原型链中查找
Child.prototype = new Parent()

var child1 = new Child('johe',18)
child1.names.push("johe2")
console.log(child1.name);//johe
console.log(child1.age);18
//["johe","johe2"]
console.log(child1.names);

var child2 = new Child('child2',19)
console.log(child2.name);//child2
console.log(child2.age);19
//["johe"]
console.log(child1.names);

原型式继承(Object.create)

Object.create的模拟实现

function createObj(o){
    function F();
    F.protoype = o;
    return new F()
}

缺点:
包含引用类型的属性值始终都会共享响应的值,这点跟原型链继承一样。

寄生组合式继承(组合继承优化)

组合继承的最大缺点就是会调用两次父构造函数
一次是设置子类型实例的原型:

Child.protoype = new Parent()

一次是创建子类型实例的时候:

function Child(name,age){
    Parent.call(this,name)
}

var child1 = new Child('johe',18)

这个时候Child.prototype这个对象内的属性其实是没用的,因为子类型实例已经调用了父构造函数进行了属性实例化。

所以就用到了寄生组合式继承,让Child.prototype间接的访问到Parent.prototype

function Parent(name){
    this.name = name
    this.names = ['johe']
}
Parent.prototype.getName = function(){return this.name}

function Child(name,age){
    Parent.call(this,name)
    this.age = age
}

function F(){}

F.prototype = Parent.prototype

Child.prototype = new F()

var child1 = new Child('johe',18)

封装继承方法:

function Parent(name){
    this.name = name
    this.names = ['johe']
}
Parent.prototype.getName = function(){return this.name}

function Child(name,age){
    Parent.call(this,name)
    this.age = age
}

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

function setPrototype(child,parent){
    var prototype = createObject(parent)
    prototype.constructor = Child
    Child.protoype = prototype
}

setPrototype(Child,Parent)

这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式

这里为什么不直接使用Child.prototype=Parent.prototype,是因为Parent.constructor应该指向Parent,并且如果我们需要给Child实例的原型设置方法和属性时,会影响到Parent的实例,这明显是不合理的。

ES6的继承

ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。

class Fruits{
    constructor(type,size){
        this.type = type;
        this.size = size;
    }
    static sayHello(){
        console.log("hello");
    }
    sayType(){
        console.log('my type is '+this.type);
        return this.type;
    }
}

class Banana extends Fruits{
    constructor(type,size,color){
        super(type,size);
        this.color = color;
    }
    sayColor(){
        console.log('my color is '+ this.color);
        return this.color;
    }
}
let fruit = new Fruits("fruit","small");
let banana = new Banana("banana","small","yellow");
 //parent: Fruits {type:'fruit',size:'small'}
console.log('parent:',fruit);
//child: Banana {type:'banana',size:'small',color:'yellow' }
console.log('child:',banana);

//hello
console.log(Fruits.sayHello());
console.log(Banana.sayHello());

//true
console.log(Fruits.hasOwnProperty("sayHello"));
//false
console.log(Banana.hasOwnProperty("sayHello"));

//false
console.log(fruit.hasOwnProperty("sayType"))
//true
console.log(Fruits.prototype.hasOwnProperty("sayType"));
//my type is banana
console.log(banana.sayType());
//false
console.log(banana.hasOwnProperty("sayType"));
//Fruits {}
console.log(banana.__proto__);

首先查看class语法糖做了什么:

  • 将构造函数内的this属性实例化
  • 将构造函数外的非static函数给设置到构造函数的原型对象中,共享属性
  • static函数设置为构造函数的属性

用ES5实现就是组合方式来创建对象:

function Fruits(type,size){
    this.type = type;
    this.size = size;
}
Fruits.sayHello = function(){
    console.log('Hello');
}
Fruits.prototype = {
    constructor:Fruits,
    sayType:function(){
        console.log('my type is' +this.type);
        return this.type;
    }
}

再看看extends做了什么:

  • 继承父类的实例属性(调用父类的构造函数)
  • 继承父类的共享属性(原型链上有父类的原型对象)
  • 子类构造函数的proto指向父类构造函数(两个都是对象)(继承静态函数)
//true
console.log(Banana.__proto === Fruits);
//true
console.log(banana instance of Fruits);

es6继承的es5实现

知道extends做了什么之后,我们可以知道其转化成es5就是寄生组合式继承(组合=原型链继承+借用构造函数),并且设置子类构造函数的proto为父类构造函数.


function Fruits(type,size){
    this.type = type;
    this.size = size;
}
Fruits.sayHello = function(){
    console.log('Hello');
}
Fruits.prototype = {
    constructor:Fruits,
    sayType:function(){
        console.log('my type is' +this.type);
        return this.type;
    }
}

function Banana(type,size,color){
    Fruits.call(this,type,size);
    this.color = color;
}

function createObject(Parent){
    function Empty(){};
    Empty.prototype = Parent.prototype;
    return new Empty();
}

Banana.prototype = createObject(Fruits);
Banana.prototype.constructor = Banana;
Banana.prototype.sayColor = function(){
    console.log(this.color);
}

Banana.__proto__ = Fruits;

通过babeljs转码成ES5来查看,更严谨的实现。

//es6版本
class Parent{
    constructor(name){
        this.name = name;
    }
    static sayHello(){
        console.log('hello');
    }
    sayName(){
        console.log('my name is ' + this.name);
        return this.name;
    }
}
class Child extends Parent{
    constructor(name, age){
        super(name);
        this.age = age;
    }
    sayAge(){
        console.log('my age is ' + this.age);
        return this.age;
    }
}
// 对转换后的代码进行了简要的注释
"use strict";
// 主要是对当前环境支持Symbol和不支持Symbol的typeof处理
function _typeof(obj) {
    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
        _typeof = function _typeof(obj) {
            return typeof obj;
        };
    } else {
        _typeof = function _typeof(obj) {
            return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
        };
    }
    return _typeof(obj);
}
// _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。
function _possibleConstructorReturn(self, call) {
    if (call && (_typeof(call) === "object" || typeof call === "function")) {
        return call;
    }
    return _assertThisInitialized(self);
}
// 如何 self 是void 0 (undefined) 则报错
function _assertThisInitialized(self) {
    if (self === void 0) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
}
// 获取__proto__
function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
}
// 寄生组合式继承的核心
function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function");
    }
    // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 
    // 也就是说执行后 subClass.prototype.__proto__ === superClass.prototype; 这条语句为true
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            writable: true,
            configurable: true
        }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
}
// 设置__proto__
function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
    };
    return _setPrototypeOf(o, p);
}
// instanceof操作符包含对Symbol的处理
function _instanceof(left, right) {
    if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
        return right[Symbol.hasInstance](left);
    } else {
        return left instanceof right;
    }
}

function _classCallCheck(instance, Constructor) {
    if (!_instanceof(instance, Constructor)) {
        throw new TypeError("Cannot call a class as a 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);
    }
}
//将共享属性设置到原型对象上,静态属性设置到构造函数上
function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
}

// ES6
var Parent = function () {
    function Parent(name) {
        _classCallCheck(this, Parent);
        this.name = name;
    }
    _createClass(Parent, [{
        key: "sayName",
        value: function sayName() {
            console.log('my name is ' + this.name);
            return this.name;
        }
    }], [{
        key: "sayHello",
        value: function sayHello() {
            console.log('hello');
        }
    }]);
    return Parent;
}();

var Child = function (_Parent) {
    _inherits(Child, _Parent);
    function Child(name, age) {
        var _this;
        _classCallCheck(this, Child);
        // Child.__proto__ => Parent
        // 所以也就是相当于Parent.call(this, name); 是super(name)的一种转换
        // _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。
        _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));
        _this.age = age;
        return _this;
    }
    _createClass(Child, [{
        key: "sayAge",
        value: function sayAge() {
            console.log('my age is ' + this.age);
            return this.age;
        }
    }]);
    return Child;
}(Parent);

var parent = new Parent('Parent');
var child = new Child('Child', 18);
console.log('parent: ', parent); // parent:  Parent {name: "Parent"}
Parent.sayHello(); // hello
parent.sayName(); // my name is Parent
console.log('child: ', child); // child:  Child {name: "Child", age: 18}
Child.sayHello(); // hello
child.sayName(); // my name is Child
child.sayAge(); // my age is 18


从babel转译我们可以认识到几点:

  • 尽量使用Object.create来创造父构造函数的实例
//创建一个superClass的实例,constructor属性指向subClass
subClass.prototype = Object.create(superClass&&superClass.prototype,{
    constructor:{
        value:subClass,
        writable:true,
        configurable:trues
    }
});
//替代方案
function Empty(){};
Empty.prototype = superClass.prototype;
subClass.protoype = new Empty();
subClass.protoype.constructor = subClass;
  • 尽量使用Object.setPrototypeOf而不是proto
Object.setPrototypeOf(subClass,superClass);
//替代方案,不推荐
subClass.__proto__ = superClass.__proto__

模拟一下转换实现:

class Fruit{
    constructor(name){
        this.name = name;
    }
    static sayHello(){
        console.log("hello");
    }
    sayName(){
        console.log(this.name);
    }
}
class Banana extends Fruit{
    constructor(name,color){
        super(name);
        this.color = color;
    }
    sayColor(){
        console.log(this.color);
    }
}

//转换实现:
function createPropertiesByObject(target,props){
    for(var i =0;i

你可能感兴趣的:(一篇就够-JS继承的多种方式和实现)