详细理解JS中的继承

正式说继承之前,有两个相关小点:

  1. JS只支持实现继承,即继承实际的方法,不支持接口继承(即继承方法的签名,但JS中函数没签名)
  2. 所有对象都继承了Object.prototype上的属性和方法。
  • 说继承之前还要再说一下原型。原型之所以很重要,原因之一就是可以利用它来实现JavaScript的继承。重写一个函数的原型对象,将其指定为另一个函数的实例,这样便实现了一个简单的原型链继承。

看一个利用原型链来实现继承的基础例子:

 1 function Super(){
 2     this.name=‘super‘;
 3 }
 4 Super.prototype.getName=function(){
 5     console.log(this.name);
 6 }
 7 function Sub(){
 8     this.name=‘sub‘;
 9 }
10
11 Sub.prototype=new Super();//重写原型,实现继承
12 var instance=new Sub();
13 instance.getName();//sub继承了getName方法

重写原型会让Sub的原型获得Super构造函数上和Super原型上的所有属性和方法; 但是这样单纯使用原型来继承也有问题,比如将上面的代码修改一下,在原型上添加一个引用类型的属性:

 1
 2 function Super() {
 3     this.name=‘super‘;
 4 }
 5 Super.prototype.getName=function() {
 6     console.log(this.name);
 7 };
 8 Super.prototype.color = ["red", "black"];
 9
10 function Sub() {
11 }
12
13 Sub.prototype=new Super();//实现继承
14
15 var instance=new Sub();
16 instance.color.push(‘white‘);//改变instance的color属性,push一个新的项
17 var ins = new Sub();
18 console.log(ins.color); //["red", "black", "white"]  可以看到ins的color属性也被改变了

可以看到在instance中修改color属性,但是在另一个实例ins中这个修改也被反映出来了,这就是一个问题。

  • ①  借用构造函数(又叫经典继承,伪造对象)

如果将属性和方法都写在原型上,利用其共享性来实现继承理论上没有问题,但是原型链的继承共享性固然好,可一个问题就出在其共享性上,对于引用类型的值,当在一个实例上修改了引用类型值之后,其他共享该引用类型的所有实例都随之改变了,如下:

1 function Arr(){
2 }
3 Arr.prototype.array=[‘red‘,‘black‘];
4 arr01=new Arr();
5 arr02=new Arr();
6 arr01.array.push(‘white‘);
7 console.log(arr02.array);//["red", "black", "white"] 

综上所以一般很少单独使用原型链,于是就有了借用构造函数这种思维,虽然这种做法也有一定的局限,如下:

 1 function superType(){
 2     this.color=[‘red‘,‘blue‘,‘yellow‘];
 3 }
 4 function subType(){
 5     //继承了superType
 6     superType.call(this);
 7 }
 8
 9 var instance01=new subType();
10 instance01.color.push(‘black‘);
11 console.log(instance01.color);//[‘red‘,‘blue‘,‘yellow‘,‘black‘]
12
13 var instance02=new subType();
14 console.log(instance02.color);//[‘red‘,‘blue‘,‘yellow‘]

借用构造函数这种方法主要就是:利用parent . call(this)来继承父级构造函数上公有的属性,且在一个实例上进行修改不会对其他实例造成影响;[ 注意:使用call这种不能继承原型上属性和方法的哦 ]

当然,如果上面的例子中superType()有参数的话,在subType函数内部调用时也可以为其传递参数:superType . call(this,parameter);

  • ②  下面来说JS中最常用的继承模式:组合继承
 1 function superType(name){
 2     this.name=name;
 3     this.color=[‘red‘,‘blue‘,‘yellow‘];
 4 }
 5 superType.prototype.sayName=function(){
 6     console.log(this.name);
 7 }
 8
 9 function subType(name,age){
10     //继承superType的属性
11     superType.call(this,name);
12     this.age=age;
13 }
14 //继承superType的方法
15 subType.prototype=new superType();
16 subType.prototype.constructor=subType;
17 subType.prototype.sayAge=function(){
18     console.log(this.age);
19 };
20
21 var instance01=new subType(‘lazy‘,20);
22 instance01.color.push(‘black‘);
23 console.log(instance01.color);//[‘red‘,‘blue‘,‘yellow‘,‘black‘]
24 instance01.sayAge();//20
25 instance01.sayName();//lazy
26
27 var instance02=new subType(‘chen‘,21);
28 console.log(instance02.color);//[‘red‘,‘blue‘,‘yellow‘]
29 instance02.sayAge();//21
30 instance02.sayName();//chen

这种方法就是既利用了call()来继承构造函数中公有的属性,同时又利用原型来继承原型对象上的属性和方法;这样让subType的实例既可以分别拥有各自独立的属性,也可以共用相同的方法了。

JS中用得做多的继承是组合式继承,而组合式继承最大的问题就是每次new一个subType的实例时,都会两次调用父级构造函数superType;

第一次是在重写subType的原型  : subType.prototype = new superType()  的时候。这一次调用让subType获得了superType构造函数上和原型对象上的属性和方法,且这些属性和方法是加在subType的原型对象中的;

第二次是在function subType(){

    superType.call(this,name);

      }

的时候,这一次在调用call方式时只获得了superType构造函数上的属性。注意这一次是在调用subtype构造函数创建subtype的实例,所以这次获得的属性方法是加在subType的实例中的,且这些实例中的属性其实会覆盖前面原型中同名的属性;

这样算起来每new一个subType的实例,就会两次调用superType。虽然这样两次下来就完全包含了superType的全部实例属性和方法,但执行了两次superType效率不算高。所以我们再来看另一个方法:

  • ③  寄生组合式继承

这种方法可以解决两次调用superType的情况,实际上 思路跟组合式继承是一样的;

 1 function superType(name){
 2     this.name=name;
 3     this.color=[‘red‘,‘blue‘,‘yellow‘];
 4 }
 5 superType.prototype.sayName=function(){
 6     console.log(this.name);
 7 }
 8
 9 function subType(name,age){
10     //继承superType
11     superType.call(this,name);
12     this.age=age;
13 }
14
15 function inheritPrototype(sub,sup){
16     //创建超类型构造函数的原型副本
17     var prototype=Object(sup.prototype);
18     //为其指定构造函数,增强对象
19     prototype.constructor=sub;
20     //重写sub的原型对象
21     sub.prototype=prototype;
22 }
23
24 //copy一份超类型构造函数的原型对象给子类型构造函数
25 inheritPrototype(subType,superType);
26
27 subType.prototype.sayAge=function(){
28     console.log(this.age);
29 };
30
31 var instance01=new subType(‘lazy‘,20);
32 instance01.color.push(‘black‘);
33 console.log(instance01.color);//[‘red‘,‘blue‘,‘yellow‘,‘black‘]
34 instance01.sayAge();//20
35 instance01.sayName();//lazy
36
37 var instance02=new subType(‘chen‘,21);
38 console.log(instance02.color);//[‘red‘,‘blue‘,‘yellow‘]
39 instance02.sayAge();//21
40 instance02.sayName();//chen

这种方法主要依然是利用   借用构造函数的方法来继承构造函数的属性,利用原型链的混合方法来继承方法;基本思路是:不必为了重写subType的原型而去调用一次superType,因为我们需要的也只是superType的原型对象的一个副本而已,所以有了inheritPrototype函数:

1 function inheritPrototype ( sub ,super) {
2      var   prototype = Object( super.prototype );   //创建副本
3      sub.prototype = prototype ;
4      prototype . constructor = sub ;
5 }

相比组合式继承这种方法高效率的地方就在于它只调用了一次superType构造函数;

  • ④寄生式继承 :在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种很有用的模式,看个例子:
 1 function createAnother(original) {
 2     var clone = Object(original);
 3     clone.sayHi = function () {
 4         console.log(‘hi‘);
 5     };
 6     return clone;
 7 }
 8
 9 var person = {
10     name: ‘lazy‘,
11     friends:[1,2,3]
12 };
13
14 var anotherPerson = createAnother(person);
15 anotherPerson.sayHi();
16 console.log(anotherPerson.name);

这个例子中,基于person对象创建了一个新的对象anotherPerson,这个新的对象不仅具有person的所有属性和方法,而且还有自己的sayHi方法;

菜鸟小白一枚,可能上述有错误或理解不对的地方,恳请指出~~谢谢!

时间: 2024-08-01 15:52:59

详细理解JS中的继承的相关文章

快速理解JS中的继承与原型链

语言是用来表达的工具.当我们需要代指某个东西的时候,通常称其为一个对象.在编程语言中,对象并不像真实世界中那样随处可见,随口可以指代.通常我们只有少数的原生对象,剩下的,需要我们自己去创建.在Java语言中,创建一只会“咯咯咯”叫的鸡时,我们是这么做的: public class Chicken{ public void makeSound(){ System.out.println("咯咯咯"); } } Chicken littleChicken = new Chicken();

理解JS中的call、apply、bind方法

理解JS中的call.apply.bind方法(*****************************************************************) 在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:apply . call .bind 三者都是用来改变函数的this对象的指向的:apply . call .bind 三者

理解js中的作用域以及初探闭包

前言 对于js中的闭包,无论是老司机还是小白,我想,见得不能再多了,然而有时三言两语却很难说得明白,反正在我初学时是这样的,脑子里虽有概念,但是却道不出个所以然来,在面试中经常会被用来吊自己的胃口,考察基础,虽然网上自己也看过不少相关闭包的文章,帖子,但貌似这玩意,越看越复杂,满满逼格高,生涉难懂的专业词汇常常把自己带到沟里去了,越看越迷糊,其实终归结底,用杨绛先生的一句话就是:"你的问题在于代码写得太少,书读得不够多",其实在我看来前者是主要的,是后者的检验, 自知目标搬砖20年(还

转:彻底理解js中this的指向,不必硬背

转:http://www.cnblogs.com/pssp/p/5216085.html 首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解this的时候会有种琢磨不透的感觉),那么接下来我会深入的探讨这个问题. 为什么要学习this?

理解JS中的prototype

JS中的phototype是JS中比较难理解的一个部分 本文基于下面几个知识点: 1 原型法设计模式 在.Net中可以使用clone()来实现原型法 原型法的主要思想是,现在有1个类A,我想要创建一个类B,这个类是以A为原型的,并且能进行扩展.我们称B的原型为A. 2 javascript的方法可以分为三类: a 类方法 b 对象方法 c 原型方法 例子: function People(name) { this.name=name; //对象方法 this.Introduce=function

彻底理解js中this的指向,不必硬背。

原文链接:http://www.cnblogs.com/pssp/p/5216085.html 首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解this的时候会有种琢磨不透的感觉),那么接下来我会深入的探讨这个问题. 为什么要学习th

轻松理解JS中的面向对象,顺便搞懂prototype和__proto__的原理介绍

这篇文章主要讲一下JS中面向对象以及 __proto__,ptototype和construcator,这几个概念都是相关的,所以一起讲了. 在讲这个之前我们先来说说类,了解面向对象的朋友应该都知道,如果我要定义一个通用的类型我可以使用类(class).比如在java中我们可以这样定义一个类: public class Puppy{ int puppyAge; public Puppy(age){ puppyAge = age; } public void say() { System.out.

js 中的继承

面试的时候总是被问到js的继承,平时都是应用,最近有时间就把js 的继承整理了一下,和java 中的继承做了一下比较,代码如下: js继承有5种实现方式: 1.对象冒充 <script>   function Parent(username){      this.username = username;      this.hello = function(){        alert(this.username);      }    }    function Child(userna

js中的继承问题

1.继承的概念:把别人的拿过来变成自己的,但自己不受影响. 2.js中最基本的继承就是原型继承. 3.原型继承:通过修改子级构造函数的prototype指向父级构造函数的实例对象. function Animal(name){ this.name=name; this.favor=['eating','sleeping']; } Cat.prototype=new Animal('Kitty'); function Cat(color){ this.color=color; } var cat=