js中的实现继承的几种方式

大纲:
  • 原型链
  • 借用构造函数
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承

1、原型链:

  • 什么是原型链?

原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法

 function Father(){     
    this.hair = "black";
  }
  Father.prototype.getHair = function(){
    return this.hair
  }

  function Son(){
    this.eyes = "blue";
  }
//Son继承了Father
//没有使用Son的默认原型,而是换了一个新原型。本质是重写原型
  Son.prototype = new Father();
 
  Son.prototype.getEyes =  function(){
    return this.eyes;
  }
//Son的实例对象,并不是函数
  var Gson = new Son();
  console.log(Gson.getHair());           //black
  console.log(Gson.getEyes());         //blue
  console.log(Son.prototype.getHair());       //black
  • 继承的实现:

1、通过创建Father的实例赋值给Son.prototype实现的继承。
2、实现的本质是重写原型对象,以一个新类型的实例取代。
3、即存在Father实例中的所有属性和方法,现在也存在Son.prototype中了

  • 捋一下Object,Father和Son,Gson之间的关系:

1、所有的引用类型都默认继承了Object,继承的方式也是原型链
2、Gson是Son的实例对象,通过var Gson = new Son()。此时Gson里的__ proto__指向Son的prototype。

  console.log(Gson.__proto__ == Son.prototype);   //true

3、Son继承了Father,通过Son.prototype = new Father()。此时Son的prototype作为实例对象,他的__ proto__属性指向Father的prototype

console.log(Son.prototype.__proto__ == Father.prototype);  //true

4、由于Son实现继承的本质是重写原型对象。Son.prototye的constructor属性已经不再指向Son。而是指向Father

console.log(Son.prototype.constructor == Son);   //false
console.log(Son.prototype.constructor == Father);    //true

5、所有函数的默认原型都是Object的实例,所以默认的原型都有__ proto__属性指向Object.prototye。这也是所有自定义类型都会继承toString(),valueOf()等默认方法的根本原因

console.log(Father.prototype.__proto__ == Object.prototype);  //true
console.log(Son.prototype.__proto__ == Object.prototype);  //true

6、Son继承了Father,Father继承了Object。而Gson是Son的实例对象。

  • 简单图示:


    image.png
  • 注意:

不能使用对象字面量创建原型方法。因为会改变constructor的指向,会导致原型链被切断

  • 问题:

1、包含引用类型值的原型。
2、在创建子类型的实例时,不能向超类型的构造函数中传递参数

 function Father(){
    this.colors = ["black","blue","red"]
  }
  function Son(){}

  Son.prototype = new Father();

  var Gson1 = new Son();
  Gson1.colors.push("yellow");
  console.log(Gson1.colors);   //["black", "blue", "red", "yellow"]

  var Gson2 = new Son();  
  console.log(Gson2.colors);    //["black", "blue", "red", "yellow"]
//这是因为Father的实例对象是Son.prototye,而prototype属性上的方法和属性都会共享

2、借用构造函数:(伪造对象或经典继承)

  • 基本思想:

在子类型构造函数的内部调用超类型构造函数。

  • 例子:

实际上是在Son的要创建的新实例(Gson1,Gson2)环境下调用了Father。
1、使用Father.call()方法,可以解决原型链包含引用类型值的原型的问题

 function Father(){
    this.colors = ["black","blue","red"]
  }
  function Son(){
    Father.call(this)        //this指向Son的新实例对象
  }

  var Gson1 = new Son();
  Gson1.colors.push("yellow");
  console.log(Gson1.colors);   //["black", "blue", "red", "yellow"]

  var Gson2 = new Son();  
  console.log(Gson2.colors);    //["black", "blue", "red"]

2、解决原型链传递参数问题

function Father(name){
    this.name = name
  }
  function Son(){
    //继承了Father,并传递参数
    Father.call(this,"Tom");
    //实例属性,要在继承之后添加
    this.age = 36;      
  }

  var Gson = new Son();
  console.log(Gson.name);     //Tom
  console.log(Gson.age);      //36 

3、组合继承:(伪经典继承)

是将原型链和借用构造函数相结合。
1、使用原型链实现对原型属性和方法的继承
2、通过借用构造函数来实现对实例属性的继承

  • 优点:

1、通过在原型上定义方法实现了函数复用
2、还能保证每个实例都有它自己的属性

  • 例子:
 function Father(){
    this.colors= ["black","blue","red"];   
  }
 
  function Son(name,age){
    //继承属性
    Father.call(this);
    this.name = name;
    this.age = age; 
  }

  //继承方法
  Son.prototype = new Father();
  Son.prototype.constructor = Son;
  Son.prototype.isAge = function(){
    console.log(this.age);
}
  Son.prototype.isName = function(){
    console.log(this.name);
  }


  var Gson1 = new Son("lili",11);
  Gson1.colors.push("yellow")
  console.log(Gson1.colors);         //["black", "blue", "red", "yellow"]
  console.log(Gson1.name);         //lili
  console.log(Gson1.age);            //11

  var Gson2 = new Son("bree",20);
  console.log(Gson2.colors);       //["black", "blue", "red"]
  console.log(Gson2.name);        //bree
  console.log(Gson2.age);          //20
  • 问题:

1、无论什么情况下,都会调用两次超类型构造函数。
2、一次是在创建子类型原型的适合
3、另一次是在子类型构造函数内部

4、原型式继承

  • 思想:

借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。

function obj(o){ //本质上obj()对传入的对象执行了一次浅复制
        function F(){}
        F.prototype = o;
        return new F();
    }
var lion = {
        name:"lion",
        friends:["giraffe","elephant","rabbit"]
    }
//将lion传入obj中,返回一个新对象。
//这个新对象的原型指向lion
//即lion上的属性被加到新对象上的原型上了
    var fox = obj(lion);
    fox.name = "fox";
    fox.friends.push("cat");     //friends属性被共享了

    var wolf = obj(lion);
    wolf.name = "wolf";
    wolf.friends.push("tiger");

console.log(lion.friends); //["giraffe", "elephant", "rabbit", "cat", "tiger"]
  • Object.create方法:

1、只有一个参数时,用法和obj()一样
2、有两个参数时,用法如下:

var lion = {
        name:"lion",
        friends:["giraffe","elephant","rabbit"]
    }
//类似描述符修改属性
    var fox = Object.create(lion,{
        name:{
            value:"fox"
        }
    });
    console.log(fox.name);
  • 优点:

当没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

  • 缺点:

包含引用类型值的属性始终会共享相应的值。

5、寄生式继承:

  • 本质:

1、创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再想真的是他做了所有工作一样返回对象
2、与寄生构造函数和工厂模式类似

function obj(o){ 
        function F(){}
        F.prototype = o;
        return new F();
    }
    function clo(z){       //封装继承过程
        var clone = obj(z);      //继承函数
        clone.sayHi = function(){
            alert('hi') 
        };
        return clone            //返回
    }
    var lion = {
        name:"lion",
        friends:["giraffe","elephant","rabbit"]
    }
    var zoo = clo(lion);
    zoo.sayHi()        //hi
    console.log(zoo.name); //lion
    console.log(zoo.friends); //["giraffe", "elephant", "rabbit"]
  • 注意:

obj()不是必需的,任何能够返回新对象的函数都适用于此模式

  • 缺点:

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

6、寄生组合式继承

  • 概念:

通过借用构造函数来继承属性,通过原型链的混成形式来继承方法

  • 基本思路:

不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已

  • 本质:

使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

  • 例子:
function obj(o){ 
        function F(){}
        F.prototype = o;
        return new F();
    }
    function inher(son,father){
        var pro = obj(father.prototype);  //创建对象
        pro.constructor = son;     //增强对象
        son.prototype = pro;    //指定对象
    }
    function Father(name){
        this.name = name;
        this.colors = ["black","blue","yellow"]
    }
    Father.prototype.isName = function(){
        alert(this.name)
    }
    function Son(name,age){
        Father.call(this,name);    //只调用一次Father
        this.age=age;
    }
    inher(Son,Father);
    Son.prototype.isAge=function(){
        alert(this.age)
    }
  • 优点:

1、只调用一次Father构造函数,也避免了再Son.prototype上面创建不必要的属性。
2、原型链能保持不变
3、是引用类型最理想的继承方式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355