JS-Inherit

什么是继承?

子类拿到父类的属性和方法就是继承。

为什么要继承?

因为要复用和改造。子类想拿到父类的属性和方法,并且在不改变父类的基础上添加新的属性和方法。

JS本身并没有继承,但是代码的复用和改造对于一个语言来说是十分重要的,所以得利用构造函数、原型链等方法实现继承的功能。

那什么是JS的构造函数?

在Java里面,类是对象的模板,对象是类的实例,但是在ES6之前,JS并没有存在类的概念,而是通过构造函数和原型链来实现这个功能。

构造函数的诞生是为了解决在使用对象字面量创建一系列同一类型的对象时,这些对象可能具有一些相似的特征(属性)和行为(方法),此时会产生很多重复的代码,而使用构造函数就可以实现代码的复用。

function Person(name, gender, hobby) {
    this.name = name;
    this.gender = gender;
    this.hobby = hobby;
    this.age = 6;
}
使用构造函数
var p1 = new Person('zs', '男', 'basketball');
var p2 = new Person('ls', '女', 'dancing');
var p3 = new Person('ww', '女', 'singing');
var p4 = new Person('zl', '男', 'football');

不使用
var p1 = { name: 'zs', age: 6, gender: '男', hobby: 'basketball' };
var p2 = { name: 'ls', age: 6, gender: '女', hobby: 'dancing' };
var p3 = { name: 'ww', age: 6, gender: '女', hobby: 'singing' };
var p4 = { name: 'zl', age: 6, gender: '男', hobby: 'football' };

显然,构造函数的使用使代码更加简洁,也更加体现了面向对象这门语言的特点。

重新回到问题上来,什么是构造函数?

在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。

那么new在构造函数这里面发挥了什么样的作用?

1、开辟一块内存,分配给构造函数的新实例。

2、this指向该内存

3、执行该实例函数体内的代码

4、返回this

什么是JS的原型链呢?

原型链是指实例对象和构造他的原型之间的连接,也就是_proto__和prototype之间的关系,在JS里面

实例的_proto__会指向构造他的构造函数的prototype

var arr = new Object();

console.log(arr.__proto__ == Object.prototype);//true

当一个实例需要调用一个属性或者方法的时候,首先会先寻找自身有没有定义这个属性或方法,如果没有则会利用_proto__指向父构造函数的prototype即原型链,一层一层的向上寻找,直到找到该属性方法,或者一直找到最终的Object也没有找到的话,则返回underfinded。

所以根据构造函数和原型链的这些特点,奠定了实现JS继承的基础。

(1)原型链继承

ECMAScript中将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

function SuperType(){
      this.prototype=true;
}
SuperType.prototype.getSuperValue=function(){
      return this.property;
}
function SubType(){
      this.subproperty=false;
}
//通过创建SuperType的实例继承了SuperType
SubType.prototype=new SuperType();

SubType.prototype.getSubValue=function(){
      return this.subproperty;
}
var instance=new SubType();
alert(instance.getSuperValue());  //true

在上述代码中,instance指向SubType的原型,SubType的原型又指向SuperType的原型SubType.prototype=new SuperType();这一句代码实现的是,在SubType保留了自己的属性和方法之后,也让自己获得了SuperType的属性和方法(_proto__指向了SuperType的prototype).

存在两个主要的问题。

(1)包含引用类型值的原型属性会被所有实例共享,这会导致对一个实例的修改会影响另一个实例。

(2)在创建子类型的实例时,不能向超类型的构造函数中传递参数。由于这两个问题的存在,实践中很少单独使用原型链。

以下例子清楚的说明了第一个问题:

function SuperType(){
      this.colors=["red", "blue", "green"];
}
function SubType(){
}
//继承了SuperType
SubType.prototype=new SuperType();
var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors);   //red,blue,green,black

var instance2=new SubType();
alert(instance2.colors);    //red,blue,green,black

(二)借用构造函数继承

在解决原型中包含引用类型值所带来的问题中,使用借用构造函数技术来解决。借用构造函数的基本思想,即在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法可以在新创建的对象上执行构造函数。

如下例子:

function SuperType(){
      this.colors=["red", "blue", "green"];
}
function SubType(){
      //继承SuperType
      SuperType.call(this);
}
var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors);  //red,bllue,green,black

var instance2=new SubType();
alert(instance2.colors);  //red,blue,green

上面例子中,通过使用call()方法(或者apply()方法),在新创建的SubType实例的环境下调用了SuperType构造函数。这样一来就会在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例都会有自己的colors属性副本。

相对于原型链而言,借用构造函数可以在子类型构造函数中向超类型构造函数传递参数。如下例子:

function SuperType(name){
      this.name=name;
}
function SubType(){
      //继承了SuperType,同时还传递了参数
      SuperType.call(this,"mary");
      //实例属性
      this.age=22;
}
var instance=new SubType();
alert(instance.name);  //mary
alert(instance.age);  //22`

借用构造函数存在两个问题:

(1)无法避免构造函数模式存在的问题,方法都在构造函数中定义,因此无法复用函数。

(2)在超类型的原型中定义的方法,对子类型而言是不可见的。因此这种技术很少单独使用。

(三)组合继承

组合继承,指的是将原型链和借用构造函数的技术组合到一起。思路是使用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数的复用,又能够保证每个实例都有它自己的属性。

function SuperType(name){
      this.name=name;
      this.colors=["red", "blue", "green"];
}
SuperType.prototype.sayName=function(){
      alert(this.name);
};
function SubType(name, age){
      //继承属性    使用借用构造函数实现对实例属性的继承
      SuperType.call(this,name);
      this.age=age;
}
//继承方法     使用原型链实现
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
subType.prototype.sayAge=function(){
      alert(this.age);
};
var instance1=new SubType("mary", 22);
instance1.colors.push("black");
alert(instance1.colors);   //red,blue,green,black
instance1.sayName();  //mary
instance1.sayAge();  //22

var instance2=new SubType("greg", 25);
alert(instance2.colors);   //red,blue,green
instance2.sayName();  //greg
instance2.sayAge();  //25

这样一来,

子函数的实例对象既可以拥有属于自己的属性,也可以使用父函数所定义的方法。