JavaScript ES5类 原型 原型链 组合原型寄生式继承

Posted  Island

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript ES5类 原型 原型链 组合原型寄生式继承相关的知识,希望对你有一定的参考价值。

ES5类 原型  原型链 继承

  • javascript中,原型是相对于构造函数(类)的叫法(或者说概念),原型链是相对于构造函数(类)的实例对象的叫法。
  • 对于JavaScript对象,如果在对象自身上找不到该属性,那么就会向上沿着原型链继续查找该属性

创建一个ES5类

在ES5中,类由函数名首字母大写的函数,通过关键字new创建。

类的构造函数就是函数自身

ES5类的原型对象prototype是自身构造函数,该类的实例化对象的原型链对象__proto__也是该构造函数,这二者指向同一个引用

 类的prototype属性与对象的原型链__proto__属性:

因此,给类的原型对象prototype设置属性和方法,相当于给类的实例化对象的原型链对象设置属性和方法,因为二者是同一引用。

如果要访问这些prototype上的属性,那么可以在实例化对象的原型链上找到它们。

 ES5类与继承

以上,如果把一个类Box的原型对象设置为另一个类的实例化对象F,那么Box实例化对象的原型链就是该F对象。

F对象的原型链拥有F类原型的属性和方法(同一个引用),而Box类的实例化对象的原型链__proto__是F对象,也拥有了F类prototype的属性和方法,并且这些属性可以沿着原型链被访问到

 

 

 

以上理论上完成了继承,但是存在缺陷。因为Ball.prototype = new F() ,会把Ball.prototype原有的属性和方法覆盖掉,这显然不是我们想要的,也不符合继承的特性。

  

继承的实现

1 组合式继承

/*
            组合式继承:
                1 冒充:
                        子类构造函数调用外面父类构造函数,并绑定父类构造函数的this为子类的实例
                        这样子类的实例掉用父类的构造函数为自己设置了相应属性
                2 子类原型对象设置为父类对象:
                        其实就是将子类对象的原型链设置为父类对象
            以上:
                1冒充完成了子类对象自身属性的设置
                2完成了子类对象原型链的设置
            缺陷:
                父类构造函数执行两次,1,2各一次
                2会造成子类原型对象原有的属性和方法被覆盖、重置
        */
        function Box(_r) {
            //设置实例化对象的属性
            this.r = _r;
            console.log("constructor run");
        }
        Box.a = 3;
        Box.run = function () {
            console.log(Box.a);
        }
        //设置类的原型属性,设置实例化对象的原型链属性
        Box.prototype = {
            b: 10,
            play: function () {

            }
        }
        // 设置类的原型对象的构造函数为Box
        Object.defineProperty(Box.prototype, "constructor", {
            value: Box
        });

        function Ball(_r) {
            //冒充
            Box.call(this, _r);
        }
        Ball.prototype.c = 20;
        //设置子类对象的原型链为父类原型对象,会覆盖原有的方法和属性,如c
        Ball.prototype = new Box(3);
        var ball = new Ball(100);
        console.log(ball);

运行结果:

2 原型继承

 /* 
        
             原型继承:
                 1 设置中间类F,用F的原型对象接收父类的原型对象
                 2 不调用父类构造,解决了组合继承调用了两次父类构造的弊病
             总结:
                 只完成了原型的继承,没有调用父类的构造函数来设置子类对象的自身属性
                 方法是用一个类F(){},设置F的原型对象为父的原型对象,设置子类原型为F类实例化对象
                 F解决了实例化子类对象父类构造函数执行了两次的问题(new Box()变成了new F(),不调用父类构造)
              问题:
                 因为一次也不调用父类构造,使得对象实例化时无法传参
                 依然覆盖了子类原有原型属性,继承时,属性相同,应当保留子类属性,覆盖父类属性
        */

        function Box(_r) {
            //设置Box类实例化对象的属性
            this.r = _r;
            console.log("constructor run");
        }
        Box.a = 3;
        Box.run = function () {
            console.log(Box.a);
        }
        //设置类的原型属性,设置实例化对象的原型链属性
        Box.prototype = {
            b: 10,
            play: function () {

            }
        }
        Object.defineProperty(Box.prototype, "constructor", {
            value: Box
        });
        function F() { }
        F.prototype = Box.prototype;
        function Ball() { }
        Ball.prototype = new F();
        console.log(Ball.prototype);
        // Ball.prototype =Object.create(F.prototype);//IE9以上可以这样写
        console.log(new Ball());

 

运行结果:

 3 寄生式继承

  1. 组合继承存在调用两次父类构造的问题
  2. 原型继承存在不能实例化对象不能传参的问题
  3. 组合继承和原型继承都存在子类原有原型属性被覆盖的问题
  4. 因此推荐使用寄生式继承
 /* 
              寄生式继承:
                  1 解决子类原型对象属性被覆盖的问题:
                    设置F.property接收父类原型属性,将子类原型属性复制到F.property
                    再将F.peoperty设置给子类原型完成覆盖
                  2  解决调用父类构造问题:
                     将父类当成属性设置到子类的原型上,子类实例对象就可以在原型链上找到父类构造函数,
                     子类可以通过this调用该方法实现实例化,不通过调用外面父类的构造函数也可以完成传参
                     这样即实现了功能,又避免了组合继承调用父类构造次数过多和原型继承实例化无法通过构造传参的问题
        */
        function Box(_r) {
            //设置Box类实例化对象的属性
            this.r = _r;
            console.log("constructor run");
        }
        Box.a = 3;
        Box.run = function () {
            console.log(Box.a);
        }
        Box.prototype = {
            b: 10,
            play: function () {

            }
        }

        function Ball(_r) {
            this.supClass.apply(this, arguments);
        }
        Ball.prototype.walk = function () {
            console.log("walk");
        }
        extend(Ball, Box);
        Ball.prototype.play = function () {
            this.supClass.prototype.play.apply(this.arguments);
            console.log("finish method");
        }
        var b = new Ball(10);//print constructor run
        b.play();  // print finish method
        console.log(b);

        function extend(subClass, supClass) {
            function F() { }
            //将子类原型属性覆盖掉父类原型相同的属性,再设置给子类
            F.prototype = supClass.prototype;
            //复制原来原型下的内容
            if (Object.assign) {
                //Objec.asssign不兼容IE
                Object.assign(F.prototype, subClass.prototype);
            } else {
                if (Object.getOwnPropertyNames) {
                    var names = Object.getOwnPropertyNames(subClass.prototype);
                    for (var i = 0; i < names.length; i++) {
                        Object.defineProperty(F.prototype, name[i], Object.getOwnPropertyDescriptor(names[i]));
                    }
                }
                else {
                    for (var prop in subClass.prototype) {
                        F.prototype[prop] = subClass.prototype[prop];
                    }
                }
            }
            //完成属性覆盖
            subClass.prototype = new F();
            //将子类构造设置为自身,不调用外面父类的构造
            subClass.prototype.constructor = subClass;
            //父类设置到子类原型上去以供this调用
            subClass.prototype.supClass = supClass;
            if (supClass.prototype.constructor === Object) {
                supClass.prototype.constructor = supClass;
            }
        }

执行结果:

以上是关于JavaScript ES5类 原型 原型链 组合原型寄生式继承的主要内容,如果未能解决你的问题,请参考以下文章

ES5和ES6中对于继承的实现方法

JavaScript原型和原型链(必考三座大三之一)

JavaScript原型链以及ES3ES5ES6实现继承的不同方式

JavaScript原型链以及ES3ES5ES6实现继承的不同方式

_proto_ && prototype (原型 && 原型链)

JavaScript this指向ES5组合继承(构造函数+原型对象)错误处理