【Javascript设计模式】第一课 Javascript中的继承
在Javascript中每个类有三个部分:
1、第一部分是构造函数内,这是供实例化对象复制用的。
2、第二部分是构造函数外,通过点语法添加的,这是供类使用的,实例化对象是访问不到的。
3、第三部分是类的原型中,实例化对象可以通过其原型链间接访问到,也是为供所有实例化对象所共用的。
一、 子类的原型对象 ——类式继承
类式继承是最常见最简单的继承模式,类式继承用一句话概括就是“父类实例指向子类原型”
/**
* 声明一个父类
* @constructor
*/
var SuperClass = function(){
this.superValue = 'super';
}
//为父类添加共有方法
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
}
//声明一个子类
var SubClass = function(){
this.subValue = 'sub';
}
//将父类的实例赋值给子类的原型 子类继承父类
SubClass.prototype = new SuperClass();
//为子类添加共有方法
SubClass.prototype.getSubValue = function(){
return this.subValue;
}
原理概括:类的原型对象的作用是为类的原型添加共有方法,但是类不能直接访问这些属性和方法,必须通过原型prototype来访问,而我们实例化父类时,新创建的对象赋值了构造函数内的属性和方法并且将原型_prototype指向了父类的原型对象,这样就拥有了父类对象原型上的属性和方法,并且这个新创建的对象可以直接访问到父类原型对象上的属性和方法。如果我们将这个新创建的对象赋值给子类的原型,那么子类的原型就可以访问到父类的属性和方法,同时也可以访问从父类构造函数中复制的属性和方法。
测试
var instance = new SubClass();
console.info(instance.getSubValue()); //sub
console.info(instance.getSuperValue());//super
注意:这里说的SubClass继承SuperClass说的是SubClass的原型继承了SuperClass,我们可以通过instanceof来验证一下
console.info(instance instanceof SubClass);//true
console.info(instance instanceof SuperClass);//true
console.info(SubClass instanceof SuperClass); //false
console.info(SubClass.prototype instanceof SuperClass);//true;
不过,这里的类式继承有两个缺点:
第一、由于子类通过prototype来对父类进行实例化,继承了父类,所以说父类中的共有属性要是引用类型,就会在子类中被所有的实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他的子类
var Weaker = function(){
this.books = ['javascript设计模式','Java设计模式','Spring源码深度解析'];
}
Weaker.prototype.getSuperValue = function(){
console.info(this.books);
}
var WeakerSub = function () {
}
- WeakerSub.prototype = new Weaker();
测试如下:
var instance1 = new WeakerSub();
var instance2 = new WeakerSub();
console.info(instance1.books); //["javascript设计模式", "Java设计模式", "Spring源码深度解析"]
instance1.books.push('深入浅出Node.js');
console.info(instance2.books); // ["javascript设计模式", "Java设计模式", "Spring源码深度解析", "深入浅出Node.js"]
第二、由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类的时候,无法向父类传递参数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化。
二、创建即继承——构造函数继承
构造函数继承中子类的prototype不再指向父类的实例,而是通过在子类中调用父类的call方法来实现继承
/**
* 声明父类
* @param id
*/
var constructedSuperClass = function (id) {
- //引用类型共有属性
this.books = ['javascript设计模式','Java设计模式','Spring源码深度解析'];
//值类型共有属性
this.id = id;
}
//父类声明的共有方法
constructedSuperClass.prototype.showBooks = function () {
conole.info(this.books);
}
//声明一个子类
var constructedSubClass = function(){
constructedSuperClass.call(this,id);//call方法更改了函数作用的环境
}
测试:
var instance1 = new constructedSubClass('1');
var instance2 = new constructedSubClass('2');
console.info(instance1.books); //["javascript设计模式", "Java设计模式", "Spring源码深度解析"]
console.info(instance1.id); //1
instance1.books.push('深入浅出Node.js');
console.info(instance2.books); //["javascript设计模式", "Java设计模式", "Spring源码深度解析"]
console.info(instance2.id); //2
console.info(instance1.books); // ["javascript设计模式", "Java设计模式", "Spring源码深度解析", "深入浅出Node.js"]
原理概括:constructedSuperClass.call(this,id)这句话是构造函数继承的精华,由于这个方法可以更改函数的作用的环境,因此在子类中,调用父类的call方法就是将子类中的变量在父类中执行一遍。由于这种继承没有涉及到prototype,所以父类的原型方法自然又不会被子类继承,如果想要被子类继承,就必须放在构造函数内,这样创建出来的实例都会单独拥有一份而不能被共用。
缺点:违背代码复用原则。
三、类式和构造函数的组合——组合继承
类式继承和构造函数继承都有各自的缺点和优点,那么将两者的优点组合在一起,就是所谓的组合继承,即:在子类的构造函数中执行父类的构造函数,在子类的原型上实例化父类
var CombineSuperClass = function (name) {
this.name = name;
this.books = ['javascript设计模式','Java设计模式','Spring源码深度解析'];
}
CombineSuperClass.prototype.getName = function(){
console.info(this.name);
}
//声明子类
var CombineSubClass = function (name,time) {
//构造函数式继承父类的name属性
CombineSuperClass.call(this,name);
//子类中共有属性
this.time = time;
}
//类式继承 子类原型指向父类实例
CombineSubClass.prototype = new CombineSuperClass();
//子类原型方法
CombineSubClass.prototype.getTime = function () {
console.info(this.time);
}
测试:
var instance1 = new CombineSubClass('instance1',new Date());
instance1.books.push('深入浅出node.js');
console.info(instance1.books); //['javascript设计模式','Java设计模式','Spring源码深度解析','深入浅出node.js'];
instance1.getName();//instance1
instance1.getTime();// Date {Fri Sep 30 2016 22:15:15 GMT+0800}
var instance1 = new CombineSubClass('instance2',new Date());
instance1.books.push('Java Web 开发详解');//['javascript设计模式','Java设计模式','Spring源码深度解析','Java Web 开发详解'];
console.info(instance1.books);
instance1.getName();//instance2
instance1.getTime();// Date {Fri Sep 30 2016 22:15:15 GMT+0800}
四、洁净的继承者——原型式继承
原型式继承是根据道格拉斯.克罗克福德的《Javascript中原型式继承》得到的。
var inheritObject = function(o){
//声明一个过渡函数对象
function F(){}
//过渡对象的原型继承父级对象
F.prototype = o;
//返回过渡对象的实例,过渡对象的原型继承父级对象
return new F();
}
看结构这是类式继承的一个封装,其中的过渡对象相当于类式继承中的子类,只不过在原型式中作为一个过渡对象存在的,目的是为了创建要返回的新的实例化对象.
测试如下:
var book = {
name: 'js book',
alikebook: ['css book', 'html book']
};
var newbook = inheritObject(book);
newbook.name = 'ajax book';
newbook.alikebook.push('jquery book');
var otherbook = inheritObject(book);
otherbook.name = 'bootstrap book';
otherbook.alikebook.push('node book');
console.info(newbook.name);//ajax book
console.info(newbook.alikebook);// ["css book", "html book", "jquery book", "node book"]
console.info(otherbook.name);//bootstrap book
console.info(otherbook.alikebook);// ["css book", "html book", "jquery book", "node book"]
测试结果跟类式继承一样,父类对象中的值类型的属性被复制,引用类型的属性被共享.
五 进一步封装——寄生组合式继承
寄生组合式继承是寄生式继承和构造函数继承的组合,这里处理的不是对象,而是类的原型
var inheritPrototype = function(subClass,superClass){
//赋值一份父类的原型对象保存到变量中
var p = inheritObject(superClass.prototype);
//修正因为重写子类原型导致子类的constructor属性被修改
p.constructor = subClass;
//设置子类的原型
subClass.prototype = p;
}
var SuperClass = function(name){
this.name = name;
this.color = ['red','green'];
}
SuperClass.prototype.getName = function(){
console.info(this.name);
}
var SubClass = function(name,time){
SuperClass.call(this,name);
this.time = time;
}
inheritPrototype(SubClass,SuperClass);
SubClass.prototype.getTime = function () {
console.info(this.time);
}
var instance1 = new SubClass('color1','1');
var instance2 = new SubClass('color2','2');
instance1.color.push('grey');
console.info(instance1.color); // ["red", "green", "grey"]
console.info(instance2.color);//['red','green']
instance1.getName();//color1
instance2.getName();//color2
instance1.getTime();//1
寄生组合继承的图示如下:
高质量的代码就是对程序自己最好的注释。当你打算要添加注释时,问问自己,“我如何能改进编码以至于根本不需要添加注释?”改进你的代码,然后才是用注释使它更清楚。