一、原型链继承

用于继承方法
正常情况下,两个函数之间不会有任何关系,如下

  1. function Father() {};
  2. function Son() {};
  3. Father.prototype.FatherWay = () => {
  4. console.log('这是父类的方法!');
  5. }
  6. Son.prototype.SonWay = () => {
  7. console.log('这是子类的方法!');
  8. }
  9. let s = new Son();
  10. s.SonWay(); // success
  11. 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

这种写法会变成双向继承FatherSon可同时调用对方的函数,错误,父类不能调用子类函数

  • 正确的写法 ```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 />![46303034-3593-4BA4-8F8E-444207387850.jpeg](https://cdn.nlark.com/yuque/0/2021/jpeg/22695934/1631758944841-67a9b7dc-ef60-47bc-8919-0435877d3e61.jpeg#clientId=u6988e6a8-1b98-4&crop=0&crop=0&crop=1&crop=1&from=ui&height=518&id=u3ad93b74&margin=%5Bobject%20Object%5D&name=46303034-3593-4BA4-8F8E-444207387850.jpeg&originHeight=2154&originWidth=1668&originalType=binary&ratio=1&rotation=0&showTitle=false&size=244410&status=done&style=none&taskId=uc75e91e9-e072-4c6a-845c-37ce06264e1&title=&width=401)

**继承后,原型如下**<br />![B4FAFD95-8664-4A54-9727-E9F4144B5500.jpeg](https://cdn.nlark.com/yuque/0/2021/jpeg/22695934/1631759029147-8e4c5bdb-51c5-435e-994a-2e82545cb9a5.jpeg#clientId=u6988e6a8-1b98-4&crop=0&crop=0&crop=1&crop=1&from=ui&height=510&id=ufffdde78&margin=%5Bobject%20Object%5D&name=B4FAFD95-8664-4A54-9727-E9F4144B5500.jpeg&originHeight=2154&originWidth=1668&originalType=binary&ratio=1&rotation=0&showTitle=false&size=217094&status=done&style=none&taskId=u8c96bbeb-bc75-4637-9351-002c63a78ed&title=&width=395)

- 关于构造函数的注意点
```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);

image.png

其次,当赋值的时候,会给自己添加新的 ** pro **属性,而不是去重写父类的 ** pro **属性,即:

  1. 读取时会自动读取原型链上的属性
  2. 赋值时不会读去原型链,只要自己没有,就直接加给自己 ```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 />![截屏2021-09-16 下午12.47.28.png](https://cdn.nlark.com/yuque/0/2021/png/22695934/1631767651641-2b2cf2d4-9d4b-4f2b-9f44-35df0819ae99.png#clientId=u397819ef-9d6f-4&crop=0&crop=0&crop=1&crop=1&from=drop&height=216&id=ue96ddd03&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2021-09-16%20%E4%B8%8B%E5%8D%8812.47.28.png&originHeight=430&originWidth=958&originalType=binary&ratio=1&rotation=0&showTitle=false&size=157845&status=done&style=none&taskId=ua9686ac6-4a06-4a5c-9b82-522007c13c1&title=&width=482)<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);

输出如下:
截屏2021-09-16 下午12.55.55.png

虽然 Son.__proto__中仍然包含了 pro 属性,但 Son自身也包含了这一属性,说明属性已经实现了继承