图解JavaScript 继承

JavaScript作为一个面向对象语言,可以实现继承是必不可少的,但是由于本身并没有类的概念(不知道这样说是否严谨,但在js中一切都类皆是对象模拟)所以在JavaScript中的继承也区别于其他的面向对象语言。可能很多初学者知道实现js继承的方法,但却对实现继承的原理一头雾水。所以,今天我们就来图解JavaScript继承。(因为继承有关于原型相关知识,所以希望大家对原型有一定的了解推荐阅读:理解JavaScript原型   梳理JavaScript原型整体思路)。

下面我们就开始看看JavaScript各种继承方式及他们的原理

1.默认模式

  1.  1        /**
     2          * [默认继承模式]
     3          */
     4         function Parent(name) {
     5             this.name = name || ‘Adam‘;
     6         }
     7         var parent = new Parent();
     8         Parent.prototype.say = function() {
     9             return this.name;
    10         };
    11         function Child(name) {}
    12         Child.prototype = new Parent();
    13         var kid = new Child();
    14         console.log(kid.hasOwnProperty(‘name‘));//false
    15         console.log(parent.hasOwnProperty(‘name‘));//true
    16         console.log(kid.say());//Adam
    17         kid.name = ‘lili‘;
    18         console.log(kid.say());//lili
    19         console.log(parent.say());//Adam

    上面代码实现继承的是第12行的 Child.prototype = new Parent();通过这句代码将Child的原型成为Parent的一个实例。

根据上图可以看出,Parent是一个构造函数,并且有一个Parent.prototype对象,对象内部有一个say()方法(此处不一一列举prototype其他那只属性方法)。又存在一个Child构造函数,我们让Parent实例化出的对象当作Cihld的prototype(毕竟prototype也是一个对象,所以无何不可)。其实为了更方便理解,这句话可以改成 var parent1 = new Parent(); Child.prototype = parent1l; 这样就比较显而易见了。这样赋值的结果就是:Child是Parent实例化出的对象,所以Child.__proto__是Parent.prototype。kid为Cihld实例化出的对象,所以:kid.__proto__是Parent    建立原型链 kid--->Child--->Parent(Child.prototype)--->Parent.prototype  由此形成继承。

默认模式的方式没有继承上级构造函数自身的属性,只是可以通过原型链向上查找而使用它而已。如果继承者为自己设置该属性,则会屏蔽原型链上的其他同名属性。

看一看上面代码的输出可以看出14、15行证明继承而来的属性并没能在自身创造一个新的该属性,只是通过原型向上查找的方式来获取该属性,正因为如此16~19行的输出可以看出,kid对name属性的更改会影响到父构造函数中的name属性。

2.借用构造函数

 1        /**
 2          * [借用构造函数]
 3          */
 4         function Article(tags) {
 5             this.tags = tags || [‘js‘, ‘css‘];
 6         }
 7         Article.prototype.say = function() {
 8             return this.tags
 9         }
10         var article = new Article();
11         function StaticPage(tags) {
12             Article.apply(this,arguments);
13         }
14         var page = new StaticPage();
15         console.log(page.hasOwnProperty(‘tags‘));//true
16         console.log(article.hasOwnProperty(‘tags‘));//true
17         console.log(page.tags);//[‘js‘, ‘css‘]
18         page.tags = [‘html‘, ‘node‘];
19         console.log(page.tags);//[‘html‘, ‘node‘]
20         console.log(article.tags);//[‘js‘, ‘css‘]
21         //console.log(page.say());//报错 undefined is not a function
22         console.log(article.say());//[‘js‘, ‘css‘]

上面代码实现继承的是第12行的Article.apply(this,arguments);通过这句代码通过使用apply方法调用Article构造函数更改this指向(关于this:JavaScript中我很想说说的this)。

从上图可以很明显看出Article与StaticPage并没有连接,也就是说使用借用构造函数的方式,因为直接以修改调用位置的方法使用Article构造函数,所以继承了Article内部的属性,独立创建出属性,但是由于没有使用StaticPage.prototype所以StaticPage会自动创建出一个空的prototype对象。所以StaticPage并没有继承到Article原型链上的方法。

在上面的例子代码中有很多个输出,现在我们来研究一下输出那些答案的原因并借以证明上面的话~

首先15、16行判断tags是不是article和page(注意这是两个实例化出的对象)的自身属性,返回值皆为true,由此可以说明StaticPage的确继承了Article中添加到this的属性。

17、18、19、20行中,在page没有为tags专门赋值时可输出父构造内部tags的值即 [‘js‘, ‘css‘] 当赋值为 [‘html‘, ‘node‘] 后page的tags值改变但article的值并没有改变(20行),由此可见StaticPage继承Article是独立创造了其内部的属性(因为是修改调用位置的方式,所以会创建新的属性而不会产生关联)。

21、22行调用say方法。报错证明page并没能继承到Article.prototype上的方法。

3.借用和设置原型(组合继承)

 1        /**
 2          * 借用和设置原型
 3          */
 4         function Bird(name) {
 5             this.name = name || ‘Adam‘;
 6         }
 7         Bird.prototype.say = function() {
 8             return this.name;
 9         };
10         function CatWings(name) {
11             Bird.apply(this, arguments);
12         }
13         CatWings.prototype = new Bird();
14         var bird = new CatWings("Patrick");
15         console.log(bird.name);//Patrick
16         console.log(bird.say());//Patrick
17         delete bird.name;
18         console.log(bird.say());//Adam

借用和设置原型的方式是最常用的继承模式,它是结合前面两种模式,先借用构造函数再设置子构造函数的原型使其指向一个构造函数创建的新实例。

首先CatWings使用借用构造函数的方式创建新的示例bird这样bird可以独立创建出name属性而不用与父构造函数的name有关联。再将CatWings.prototype赋给Bird的实例化对象,这样又将这两个构造函数连接在一起是bird对象可以访问父构造函数原型链上的方法。

4.共享原型

 1         /**
 2          * 共享原型
 3          */
 4         function A(name) {
 5             this.name = name || ‘Adam‘;
 6         }
 7         A.prototype.say = function(){
 8             return this.name;
 9         };
10         function B() {}
11         B.prototype = A.prototype;
12         var b = new B();
13         console.log(b.name);
14         b.name = ‘lili‘;
15         console.log(b.say());

上面代码实现继承的是第11行的B.prototype = A.prototype;通过这句代码将B的原型更改为A的原型。

这种方法很简单,没有什么太多需要解释的,但是它的弊端也很大:它并不能继承到父构造内部的属性,而且也只是可以使用父构造原型上的属性方法,并且子对象更改原型链上的属性或方法同时会影响到父元素~

5.临时构造函数

 1         /**
 2          * 临时构造函数
 3          */
 4         function C(name) {
 5             this.name = name || ‘Adam‘;
 6         }
 7         C.prototype.say = function() {
 8             return this.name;
 9         };
10         function D() {}
11         var E = function() {};
12         E.prototype = C.prototype;
13         D.prototype = new E();

临时构造函数的意思就是通过一个临时的构造函数来实现继承,正如上面代码的11、12行。

这个图可能画的不是那么易于理解,但我们的重点放在D.prototype那个椭圆上,你就会发现上面同样写着 new E() 是的他就是一个E构造函数的实例,而E在整个继承过程中不会出现实际的用处,他的作用只是为了对父构造和子构造做一个连接,所以被称为临时构造函数。这样做的优点是什么呢? 首先他能解决共享原型的最大弊端就是可以同时更改同一个原型并且会影响到其他人,但这种方法中,虽然E与C是共享原型,但D使用过默认继承的方式继承的原型,就没有权限对C.prototype进行更改。

6.原型继承

原型继承与上述几种继承模式有着很大的区别,上面的继承模式皆是模拟类的继承模式,但原型继承中并没有类,所以是一种无类继承模式。

 1        /**
 2          * [原型继承]
 3          * @type {Object}
 4          */
 5         function object(proto) {
 6             function F() {}
 7             F.prototype = proto;
 8             return new F();
 9         }
10
11         var person = {
12             name: ‘nana‘,
13             friends: [‘xiaoli‘, ‘xiaoming‘]
14         };
15
16         var anotherPerson = object(person);
17         anotherPerson.friends.push(‘xiaohong‘);
18         var yetAnotherPerson = object(person);
19         anotherPerson.friends.push(‘xiaogang‘);
20         console.log(person.friends);//["xiaoli", "xiaoming", "xiaohong", "xiaogang"]21         console.log(anotherPerson.__proto__)//Object {name: "nana", friends: Array[4]}

可以看到上面5~9行object函数通过object函数我们实现了原型继承。而在整个代码中,虽然实现了anotherPerson和yetAnotherPerson对person这个对象的继承,但其中并没有构造函数。

由上图可以看出,因为构造函数的原型本就是一个对象,现在将一个需要被继承的对象设定为一个构造函数F的原型,并用这个构造函数实例化出一个对象anotherPerson,这样,这个对象anotherPerson就可以通过原型链查找找到person这个对象,并使用他上面的属性或者方法。

在ECMAScript5中,这种模式已经通过方法Object.create()来实现,也就是说,不需要推出与object()相类似的函数,他已经嵌在JavaScript语言之中。

由于在我对JavaScript继承的学习过程中有了很多对实现原理不理解的地方,导致我一直不能记住并且正确使用这部分知识,所以当我感觉自己对继承有了一部分了解之后写下了这篇博客,博客中的内容都是我个人的理解,如果有解释不到位或理解有偏差的地方还请大神告知,小女子在此谢过~

时间: 2024-10-06 01:24:15

图解JavaScript 继承的相关文章

图解Javascript原型链

本文尝试阐述Js中原型(prototype).原型链(prototype chain)等概念及其作用机制.上一篇文章(图解Javascript上下文与作用域)介绍了Js中变量作用域的相关概念,实际上关注的一个核心问题是:“在执行当前这行代码时Js解释器可以获取哪些变量”,而原型与原型链实际上还是关于这一问题. 我们知道,在Js中一切皆为对象(Object),但是Js中并没有类(class):Js是基于原型(prototype-based)来实现的面向对象(OOP)的编程范式的,但并不是所有的对象

闲聊javascript继承和原型

javascript继承已经是被说烂的话题了,我就随便聊一点~ 一.javascript的复制继承 javascript的继承有复制继承和原型继承,基于复制继承用的不太多,而且无法通过instanceof的验证 //拷贝继承,prototype.js的extend=> function extend(destination,source){ for(var property in source) destination[property]=source[properyt]; return des

javascript继承的三种方法

javascript并不是纯粹的面向对象的语言,因此也没有明确的继承方式,但可以通过一些方式来模拟继承.本文总结常见的javascript继承模拟方式 1,对象继承 //父类 function Person(name,age){ this.name = name; this.age = age; }; Person.prototype.height = "170cm"; //子类 function Boy(){ this.speak = function(){ alert("

javascript继承

原型链继承 1 <script> 2 function Parent(){ 3 this.name = 'mike'; 4 } 5 6 function Child(){ 7 this.age = 12; 8 } 9 Child.prototype = new Parent();//Child继承Parent,通过原型,形成链条 10 11 var test = new Child(); 12 alert(test.age); 13 alert(test.name);//得到被继承的属性 14

JavaScript继承基础讲解,原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承

说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象JS基础讲解,工厂模式.构造函数模式.原型模式.混合模式.动态原型模式>,接下来讲一般通过那些方法完成JavaScript的继承. 原型链 JavaScript中实现继承最简单的方式就是使用原型链,将子类型的原型指向父类型的实例即可,即“子类型.prototype = new 父类型();”,实现方法如下

javascript继承—prototype属性介绍(2)

js里每一个function都有一个prototype属性,而每一个实例都有constructor属性,并且每一个function的prototype都有一个constructor属性,这个属性会指向自身.这会形成一个非常有意思的链式结构.举例如下: function Person(){ this.name =12; } console.log(Person.prototype); console.log(Person.prototype.constructor);//输出Person,指向自身

一种基于ES5的JavaScript继承

关于JavaScript继承,方式很多,包括compile-to-javascript的语言TypeScript, CoffeeScript以及网站MDN, GitHub, Modernizr各种polyfill都给出了稳妥的实现方案. 从ES5的角度看,这其中一些方案在功能上OK,但在语义上却不尽如人意. 本人从这些方案中采取一些比较潮的思路,整理出一份方案,可实现与原生DOM类继承的风格一致,达到功能和语义兼得的效果(当然,就别再老想着99后ES3了). 如果你的WebApp是基于ES5运行

面向面试编程——javascript继承的6种方法

javascript继承的6种方法 1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 1.原型链继承. <script type="text/javascript"> function Person(name,sex) { this.name=name; this.sex=sex; this.friends=['李四']; this.getName=function(){ alert(this.name

javascript继承的实现方式介绍

javascript继承的实现方式介绍:作为面向对象的一门语言,继承自然是javascript所比不可少的特性,下面就简单介绍一下javascript实现继承的几种方式,希望能够对需要的朋友带来一定的帮助,下面进入正题.一.对象冒充: function A() { this.name="蚂蚁部落"; this.address="青岛市南区"; } function B() { this.target="提供免费的教程"; this.newA=A;