要理解继承,首先需要理解原型链

function Person(name, age, gender){
	this.name = name
	this.age = age
	this.gender = gender
}

Person.prototype.hi = function (){
	console.log(`Hi, I'm ${this.name}`)
}

let f = new Person('zoe', 18, 'female')

f.__proto__ === Person.prototype // true,ES5写法
Object.getPrototypeOf(f) === Person.prototype // true,ES6写法, __proto__ 是浏览器厂商私建
Person.prototype.__proto__ === Object.prototype // true

首先定义几个基本名词:

  • 实例对象:通过new xxx出来的对象叫做实例对象
  • 构造函数:如上方代码中的Person,一般以大写字母开头命名 - __proto__:ES6 之前,没有明确访问原型的方法,只能通过__proto__来访问,它的作用就是找到实例对象的原型
  • prototype:原型,是构造函数的一个属性
  • constructor:构造函数,写过 Java 这类 oop 的同学应该不陌生,这个在 ES5 中不经常接触到,ES6 中的 Class 经常用到,

然后通过上面的代码,我们可以得到这样的公式:

实例对象.__proto__ === 构造函数.prototype
构造函数.prototype.__proto__ === Object.prototype (长继承不适用,这里的继承链是 Person 继承 Object)

其次,Object 是原型链的顶端
Object.prototype.__proto__ === null

ok,那么明白了这个,我们就来讨论继承,继承本质上只是用来减少重复代码的一种编程手段,如果我们有多个类,并且都有重复的属性,那么我们可以将重复的属性提取到父类身上,再由父类通过继承给子类。再简化一下:子类构造函数的实例对象也能访问到父类的属性或方法

function Male(name, age){
	this.name = name
	this.age = age
	this.gender = 'male'
}

let m = new Male("m", 24)

function Female(name, age){
	this.name = name
	this.age = age
	this.gender = 'female'
}

let f = new Female("f", 24)

在上面这个例子中,我们定义了两个构造函数,分别来构造男性和女性,但是 name 和 age 重复了,此时我们可以创建一个父类,来优化掉重复的代码

function Human(name, age){
	this.name = name
	this.age = age
}

Human.prototype.sayHi = function (){
	console.log(`Hi, I'm ${this.name}, ${this.name} years old`)
}

// ok,有父类了,接下来考虑如何继承

function Male(name, age){
	// 父类实例化对象的属性我也得有
	Human.call(this, name, age)
	this.gender = 'male'
}

// 还得能访问到父类原型上的方法
Male.prototype = Object.create(Human.prototype) //Object.create 创建一个新的对象,新对象的 __proto__ 指向参数对象
Male.prototype.constructor === Male // 上一步中构造函数被 Human 覆盖掉了,这里改回来

// 到这里就结束了,Male 的 prototype 是一个空对象,并且 prototype 的 __proto__ 指向了 Human 的 prototype
// 其实 ES6 Class 的继承 extends 也是做了和上面两行代码相同的事

let m = new Male("zink", 24)
m.sayHi() // sayHi是 Human 原型上的方法
console.log(m) // Male {name: 'zink', age: 24, gender: 'male'}