Javascript继承
学过后端语言的同学对继承并不陌生,但是对JS继承少许还是有些困惑,不要试图问我是如果知道的,其实javascript继承主要是基于原型prototype
实现的。
其实当你真正了解了原型链时候,再看js继承,其实比OOP语言更灵活、更简单一些。接下来我们来看看原型链继承吧:
//父类
function Animal(){}
//子类
function Dog(){}
//继承
Dog.prototype = new Animal();
其实,就是把子类的prototype
指向父类的实例,继承就完成了,很简单吧。这就是原型链继承
上面只是一个简单的继承结果,并无实际意义,继承的目的就是要共享父类的属性和方法,接下来我们一步一步来揭开这神秘的面纱
/**
*
* 父类,带属性
* @constructor
* @param name 名字
* @param type 动物分类
* @constructor
*/
function Animal(name,type) {
this.name = name || 'your name';
this.type= type || 0;
this.coatColor= ['white','block','yellow','brown']; //引用类型
//函数也是引用类型
this.speak = function () {
console.log(this.name+' speaking .');
}
}
}
/**
* 为父类新增一个方法
* @returns {boolean}
*/
Animal.prototype.say= function () {
console.log('my name is '+this.name);
};
/**
* 子类
* @constructor
*/
function Dog(name) {
this.name = name;
this.foot= 4;
}
//实现继承-原型链继承 => (子类 -> 子类原型->父类) ;继承 注意,继承必须要写在子类方法定义的前面
Dog.prototype = new Animal();
/**
* 子类方法
* 为子类新增一个方法(在继承之后,否则会被覆盖/异常) dog.run is not a function
*/
Dog.prototype.run = function () {
console.log('The '+ this.name +' was runing.');
};
var dog = new Dog('taiSen');
console.log(dog.name); //dog --子类覆盖父类的属性
console.log(dog.type); // 0 --父类的属性
console.log(dog.foot); //4 --子类自己的属性
dog.say(); //my name is taiSen --继承自父类的方法
dog.run(); //The taiSen was runing. --子类自己的方法
以上,看起来我们好像已经完成了一个完整的继承了。但是,原型链继承有一个缺点
,就是属性
如果是引用类型
的话,会共享
引用类型 ,接下来我个Animal增加引用类型属性this.coatColor,测试下
//测试下
var dog1= new Dog();
var dog2 = new Dog();
dog1.coatColor.push('blue');
console.log(dog1.coatColor); // [ 'white', 'block', 'yellow', 'brown', 'blue' ]
console.log(dog2.coatColor); // [ 'white', 'block', 'yellow', 'brown', 'blue' ]
dog1,dog2 输出的coatColor一样,说明引用类型属性会被
所有实例共享
— 这就是原型链继承的缺点,那么我们如果解决这个问题呢? 接下来我们就要借用————
构造函数继承
//子类
function Cat() {
Animal.call(this) // 构造函数继承(继承属性)
}
//测试下
var cat1= new Cat();
var cat2 = new Cat();
cat1.coatColor.push('red');
console.log(cat1.coatColor); // [ 'white', 'block', 'yellow', 'brown', 'red' ]
console.log(cat2.coatColor); // [ 'white', 'block', 'yellow', 'brown']
从结果看,我们就解决了引用类型被所有实例共享的问题了。
注意
:这里跟
原型链继承
有个比较明显的区别是并没有使用
prototype
继承,而是在子类里面执行父类的构造函数, 相当于把父类的代码复制到子类里面执行一遍,这样做的另一个好处就是可以给父类传参。
测试代码:
/比如:
function Pig(name) {
Animal.call(this,name);
}
var pig1= new Animal('big Pig');
var pig2 = new Animal('small Pig');
console.log(pig1.name); // big Pig
console.log(pig2.name); //small Pig
看起来是不是很像java,C#语言啊,以上构造函数解决了引用类型被所有实例共享的问题。
注意
: 正因为构造函数解决了解决了引用类型被所有实例共享的问题,导致了一个相对很矛盾的问题出现了,——————
函数
也是
引用类型,函数也没办法共享了.也就是说,每个实例里面的函数,虽然功能一样,但是却不是同一个函数,就相当于我们每实例化一个子类,就复制了一遍的函数代码。
//父类新增this.speak函数
function Animal(name,type) {
this.name = name || 'your name';
this.type= type || 0;
this.coatColor= ['white','block','yellow','brown']; //引用类型
//函数也是引用类型
this.speak = function () {
console.log(this.name+' speaking .');
}
}
//测试
console.log(pig1.speak===pig2.speak); // false
以上证明,父类的函数,在子类的实例下是不共享的。
怎么办呢?,以上可以看出原型链继承 和 构造函数继承 这两种继承方式的优缺点刚好是互相矛盾的,那么我们有没有办法鱼和熊掌兼得呢? 答案是肯定的————组合继承
// 父类
function Animal() {
this.name = name || 'your name';
this.type= type || 0;
this.coatColor= ['white','block','yellow','brown']; //引用类型
}
// 父类函数
Animal.prototype.speak =function () {
console.log(this.name+' speaking .');
}
// 子类
function Chicken(){
Animal.call(this) // 构造函数继承(继承属性)
}
// 继承
Chicken.prototype = new Animal() // 原型链继承(继承方法)
总结
继承方式 | 核心代码 | 优缺点 | 用法 |
---|---|---|---|
原型链继承 | Dog.prototype = new Animal() | 实例的引用类型共享 | 继承属性 |
构造函数继承 | 在子类Cat)里执行 Animal.call(this) | 会独享所有属性,包括引用属性(重点是函数) | 继承方法 |
组合继承 | 利用原型链继承要共享的属性,利用构造函数继承要独享的属性 | 实现相对完美的继承 | 结合上两位 |
本文中的代码见demo coding ,如果觉得对您有用,帮加个star,万分感谢!
今天就写到这,讲述了3种继承方式,其实J继承还有很多继承方式。其他留在下期再见咯。
感觉各位的收看,欢迎提问。