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中传递特殊字符(+,&)的方法,有需要的朋友可以参考一下
    2014-01-01
  • 纯JS代码实现一键分享功能

    纯JS代码实现一键分享功能

    在qq空间,新浪微博,人人网等网络平台大家经常可以看到一键分享功能,那么基于js代码是如何实现一键分享的呢?下面脚本之家小编给大家介绍js实现一键分享功能的代码,需要的朋友参考下吧
    2016-04-04
  • JS实现排行榜文字向上滚动轮播效果

    JS实现排行榜文字向上滚动轮播效果

    这篇文章主要为大家详细介绍了JS实现排行榜文字向上滚动轮播效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-11-11
  • postman自定义函数实现 时间函数的思路详解

    postman自定义函数实现 时间函数的思路详解

    Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。这篇文章主要给大家介绍postman自定义函数实现 时间函数的思路详解,感兴趣的朋友一起看看吧
    2019-04-04
  • javascript中的offsetWidth、clientWidth、innerWidth及相关属性方法

    javascript中的offsetWidth、clientWidth、innerWidth及相关属性方法

    这篇文章主要介绍了javascript中的offsetWidth、clientWidth、innerWidth及相关属性方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • JavaScript中setInterval()和setTimeout()的用法及区别

    JavaScript中setInterval()和setTimeout()的用法及区别

    这篇文章主要给大家介绍了关于JavaScript中setInterval()和setTimeout()用法及区别的相关资料,Javascript的setTimeOut和setInterval函数应用非常广泛,它们都用来处理延时和定时任务,需要的朋友可以参考下
    2023-11-11
  • JavaScript动态检验密码强度的实现方法

    JavaScript动态检验密码强度的实现方法

    平时我们会在某些网站的注册页面或者更改密码的页面发现当我们输入密码时,会有一个类似于进度条的长条进行提示用户输入的密码强度。那么这种效果怎么实现的呢?下面小编给大家介绍下js动态检验密码强度的实现方法,一起看看吧
    2016-11-11
  • JavaScript中变量提升和函数提升实例详解

    JavaScript中变量提升和函数提升实例详解

    这篇文章主要给大家介绍了关于JavaScript中变量提升和函数提升的相关资料,以及JS变量提升和函数提升的顺序,文中给出了详细的介绍,需要的朋友可以参考下
    2021-07-07
  • JavaScript eval() 函数介绍及应用示例

    JavaScript eval() 函数介绍及应用示例

    eval(String) 函数可计算某个字符串,并执行其中的的 JavaScript 代码,该方法只接受原始字符串作为参数
    2014-07-07
  • JavaScript数组去重的3种方法和代码实例

    JavaScript数组去重的3种方法和代码实例

    这篇文章主要介绍了JavaScript数组去重的3种方法和代码实例,本文直接给出实例代码,需要的朋友可以参考下
    2015-07-07

最新评论