JavaScript--浅谈继承

ECMAScript实现继承的方式:

1.原型链

利用原型让一个引用类型继承另一个引用类型的属性和方法。

function Father(){
   this.name = 'father'  
}

function Son(){
    
}

//继承Father,将Father的实例赋给Son的原型
Son.prototype = new Father()

var son1 = new Son()
son1.name  // father     继承了Father的name属性
View Code

之前在《浅谈创建对象》中,我们提出 原型模式创建对象 是可以修改原型的,如下

 1 function Father(){
 2   this.name = 'father'  
 3 }
 4 
 5 function Son(){
 6 }
 7 
 8 //给Son添加原型属性
 9 Son.prototype.age = '1'
10 
11 //重写Son原型对象,继承Father
12 Son.prototype = new Father()
13 
14 //创建Son实例,可以放在修改原型之前,修改原型能够从实例上立即反映出来
15 var son1 = new Son()
16 
17 Son.prototype.job = '程序员'
18 
19 son1.age // error  
20 //在重写Son原型对象之前 给Son原型添加属性是没用的,重写会切断与之前原型对象的联系
21 
22 son1.job //  程序员
23 
24 Son.prototype = {
25      height: 1.70  
26 }
27 
28 son1.height  // error  上述方法也是属于重写原型对象
View Code

类似于原型模式,单独使用原型链实现继承的问题:

a.原型链中的引用类型的值会被所有实例共享,并可以修改,而修改后会反映到所有的实例上,这样实例就不能拥有私有的引用类型的属性。

b.在创建子类型实例时,我们无法在不影响其他实例的同时向父类型的构造函数中传递参数。

 

2.借用构造函数

function Father(name){
  this.name = name; 
  this.colors = ['red','green','blue']  
}

function Son(name){
  //在子类型构造函数中调用父类型的构造函数,通过call函数等强制绑定作用域
  Father.call(this,name)  
}

var son1 = new Son('张三')
son1.name  //张三
son1.colors.push('yellow')

var son2 = new Son('李四')
son2.name  //李四

son1.colors  // ['red','green','blue','yellow']
son2.colors // ['red','green','blue']
View Code

相对原型链而言,借用构造函数可以向父类型的构造函数传参

类似于构造函数模式,单独使用借用构造函数模式实现继承的问题:

a.方法都在构造函数中定义,每次创建对象就会重新实例化一次方法属性

b.父类型的原型属性,子类型是访问不到的。

 

3.组合继承

使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

 1 function Father(name){
 2  this.name = name;
 3  this.colors = ['red','green','blue']
 4 }
 5 Father.prototype.sayName = function (){
 6    alert(this.name)
 7 }
 8 
 9 function Son(name,age){
10  Father.call(this,name);
11  this.age = age;
12 }
13 
14 //重写Son的原型实现继承,这种重写会导致Son的实例的constructor属性都变成父类型的构造函数Father,Son.constructor  //Father 而不是Son
15 Son.prototype = new Father()
16 //对于某些对constructor属性比较在意的场景,可以手动的将Son的constructor属性设置成Son,Son.constructor  //Son
17 Son.prototype.constructor = Son;
18 Son.prototype.sayAge = function(){
19    alert(this.age)
20 }
21 
22 var son1 = new Son('张三',20)
23 son1.colors.push('yellow')
24 son1.sayName(); //张三
25 son1.sayAge(); //20
26 
27 var son2 = new Son('李四',21)
28 son2.sayName(); //李四
29 son2.sayAge(); //21
30 
31 son1.colors  //  ['red','green','blue','yellow']
32 son2.colors //  ['red','green','blue']
View Code

无论什么情况下,都会调用2次父类型的构造函数,

第一次在创建子类型原型的时候,Son.prototype = new Father(),

第二次在子类型构造函数内部,Fahter.call(this,name)

4.原型式继承

 1 var Father = {
 2     name:'father',
 3     friends:['张三','李四']  
 4 }
 5 //只传入一个参数的情况下,这2种写法的行为一样的
 6 var son1 = Object(Father)
 7 son1.name = '张三'
 8 var son2 = Object.create(Father)
 9 son2.friends.push('王五')
10 
11 son1.name // 张三
12 son2.name // 张三  ,使用object.create 类似于原型链的继承方式,超类原型的所有属性都是共享的
13 son1.friends // ['张三','李四']  
14 son2.friends // ['张三','李四','王五']
15 
16 var son3 = Object.create(Father,{
17    name:{
18         value:'王五'
19    }    
20 })  
21 son3.name // 王五
22 son3.friends // ['张三','李四','王五']
View Code

 

在不想创建构造函数,只是想一个对象与另一个对象保持类似的情况下,原型式继承是可以胜任的,不过他们的引用类型的属性还是所有实例共享的。

5.寄生式继承

function createSon(Father){
 var Son = Object(Father);
  //在这里可以添加属于子类型的属性  
 Son.sayHi = function(){
    alert('hi')
 }
 return Son
}
View Code

和原型式继承一样,在不考虑自定义类型和构造函数的情况下,只想返回一个类似的新对象时,寄生式继承可以做到,不过和构造函数模式类似,方法都在函数中定义,每次创建对象就会重新实例化一次方法属性,所有的实例的sayHi方法都是一个新的实例

6.寄生组合式继承

为了解决组合继承调用2次父类型构造函数的问题

首先我们要搞清楚为什么组合继承会调用2次父类型的构造函数,组合继承的2次调用分别是 子构造函数中的 借用构造函数 和 原型链继承的实现。

其实多余的一次调用就是 原型链继承的实现中我们必须要拿到一个父类型的实例用来作为子类型的原型,这样修改子类型的原型只会修改父类型的实例原型,而不会影响父类型的其他实例

这就是为什么 在原型链继承中,我们用 Son.prototype = new Father() //获取父类型的实例重写子类型的原型  而不是 Son.prototype = Father.prototype // 这样写的话,我们在Son.prototype上新添加一个属性,会影响到Father的其他实例

怎么才能只获得一个与父类型prototype相似的对象而不调用父类型的构造函数呢?

在不考虑自定义类型和构造函数的情况下,只想返回一个类似的新对象时,寄生式继承可以做到

fucntion expendPrototype(Son,Father){
  //创建父类型的原型副本
  var prototype = Obejct(Father.prototype);
  //弥补重写原型而失去的默认的constructor属性
  prototype.constructor = Son;
  //重写子类型的原型
  Son.prototype = prototype;
}
View Code

这样 我们用这个方法去代替之前的 Son.prototype = new Father(),从而解决了多一次无用的构造函数调用.

 

posted @ 2018-05-31 14:11  赵皖华  阅读(272)  评论(2编辑  收藏  举报