js中的继承-原型与原型链

面向对象的语言支持两种继承方式,接口继承和实现继承
js无法实现接口继承,只支持实现继承,主要通过原型链来实现。
具体实现方式有以下几种:

  • 原型链
  • 借用构造函数
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承
    逐一看下:

原型链

每个函数都有prototype(原型)属性,指向一个原型对象
原型对象都包含一个指向构造函数的指针(constructor)
通过函数实例化的对象中,都有一个指向原型对象的内部指针

通过这种特性,可以让一个对象A的原型对象等于另一个实例化的对象B,对象B的原型对象等于另一个实例化的对象C,这样层层递进,就构成了实例与原型的链条,也就是原型链

    function Person() {
      this.name = 'wang'
    } 
    Person.prototype.sayName = function () {
      console.log(this.name);
    }

    function Man() {
      this.age = 18;
    }
    //继承:子类的原型等于父类的实例对象
    Man.prototype = new Person(); 
    Man.prototype.sayAge = function () {
      console.log(this.age);
    }

    var p1 = new Man();
    p1.sayName();  //wang
    p1.sayAge();   //18

代码中子类的方法都可以正常使用,这就涉及到了对象的原型搜索机制

当访问一个实例属性时,首先在实例中搜索该属性,如果没有找到,则会继续搜索实例的原型,如果没有找到并且原型对象还有原型,则继续向上搜索,直到搜索到没有原型对象为止,也就是截止到Object,因为js中所有对象都继承自Object。
这也就能解释为什么所有实例都有toString()等公用方法。

对象识别

利用原型链继承实现的子类可以识别属于子类、父类、Object

    console.log(p1 instanceof Object);  //true
    console.log(p1 instanceof Person);  //true
    console.log(p1 instanceof Man);     //true
派生判断
    console.log(Object.prototype.isPrototypeOf(p1));  //true
    console.log(Person.prototype.isPrototypeOf(p1));  //true
    console.log(Man.prototype.isPrototypeOf(p1));     //true
原型链的问题:
  1. 属性为引用类型时,由于共享机制,操作实例属性会影响其他实例
  2. 无法在不影响其他实例的情况下,给父类的构造函数传参
    function Person() {
      this.name = 'wang'
      this.friends = ['zhang', 'liu']
    } 

    function Man() {
      this.age = 18;
    }
    //继承:子类的原型等于父类的实例对象
    Man.prototype = new Person();

    var p1 = new Man();
    var p2 = new Man();

    console.log(p1.friends);  //["zhang", "liu"]
    p2.friends.push('li');
    console.log(p1.friends);  //["zhang", "liu", "li"]

可以看到数组属性受到影响,所以实践中很少单独使用原型链。

借用构造函数

也叫伪造对象或经典继承,为解决原型链不足而生

基本思想:在子类构造函数内部调用父类构造函数

    function Person(name) {
      this.name = name;
      this.friends = ['zhang', 'liu']
    }

    function Man(name) {
      Person.call(this, name)
    }

    var p1 = new Man('wang');
    var p2 = new Man('zhang');
    console.log(p1.name);  //wang
    console.log(p2.name);  //zhang
    console.log(p1.friends);  //["zhang", "liu"]
    p2.friends.push('li');
    console.log(p2.friends);  //["zhang", "liu", "li"]
    console.log(p1.friends);  //["zhang", "liu"]

可以看出即可以给父类的构造函数传参,引用类型的属性又互不影响

问题

如果仅用借用构造函数,就会碰到构造函数的共性问题,无法将函数复用造成资源浪费,所以借用构造函数的技术也很少单独使用。

组合继承

也叫伪经典继承,将原型链和借用构造函数组合来用,发挥二者之长
使用原型链实现对原型属性和方法的继承
使用借用构造函数实现对实例属性的继承

    function Person(name) {
      this.name = name;
      this.friends = ['zhang', 'liu']
    }
    Person.prototype.sayName = function () {
      console.log(this.name);
    } 
    
    function Man(name, age) {
      //用构造函数继承属性
      Person.call(this, name);
      this.age = age;
    }
    //用原型链继承实例
    Man.prototype = new Person();
    Man.prototype.sayAge = function () {
      console.log(this.age);
    }

    var p1 = new Man('wang', 18);
    console.log(p1.name); //wang
    p1.sayName()  //wang
    p1.sayAge();  //18

    console.log(p1.friends);  //["zhang", "liu"]
    console.log(p1.__proto__.friends);  //["zhang", "liu"]

代码中用到了2种方式一起实现了继承
通过对象的__proto__属性,可以访问到对象的原型
可以看到实例对象和实例的原型对象同时存在父类属性和方法
组合继承避免了各自的缺陷,融合了优点,成为最常用的继承模式
但也有不足之处:

...
Person.call(this, name);  //第二次执行Person()
...
Man.prototype = new Person();  //第一次执行Person()

组合继承会指向两次父类的构造函数,在性能上不够理想
所以可以采用寄生组合式继承优化实现

在不同场景下,还有其他可选择的轻量级继承方式

原型式继承

核心思想:原型可以基于已有对象创建新对象,同时不必创建自定义类型

    function object(obj) {
      function Fn() { }
      Fn.prototype = obj;
      return new Fn();
    }

在函数内部,先创建了一个临时的构造函数,再将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质是执行了传入对象的浅复制。

    var person = {
      name: 'wang',
    } 
    var p1 = object(person);
    console.log(p1.name);

在ES5中通过新增Object.create()方法规范了原型式继承

    var person = {
      name: 'wang',
      friends: ['zhang', 'liu']
    }
    var p1 = Object.create(person);
    console.log(p1.friends);  //["zhang", "liu"]
    var p2 = Object.create(person);
    p2.friends.push('li');
    console.log(p1.friends);  //["zhang", "liu", "li"]

通过Object.create()可实现原型式继承
这种方式适用于让一个对象与另一个对象保持类似的情况,可不用构造函数
问题:可以看出,存在引用类型的属性的共享问题,也就是会被其它实例修改

寄生式继承

实现思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象

    var person = { 
      name: 'wang',
      friends: ['zhang', 'liu']
    }
    function createObject(obj){
      var copy = Object.create(obj);
      copy.sayName = function(){
        console.log(this.name);
      }
      return copy;
    }

    var p1 = createObject(person);
    p1.sayName();  //wang

类似寄生构造函数和工厂模式,
适用于:主要考虑对象而不是自定义类型和构造函数的情况
问题:在函数体内部定义方法,会无法使函数复用而降低效率

寄生组合式继承

针对组合式继承模式指向两次父类构造函数的不足,来优化

//实现父类原型拷贝副本给子类
    function inheritPrototype(subObj, superObj) {
      var prototype = Object.create(superObj.prototype);  //创建对象
      prototype.constructor = subObj;                     //增强对象
      subObj.prototype = prototype;                       //指定对象
    }

    function Person(name) {
      this.name = name;
      this.friends = ['zhang', 'liu']
    }
    Person.prototype.sayName = function () {
      console.log(this.name);
    }

    function Man(name, age) {
      //用构造函数继承属性,只需执行一次Person()
      Person.call(this, name);
      this.age = age;
    } 

    //Man.prototype = new Person();
    inheritPrototype(Man, Person);       //拷贝父类原型的副本,无需执行构造函数
    Man.prototype.sayAge = function () {
      console.log(this.age);
    }
 
    var p1 = new Man('wang', 18);
    console.log(p1.name); //wang
    p1.sayName()  //wang

寄生组合式继承是引用类型最理想的继承方式。

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

推荐阅读更多精彩内容

  • 美丽的早晨,美丽的心情。和老公去跑了步,全身出汗后的淋漓舒畅。让现在的坐在 书桌旁的我,心情美美的,愿我们都有一个...
    晓艳_f118阅读 177评论 0 0
  • 2019-10-7 1.一觉睡到自然醒,天气好,心情也好,小长假最后一天,一切都还是美好的。 2.玩够了疯够了,该...
    傲雪枫阅读 199评论 0 0
  • 今天是什么日子 起床:6:00 就寝:21:20 天气:阳光灿烂,空气清新 心情:愉快 任务清单 昨日完成的任务,...
    木子化敏阅读 97评论 0 4
  • 好久都没写作业了,都不知道扯啥啦~今天跟着豆豆花花学习了入门知识,最主要是学习了用幕布和markdown的操作,开...
    Aloptna阅读 204评论 0 0
  • 英语中有十大词性,分别是6个实词和4个虚词。 6个实词分别是: 1)名词 n. 表示人、事、物名称的词 2)代词...
    花上阅读 7,580评论 0 1