Javascript —— 继承
本文是本人学习,张容铭著的《Javascript 设计模式》的笔记与总结。仅供学习交流。
每个类都有3个部分:
1、第一部分是构造函数内的,这是供实例化对象复制用的。
2、第二部分是构造函数外的,直接通过点语法添加,这是供类使用的,实例化对象是访问不到的。
3、第三部分是类的原型中的,实例化对象是可以通过其原型链间接地访问到,也是为供所有实例化对象所共用的。
一、子类的原型对象————类式继承
类式继承:通过子类的原型prototype对父类实例化来实现。
//类式继承
//声明父类
function SuperClass(){
this.superValue = true;
}
//为父类添加共有方法
SuperClass.prototype.getSuperValue = function () {
return this.superValue;
};
// 声明子类
function SubClass(){
this.subValue = false;
}
// 继承父类
SubClass.prototype = new SuperClass ();
// 为子类添加共有方法
SubClass.prototype.getSubValue = function (){
return this.subValue;
}
上面声明了2个类,而且第二个类的原型prototype 被赋予了第一个类的实例。
类的原型对象的作用就是为类的原型添加共有方法,但类不能直接访问这些属性个方法,必须通过原型prototype来访问。
var instance = new SubClass();
console.log(instance.getSuperValue() ); // true
console.log(instance.getSubValue()); //false
通过instanceof 来检测某个对象是否是某个类的实例,或者说某个对象是否继承了某个类。
console.log(instance instanceof SuperClass); //true
console.log(instance instanceof SubClass); //true
console.log(SubClass instanceof SuperClass); //false
第三个false,说明 ,instanceof 是判断前面的对象是否是后面类(对象)的实例,它并不表示两者的继承。
>> SubClass 继承 SuperClass 时是通过将superClass的实例赋予给SubClass 的原型prototype,所以说 SubClass.prototype继承了SuperClass
console.log(SubClass.prototype instanceof SuperClass); //true
>> Object是所有对象的祖先
console.log(instance instanceof Object); //true
类式继承的缺点
1、一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类。原因:
由于子类通过其原型prototype对父类实例化,继承了父类。所以说父类中的共有属性要是引用类型,就会在子类中被所有实例共用。
示例代码:
function SuperClass () {
this.books = ['Javascript','html','css'];
}
function SubClass () {}
SubClass.prototype = new SuperClass ();
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance2.books); // ['Javascript','html','css'];
instance1.books.push('设计模式');
console.log(instance2.books); // ['Javascript','html','css','设计模式'];
2、在实例化父类的时候也无法对父类构造函数内的属性进行初始化。原因:
由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的。
二、创建即继承——构造函数继承
构造函数式:通过在子类的构造函数环境中执行一次父类的构造函数来实现的。用call(this,变量);
//构造函数式继承
//声明父类
function SuperClass (id){
//引用类型共有属性
this.books = ['JavaScript','html','css'];
//值类型共有属性
this.id = id;
}
//父类声明原型方法
SuperClass.prototype.showBooks = function (){
console.log(this.books);
}
//声明子类
function SubClass (id) {
//继承父类
SuperClass.call (this, id);
}
//创建第一个子类的实例
var instance1 = new SubClass (10);
var instance2 = new SubClass (11);
instance1.books.push('设计模式');
console.log(instance1.books); // ['JavaScript','html','css','设计模式'];
console.log(instance1.id); //10
console.log(instance2.books); //['JavaScript','html','css'];
console.log(instance2.id); //11
instance1.showBooks (); //TypeError
构造函数式继承的精华:
SuperClass.call (this, id);
优缺点:
call将子类的变量在父类中执行一遍,由于父类是给this绑定属性的,所以子类自然也就继承了父类的共有属性。而这种类型的继承没有涉及原型prototype,所以父的原型方法自然不会被子类继承。如果想被子类继承就必须要放在构造函数中。
三、组合继承 (类式+ 构造式)
组合继承结合了类式继承 + 构造函数继承
1、类式继承:通过子类的原型prototype对父类实例化来实现。
2、构造函数式:通过在子类的构造函数环境中执行一次父类的构造函数来实现的。用call(this,变量);
//组合式继承
//声明父类
function SuperClass(name){
//值类型共有属性
this.name = name;
//引用类型共有属性
this.books = ['html'、'css'、'Javascript'];
}
// 父类原型共有方法
SuperClass.prototype.getName = function () {
console.log(this.name);
};
//声明子类
function SubClass (name, time) {
//构造函数式继承父类name属性
SuperClass.call(this.name);
//子类中新增共有属性
this.time = time;
}
//类式继承 子类原型继承父类
SubClass.prototype = new SuperClass();
//子类原型方法
SubClass.prototype.getTime = function (){
console.log(this.time);
}
如上,在子类构造函数中执行父类构造函数,在子类原型上实例化父类就是组合模式。这样融合了类式继承和构造函数继承的优点,并且过滤掉其缺点,你测试看看。
测试代码:
var instance1 = new SubClass("js book", 2014);
instance1.book.push("设计");
console.log(instance1.books); //["html","css","Javascript","设计模式"]
instance1.getName (); //js book
instance1.getTime (); // 2014
var instance2 = new SubClass ("css book", 2013);
console.log(instance2.books); //["html","css","Javascript"]
instance2.getName (); //css book
instance2.getTime (); //2013
优缺点
子类的实例中更改父类继承下来的引用类型属性如books,不会影响到其他实例,并且子类实例化过程中又能将参数传递到父类的构造函数中。
四、原型式继承
//原型式继承
function inheritObject (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("xml book");
var otherBook = inheritObject (book);
otherBook.name = "flash book";
otherBook.alikeBook.push("as book");
console.log(newBook.name); //ajax book
console.log(newBook.alikeBook); //["css book","html book","xml book","as book"]
console.log(otherBook.name); //flash book
console.log(otherBook.alikeBook); //["css book","html book","xml book","as book"]
console.log(book.name); // js book
console.log(book.alikeBook); //["css book","html book","xml book","as book"]
优缺点
跟类式继承一样,父类对象book中的值类型的属性被复制,引用类型的属性被共同用
五、寄生式继承
// 寄生式继承
// 声明基对象
var book = {
name : "js book",
alikeBook : ["css book", "html book"]
}
function createBook (obj){
//通过原型继承方式创建新对象
var o = new inheritObject(obj);
//拓展新对象
o.getName = function (){
console.log(name);
};
//返回拓展后的新对象
return o;
}
优缺点
寄生式继承是对原型的第二次封装,并且在这第二次封装过程中对继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法而且还添加新的属性和方法。
六、寄生组合式继承
/**
* 寄生式继承 继承原型
* 传递参数 subClass 子类
* 传递参数 superClass 父类
**/
function inheritPrototype(subClass, superClass){
//复制一份父类的原型副本保存在变量中
var p = inheritObject (superClass.prototype);
//修正因为重写子类原型导致子类的constructor 属性被修改;
p.constructor = subClass;
//设置子类的原型
subClass.prototype = p;
}
测试用例
// 定义父类
function SuperClass (name){
this.name = name;
this.colors = ["red","blue","green"];
}
// 定义父类原型方法
SubClass.prototype.getName = function () {
console.log(this.name);
}
// 定义子类
function SubClass (name, time){
// 构造函数式继承
SuperClass.call (this, name);
// 子类新增属性
this.time = time;
}
//寄生式继承父类原型
inheriPrototype(SubClass, SuperClass);
//子类新增原型方法
SubClass.prototype.getTime = function (){
console.log(this.time);
};
// 创建两个测试方法
var instance1 = new SubClass("js book", 2014);
var instance2 = new SubClass("css book", 2013);
测试代码
instance1.colors.push("black");
console.log(instance1.colors); //["red","blue","green","black"]
console.log(instance2.colors); //["red","blue","green"]
instance2.getName (); //css book
instance2.getTime (); //2013
七、多继承
一些面向对象语言中支持多继承,在javascript中也能实现,但是有些局限性。只有一条原型链,理论上不能继承多个父类。而通过 extend 方法可以做到。
// 单继承 属性复制
var extend = function (target, source){
//遍历源对象中的属性
for (var property in source){
//将源对象中的属性复制到目标对象中
target[property] = source[property];
}
//返回目标对象
return target;
}
extend方法是一个浅复制的过程,他只能复制值类型的属性,对于引用类型的属性是不行的。
单继承测试代码
var book = {
name : 'javascript 设计模式',
alike : ['css','html','javascript']
}
var anotherBook = {
color : 'blue'
}
extend(anotherBook, book);
console.log(anotherBook.name); //Javascript 设计模式
console.log(anotherBook.alike); // ['css','html','javascript']
anotherBook.alike.push('ajax');
anotherBook.name = '设计模式';
console.log(anotherBook.name); // 设计模式
console.log(anotherBook.alike); //['css','html','javascript','ajax']
console.log(book.name); //Javascript 设计模式
console.log(book.alike); // ['css','html','javascript']
多继承
//多继承 属性复制
var mix = function (){
var i = 1, // 从第二个参数起为被继承的对象
len = arguments.length, //获取参数长度
target = arguments[0], ///第一个对象为目标对象
arg; //缓存参数对象
// 遍历被继承对象
for(; i<len; i++){
// 缓存当前对象
arg = arguments[i];
//遍历被继承对象中的属性
for (var property in arg){
//将被继承对象中的属性复制到目标对象中
target[property] =arg[property];
}
}
// 返回目标对象
return target;
}
//将mix绑定到原生对象Object上,所有的对象都可以拥有这个方法。
Object.prototype.mix = function (){
var i = 0, //从第一个参数起为被继承的对象
len = arguments.length, // 获取参数长度
arg;
//遍历被继承的对象
for(; i<len; i++){
// 缓存当前对象
arg = arguments[i];
//遍历被继承对象中的属性
for (var property in arg){
//将被继承对象中的属性复制到目标对象中
this[property] = arg[property];
}
}
}
// 调用
var book1 = {name:'书一',publish:'广州'};
var book2 = {cover:'http://www.baidu.com',title:'sdfdf'};
var otherBook = {content:'df'};
otherBook.mix(book1,book2);
console.log(JSON.stringify(otherBook)); //{"content":"df","name":"书一","publish":"广州","cover":"http://www.baidu.com","title":"sdfdf"}
八、多种调用方式————多态
//场景: 一个参数返回平方, 两个参数相乘,三个参数相加再相乘,三个以上相加
// 多态
function total () {
// 获取参数
var arg = arguments,
//获取参数长度
len = arg.length;
switch(len){
//如果没有参数
case 0:
return 0;
case 1:
return Math.pow(10,arg[0]);
case 2:
return arg[0] * arg[1];
case 3:
return (arg[0] + arg[1])*arg[3];
defult:
var a=0;
for(var i=0; i<len ;i++){
a += arg[i];
}
return a;
}
}
根据不同数量的参数有不同的处理