要理解继承,首先需要理解原型链
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'}