上一片文章,给你们分享了对象的建立和函数的建立及优缺点。还不知道的小伙伴能够去面向对象,搞定对象这节先了解一下,回过头再来看这篇文章。javascript
什么是原型链以及书中介绍了好多种继承方法,优缺点是什么!java
当谈到继承时,JavaScript 只有一种结构:对象。每一个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个本身的原型对象( proto ) ,层层向上直到一个对象的原型对象为null。根据定义,null没有原型,并做为这个原型链中的最后一个环节。---来着MDNapp
这里是官方给出的解释,咱们用个例子,来具体的去看看这个原型链。在举例以前,咱们先来了解一下,原型和实例的关系。函数
每一个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。post
上面的这段还若是你还没理解,我仍是建议你去看面向对象,搞定对象,这里已经给你详细的解释了。这里就很少说了。
那咱们想一下,若是让一个函数的实例对象,指向函数的原型对象,会发生什么?ui
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
//继承 Father
Son.prototype = new Father();//Son.prototype被重写,致使Son.prototype.constructor也一同被重写
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
alert(instance.getFatherValue());//true
复制代码
instance实例经过原型链找到了Father原型中的getFatherValue方法. 为了找到getFatherValue属性,都经历了什么呢?this
instance --> new Father() --> Father.prototype --> object.prototype --> undefined 这就是你日思夜想不得解的原型链啊!是否是很好理解了。
实例与原型链接起来的链条,叫作原型链(我本身定义的)。 spa
上面的例子,就是原型链继承的标准例子。可是原型链继承,是有问题的。高级程序设计上说,他有两个问题:prototype
是有这两个问题,但我说也不必定,当你原型链不须要饮用类型,建立的子类型不须要传参!那就不存在问题啊!哈哈哈
别闹,毕竟这样的需求不多,甚至根本不可能有,仍是老实儿解决上面的问题吧。设计
原理:在子类型构造函数中调用超类型构造函数。
先来看看原型链函数的问题:
function Father (){
this.colors = ['red','green','blue'];
}
function Son (){
}
Son.prototype = new Father();
let instance1 = new Son();
instance1.colors.push('white');
console.log(instance1.colors);
let instance2 = new Son();
console.log(instance2.colors);
复制代码
执行以后,就会发现,返回的结果是同样的,也就是说,全部的实例都会共享colors这个属性。这并非不咱们想要的结果。
在子类型构造函数中调用超类型构造函数。
function Father(name){
this.colors = ['red','green','blue'];
}
function Son(name){
Father.call(this,name);
}
let instance1 = new Son();
instance1.colors.push('white');
console.log(instance1.colors); // ['red','green','blue','white'];
let instance2 = new Son();
console.log(instance2.colors); // ['red','green','blue'];
复制代码
请记住,函数只是在特定环境中执行的代码的对象,所以能够经过call()或apply()方法也能够在新建立的对象上执行构造函数。
这段话很好理解:谁干(调)的,谁负责。
结合上面的代码,instance1调用的colors属性,那就你instance1对象负责,我instance2没作任何事,我不负责。
function Father(name){
this.colors = ['red','green','blue'];
this.name = name; //新增code
}
function Son(name){
Father.call(this,name); //将name,传递给Father。
}
let instance1 = new Son('hanson'); //建立实例对象时,传入参数
instance1.colors.push('white');
console.log(instance1.colors); // ['red','green','blue','white'];
console.log(instance1.name); // 'hanson'
let instance2 = new Son();
console.log(instance2.colors); // ['red','green','blue'];
复制代码
很好理解嘛,经过构造函数Son,咱们给Father传了参。完美解决传参问题。
如何解决这两个问题呢?引出咱们下一个继承方法---组合继承。
组合继承也叫作伪经典继承,指的是将原型链和借用构造函数的技术组合在一块儿,从而发挥两者之长的继承模式
话很少说,上代码!
function Father(name){
this.colors = ['red','green','blue'];
this.name = name;
}
Father.prototype.sayName = function(){
console.log(this.name)
}
function Son(name,age){
Father.call(this,name); //将name,传递给Father。
this.age = age;
}
Son.prototype = new Father();
Son.prototype.sayAge = function(){
console.log(this.age);
}
let instance1 = new Son('hanson',18);//建立实例对象时,传入参数
Son.prototype.constructor = Son;
instance1.colors.push('white');
console.log(instance1.colors); // ['red','green','blue','white'];
console.log(instance1.sayAge); // 18
let instance2 = new Son('grey',20);
console.log(instance2.colors); // ['red','green','blue'];
console.log(instance2.sayName()); // 'grey'
console.log(instance2.sayAge()); //18
复制代码
总结:分别拥有本身的属性,还享有公共的方法,真好。
这两个继承方式,都是一个叫克罗克德的人提出的,咱也不知道,咱也不敢问,估计式为了后面的组合式寄生继承作铺垫?
//原型式继承
var person = {
name:'hanson',
friends:['sha','feng','qiang']
}
var another = Object(person); //复制一份
another.name = 'bo';
another.friends.push('lei');
console.log(another.friends); //['sha','feng','qiang','lei']
console.log(another.name); //'bo'
console.log(person.friends); //['sha','feng','qiang','lei']
复制代码
相比上面的方法,这个要简便的多,没用到构造函数,原型链。但问题也十分明显,污染引用属性。
//寄生式继承---也是克罗克德提出来的
function creat(obj) {
var clone = Object(obj);
clone.sayName = function(){
console.log('hanson')
}
return clone;
}
var person = {
age:18,
friends:['qiang','sha','feng']
}
var another = creat(person);
console.log(another.sayName());
console.log(person.sayName());
复制代码
其实道理没变,clone了一份对象,可是,一样,peron对象也被玷污了!不信,你打印一下,person也有了sayName()方法。牺牲太大,反正我不用~
在讲解以前呢,咱们先来看看,组合继承的缺点。
function Father(name){
this.name = name;
this,friends = ['qiang','sha','feng'] ;
}
Father.prototype.constructor = function(){
console.log(this.name);
}
function Son(name,age){
Father.call(this); //第二次调用
}
Son.prototype = new Father(); //第一次调用
Son.prototype.constructor = Son;
Son.prototype.sayName = function(){
console.log(this.name);
}
复制代码
咱们第一次调用超类型构造函数(Father),无非是想指定子类型的原型,让他们直接创建联系而已。 给他个副本,又如何!
function inheritPrototype(Son,Father){
var prototype = Object(Father.prototype);
prototype.constructor = Son;
Son.prototype = prototype;
}
复制代码
这个函数,接收两个参数,一个子类型,一个超类型。在函数内部,
function Father(name){
this.name = name;
this,friends = ['qiang','sha','feng'] ;
}
Father.prototype.constructor = function(){
console.log(this.name);
}
function Son(name,age){
Father.call(this); //第二次调用
}
inheritPrototype(Son,Father);
Son.prototype.sayName = function(){
console.log(this.name);
}
复制代码
这个例子,只调用了一次超类型构造函数,避免了在子类型上建立没必要要的属性和方法。是最理想的继承方式。 可是,我可能不会用。。。你会用吗?