懂得不少道理但依然过不好这一生。
JS继承方法看了不少,但面向对象这块内容实在是太抽象,绕绕的自己就绕进去了,今天看了一篇关于继承方法总结的文章感觉豁然开朗,于是乎自己敲了敲总结一下。新手小白有任何问题还请指正。
方法一:构造函数继承
function Father1() {
this.father_Property1 = "father_Property111"
this.father_Property2 = ["aaa","bbb","ccc"]
}
function Son1() {
Father1.call(this); // 或apply
this.type = "son1";
}
new Son1().father_Property1;
new Son1().father_Property2;
第六行,子类Son1中执行父类Father1的构造函数通过这种调用,把父类构造函数的this指向为子类实例化对象引用,从而导致父类执行的时候父类里面的属性都会被挂载到子类的实例上去。
console.log(new Son1().father_Property1) //father_Property111
console.log(new Son1().father_Property2) //["aaa", "bbb", "ccc"]
但是通过这种方式,父类原型上的东西是没法继承的,因此函数复用也就无从谈起。如下:
Father1.prototype.father_Property3 = "father_Property333";
Father1.prototype.father_Property4 = function() {
console.log("father_Property444");
}
new Son1().father_Property3;
new Son1().father_Property4;
console.log(new Son1().father_Property3) //undefined
console.log(new Son1().father_Property4) //undefined
缺点:Son1无法继承Father1的原型对象,并没有真正的实现继。生动点来说就是Father1只是Son1的“义父”,Son1只继承到了他的一部分,Father1原型上的对象并没有继承过来
方法二:原型链式继承
function Father2() {
this.father_Property1 = "father_Property111";
this.father_Property2 = ["aaa","bbb","ccc"];
}
function Son2() {
this.father_Property1 = "Son2";
}
Son2.prototype = new Father2();
首先实现原型链式继承,下面检验一下Son2有没有继承了Father2原型上的属性和方法,我们为Father2的原型添加新属性father_Property3。
Father2.prototype.father_Property3 = "father_Property333"
new Son2().father_Property3;
console.log(new Son2().father_Property3)//father_Property333
这种方式解决了构造函数继承方式的缺点,Father2现在是Son2的亲爹了,原型上的属性和方法都继承给了Son2。
但是这里产生了一个问题:
var s1 =new Son2();
s1.father_Property2.push("ddd");
console.log(s1.father_Property2)//["aaa", "bbb", "ccc", "ddd"]
//再new一个
var s2 =new Son2();
console.log(s2.father_Property2) //["aaa", "bbb", "ccc", "ddd"]
缺点:new了两个Son2的实例,s1和s2,我们给s1的 father_Property2属性添加了一个字段,但是最后打印的时候s1和s2的 father_Property2属性都改变了,我们并没有s2.xxx去操作它,s2被s1影响了,造成这种现象的原因就是原型链上中的原型对象它俩是共用的。
这显然不是我们想要的,s1和s2这个两个对象应该是隔离的,这是这种继承方式的缺点。
方法三:组合式继承
构造函数继承和原型链继承两种方式结合起来
function Father3() {
this.father_Property1 = "father_Property111";
this.father_Property2 = ["aaa","bbb","ccc"];
}
function Son3() {
Father3.call(this);
this.type = "son3";
}
Son3.prototype = new Father3();
Father3.call(this);
Son3.prototype = new Father3();
这按句话结合了以上两种方法的优点
var s1 =new Son3();
s1.father_Property2.push("ddd");
console.log(s1.father_Property2) //["aaa", "bbb", "ccc", "ddd"]
var s2 =new Son3();
console.log(s2.father_Property2) //["aaa", "bbb", "ccc"]
ok,问题解决
但是!!!!!!没错还有但是
父类的构造函数被执行了两次,第一次是Son3.prototype = new Father3(),第二次是在实例化的时候,这是没有必要的。
优化1:
直接把父类的原型对象赋给子类的原型对象
function Father3() {
this.father_Property1 = "father_Property111";
this.father_Property2 = ["aaa","bbb","ccc"];
}
function Son3() {
Father3.call(this);
this.type = "son3";
}
Son3.prototype = Father3.prototype;
var s1 =new Son3();
var s2 =new Son3();
Son3.prototype = Father3.prototype 父类的原型对象赋给子类的原型对象,父类的构造函数被执行了一次,优化get!
但是!!!!!
console.log(s1 instanceof Son3) //true
console.log(s1 instanceof Father3) //true
返回结果都是 true 我们无法区分实例对象s1到底是由 Son3 直接实例化的还是 Father3 直接实例化的。
console.log(s1.constructor.name); // Parent3
可以,自己的东西被爹抢走了
这是因为Son3.prototype = Father3.prototype 不光将Son3继承了Father3,同时还会使Son3的实例化对象指向Father3
s1的构造函数居然是父类,而不是子类Son3,这显然不是我们想要的。
优化2:
最完美方式
function Father3() {
this.father_Property1 = "father_Property111";
this.father_Property2 = ["aaa","bbb","ccc"];
}
function Son3() {
Father3.call(this);
this.type = "son3";
}
Son3.prototype = Object.create(Father3.prototype);
Son3.prototype.constructor = Son3;
Object.create是一种创建对象的方式
1.方法内部定义一个新的空对象obj
2.将obj._proto _的对象指向传入的参数proto
3.返回一个新的对象
var s1 =new Son3();
var s2 =new Son3();
console.log(s1 instanceof Son3) //true
console.log(s1 instanceof Father3) //true
console.log(s1.constructor.name); // Son3
Son3.prototype.constructor = Son3 将错误的指向再给指回来!
完美解决。撒花~
如果您觉得对您有帮助!就请帮我点个喜欢😍😍😍