前言
面向对象的三大特性:继承、封装、多态。JavaScript 作为一种面向对象的弱类型语言,与传统的面向对象语言的不同,JavaScript 是通过原型来实现继承的。
原型链
在学习 JavaScript 的继承之前,我们先了解 JavaScript 的原型链。
创建对象有几种方法
// 字面量
var obj = { name: 'zhangsan', age: 18 }
// new Object
var obj = new Object({ name: 'zhangsan', age: 18 })
// 通过构造函数创建对象
function M(name, age) {
this.name = name
this.age = age
}
var obj = new M('zhangsan', 18)
// Object.create
var obj = Object.create({ name: 'zhangsan', age: 18 })
构造函数、实例和原型之间的关系
我们从下图开始分析:

通过分析这个图,我们可以知道:
- 构造函数通过
new关键字,生成实例对象 - 构造函数上有个
prototype属性,指向构造函数的原型 - 实例对象上有个
__proto__属性,指向构造函数的原型 - 原型对象上有个
constructor属性,指向构造函数
原型链的概念
当我们获取一个对象上的属性时,首先会在该对象本身的属性上查找,如果没有找到,则会去对象的
__proto__原型上查找,如果还没找到,会继续去原型的__proto__上查找,直到为null时才会停止查找。这样一层一层往上的链式查找过程,我们称之为原型链。
instanceof 的原理
我们从图片分析:

当实例对象的 __proto__ 属性和构造函数的 prototype 属性相等时,instanceof 返回 true。
如果不相等,则会继续把原型的 __proto__ 属性与构造函数的 prototype 属性作比较,在原型链上只要有相等的,instanceof 返回 true,否则返回 false。
举个🌰:
function M (name) {
this.name = name
}
var m = new M('zhangsan')
console.log(m instanceof M) // true
console.log(m.__proto__ === M.prototype) // true
console.log(m instanceof Object) // true
console.log(m.__proto__.__proto__ === Object.prototype) // true
new 运算符
var foo = function() {}
var obj = new foo()
- 一个新对象被创建,它继承自
foo.prototype - 构造函数
foo被执行。执行时,相应的传参会被传入,同时上下文(this)会被指定为这个新实例 - 如果构造函数返回了一个对象,则这个对象会取代整个
new出来的结果返回。如果没有,则返回步骤1创建的对象
继承
类的声明和生成实例
// ES5 中类的声明
function Animal(name) {
this.name = name
}
// ES6中类的声明
class Animal2 {
constructor(name) {
this.name = name
}
}
// 生成实例
var cat = new Animal('cat')
var dog = new Animal2('dog')
类的继承
第一种:借助构造函数继承
function Parent() {
this.name = 'parent'
}
function Child() {
Parent.call(this)
this.type = 'child'
}
var child = new Child()
console.log(child.name) // parent
console.log(child.type) // child
缺点:只继承了父类构造函数的属性,没有继承父类原型上的属性
第二种:原型链实现继承
function Parent() {
this.name = 'parent'
}
function Child() {
this.type = 'child'
}
Child.prototype = new Parent()
var child = new Child()
console.log(child.name) // parent
console.log(child.type) // child
// 缺点:
var child2 = new Child()
child1.__proto__.name = 'haha'
console.log(child2.name) // haha
缺点:
- 新实例无法向父类构造函数传参
- 共用一个原型对象,一个实例修改了原型属性,另一个实例原型上的属性也会被修改
第三种:组合继承
function Parent() {
this.name = 'parent'
this.arr = [1, 2, 3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = new Parent()
// 解决了共用一个原型对象属性的问题
var child1 = new Child()
var child2 = new Child()
child1.arr.push(4)
console.log(child1.arr) // [1, 2, 3, 4]
console.log(child2.arr) // [1, 2, 3]
缺点:调用了两次父类构造函数,消耗内存
组合继承的优化1
function Parent() {
this.name = 'parent'
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Parent.prototype
// 问题:child 原型上的构造函数并不是 Child
var child = new Child()
child.__ptoto__.constructor === Parent // true
缺点:无法判断实例的构造函数
组合继承的优化2
function Parent() {
this.name = 'parent'
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
var child = new Child()
console.log(child instanceof Child) // true
console.log(child instanceof Parent) // true
console.log(child.constructor) // Child
优点:修复了组合继承的问题,是目前最常用的继承方式
欢迎关注我的前端公众号:抬头仰望星空
