美文网首页
一篇就够-JS继承的多种方式和实现

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

作者: johe_jianshu | 来源:发表于2020-03-24 19:15 被阅读0次

    原型链继承

    方法:子构造函数的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<props.length;i++){
            let descriptor = props[i];
            //省略
            Object.defineProperties(target,descriptor.key,descriptor);
        }
    }
    function createProperties(target,protoProps,staticProps){
        createPropertiesByObject(target.prototype,protoProps);
        createPropertiesByObject(target,staticProps);
    }
    
    function createPolyFill(Parent){
        function Empty(){};
        Empty.protoype = Parent.prototype;
        return new Empty();
    }
    
    function inherit(Child,Parent){
        if(typeof Child!=='function'||typeof Parent!=='function'){
            throw new Error("");
        }
        Child.prototype = Object.create ? Object.create(Parent&&Parent.prototype,{
            constructor:{
                value:Child,
                writable:true,
                configurable:true
            }
        }) : createPolyFill(Parent);
        if(!Object.create){
            Child.prototype.constructor = Child;
        }
    }
    
    function setPrototypeOf(Child,Parent){
        setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf : function(C,P){
            C.__proto__ = P
        }
        setPrototypeOf(Child,Parent); 
    }
    
    var Fruit = function(){
        function Fruit(){
            this.name = name;
        }
        createProperties(Fruit,[{
            key:"sayName",
            value:function(){
                console.log(this.name)
            }
        }],[{
            key:"sayHello",
            value:function(){
                console.log("hello");
            }
        }])
        return Fruit;
    }()
    
    var Banana = function(Parent){
        inherit(Banana,Parent);
        function Banana(name,color){
            Fruit.call(name);
            this.color = color;
        }
        createProperties(Banana,[{
            key:"sayColor",
            value:function(){
                console.log(this.color)
            }
        }])
        setPrototypeOf(Banana,Parent);
        return Banana;
    }(Fruit)
    

    相关文章

      网友评论

          本文标题:一篇就够-JS继承的多种方式和实现

          本文链接:https://www.haomeiwen.com/subject/mceayhtx.html