一、原型链继承
用于继承方法
正常情况下,两个函数之间不会有任何关系,如下
function Father() {};
function Son() {};
Father.prototype.FatherWay = () => {
console.log('这是父类的方法!');
}
Son.prototype.SonWay = () => {
console.log('这是子类的方法!');
}
let s = new Son();
s.SonWay(); // success
s.FatherWay(); // error
为了让Son()
实现继承 Father()
的效果,关键在于 子类的原型是父类的实例对象
- 错误的写法 - 1 ```javascript
Father.prototype.FatherWay = () => { console.log(‘这是父类的方法!’); } Son.proto = Father.prototype // 实例对象才有隐式原型
Son.prototype.SonWay = () => { console.log(‘这是子类的方法!’); }
因此需要修改的是 ` prototype `
- **错误的写法 - 2**
```javascript
Father.prototype.FatherWay = () => {
console.log('这是父类的方法!');
}
Son.prototype = Father.prototype // 变成双向继承
Son.prototype.SonWay = () => {
console.log('这是子类的方法!');
}
let s = new Son();
let f = new Father();
s.FatherWay(); // success
s.SonWay(); // success
f.SonWay(); // success
这种写法会变成双向继承,Father
和Son
可同时调用对方的函数,错误,父类不能调用子类函数
- 正确的写法 ```javascript Father.prototype.FatherWay = () => { console.log(‘这是父类的方法!’); } Son.prototype = new Father();
Son.prototype.SonWay = () => { console.log(‘这是子类的方法!’); }
let s = new Son(); let f = new Father(); s.FatherWay(); // success s.SonWay(); // success f.SonWay(); // error
**初始情况下,各对象的原型如下:**<br />
**继承后,原型如下**<br />
- 关于构造函数的注意点
```javascript
...
Son.prototype = new Father();
Son.prototype.constructor = Son; // 需要额外加入这一句
...
构造函数constructor
处于原型对象中,由于 Son
的原型指向被改为了 Father
,因此其构造函数也变成的父类的构造函数,需要用Son.prototype.constructor = Son;
将其改回来
- 继承属性的注意点
这种方法并不会继承属性,但是能读取父类的属性,举例如下
首先,其能够正常读取父类属性
function Father() {
this.pro = 1; // 父类属性,并未加入 Father.prototype,无法通过原型读取
};
function Son() {
this.sonPro = 2; // 子类属性
};
Son.prototype = new Father();
Son.prototype.constructor = Son;
let s1 = new Son();
let s2 = new Son();
console.log(s1.pro); // 1
console.log(s2.pro); // 1,正常读取
且 s1.pro 和 s2.pro 时同一个
s1.__proto__.pro = 100;
console.log(s1, s2);
其次,当赋值的时候,会给自己添加新的 ** pro **
属性,而不是去重写父类的 ** pro **
属性,即:
- 读取时会自动读取原型链上的属性
- 赋值时不会读去原型链,只要自己没有,就直接加给自己 ```javascript function Father() { this.pro = 1; // 父类属性 }; function Son() { this.sonPro = 2; // 子类属性 }; Son.prototype = new Father(); Son.prototype.constructor = Son;
let s = new Son(); s.pro = 1000; console.log(s); console.log(s.pro); // 1000 console.log(s.proto.pro); // 1
console.log(s.prototype); // undefined
**输出结果如下:**<br /><br />显然,` s.pro = 1000 `并未覆写父类的 ` pro `,而是创建了一个新的,<br />另外,**chrome** 输出时会将 **__proto__** 写成 **prototype**,但代码中直接写 ` s.prototype `会导致 **undefined**,需要自己分辨到底用 **__proto__** 还是 **prototype**
**关于解决继承属性的问题,用“三、组合继承”**
---
<a name="q8Ykr"></a>
### 二、借用构造函数基础
**用于继承属性**<br />本质是使用` call(this, ... )`,在自己的函数中,**调用别人**的构造函数
```javascript
function Person(name, sex) {
this.name = name;
this.sex = sex;
};
function Student(name, sex, age) {
Person.call(this,name, sex); // 让 this 来借用 Person()
this.age = age;
};
let s = new Student('张三', '人妖', 12);
console.log(s);
本质没有继承,只是简化了代码,相当于
...
function Student(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
};
...
三、组合继承
同时继承方法和属性
“一、原型链继承”中已经提到,其只能继承方法,不能继承属性(因为方法定义在了原型中,而属性定义在了对象自身中);
将“一”和“二”结合起来,便可以实现组合继承,同时继承方法和属性
function Father() {
this.pro = 1; // 父类属性
};
function Son() {
Father.call(this); // 用于继承属性
this.sonPro = 2; // 子类属性
};
Son.prototype = new Father(); // 用于基础方法
Son.prototype.constructor = Son;
Father.prototype.FatherWay = () => {
console.log('这是父类的方法!');
}
Son.prototype.SonWay = () => {
console.log('这是子类的方法!');
}
let s = new Son();
s.FatherWay(); // success
s.SonWay(); // success
console.log(s);
输出如下:
虽然 Son.__proto__
中仍然包含了 pro 属性,但 Son
自身也包含了这一属性,说明属性已经实现了继承