2. 继承
2.1 call()
- 可以调用某个函数
- 可以修改函数运行时的this指向(注意是修改此次运行时的this指向,而不是永久的this指向)
<script>
// call 方法
function fn(x, y) {
console.log(this); //修改之前this指向的是window,修改之后this指向的是o这个对象
console.log(x + y); //3
}
var o = {
name: 'andy'
};
// fn();传统的函数调用方法
// 1. call() 可以调用函数
fn.call(); //Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2); //{name: "andy"}
</script>
2.2 利用构造函数继承父类的属性
ES6之前是利用构造函数继承父类的属性;利用原型对象继承父类的方法。
利用构造函数继承父类的属性的核心方法是:通过call把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。
<script>
//借用父构造函数继承属性
//1. 父构造函数
function Father(uname,age){
//这里的this原本指向父构造函数的实例对象
this.uname = uname;
this.age = age;
}
//2. 子构造函数
function Son(uname, age){
//这里的this指向子构造函数的对象实例
}
</script>
现在上述两个构造函数没有任何关系,那么子构造函数如何才可以使用父构造函数的两个属性呢?思路:将父构造函数中的this指向改成子构造函数的实例对象。
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例father
console.log(this);
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age); //在调用父类构造方法的同时,将父类的this指向改为son
this.score = score; //这是子构造函数独有的属性
}
var son = new Son('刘德华', 18, 100); //Son {uname: "刘德华", age: 18, score: 100}
console.log(son); //Son {uname: "刘德华", age: 18, score: 100}
var farther = new Father('张学友', 20); //父构造函数的this指向还是指向父构造函数的实例对象 Father {uname: "张学友", age: 20}
</script>
在子构造函数中写上Father.call(this, uname, age);这行代码就相当于将父构造函数的代码
console.log(this);
this.uname = uname;
this.age = age;
搬移到子构造函数中。子构造函数就相当于变成了
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
console.log(this);
this.uname = uname;
this.age = age;
this.score = score; //这是子构造函数独有的属性
}
2.3 借用原型对象继承父类型方法
之前提到:共有的属性写在构造函数里面,共有的方法写在原型对象里
(1)在父亲的原型对象中添加一个公共方法money()
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
//父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
</script>
通过打印儿子可以看出,儿子没有继承父类的money(),儿子只是调用了父亲的构造函数,没有调用父亲的原型对象,所以儿子找不到父亲的money()
(2)若将父构造函数的原型对象赋值给子构造函数的原型对象,如下:
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
//父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
Father.call(this, uname, age);
this.score = score;
}
//若将父构造函数的原型对象赋值给子构造函数的原型对象,如下
Son.prototype = Father.prototype;
var son = new Son('刘德华', 18, 100);
console.log(son);
</script>
打印儿子,可以发现在儿子的对象原型的指向中(指向Son构造函数的对象原型)果然出现了money方法
(3)再追加子构造函数专门的方法
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
//父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
Father.call(this, uname, age);
this.score = score;
}
//若将父构造函数的原型对象赋值给子构造函数的原型对象,如下
Son.prototype = Father.prototype;
//子构造函数专门的方法
Son.prototype.exam = function() {
console.log("考试");
}
var son = new Son('刘德华', 18, 100);
console.log(son);
//此时再打印一下父构造函数的原型对象
console.log(Father.prototype);
</script>
此时出现问题:子构造函数的原型对象有从父构造函数中继承过来的money()也有自己独有的exam(),但是父构造函数的原型对象中也有子构造函数原型对象独有的方法exam()
出错原因:Son.prototype = Father.prototype;相当于将父原型对象的地址给子原型对象,即让子原型对象指向父原型对象。此时若修改子原型对象,那么父原型对象回一起被修改。那么正确的继承方法是什么呢?
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例father
this.uname = uname;
this.age = age;
}
//父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age); //在调用父类构造方法的同时,将父类的this指向改为son
this.score = score; //这是子构造函数独有的属性
}
//正确的继承方法
Son.prototype = new Father();
//子构造函数专门的方法
Son.prototype.exam = function() {
console.log("考试");
}
var son = new Son('刘德华', 18, 100);
console.log(son);
console.log(Father.prototype);
</script>
说明:Son.prototype = new Father();
- 父构造函数创建了一个实例对象,同时将其地址赋值给Son的原型对象,即让Son的原型对象指向这个Father的实例对象
- 而Father的实例对象可以通过自己的对象原型__proto__访问Father的原型对象prototype(链式查找)
- 而Father的原型对象prototype中存放着money这个方法
上述代码console.log(son);的执行结果
console.log(Father.prototype);打印结果如下,父亲的原型对象中没有多添加子类的exam()
但是打印console.log(Son.prototype.constructor);的时候,发现Son.prototype中的constructor指回的是父亲的构造函数
原因:Son.prototype = new Father();等同于Son.prototype={}说明Son.prototype中的方法完全被覆盖了,Son就没有自己原先的constructor,修改办法:Son.prototype.constructor = Son;如下
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例father
this.uname = uname;
this.age = age;
}
//父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age); //在调用父类构造方法的同时,将父类的this指向改为son
this.score = score; //这是子构造函数独有的属性
}
Son.prototype = new Father();
//修改办法:如果利用对象的形式修改了原型对象,别忘了用constructor指回原来的构造函数
Son.prototype.constructor = Son;
//子构造函数专门的方法
Son.prototype.exam = function() {
console.log("考试");
}
var son = new Son('刘德华', 18, 100);
console.log(son); //Son {uname: "刘德华", age: 18, score: 100}
console.log(Father.prototype);
console.log(Son.prototype.constructor); //!!!指向了父构造函数
</script>