基础概念
说继承之前简单介绍一下,几个概念,有助于更深刻的理解继承。
-
能用口述或者文字的形式,说一下什么是函数吗?
按照犀牛书的说法,函数简单的说就是重复执行的代码块。函数是这样的一段JavaScript 代码,它只定义一次,但可能被执行或调用任意次。//使用方式 //1.直接声明 function(){} //2.函数表达式 let fun = function(){} //3. new Function() var fun1 = new Function (arg1 , arg2 ,arg3 ,…, argN , body );
-
能用口述或者文字的形式,说一下什么是构造函数吗?
通过 new 函数名 来实例化对象的函数叫构造函数。任何的函数都可以作为构造函数存在。之所以有构造函数与普通函数之分,主要从功能上进行区别的,构造函数的主要 功能为 初始化对象,特点是和new 一起使用。new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法。构造函数定义时首字母大写(规范)。// 语法 funtion SupType(name){ this.name = name; } var P = new SupType ('小张'); P.name; // '小张'
-
能用口述或者文字的形式,说一下什么是原型吗?
在JavaScript高级程序设计中给出的解释是· 每一个构造函数(对应的就是类函数)都有一个prototype属性(强调下是属性),这个prototype属性会指向一个原型对象(强调下是对象)。该原型属性指向的原型对象称之为原型 -
能用口述或者文字的形式,说一下什么是原型链吗?
每一个构造函数的原型属性会链式指向原型对象,每个原型对象都会有个constructor属性会指向构造函数(未定义时默认指向构造函数)其中形成了一种链式结构,我们称之为原型链。
简单的原型链图
复杂的原型链图
上面的内容很重要,有助于你更深刻的理解,记忆下面的内容,请仔细看完!
ES5相对来说最完美的继承
什么是寄生组合继承?
寄生组合式继承,实际是通过借用构造函数来继承属性,通过原型链形式来继承方法,通过寄生可以不必为了指定子类的原型而调用超类构造函数,我们只需超类的原型副本即可——使用寄生式继承来继承超类原型,然后将结果(实例)指定给子类原型。
优点总结:
1、属性私有化(构造函数来继承属性);
2、方法公用(原型链形式来继承方法);
3、不调用超类构造函数(寄生特性)。
代码‘
// 继承-寄生函数
function inheritPrototype(subType, superType) {
// 通过 Object.create 复制父类构造函数
var prototype = Object.create(superType.prototype);
// 把构造函数复制为子类
prototype.constructor = subType;
// 把构造函数复制为子类后影像了子类原型,子类原型更变为父类原型,所以需要改正
subType.prototype = prototype;
}
// 父类构造函数
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;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
alert(this.age);
}
var instance = new SubType("lichonglou");
console.log(instance.name)
// 指向SubType 如果没有修正原型的构造函数,则会指向父类构造函数
console.log(instance.constructor)
ES5其他继承方法
构造函数继承
也有2种继承方式,冒充继承,绑定this方式继承
冒充继承
function Person(name,age){
this.name = name ;
this.age = age;
this.showName = function(){
console.log('我是'+name);
}
}
/**
* @description 以自身运行环境运行函数,这时函数内的this均指向Child,
* 因此父类的属性全部移植到子类中
*/
function Child(){
//这三句代码最关键
this.temp = Person; //创建一个自身缓存函数并将父类构造赋值
this.temp('李端','26');
delete this.temp;//删除缓存函数
}
var child = new Child();
child.showName();//我是李端
绑定this方式继承
function Person(name,age){
this.name = name ;
this.age = age;
this.showName = function(){
console.log('我是'+name);
}
}
/**
* @description 以自身运行环境运行函数,这时函数内的this均指向Child,
* 因此父类的属性全部移植到子类中
*/
function Child(){
// 改变this指向的方法有3个,下面分别是 bind,call,apply的用法
Person.bind(this)('李端','26'); //绑定this到Person运行环境执行函数
// Person.call(this,'李端','26');
// Person.apply(this,['李端','26']);
}
var child = new Child();
child.showName(); //我是李端
优点:
属性私有化,在创建Child的实例时,可以向Parent传递参数
缺点:
因为方法和属性只能写在构造函数中,因此不能实现函数复用 只能继承父类的实例属性和方法,不能继承原型属性/方法
原型继承
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function(){
alert('使用原型得到'+this.name);
}
var per = new Person('李端','26');
per.sayHello();
//创建新对象并实现继承
function Student(){};
Student.prototype = new Person('端瑞','23')
Student.constructor = Student
var stu = new Student();
stu.sayHello();
优点:
父类原型被继承,方法可复用
缺点:
1.引用值共享问题
2.不能传参、在创建Child的实例时,不能向Parent传递参数;如果传递也不会有作用
寄生继承
寄生式(parasitic)继承是与原型式继承紧密相关的一种思路,并且同样也是由克罗克福德推而广之的。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。以下代码示范了寄生式继承模式。
在这个例子中,createAnother()函数接收了一个参数,也就是将要作为新对象基础的对象。然后,把这个对象(original)传递给 object()函数,将返回的结果赋值给 clone。再为 clone 对象添加一个新方法 sayHi(),最后返回 clone 对象。可以像下面这样来使用 createAnother()函数:
function createAnother(original){
var clone = Object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
重点:
就是给原型式继承外面套了个壳子。
优点:
没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:
没用到原型,无法复用。
组合继承
组合继承其实就是,取构造函数的继承的优点(属性私有化) + 原型的优点(方法公用),让其构造继承属性,原型继承方法。
实现的方法很多,但是大同小异,下面展示一种:
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("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
优点(结合了原型链继承和构造函数继承的优点):
1.Parent上的原型可以被继承
2.解决了引用值共享的问题
3.可以通过Child向Parent传参
缺点:
函数多执行了一次call
组合继承最大的缺点是会调用两次父构造函数。
一次是设置子类型实例的原型的时候:
SubType.prototype = new SuperType();
一次在创建子类型实例的时候:
var instance1 = new SubType(“Nicholas”, 29);
回想下 new 的模拟实现,其实在这句中,我们会执行:
SuperType.call(this, name);
在这里,我们又会调用了一次 Parent 构造函数。