JavaScript做为一个面向对象语言,能够实现继承是必不可少的,可是因为自己并无类的概念(不知道这样说是否严谨,但在js中一切都类皆是对象模拟)因此在JavaScript中的继承也区别于其余的面向对象语言。可能不少初学者知道实现js继承的方法,但却对实现继承的原理一头雾水。因此,今天咱们就来图解JavaScript继承。(由于继承有关于原型相关知识,因此但愿你们对原型有必定的了解推荐阅读:理解JavaScript原型 梳理JavaScript原型总体思路)。css
下面咱们就开始看看JavaScript各类继承方式及他们的原理html
1.默认模式node
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的一个实例。app
根据上图能够看出,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 由此造成继承。函数
默认模式的方式没有继承上级构造函数自身的属性,只是能够经过原型链向上查找而使用它而已。若是继承者为本身设置该属性,则会屏蔽原型链上的其余同名属性。学习
看一看上面代码的输出能够看出1四、15行证实继承而来的属性并没能在自身创造一个新的该属性,只是经过原型向上查找的方式来获取该属性,正由于如此16~19行的输出能够看出,kid对name属性的更改会影响到父构造函数中的name属性。this
2.借用构造函数spa
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)。prototype
从上图能够很明显看出Article与StaticPage并无链接,也就是说使用借用构造函数的方式,由于直接以修改调用位置的方法使用Article构造函数,因此继承了Article内部的属性,独立建立出属性,可是因为没有使用StaticPage.prototype因此StaticPage会自动建立出一个空的prototype对象。因此StaticPage并无继承到Article原型链上的方法。3d
在上面的例子代码中有不少个输出,如今咱们来研究一下输出那些答案的缘由并借以证实上面的话~
首先1五、16行判断tags是否是article和page(注意这是两个实例化出的对象)的自身属性,返回值皆为true,由此能够说明StaticPage的确继承了Article中添加到this的属性。
1七、1八、1九、20行中,在page没有为tags专门赋值时可输出父构造内部tags的值即 ['js', 'css'] 当赋值为 ['html', 'node'] 后page的tags值改变但article的值并无改变(20行),因而可知StaticPage继承Article是独立创造了其内部的属性(由于是修改调用位置的方式,因此会建立新的属性而不会产生关联)。
2一、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();
临时构造函数的意思就是经过一个临时的构造函数来实现继承,正如上面代码的十一、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继承的学习过程当中有了不少对实现原理不理解的地方,致使我一直不能记住而且正确使用这部分知识,因此当我感受本身对继承有了一部分了解以后写下了这篇博客,博客中的内容都是我我的的理解,若是有解释不到位或理解有误差的地方还请大神告知,小女子在此谢过~