js中的继承
假设我们有一个Animal类,我们想构造Cat类,Cat类可以继承Animal类的属性和方法。以这个场景为列,我来讲一讲我所理解的js的继承。
- 构造继承
function Animal(name){
this.name = name;
this.age = 15;
}
function Cat(name){
Animal.call(this, name);
this.catName = 'cat';
}
let o1 = new Cat('test1');
console.log(o1);
//Cat {name: "test1", age: 15, catName: "cat"}
这就是构造继承,在子类的构造函数中,调用父类的构造函数,这样父类构造函数中的属性就出现在了子类的构造函数中。
缺点:子类无法继承父类原型上的方法。
Animal.prototype.say = function(){
console.log(this.name)
}
o1.say;//undefined
- 原型继承
function Cat2(name){
this.catName = name;
}
Cat2.prototype = new Animal();
let o2 = new Cat2('test2');
console.log(o2);
// Cat2 {catName: "test2"}
o2.name;//undefined
o2.age;//15
o2.say;
//ƒ (){
// console.log(this.name)
//}
这种方法的缺点是无法进行父类传参初始化属性的继承,而且继承的父类的属性是所有子类共享的new Animal()这个对象实例上的属性
,这就会导致如果更改了该对象实例的属性,那么这个影响就是所有子类实例共享的。
我们对父类做出如下更改
function Animal(name){
this.name = name;
this.age = 15;
this.friend = [1,2,3];
}
function Cat21(name){
this.catName = name;
}
Cat21.prototype = new Animal();
let o3 = new Cat21('o3');
let o4 = new Cat21('o4');
console.log('o3.age',o3.age);
console.log('o4.age',o4.age);
console.log('o3.friend',o3.friend);
console.log('o4.friend',o4.friend);
//更改数组friend
o3.friend.push(4);
console.log('o3.friend',o3.friend);
console.log('o4.friend',o4.friend);
//更改属性age
o3.age = 19;
console.log('o3.age', o3.age);
console.log('o4.age', o4.age);
输出如下
o3.age 15
o4.age 15
o3.friend (3) [1, 2, 3]
o4.friend (3) [1, 2, 3]
o3.friend (4) [1, 2, 3, 4]
o4.friend (4) [1, 2, 3, 4]
o3.age 19
o4.age 15
我们可以发现更改friend时,这个更改在子类实例上都发生了改变,而更改age时,只在更改的实例上发生了变化。
我们来看看o3和o4
可以发现o3上的age属性是直接在实例上的,而o3和o4的实例本身都是没有friend属性的。
这是为什么呢?
这是因为在查找对象的属性和方法时,是沿着原型链进行查找的,而你更改属性时,如果这个属性不是一个引用类型,是会直接为实例对象本身添加一个相应的属性,如果是引用类型,是会改变所引用对象指向的内容的。 而原型链上的方法,是等同于非引用类型的属性的。
因此,如果我们想要修改age,让所有实例共享修改后的结果,我们可以这么修改
o3.__proto__.age = 99;
当然,这个前提是子类实例上还没有新增age属性。
- 构造 + 原型继承
function Animal(name){ this.name = name; this.age = 15; this.friend = [1,2,3]; } Animal.prototype.say = function(){ console.log(this.name); } function Cat3(name){ Animal.call(this, name); this.catName = 'test3'; } Cat3.prototype = new Animal(); let o31 = new Cat3('o31'); let o32 = new Cat3('o32'); console.log('o31', o31); console.log('o32', o32); console.log('o31.friend', o31.friend); console.log('o32.friend', o32.friend); o31.friend.push(4); console.log('o31.friend', o31.friend); console.log('o32.friend', o32.friend); o31.say();
得到的输出如下:
可以发现父类的属性和方法子类实例都可以继承,但是还是有一个问题,那就是父类的构造函数多执行了一次,而这个多余的操作是不必要的。
- 原型+构造+优化1
function Cat4(name){ Animal.call(this, name); this.catName = name; } Cat4.prototype = Object.create(Animal.prototype); let o41 = new Cat4('o41'); let o42 = new Cat4('o42'); console.log('o41', o41); console.log('o42', o42); console.log('o41.friend', o41.friend); console.log('o42.friend', o42.friend); o41.friend.push(4); console.log('o41.friend', o41.friend); console.log('o42.friend', o42.friend); o41.say();
如此,我们就少进行了一个父类构造函数的执行,但是这还是有问题的
o41 instanceof Cat4; //true
o41 instanceof Animal; //true
o41.constructor;//
//ƒ Animal(name){
// this.name = name;
// this.age = 15;
// this.friend = [1,2,3];
//}
也就是无法通过instanceof来确认实例对象是由父类构造还是子类构造。
- 原型+构造函数+优化2
由于instanceof
的本质就是在原型链上进行constructor
属性的查找 ,我们可以做如下优化
function Cat5(name){
Animal.call(this, name);
this.catName = name;
}
Cat5.prototype = Object.create(Animal.prototype);
Cat5.prototype.constructor = Cat5;
let o51 = new Cat5('o51');
o51.constructor;
//ƒ Cat5(name){
//Animal.call(this, name);
//this.catName = name;
//}
以上是个人总结的继承相关的知识点,欢迎老铁们在评论区进行补充。