继承
什么是继承:所谓继承就是在两个构造方法间创立的一种联系,通过这种联系,可以使下级构造方法创建出来的对象想用上级构造方法中的属性和方法。JS中没有专门用于继承的语法,可以通过借用构造方法(伪造构造方法)、原型继承、组合继承三种方式实现继承。
构造方法继承
call方法和apply方法
作用:这两个方法的作用是相同的,作用是扩展函数的作用域。注意这两个方法是所有函数对象中都具有的方法。
格式:被借用的函数.call(借用者, 参数列表); 被借用的函数.apply(借用者, [参数列表]); apply方法的参数列表以数组形式保存。
注意:这两个具有扩展被借用函数作用域的功能,且也具有立即执行被借用函数的功能。
借用构造方法继承
//格式
构造方法1(){}
构造方法2(){ 构造方法1.call(this, 参数列表); }
//实现Student构造方法继承Person构造方法
<script type="text/javascript">
function Person(name, age, gender, country) {
this.name = name;
this.age = age;
this.gender = gender;
this.country = country;
}
function Student(name, age, gender, country, subject) {
Person.call(this, name, age, gender, country);
this.subject = subject;
}
var s = new Student("Tom", 18, "男", "England", "汉语言文学");
</script>
注意:
借用构造方法的继承主要是借用对私有属性的初始化功能,原型中的内容不会被继承下来。
借用构造方法的继承方式可以实现“多继承”。
<script type="text/javascript">
//实现多继承
function Person1(name, age) {
this.name = name;
this.age = age;
}
function Person2(gender, country) {
this.gender = gender;
this.country = country;
}
function Student(name, age, gender, country, subject) {
Person1.call(this, name, age);
Person2.call(this, gender, country);
this.subject = subject;
}
Person1.prototype.speak = function(){ console.log( "hello world" ); };
var s = new Student("Tom", 18, "男", "England", "汉语言文学");
//s.speak(); //原型中的内容不会被继承,此时会报错
</script>
当没有使用call方法或者apply方法,而是直接在下级构造方法中调用上级构造方法,如下格式时,此时没有将Person中的属性初始化,原因是函数中的this指的是调用函数的对象,此时是window对象调用Person,所以是为window对象的属性赋值。
<script type="text/javascript">
function Person(name, age, gender, country) {
this.name = name;
this.age = age;
this.gender = gender;
this.country = country;
}
function Student(name, age, gender, country, subject) {
Person(name, age, gender, country); //直接调用
this.subject = subject;
}
var s = new Student("Tom", 18, "男", "England", "汉语言文学");
console.log(s); //Student {subject: "汉语言文学"}
</script>
原型继承
将下级方法的prototype属性执行上级方法的一个实例,通过这种形式所建立起来的继承关系就是原型继承。
<script type="text/javascript">
function Animal() {
if (Animal.prototype.eat == undefined) {
Animal.prototype.eat = function() { console.log("吃东西"); }
}
}
function Person(name) {
this.name = name;
}
Person.prototype = new Animal();
Person.prototype.constructor = Person;
var p = new Person("Tom");
p.eat(); //吃东西
</script>
注意:
原型继承后属性的查找顺序:先在私有属性中查找,如果有则返回对应的值,如果没有则到它的原型中查找。如果原型中也没有,那么他会沿着__proto__属性指向继续向上查找。如果能够找到,则返回对应的值,如果找不到则返回undefined(注意当查找到Object的原型时还没找到,则返回undefined)。通过原型继承,使原型对象建立起来了一种链型结构,我们把这种链型结构称为原型链。
下级函数对原型修改工作需要在继承后面完成,如果在继承前面完成,那么原型的修改是修改的老原型。
原型继承不仅能够继承原型中的内容,也能够继承上级函数中的私有属性。
在原型链上查找属性时,找到第一个值即停止查找。
原型继承的问题是没有办法对上级方法的私有属性进行初始化。
下级方法更改被创建的内容,即父级方法的重写。
<script type="text/javascript">
function Animal() {
if (Animal.prototype.eat == undefined) {
Animal.prototype.eat = function() { console.log("吃东西"); }
}
}
function Person(name) {
this.name = name;
}
Person.prototype = new Animal();
Person.prototype.constructor = Person;
Person.prototype.eat = function() { console.log("吃完东西睡觉"); } //重写父级方法
var p = new Person("Tom");
p.eat(); //吃完东西睡觉
</script>
为什么在继承时不将下级方法的prototype直接指向上级方法的prototype,因为这样做两个构造函数共享一个原型,下级方法的操作会影响上级方法的原型,这样做下级方法获取到使用权限过重。
如上面案例 Person.prototype = new Animal(); 不能替换为 Person.prototype = Animal.prototype;
<script type="text/javascript">
function Animal() {
if (Animal.prototype.eat == undefined) {
Animal.prototype.eat = function() { console.log("吃东西"); }
}
}
function Person(name) {
this.name = name;
}
Person.prototype = Animal.prototype; //将Person.prototype = new Animal(); 替换为 Person.prototype = Animal.prototype;
Person.prototype.constructor = Person;
Person.prototype.eat = function() { console.log("吃完东西睡觉"); }
var p = new Person("Tom");
p.eat(); //吃完东西睡觉
var a = new Animal();
a.eat(); //吃完东西睡觉
</script>
如果下级和上级的属性名相同,那么下级的属性值会屏蔽掉上级的属性值,如果想获取上级的属性值可以使用uber来获取。
格式:下级方法.prototype.uber = new 上级方法();
调用:下级对象.uber.属性;
<script type="text/javascript">
function Animal() {
if (Animal.prototype.eat == undefined) {
Animal.prototype.eat = function() { console.log("吃东西"); }
}
}
function Person(name) {
this.name = name;
}
Person.prototype = new Animal();
Person.prototype.constructor = Person;
Person.prototype.eat = function() { console.log("吃完东西睡觉"); }
Person.prototype.uber = new Animal();
var p = new Person("Tom");
p.uber.eat(); //吃东西
</script>
组合继承
借用构造方法继承和原型继承的组合
<script type="text/javascript">
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
function Student(name, age, gender, subject) {
Person.call(this, name, age, gender);
this.subject = subject;
}
Person.prototype.speak = function() { console.log("hello world"); }
Student.prototype = new Person();
Student.prototype.constructor = Student;
var s = new Student("Tom", 18, "男", "汉语言文学");
console.log(s);
</script>
通过控制台查看打印的s对象的_proto_属性我们看到,_proto_中的age、gender、name都为undefined,此时我们不想要这些没有赋值的属性,而只想要继承一个纯净的原型,则需要一个临时构造器。
临时构造器
<script type="text/javascript">
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
function Student(name, age, gender, subject) {
Person.call(this, name, age, gender);
this.subject = subject;
}
function temFn() {} //创建临时构造器
Person.prototype.speak = function() { console.log("hello world"); }
temFn.prototype = Person.prototype;
Student.prototype = new temFn();
Student.prototype.constructor = Student();
Student.prototype.speak = function() { console.log("你好世界"); }
var p = new Person("Pony", 18, "男");
p.speak();
var s = new Student("Tom", 18, "男", "汉语言文学");
s.speak();
</script>