JavaScript 继承详解及示例代码

 更新时间:2016年09月06日 08:56:42   作者:Percy  
本文主要介绍JavaScript 继承的知识,这里整理了详细的资料及简单示例代码,帮助大家学习参考,有兴趣的小伙伴可以参考下

有些知识当时实在看不懂的话,可以先暂且放下,留在以后再看也许就能看懂了。

几个月前,抱着《JavaScript 高级程序设计(第三版)》,啃完创建对象,就开始啃起了 继承 ,然而啃完 原型链 就实在是看不下去了,脑子越来越乱,然后就把它扔一边了,继续看后面的。现在利用这个暑假搞懂了这个继承,就把笔记整理一下啦。

原型链(Prototype Chaining)

先看一篇文章,文章作者讲的非常不错,并且还配高清套图哦。lol…

链接: [学习笔记] 小角度看JS原型链

从原文中小摘几句

  1. 构造函数通过 prototype 属性访问原型对象
  2. 实例对象通过 [[prototype]] 内部属性访问原型对象,浏览器实现了 proto 属性用于实例对象访问原型对象
  3. 一切对象都是Object的实例,一切函数都是Function的实例
  4. Object 是构造函数,既然是函数,那么就是Function的实例对象;Function是构造函数,但Function.prototype是对象,既然是对象,那么就是Object的实例对象

确定原型与实例的关系

有两种方法来检测原型与实例的关系:

instanceof :判断该对象是否为另一个对象的实例

instanceof 内部运算机理如下:

functioninstance_of(L, R){//L 表示左表达式,R 表示右表达式
varO = R.prototype;// 取 R 的显示原型
 L = L.__proto__; // 取 L 的隐式原型
while(true) {
if(L ===null)
returnfalse;
if(O === L)// 这里重点:当 O 严格等于 L 时,返回 true
returntrue;
 L = L.__proto__; 
 } 
}

上面代码摘自: JavaScript instanceof 运算符深入剖析

isPrototypeOf() :测试一个对象是否存在于另一个对象的原型链上
这两个方法的不同点请参看: JavaScript isPrototypeOf vs instanceof usage

只利用原型链实现继承

缺点:1. 引用类型值的原型属性会被实例共享; 2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数

functionFather(){
this.name ="father";
this.friends = ['aaa','bbb'];
}
functionSon(){
}
Son.prototype = newFather();
Son.prototype.constructor = Son;

vars1 =newSon();
vars2 =newSon();

console.log(s1.name);// father
console.log(s2.name);// father
s1.name = "son";
console.log(s1.name);// son
console.log(s2.name);// father

console.log(s1.friends);// ["aaa", "bbb"]
console.log(s2.friends);// ["aaa", "bbb"]
s1.friends.push('ccc','ddd');
console.log(s1.friends);// ["aaa", "bbb", "ccc", "ddd"]
console.log(s2.friends);// ["aaa", "bbb", "ccc", "ddd"]

只利用构造函数实现继承

实现方法:在子类型构造函数的内部调用超类型构造函数(使用 apply() 和 call() 方法)

优点:解决了原型中引用类型属性的问题,并且子类可以向超类中传参

缺点:子类实例无法访问父类(超类)原型中定义的方法,所以函数复用就无从谈起了。

functionFather(name,friends){
this.name = name;
this.friends = friends;
}
Father.prototype.getName = function(){
returnthis.name;
};

functionSon(name){
// 注意: 为了确保 Father 构造函数不会重写 Son 构造函数的属性,请将调用 Father 构造函数的代码放在 Son 中定义的属性的前面。
 Father.call(this,name,['aaa','bbb']);

this.age =22;
}

vars1 =newSon('son1');
vars2 =newSon('son2');

console.log(s1.name);// son1
console.log(s2.name);// son2

s1.friends.push('ccc','ddd');
console.log(s1.friends);// ["aaa", "bbb", "ccc", "ddd"]
console.log(s2.friends);// ["aaa", "bbb"]

// 子类实例无法访问父类原型中的方法
s1.getName(); // TypeError: s1.getName is not a function
s2.getName(); // TypeError: s2.getName is not a function

组合继承(Combination Inheritance)

实现方法:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

functionFather(name,friends){
this.name = name;
this.friends = friends;
}
Father.prototype.money = "100k $";
Father.prototype.getName = function(){
console.log(this.name);
};

functionSon(name,age){
// 继承父类的属性
 Father.call(this,name,['aaa','bbb']);

this.age = age;
}

// 继承父类原型中的属性和方法
Son.prototype = newFather();
Son.prototype.constructor = Son;

Son.prototype.getAge = function(){
console.log(this.age);
};

vars1 =newSon('son1',12);
s1.friends.push('ccc');
console.log(s1.friends);// ["aaa", "bbb", "ccc"]
console.log(s1.money);// 100k $
s1.getName(); // son1
s1.getAge(); // 12

vars2 =newSon('son2',24);
console.log(s2.friends);// ["aaa", "bbb"]
console.log(s2.money);// 100k $
s2.getName(); // son2
s2.getAge(); // 24

组合继承避免了单方面使用原型链或构造函数来实现继承的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式,但是它也是有缺陷的,组合继承的缺陷会在后面专门提到。

原型式继承(Prototypal Inheritance)

实现思路:借助原型基于已有的对象创建新对象,同时不必因此而创建自定义类型。

为了达到这个目的,引入了下面的函数(obj)

functionobj(o){
functionF(){}
 F.prototype = o;
returnnewF();
}
varperson1 = {
 name: "percy",
 friends: ['aaa','bbb']
};
varperson2 = obj(person1);
person2.name = "zyj";
person2.friends.push('ccc');

console.log(person1.name);// percy
console.log(person2.name);// zyj
console.log(person1.friends);// ["aaa", "bbb", "ccc"]
console.log(person2.friends);// ["aaa", "bbb", "ccc"]
ECMAScript 5 通过新增 Object.create() 方法规范化了原型式继承。在传入一个参数的情况下, Object.create() 和 obj() 方法的行为相同。

varperson1 = {
 name: "percy",
 friends: ['aaa','bbb']
};
varperson2 =Object.create(person1);
person2.name = "zyj";
person2.friends.push('ccc');

console.log(person1.name);// percy
console.log(person2.name);// zyj
console.log(person1.friends);// ["aaa", "bbb", "ccc"]
console.log(person2.friends);// ["aaa", "bbb", "ccc"]

在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,可以选择使用这种继承。

寄生式继承(Parasitic Inheritance)

寄生式继承是与原型式继承紧密相关的一种思路。

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

functionobj(o){
functionF(){}
 F.prototype = o;
returnnewF();
}
functioncreatePerson(original){// 封装继承过程
varclone = obj(original);// 创建对象
 clone.showSomething = function(){// 增强对象
console.log("Hello world!");
 };
returnclone;// 返回对象
}

varperson = {
 name: "percy"
};
varperson1 = createPerson(person);
console.log(person1.name);// percy
person1.showSomething(); // Hello world!

寄生组合式继承(Parasitic Combination Inheritance)

先来说说我们前面的组合继承的缺陷。 组合继承最大的问题就是无论什么情况下,都会调用两次父类的构造函数:一次是创建子类的原型的时候,另一次是在调用子类构造函数的时候,在子类构造函数内部又调用了父类的构造函数。

functionFather(name,friends){
this.name = name;
this.friends = friends;
}
Father.prototype.money = "100k $";
Father.prototype.getName = function(){
console.log(this.name);
};

functionSon(name,age){
// 继承父类的属性
 Father.call(this,name,['aaa','bbb']);// 第二次调用 Father() , 实际是在 new Son() 时才会调用

this.age = age;
}

// 继承父类原型中的属性和方法
Son.prototype = newFather();// 第一次调用 Father()
Son.prototype.constructor = Son;

第一次调用使的子类的原型成了父类的一个实例,从而子类的原型得到了父类的实例属性;第二次调用会使得子类的实例也得到了父类的实例属性;而子类的实例属性默认会屏蔽掉子类原型中与其重名的属性。所以,经过这两次调用, 子类原型中出现了多余的的属性 ,从而引进了寄生组合式继承来解决这个问题。

寄生组合式继承的背后思路是: 不必为了指定子类的原型而调用父类的构造函数,我们所需要的无非就是父类原型的一个副本而已 。

本质上,就是使用寄生式继承来继承父类的原型,然后将结果返回给子类的原型。

functionobj(o){
functionF(){}
 F.prototype = o;
returnnewF();
}
functioninheritPrototype(son,father){
varprototype = obj(father.prototype);// 创建对象
 prototype.constructor = son; // 增强对象
 son.prototype = prototype; // 返回对象
}
functionFather(name,friends){
this.name = name;
this.friends = friends;
}
Father.prototype.money = "100k $";
Father.prototype.getName = function(){
console.log(this.name);
};

functionSon(name,age){
// 继承父类的属性
 Father.call(this,name,['aaa','bbb']);

this.age = age;
}

// 使用寄生式继承继承父类原型中的属性和方法
inheritPrototype(Son,Father);

Son.prototype.getAge = function(){
console.log(this.age);
};

vars1 =newSon('son1',12);
s1.friends.push('ccc');
console.log(s1.friends);// ["aaa", "bbb", "ccc"]
console.log(s1.money);// 100k $
s1.getName(); // son1
s1.getAge(); // 12

vars2 =newSon('son2',24);
console.log(s2.friends);// ["aaa", "bbb"]
console.log(s2.money);// 100k $
s2.getName(); // son2
s2.getAge(); // 24

优点:使子类原型避免了继承父类中不必要的实例属性。

开发人员普遍认为寄生组合式继承是实现基于类型继承的最理想的继承方式。

最后

最后,强烈推荐两篇很硬的文章

Javascript – How Prototypal Inheritance really works
JavaScript's Pseudo Classical Inheritance diagram (需要翻墙)

摘第二篇文章的一张硬图过来:


看完之后,秒懂原型链,有木有?

以上就是对JavaScript 继承的资料整理,后续继续补充相关资料谢谢大家对本站的支持!

相关文章

  • 前端js弹出框组件使用方法

    前端js弹出框组件使用方法

    这篇文章主要为大家详细介绍了前端js弹出框组件的使用方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • js left,right,mid函数

    js left,right,mid函数

    在JAVASCRIPT中LEFT,RIGHT,MID函数的等价函数,非常适合经常用写asp的朋友
    2008-06-06
  • JS中递归函数

    JS中递归函数

    编程语言中,函数Func(Type a,……)直接或间接调用函数本身,则该函数称为递归函数。递归函数不能定义为内联函数。这篇文章主要介绍了JS中递归函数的相关资料,需要的朋友可以参考下
    2016-06-06
  • JavaScript实现点击文字切换登录窗口的方法

    JavaScript实现点击文字切换登录窗口的方法

    这篇文章主要介绍了JavaScript实现点击文字切换登录窗口的方法,涉及javascript操作div层及相关样式的技巧,需要的朋友可以参考下
    2015-05-05
  • 时间处理工具 dayjs使用示例详解

    时间处理工具 dayjs使用示例详解

    这篇文章主要为大家介绍了时间处理工具 dayjs使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • 如何使用Bootstrap创建表单

    如何使用Bootstrap创建表单

    在本章中,我们将学习如何使用 Bootstrap 创建表单。Bootstrap 通过一些简单的 HTML 标签和扩展的类即可创建出不同样式的表单
    2017-03-03
  • 关于微信小程序map组件z-index的层级问题分析

    关于微信小程序map组件z-index的层级问题分析

    这篇文章主要给大家介绍了关于微信小程序map组件z-index的层级问题的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用微信小程序具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • setTimeout()与setInterval()方法区别介绍

    setTimeout()与setInterval()方法区别介绍

    计时器setTimeout()和setInterval()两个都是js的计时功能的函数两个有些区别,下面为大家简单介绍下,希望对大家有所帮助
    2013-12-12
  • 解决JS外部文件中文注释出现乱码问题

    解决JS外部文件中文注释出现乱码问题

    中文乱码在Java Web开发中经常出现,这是由于不同的部分编码不一样造成的,一般在开发中,我们把所有能设编码的地方,全部设置成UTF-8,但是有时候还是会出现乱码的情况。下面通过本文给大家分享JS外部文件中文注释出现乱码的解决方案,一起看看吧
    2017-07-07
  • JS、jquery实现几分钟前、几小时前、几天前等时间差显示效果的代码实例分享

    JS、jquery实现几分钟前、几小时前、几天前等时间差显示效果的代码实例分享

    在新浪微博首页看到每条微博后边显示的时间并不是标准的年-月-日格式,而是经过换算的时间差,如:发表于5分钟前、发表于“2小时前”,比起标准的时间显示格式,貌似更加直观和人性化
    2014-04-04

最新评论