JavaScript继承新旧方法汇总

例子

我们生成两个构造函数,后面的例子都是让‘’猫‘’继承‘’动物‘’的所有属性和方法。

  • 动物(为了更好的理解各种继承,这里给动物附上了基本类型和引用类型)
function Animal() {    
    this.species = "动物"
    this.do = ['运动', '繁殖'] 
}
function Cat(name, color) {    
    this.name = name   
    this.color = color
}

1.简单的原型链

这可能是最简单直观的一种实现继承方式了

1.1 实现方法

function Animal() {    
    this.species = "动物"
    this.do = ['运动', '繁殖'] 
}  
function Cat(name, color) {    
    this.name = name   
    this.color = color
}
Cat.prototype = new Animal //重点!!!!!
Cat.prototype.constructor = Cat
var cat1 = new Cat('小黄', '黄色')
console.log(cat1.species) // 动物
console.log(cat1.do) // [ '运动', '繁殖' ] 

1.2 核心

这种方法的核心就这一句话:Cat.prototype = new Animal 也就是拿父类实例来充当子类原型对象

1.3 优缺点

然而这个方法虽然简单但是有一个很严重的问题:在我们修改一个实例的属性时,其他的也随之改变。

var cat1 = new Cat('小黄', '黄色')
var cat2 = new Cat('小白', '白色')
cat1.species = '哺乳动物'
cat1.do.push('呼吸')
console.log(cat1.species) // 哺乳动物
console.log(cat2.species) // 动物
console.log(cat1.do) // [ '运动', '繁殖', '呼吸' ]
console.log(cat2.do) // [ '运动', '繁殖', '呼吸' ]
  • 优点
  1. 容易实现
  • 缺点
    1. 修改cat1.do后cat2.do也变了,因为来自原型对象的引用属性是所有实例共享的。
      可以这样理解:执行cat1.do.push('呼吸');先对cat1进行属性查找,找遍了实例属性(在本例中没有实例属性),没找到,就开始顺着原型链向上找,拿到了cat1的原型对象,一搜身,发现有do属性。于是给do末尾插入了'呼吸',所以sub2.do也变了

    2. 创建子类实例时,无法向父类构造函数传参

1.4 继承链的紊乱问题

Cat.prototype = new Animal

任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的。加了这一行以后,Cat.prototype.constructor指向Animal。

alert(Cat.prototype.constructor == Animal) //true

更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。因此,在运行"Cat.prototype = new Animal();"这一行之后,cat1.constructor也指向Animal!

alert(cat1.constructor == Cat.prototype.constructor) // true

这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。

Cat.prototype.constructor = Cat

这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。

2. 借用构造函数

使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:Animal.apply(this, arguments)

2.1 实现方法

function Animal() {    
    this.species = "动物"
    this.do = ['运动', '繁殖'] 
}  
function Cat(name, color) {  
    Animal.call(this, arguments) ///重点!!!!
    this.name = name   
    this.color = color
}
var cat1 = new Cat('小黄', '黄色')
console.log(cat1.species) // 动物
console.log(cat1.do) // [ '运动', '繁殖' ] 

2.2 核心

借父类的构造函数来增强子类实例,等于是把父类的实例属性复制了一份给子类实例装上了(完全没有用到原型)

2.3 优缺点

var cat1 = new Cat('小黄', '黄色')
var cat2 = new Cat('小白', '白色')
cat1.species = '哺乳动物'
cat1.do.push('呼吸')
console.log(cat1.species) // 哺乳动物
console.log(cat2.species) // 动物
console.log(cat1.do) // [ '运动', '繁殖', '呼吸' ]
console.log(cat2.do) // [ '运动', '繁殖' ]
  • 优点:
  1. 解决了子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类构造函数传参
  • 缺点:
  1. 无法实现函数复用,过多的占用内存。
  2. 创建子类实例时,无法向父类构造函数传参

3. 组合继承(伪经典继承)

将原型链和借用构造函数的技术组合起来,发挥二者之长:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义的方法实现了函数复用,又能够保证每个实例都有它自己的属性。是实现继承最常用的方式。

3.1 实现方法

function Animal() {    
    this.species = "动物"
    this.do = ['运动', '繁殖'] 
}  
function Cat(name, color) {  
    Animal.call(this, arguments)//重点!!!!
    this.name = name   
    this.color = color
}
Cat.prototype = new Animal//重点!!!!
Cat.prototype.constructor = Cat
var cat1 = new Cat('小黄', '黄色')
console.log(cat1.species) // 动物
console.log(cat1.do) // [ '运动', '繁殖' ] 

3.2 核心

把实例函数都放在原型对象上,以实现函数复用。同时还要保留借用构造函数方式的优点,通过Animal.call(this);继承父类的基本属性和引用属性并保留能传参的优点;通过Cat.prototype = new Animal继承父类函数,实现函数复用。

3.3 优缺点

  • 优点:
  1. 不存在引用属性共享问题
  2. 可传参
  3. 函数可复用
  • 缺点:
  1. 子类原型上有一份多余的父类实例属性,因为父类构造函数被调用了两次,生成了两份。(私有属性一份,原型里面一份)

4. 原型式

道格拉斯·克罗克福德在2006年写了一篇文章,Prototypal Inheritance in JavaScript(JavaScript中的原型式继承)。在这篇文章中,他介绍了一种实现继承的方法,这种方法并没有使用严格意义上的构造函数。他的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型,为了达到这个目的,他给出了如下函数。

function object(o) {
    function F() {}
    f.prototype = o
    return new F()
}

在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅拷贝。

4.1 实现方法

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function Animal() {    
    this.species = "动物"
    this.do = ['运动', '繁殖'] 
} 
var Animal1 = new Animal 
var cat1 = object(Animal1)//重点!!!!

cat1.name = '小黄'
cat1.color = '黄色'

console.log(cat1.species) //动物
console.log(cat1.do) //["运动", "繁殖"]

4.2 核心

核心就是通过一个函数来得到一个空的新对象,再在空对象的基础上添加需要的方法(实例属性)

4.3 优缺点

  • 优点:
  1. 从已有对象衍生新对象,不需要创建自定义类型。
  • 缺点:
  1. 原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类
    原型对象,所以这个缺陷无可避免
  2. 无法实现代码复用

5. 寄生式

寄生式在我看来和原型式差别不大,只是把对空对象私有属性的添加封装成了一个函数。

5.1 实现方法

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function Animal() {    
    this.species = "动物"
    this.do = ['运动', '繁殖'] 
} 
function getCatObject(obj) {
    var clone = object(obj)//重点!!!!
    clone.name = '小黄'
    clone.color = '黄色'
    return clone
}

var cat1 = getCatObject(new Animal)

console.log(cat1.species) //动物
console.log(cat1.do) //["运动", "繁殖"]

5.2 核心

只是给原型式继承套了一个壳子而已。
对于寄生式的理解:创建新对象 -> 增强 -> 返回该对象,这样的过程叫寄生式继承,新对象是如何创建的并不重要。

5.3 优缺点

  • 优点:
  1. 不需要创建自定义类型。
  • 缺点:
  1. 无法实现代码复用

6. 寄生组合继承

前面说过,组合继承是JavaScript 最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。也就是会出现这种情况:



我们发现在私有属性和原型里面都有name和do的属性,这是因为调用了两次构造函数造成的后果,这必然会过多占用内存。
寄生组合继承完美的解决了这个问题。

6.1 实现方法

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

function Animal() {    
    this.species = "动物"
    this.do = ['运动', '繁殖'] 
} 
function Cat(name, color) {
    Animal.call(this, arguments)//重点!!!!
    this.name = name
    this.color = color
}


var proto = object(Animal.prototype)//重点!!!!
proto.constructor = Cat//重点!!!!
Cat.prototype = proto//重点!!!!

var cat1 = new Cat()

console.log(cat1.species) //动物
console.log(cat1.do) //["运动", "繁殖"]

6.2 核心

用object(Animal.prototype)切掉了原型对象上多余的那份父类实例属性

6.3 优缺点

  • 优点:
  1. 几乎完美
  • 缺点:
  1. 用起来有些麻烦,理论上没有缺点。

7. ES5使用 Object.create 创建对象

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

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

8. ES6使用 class 关键字

ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不一样的。 JavaScript 仍然是基于原型的。这些新的关键字包括 class
, constructor
, static
, extends
, 和 super
.
例子如下:

class Animal {
    constructor(species, canDo) {
        this.species = '动物'
        this.canDo = ['运动', '繁殖'] 
    }
}

class Cat extends Animal {
    constructor(name, color) {
        super()
        this.name = name
        this.color = color
    }
}
var cat1 = new Cat('小黄', '黄色')
console.dir(cat1)

9. 参考文献

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

推荐阅读更多精彩内容