笔记:JavaScript继承

本文摘录及参考自:
1. Javascript继承机制的设计思想
2. Javascript 面向对象编程(一):封装
3. Javascript面向对象编程(二):构造函数的继承
4. Javascript面向对象编程(三):非构造函数的继承
5. 继承与原型链
6. JavaScript 中的继承
7. JavaScript继承方式详解
8. JavaScript深入之继承的多种方式和优缺点
9. JavaScript深入之从原型到原型链
10. JavaScript深入之call和apply的模拟实现
11. JavaScript深入之new的模拟实现
12. JavaScript深入之创建对象
13. 深入理解JavaScrip面向对象和原型继承

继承的由来

1. 原始模式

JavaScript的继承从本质上来说是通过“原型链”(prototype chain)实现的。

我们要创建Dog这一个对象,通过对Dog的封装,原始模式可能是这样的:

例:

var Dog ={
     name:""
     type: ""
}  

var dogA = {}
dogA.name="测试1"
dogA.type="type"
var dogB ={}
dogB.name="测试2"
dogB.type="type"

可以看出,这样生成实例的方式非常麻烦,而且实例与原型之间没有联系

2. new

JavaScript在一开始的设计中,并没有引入“类”的概念(到ECMAScript6才引入class)。为了解决1中的问题,它引入了new关键字用来创建实例,new后面跟的不是类名,而是构造函数(方法)

例:

function Dog(name){
  this.name = name
}
var dogA = new Dog("测试");  // Dog {name:测试}

通过这种方式创建的dogA实例,有一个__proto__属性(3会解释这个属性)指向原型。内部使用this,该变量会绑定到实例对象上。
一般情况下,我们使用这种方式创建实例即可。但是现在有一个新的需求: 需要给Dog构造函数添加一个公共的属性type,所有通过该构造函数创建的实例共享这个属性。

例:

function Dog(name){
  this.name = name;
  this.type = "Dog"
}

var dogA = new Dog("测试A");
var dogB = new Dog("测试B");

console.log(dogA.type);  // Dog
console.log(dogB.type);  // Dog
dogA.type ="chanag type";
console.log(dogA.type);  // change type
console.log(dogB.type);  // Dog   期望是 change type

因此对于new 构造函数的方法,存在无法共享属性和方法的问题。

3. prototype

为了解决2中存在的问题, JavaScript引入了prototype属性,这个属性包含一个对象,我们称这个对象为prototype对象。所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

例:

function Dog(name){
  this.name = name;
}

Dog.prototype={ type:"Dog" }

var dogA = new Dog("测试A");
var dogB = new Dog("测试B");

console.log(dogA.type);  // Dog
console.log(dogB.type);  // Dog
//dogA.type="change type"; //这种方式只是在dogA的实例上添加了一个属性,并不是修改公共属性
Dog.prototype.type = "change type"
//dogA.__proto__.type ="chanag type";  与上面的方式效果相同
console.log(dogA.type);  // change type
console.log(dogB.type);  // change type

继承的几种方式

在了解了new 和 prototype之后,我们来看看JavaScript中继承的几种方式。

1. 构造函数绑定

使用call或apply方法,将父对象的构造函数绑定到子对象上。

例:

function Animal(){
    this.species = "动物";
}

function Cat(name,color){
    Animal.apply(this,arguments);  //Cat继承Animal
    this.name = name;
    this.color = color;
}

var cat1 = new Cat("测试1",'红色');
console.log(cat1);

输出结果如下图所示:

image.png

注意构造函数绑定的方法,不会继承 Animal.prototype上的属性和方法

2. prototype模式(组合继承)

该方式比较常见,如果“猫”的prototype对象,指向一个Animal实例,那么所有“猫”的实例,就能继承Animal了。

例:

function Animal(){
    this.species = "动物";
}

function Cat(name,color){
    this.name = name;
    this.color = color;
}

Cat.prototype = new Animal();
var cat1 = new Cat("测试1",'黄色');
console.log(cat1);

image.png

注意,该方式创建的Cat实例的constructor指向的是Animal而非Cat。原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数,而我们让Cat的prototype直接指向了一个Animal实例,因此Cat的构造函数变成了Animal。这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的)

为了解决该问题,我们需要手动将constructor属性指回Cat构造函数

例:

function Animal(){
    this.species = "动物";
}

function Cat(name,color){
    this.name = name;
    this.color = color;
}

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("测试1",'黄色');
console.log(cat1);
image.png

3. 直接继承prototype

第二种方法中,每次都要新建一个Animal对象,比较浪费内存。因此我们可以将不变的属性直接写入Animal.prototype,然后让Cat()跳过Animal(),直接继承Animal.prototype。

例:

function Animal(){
}
Animal.prototype.species = "动物"

function Cat(name,color){
    this.name = name;
    this.color = color;
}
Cat.prototype = Animal.prototype;
var cat1 = new Cat("测试1",'黄色');
console.log(cat1);
image.png

参照2. prototype模式 ,我们可以手动把Cat.prototype的constructor指回Cat

Cat.prototype.constructor = Cat;
image.png

这种方法虽然效率高,但是也有缺点 ,Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype(注意,方法2也有同样的问题,所有的Cat实例,共享了一个Animal实例)。 因此上面的代码中,已经将Animal.prototype对象的constructor属性改成了Cat

Cat.prototype.constructor = Cat;
console.log(Animal.prototype.constructor); // Cat

4. 利用空对象作为中介(寄生组合式继承)

第三种方法“直接继承prototype”存在上述的缺点,因此就有了第四种方法,利用一个空对象作为中介


function Animal(){}
Animal.prototype.species = "动物"


function Cat(name,color){
    this.name = name;
    this.color = color;
}

var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("测试1","黄色");
console.log(cat1);
image.png

我们将上面的方法,封装成一个函数,便于使用。(这实际上是ES5 Object.create的模拟实现,具体参照6. Object.create创建)

function extend(Child, Parent) {

    var F = function(){};

    F.prototype = Parent.prototype;

    Child.prototype = new F(); //空对象,消耗较少

    Child.prototype.constructor = Child;

    Child.uber = Parent.prototype;

  }

5. 拷贝继承

我们也可以通过将父对象的所有属性和方法拷贝进子对象来实现继承

例:

function Animal(){
 
}
Animal.prototype.species = "动物";

function Cat(name,color){
    this.name = name;
    this.color = color;
}

function copyExtend(Child,Parent){
    let p = Parent.prototype;
    let c = Child.prototype;
        
    // 注意constructor属性不会被遍历到
    for(let i in p ){
        c[i] = p[i];
    }
}
copyExtend(Cat,Animal);

let cat1 = new Cat("测试1",'红色');
console.log(cat1);
image.png

6. Object.create创建

ECMAScript 5 中引入了一个新方法: Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用create方法时传入的第一个参数

function Animal(){}
Animal.prototype.species = "动物";

let animal = new Animal();
let cat1= Object.create(animal);
cat1.name = "测试1";
cat1.color = "红色";
console.log(cat1);
image.png

注意这种方式创建的对象,constructor是Animal。 而且,包含引用类型的属性值始终都会共享相应的值。

例:

function Animal(){
    this.name =  'animal';
    this.testArray=['test1','test2','test3']
}
Animal.prototype.species = "动物";

let animal = new Animal();
let cat1= Object.create(animal);
cat1.name = 'cat1'
cat1.testArray.push("cat1");
console.log(cat1.name) ; // cat1
console.log(cat1.testArray); // ["test1", "test2", "test3", "cat1"]
let cat2 = Object.create(animal);
console.log(cat2.name ); //animal
console.log(cat2.testArray); // ["test1", "test2", "test3", "cat1"]

注意:修改cat1.name的值,cat2.name的值并未发生改变,并不是因为cat1和cat2有独立的 name 值,而是因为cat1.name = 'cat1',给cat1添加了 name 值,并非修改了原型上的 name 值。

7. 使用Class关键字创建

ECMAScript6引入了一套新的关键字用来实现class。

例:

class Animal{
   constructor(){
     console.log("Animal Constructor");
   }
}
Animal.prototype.species = "动物";

class Cat extends Animal{
    constructor(name,color){
        super();
        this.name = name;
        this.color = color;
    }
}

let cat1 = new Cat("测试1","红色")
console.log(cat1);
image.png

总结

JavaScript中的继承分为两种:

  • 原型链继承(对象间的继承): 借助已有的对象创建新的对象,将子类的原型指向父类,就相当于加入了父类这条原型链(例如,方法2,3,4,6,7)
  • 类式继承(构造函数间的继承): 在子类构造函数的内部调用超类的构造函数(例如1)。严格的类式继承并不是很常见,一般都是组合着用
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,118评论 0 21
  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,105评论 0 6
  • 转载地址:http://javascript.ruanyifeng.com/oop/prototype.html#...
    化城阅读 393评论 0 1
  • 曾去太行昆山村,岭秀寂静少人闻。 偶有写生学生过,鸡犬之声如乡魂。 寻果误入歧途路,突遇画师点白云。 长想隐居此山...
    绿野V仙踪阅读 318评论 0 0
  • 摘记: 而在近百年之后的今天,你会发现那时困扰着女性的问题,好像到现在也没解决。 你知道你应该追求自由,可是仍然被...
    Miki璐阅读 63评论 0 0