# 类的声明方式

首先我们来说下类的声明方式,在JS中类的声明目前有两种方式

  1. 函数
function Parent() {}
  1. class
class Parent {}

下面我们步入正题,来看一看在js中是如何实现继承的?

# 构造函数实现继承










 







function Father() {
    this.firstName = 'fang'
}

Parent.prototype.say = function() {
  console.log('hello world');
};

function Child(name) {
  Father.call(this); // 将父级的this指向子类
  this.age = 18;
}

const child = new Child('f');
console.log(child); // Child {firstName: "fang", age: 18}
child.say() // child.say is not a function

上面这种继承方法的缺陷是不能继承原型对象上的属性和方法

# 原型链实现继承














 










function Father() {
    this.firstName = 'fang'
    this.height = 175
}

Father.prototype.say = function() {
  console.log('say something');
};

function Child(name) {
  this.age = 18;
}

Child.prototype = new Father()

const child1 = new Child()
const child2 = new Child()
child1.height = 180
child1.say() // say something

console.log(child1.height) // 180
console.log(child2.height) // 175
console.log(child1.__proto__ === child2.__proto__) // true

上面的实现方式看起来是没有问题的,但是我们对其做如下改造,就会发现问题了。


 
 
 
 






 
 









function Father() {
    this.carInfo = {
        price: 1000000,
        brand: '宝马'
    }
}

function Child(name) {}

Child.prototype = new Father()

const child1 = new Child()
const child2 = new Child()

child1.carInfo.brand = '林肯'
child2.carInfo.brand = '保时捷'

console.log(child1.carInfo.brand) // 保时捷
console.log(child2.carInfo.brand) // 保时捷

console.log(child1.__proto__ === child2.__proto__) // true

可以看出我们修改child2的汽车信息把child1的也改了。因为child1和child2的原型对象是同一个对象,carInfo是一个引用类型,所以修改child1的carInfo会影响到child2的carInfo。

# 组合方式













 



 










function Father() {
    this.carInfo = {
        price: 1000000,
        brand: '宝马'
    }
}

Father.prototype.say = function() {
  console.log('say something');
};

function Child(name) {
  Father.call(this)
  this.age = 18;
}

Child.prototype = new Father()

const child1 = new Child()
const child2 = new Child()

child1.carInfo.brand = '林肯'
child2.carInfo.brand = '保时捷'

console.log(child1.carInfo.brand) // 林肯
console.log(child2.carInfo.brand) // 保时捷

上面这种组合方式父类方法执行了两次,分别是Father.call(this)Child.prototype = new Father(),这样是没有必要的,下面我们来优化一下。

# 组合继承的优化1

















 










function Father() {
    this.carInfo = {
        price: 1000000,
        brand: '宝马'
    }
}

Father.prototype.say = function() {
  console.log('say something');
};

function Child(name) {
  Father.call(this)
  this.age = 18;
}

Child.prototype = Father.prototype

const child1 = new Child()
const child2 = new Child()

child1.carInfo.brand = '林肯'
child2.carInfo.brand = '保时捷'

console.log(child1.carInfo.brand) // 林肯
console.log(child2.carInfo.brand) // 保时捷

但是无法精准判断某个实例是不是某个类直接创建的:

console.log(child1 instanceof Child) // true
console.log(child1 instanceof Father) // true
console.log(child1.constructor) // Father
child1的construtore应该是Child,可是输出的却是Father。

# 组合继承优化2















 
 














function Father() {
    this.carInfo = {
        price: 1000000,
        brand: '宝马'
    }
}
Father.prototype.say = function() {
  console.log('say something');
}
function Child(name) {
  Father.call(this)
  this.age = 18;
}

Child.prototype = Object.create(Father.prototype)
Child.prototype.constructor = Child

const child1 = new Child()
const child2 = new Child()
child1.carInfo.brand = '林肯'
child2.carInfo.brand = '保时捷'

console.log(child1.carInfo.brand) // 林肯
console.log(child2.carInfo.brand) // 保时捷

console.log(child1 instanceof Child) // true
console.log(child1 instanceof Father) // true
console.log(child1.constructor) // Child

# 问题

下面代码运行后会输出什么呢?

class Test {
    constructor() {
        this.say = () => {
            console.log('say1')
        }
    }
    say = () => {
        console.log('say2')
    }
    say() {
        console.log('say3')
    }
    static say() {
        console.log('say4')
    }
}
const test = new Test()
test.say() 
test.__proto__.say()
Test.say()

运行结果输出

say1
say3
say4

为什么会输出啊上面的结果呢?通过babel编译上述代码,得到

















 
 
 

 
 
 


 
 
 
 
 
 
 
 
 
 
 




"use strict";

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; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

var Test = /*#__PURE__*/function () {
  function Test() {
    _classCallCheck(this, Test);

    _defineProperty(this, "say", function () {
      console.log('say2');
    });

    this.say = function () {
      console.log('say1');
    };
  }

  _createClass(Test, [{
    key: "say",
    value: function say() {
      console.log('say3');
    }
  }], [{
    key: "say",
    value: function say() {
      console.log('say4');
    }
  }]);

  return Test;
}();

为了方便查看,这里以输出内容作为方法名进行解释,可以看出say2通过_defineProperty方法绑定到了实例了,say1在下面绑定到了实例了,由于方法名称都是say所以say1覆盖了say2。say3和say4通过_createClass分别绑定到了原型对象和构造函数上。所以输出结果为

say1
say3
say4

为了方便查看,我们将代码做如下修改(这里省略箭头函数的say方法)

class Test {
    constructor() {
        this.say1 = () => {
            console.log('say1')
        }
    }
    say2() {
        console.log('say2')
    }
    static say3() {
        console.log('say3')
    }
}
const test = new Test()

可以看到constructor中声明的方法或属性挂在到实例上;类中没有用static修饰的方法挂载在了原型中;Static修饰的方法挂载在了构造函数中。

那么下面代码运行会输出什么呢?请暂停下不要看答案,看看和你想的结果一样吗,为什么呢?

class Father {
    say = () => {
        console.log('father')
    }
}

class Child extends Father {
    say() {
        console.log('child')
    }
}

const child = new Child()
child.say()

在展开这个问题之前,我们先来看个知识点:Reflect.construct

在普通的函数调用中(和作为构造函数来调用相对),new.target的值是undefined,否则其值为当前函数。可以用来判断一个构造函数是否用new关键字调用。

function Fn(){
    console.log(new.target);
}
Fn();//undefined
new Fn;//[Function:Fn]

Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。

function Greeting(name) {
  this.name = name;
}


// new 的写法
const instance = new Greeting('张三');

// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);

语法:

Reflect.construct(target, argumentsList[, newTarget])

参数:

  • target被运行的目标构造函数
  • argumentsList类数组,目标构造函数调用时的参数。
  • newTarget 可选作为新创建对象的原型对象的constructor属性, 参考 new.target 操作符,默认值为target。

返回值:以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。

异常:如果target或者newTarget不是构造函数,抛出TypeError,异常。

如果Reflect.construct()方法的第一个参数不是函数,会报错。

下面我们来看下具体发生了什么?上述代码经过babel转换,结果如下
























 



 




































 









 






"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; 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); }

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }

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; }

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

function _setPrototypeOf(o, p) { 
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; 
    return _setPrototypeOf(o, p);
}

function _createSuper(Derived) { 
    var hasNativeReflectConstruct = _isNativeReflectConstruct();
    return function _createSuperInternal() { 
        var Super = _getPrototypeOf(Derived), result; 
        if (hasNativeReflectConstruct) { 
            var NewTarget = _getPrototypeOf(this).constructor;
            result = Reflect.construct(Super, arguments, NewTarget); // Child {say: ƒ}
        } else { 
            result = Super.apply(this, arguments);
        }
        return _possibleConstructorReturn(this, result); // Child {say: ƒ}
    } 
}

function _possibleConstructorReturn(self, call) { 
    if (call && (_typeof(call) === "object" || typeof call === "function")) { 
        return call;
    } 
    return _assertThisInitialized(self);
}

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () { })); return true; } catch (e) { return false; } }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

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

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

var Father = function Father() {
    _classCallCheck(this, Father);

    _defineProperty(this, "say", function () {
        console.log('father');
    });
};

var Child = /*#__PURE__*/function (_Father) {
    _inherits(Child, _Father);

    var _super = _createSuper(Child);

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

        return _super.apply(this, arguments); // Child {say: ƒ}
    }

    _createClass(Child, [{
        key: "say",
        value: function say() {
            console.log('child');
        }
    }]);

    return Child;
}(Father);

var child = new Child();

child.say(); // father

从上面的代码可以看出Child函数最后返回了一个以Child为构造函数的对象,这个对象中的say方法是Father中的say方法,所以当child调用say方法的时候会输出father。

换成ES5实现的话就比较好理解了

function Fahter() {
    this.say = function() {
        console.log('father')
    }
}

function Child() {
    Fahter.call(this)
    // 粗狂的理解就相当于直接把父类的this方法拿到Child中来了
    this.say = function() {
        console.log('father')
    }
}

Child.prototype.say = function() {
    console.log('child')
}

Child.prototype = Object.create(Fahter.prototype)
Child.prototype.constructor = Child

const child = new Child()
child.say()  // father