【笔记14】Javascript - 继承
继承的概念不陌生,在原型、原型链那里,就知道一个对象能继承到原型很多属性和方法。
各种继承的方法有优势有不足,看下继承发展史:
继承
传统形式:原型链
之前讲过用原型链可以继承很多属性和方法,但这种过多的继承了没用的属性。
Grand.prototype.company = "Apple";
function Grand(){
this.lastName = "Jobs";
}
var grand = new Grand();
Father.prototype = grand;
function Father(){
this.name = "Steve";
}
var father = new Father();
Son.prototype = father;
function Son(){
this.hobbit = "smoke";
}
var son = new Son();
不足
这种方式把“有用、没用”的属性和方法都给继承过来了。
借用构造函数
之前遇到有函数实现了部分功能,之后就没必要把已的功能再写一遍的情况。
function Person() { name, age, sex } {
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name, age, sex, grade) {
Person.call(this, name, age, sex);
this.grade = grade;
}
var student = new Student();
借用构造函数的特点,勉强算是继承了属性和方法,但这种方式都基于 new 出一个对象,不然借用里的 this 就没有意义了。
不足
借用了构造函数的方法,但不能继承构造函数的原型;
每次构造函数都要多走一个函数(写法上是省了,效率没提升,但在工业开发上因方便协作还会用);
共享原型
直接定义原型相等,强制一个原型在两个构造函数间共享;
Father.prototype.lastName = "Jobs";
function Father() { }
function Son() { }
Son.prototype = Father.prototype; // 通过这一句实现原型共享
var son = new Son();
var father = new Father();
看控制台:
多个构造函数,共用一个原型,简单明了;还可以把这种用法抽象成功能,封装成函数使用;
function inherit(Target,Origin){
Target.prototype = Origin.prototype;
}
封装成函数的目的是:让构造函数继承某个东西。这样构造函数所创建的对象就可以全继承这个东西了,要是只让对象继承,那直接改对象的 __proto__ 就完事了。
现在来整合一下上面的代码:
function inherit(Target, Origin){
Target.prototype = Origin.prototype;
}
Father.prototype.lastName = "Jobs";
function Father() { }
function Son() { }
inherit(Son,Father); // 执行这函数
var son = new Son();
var father = new Father();
console.log(son.lastName); // Jobs
注意:这里要先设置继承,后创建对象,不然对象都创建出来了,才去改继承的话,就继承不到了。
不足
如果 Son 建的对象想加个原型属性,通过修改原型属性添加的话,那 Father 建的对象就也有这个属性了,因为他俩的原型指向是同一个地方。所以,这种方式不能随便改动自己的原型。
Son.prototype.sex = "male";
console.log(father.sex); // male
如果 Son 即有继承来的原型,还能算定义创建自己的原型属性,该怎么弄?
圣杯模式
可以加一个中间层的构造函数,让他跟 Father.prototype (上层函数)共享原型,再把 Son.prototype 指向这个中间层函数,形成原型链,这样再给 Son.prototype 加属性方法就不会影响 Father.prototype 的原型了,同时还能继承他的属性方法。
F.prototype = Father.prototype;
function F(){}
Son.prototype = new F();
这么一来,即可以实现 son 的属性自由,还能继承 father 的属性方法,就是目前流行的:圣杯模式。
分析:
Son 的原型是 new F();
new F() 的原型是 F.prototype,两层关系;Son.prototype 和 F.prototype 是原型链的关系;
此时的 F.prototype 跟 Father.prototype 共享原型了,不影响 Son.prototype 设置属性方法;
理解了这一层,我们来封装一下这个函数。
function inherit(Target, Origin) {
function F() {}
F.prototype = Origin.prototype;
Target.prototype = new F();
}
Father.prototype.lastName = "Jobs";
function Father() { }
function Son() { }
inherit(Son, Father);
var son = new Son();
var father = new Father();
控制台输出:
看结果,Son 给自己的原型设置了自己的属性,又继承了 Father 原型的属性。
现在,我们看看原型链的关系:
// son.__proto__ --> new F(); new F() 是对象
// new F().__proto__ --> Father.prototype
这么看, Son 的原型是指向 Father 的原型,