zoukankan      html  css  js  c++  java
  • JavaScript 的继承

    继承是OO语言中的一个最为人津津乐道的概念。ECMAScript实现继承主要是通过原型链来实现的。

    1,原型链

    我们知道,每一个构造函数都有一个原型对象,这个函数包含一个指向构造函数的指针,同时每个实例都有一个指向原型对象的内部指针。也就是说,当我们访问一个实例的属性时,先在实例中查找,如果没找到,就通过内部指针去原型中查找,若还是没找到,再通过原型的内部指针查找原型的原型对象。

    //父类
    
        function Parent(){
    
            this.name = "父类";
    
        }
    
        //父类原型
    
        Parent.prototype.getName = function(){
    
            console.log(this.name );
    
        };
    
        //子类
    
        function Son(){
    
            this.age = 23;
    
        }
    
        //子类继承父类
    
        Son.prototype = new Parent();//注意这个地方父类的实例方法也在子类的原型里面了,例如name
    
        //给子类原型添加方法
    
        Son.prototype.getAge = function(){
    
            console.log(this.age)
    
        };
    
        //新建对象,继承子类的实例和原型
    
        var test = new Son();
    
        console.log(test.name);
    
        test.getName();
    
        console.log(test.age);
    
        test.getAge();
    
        console.log(test);
    
        console.log(Son.prototype);
    
        console.log(Parent.prototype);
    
        //确定实例与原型的关系 两种方法
    
        // instanceof
    
        alert(test instanceof Object);
    
        alert(test instanceof Son);
    
        alert(test instanceof Parent);
    
        // isPrototypeOf
    
        alert(Object.prototype.isPrototypeOf(test));
    
        alert(Son.prototype.isPrototypeOf(test));
    
        alert(Parent.prototype.isPrototypeOf(test));

    2a4151e3-7bdd-4997-9656-9e5dfb08c480

    把test和Son的原型都打印出来,就是在控制台可以看看原型链的一个继承,test的原型如上图,指向Son,Son有个age实例属性,有getAge原型属性,有name原型属性,Son 的原型指向Parent函数。整个的原型链就是这样继承下来的。

    2,借用构造函数

    原型链的的弊端就是,在创建子类的实例的时候,不能向父类的构造函数传参数。借用构造函数可以解决这个问题,组合继承就是结合构造函数和原型链来的,现在先单独的看下构造函数是怎么实现传参的功能的。

    //父类
    
        function Parent(name){
    
            this.name = name;
    
        }
    
        //子类
    
        function Son(name){
    
            Parent.call(this, name)
    
        }
    
        var p1 = new Parent('p1');
    
        console.log(p1.name);// p1
    
        var o1 = new Son('o1');
    
        console.log(o1.name);// o1
    
        var o2 = new Son('o2');
    
        console.log(o2.name);// o2

    以上可以看到,主要是借用call函数来实现传参的功能,call实现的话,只会调用父函数的示例属性和方法。

    3,组合继承

    上边已经提到组合继承,将两种优点结合起来,先看下示例,

    //父类
    
        function Parent(name){
    
            this.name = name;
    
        }
    
        Parent.prototype.getName = function() {
    
            console.log(this.name);
    
        }
    
        //子类
    
        function Son(name){
    
            Parent.call(this, name);
    
        }
    
        Son.prototype = new Parent('');
    
        Son.prototype.constructor = Son;
    
        var o1 = new Son('o1');
    
        console.log(o1.name);
    
        o1.getName();// o1
    
        var o2 = new Son('o2');
    
        console.log(o2.name);
    
        o2.getName();// o2

    以上两种已经写的比较详细了,Son.prototype.constructor = Son;这句需要注意一下,Son的原型已经被重写过,constructor这个属性需要重新指向本身,打印出o1,在控制台即可看出。

    4,原型式继承

    借用已有的对象去创建新对象,和前面的原型链相似,

    两者区别是这里是根据对象创建新对象,原型链则是根据new出来的函数实现继承。

    function creat(obj){
    
            function F(){};
    
            F.prototype = obj;
    
            return new F;
    
        }
    
        var obj = {
    
            name: 'abc',
    
            color: ['red', 'blue']
    
        }
    
        var fn1 = creat(obj);
    
        fn1.color.push('black')
    
        console.log(fn1)
    
        var fn2 = creat(obj);
    
        fn2.color.push('white')
    
        console.log(fn2)
    
        console.log(obj)

    1a738ec9-34cd-4db4-b504-f3031d49c0d3

    打印出fn1能够看出,fn1指向的是一个临时函数F,F的原型指向obj,这个函数实现了原型继承,但是类和实例的关系却是没法清晰的判断的。另,上图第一行是三个,下边是4个,正好验证了原型的动态性。

    既然跟原型链的那么像,就写了个如果obj是函数的,作为对比来看下两者在代码上有何区别,原型的继承,如下

    function obj() {
    
        }
    
        obj.prototype.name = 'abc';
    
        obj.prototype.color = ['red', 'blue'];
    
        var fn1 = new obj;
    
        fn1.color.push('black')
    
        console.log(fn1)
    
        var fn2 = new obj;
    
        fn2.color.push('white')
    
        console.log(fn2)

    打印结果跟上边是一样的,区别就是下边的能清晰的看到原型链的继承关系。

    5,寄生式继承

    寄生式跟原型式是紧密联系的,可以说是寄生式在原型式的基础上的,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后返回对象。

    function creat(obj){
    
            function F(){};
    
            F.prototype = obj;
    
            return new F;
    
        }
    
        var obj = {
    
            name: 'abc',
    
            color: ['red', 'blue']
    
        }
    
        function creatAnother(obj) {
    
            var fn1 = creat(obj);
    
            fn1.getName = function() {
    
                console.log(this.name);
    
            }
    
            return fn1;
    
        }
    
        var anotherObj = creatAnother(obj);
    
        anotherObj.getName();

    使用寄生式继承来为对象添加函数,会由于不能做到函数复用率而降低效率。 

    6,寄生组合式继承

    上面提到的组合继承是js中常用的继承模式,但是是有不足的地方,就是对父类构造函数调用了两次。一次是创建子类型原型的时候,一次是在子类型构造函数内部。

    //父类
    
        function Parent(name){
    
            this.name = name;
    
        }
    
        Parent.prototype.getName = function() {
    
            console.log(this.name);
    
        }
    
        //子类
    
        function Son(name){
    
            Parent.call(this, name);//第二次调用
    
        }
    
        Son.prototype = new Parent(''); //第一次调用
    
        Son.prototype.constructor = Son; // 将子类的constructor属性指回自己
    
        var o1 = new Son('o1');
    
        console.log(o1.name);
    
        o1.getName();
    
        var o2 = new Son('o2');
    
        console.log(o2.name);
    
        o2.getName();

    第一次调用,是给子类的原型上边加上父类的实例属性name,第二次是给子类实例添加实例属性name,子类的实例属性就屏蔽掉原型里面name属性。

    那能不能再创建子类型的原型的时候,只将父类的原型给子类,没有必要在子类的原型上创建多余的属性。按照这个想法,用两种方法实现了下,

    function creat(obj){
    
            function F(){};
    
            F.prototype = obj;
    
            return new F;
    
        }
    
        function inherit(son, parent) {
    
            var proto = creat(parent.prototype);
    
            proto.constructor = son;
    
            son.prototype = proto;

    } //父类 function Parent(name){ this.name = name; } Parent.prototype.getName = function() { console.log(this.name); } //子类 function Son(name){ Parent.call(this, name) } Son.prototype = new Parent(''); //第一种写法 // Son.prototype = Parent.prototype; //第二种写法 // inherit(Son, Parent) // 第三种写法 Son.prototype.constructor = Son; var o1 = new Son('o1'); console.log(o1); console.log(Son); console.log(Parent);

    第二种是自己写着简单实现的一个写法,后来发现这样会把原型指向搞乱掉,Son.prototype.constructor = Son;这句会将父类的constructor 也指向到Son。

    第三种就是寄生组合继承了,只调用了一次构造函数,又避免在子类创建多余属性。

  • 相关阅读:
    菜鸟学IT之四则运算升级版
    菜鸟学IT之简易四则运算程序开发
    菜鸟学IT分布式版本控制系统Git的安装与使用
    Javascript 编程范式
    【每日一题】2013年12月10日
    关于闭包的一些小东西
    【每日一题】2013年12月12日
    javascript学习计划
    新来挂号,以后就开始好好的维护这个博客了~
    【每日一题】2013年12月11日
  • 原文地址:https://www.cnblogs.com/jesse-band/p/4483486.html
Copyright © 2011-2022 走看看