面向对象有三大特性: 封装、继承、多态
| 特性 | 说明 |
|---|---|
| 封装 | 将相关联的属性和方法 封装到一个类中,以提高相关属性和方法的内聚度 |
| 继承 | 将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可 从而减少重复代码的数量,继承同时也是多态的前提 |
| 多态 | 不同的对象在执行时表现出不同的形态 |
借用原型链实现继承
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eatting = function() {
console.log(this.name + ' eatting')
}
// 子类
function Student(name, age, sno) {
// 这里对于name和age属性的赋值 其实是重复代码
// 但是如果移除,就会导致Student实例没有自己的name和age属性
// 所有的Student实例的name和age属性将都会是一样的
// 即其值就per对象的name和per对象的age
this.name = name
this.age = age
this.sno = sno
}
// 借用原型链实现继承
// 借用原型链继承可以继承父类的方法
// 但是不可以继承父类的属性
const per = new Person('per', 18)
Student.prototype = per
Student.prototype.studying = function() {
console.log(this.name + ' studying')
}

借用构造函数实现继承
为了解决借用原型链继承无法继承属性的问题,开发人员提供了一种新的技术: constructor stealing(有很多名称: 借用构造函数或者称之 为经典继承或者称之为伪造对象)
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eatting = function() {
console.log(this.name + ' eatting')
}
// 子类
function Student(name, age, sno) {
// 借用构造函数实现属性的继承
// 使用子类实例对象去调用父类的构造函数
// 以便于在子类中使用父类的构造函数对子类实例进行初始化操作
Person.call(this, name, age)
this.sno = sno
}
const per = new Person('per', 18)
Student.prototype = per
Student.prototype.studying = function() {
console.log(this.name + ' studying')
}
我们同时使用借用构造函数继承 和 借用原型链继承 的时候,这种继承方式也被称之为组合继承
组合继承已经可以实现属性和方法的继承,但是依旧存在一些弊端
-
父类的构造函数至少被调用两次
Person.call(this, name, age) const per = new Person('per', 18) Student.prototype = per -
子类和父类共有的属性,我们往往只希望复用的是初始化逻辑代码,但是实际上在子类的原型中也会存在一份子类和父类的共有属性,这其实是没有必要的
寄生组合式继承
// 父类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eatting = function() {
console.log(this.name + ' eatting')
}
// 子类
function Student(name, age, sno) {
Person.call(this, name, age)
this.sno = sno
}
// 原型式继承函数 - 本质上其实是一种工厂函数模式
// 传入一个对象,返回一个新的对象,新对象的隐式原型对象为传入的那个对象
function createObj(obj) {
// 使用一个新的空的构造函数来创建新对象
// 减少其它属性对继承带来的影响
function F() {}
F.prototype = obj
return new F()
}
// 寄生式(Parasitic)函数 - 让子类 继承(依赖,寄生) 于父类
// 在这里使用了原型式继承函数,寄生式函数, 基于原型继承,基于构造函数继承
// 所以这整个实现继承的方式 被称之为 寄生组合式继承
function inherit(subType, superType) {
subType.prototype = createObj(superType.prototype)
}
// 方法继承
inherit(Student, Person)
Student.prototype.studying = function() {
console.log(this.name + ' studying')
}
在新的ES6中,对于原型的设置提供了一个新的方法setProtorypeof
// 所以上述代码可以修改为
function createObj(obj) {
const newObj = {}
// 将参数二 作为参数一的原型对象
Object.setPrototypeOf(newObj, obj)
return newObj
}
同样在ES6中提供了Object.create方法可以用于取代createObj方法
注意: 对于浏览器而言使用
setProtorypeof是一种非常消耗性能的行为,因为其对应可能需要修改多个执行上下文所对应的[[scope]]属性值所以更推荐使用
Object.create方法去设置原型对象
Object.create方法的基本使用
const parent = {age: 23}
// Object.create方法会创建一个以第一个参数为原型的新对象
// 参数一: 返回新对象的原型 可以是一个对象 也可以是null
// 为null时 表示该对象为顶层对象
// 参数二: 会添加到返回对象中的属性
// 以对象形式进行描述
// key 为 属性名
// value 为 属性对应的属性描述符
const child = Object.create(parent, {
name: {
value: 'Klaus',
enumerable: true
}
})
console.log(child.name, child.age) // => Klaus 23
所以上述代码可以继续被修改为
function inherit(subType, superType) {
// Object.create方法,创建一个新的对象
// 新对象的隐式原型对象为传入的参数
subType.prototype = Object.create(superType.prototype)
// 为子类的原型添加对应的constructor属性
Object.defineProperty(subType.prototype, 'constructor', {
writable: true,
configurable: true,
value: subType
})
// 实现静态方法和属性的继承
Object.setPrototypeOf(subType, superType)
}
Object对象是所有对象的父类

在JavaScript中,继承本质使用的是寄生组合式继承,因此当一个构造函数的显示原型在当前实例的原型链上,那么就可以认为该构造函数是当前实例的祖先类
而按照JavaScript的原型链规则,原型链的尽头是Object.prototype,因此我们可以认为Object是所有类的父类
所以一些公共的属性和方法,如hasOwnProperty,toString,valueOf等,也都被设置到了Object.prototype上,以确保这些属性和方法可以被所有实例所访问
原型继承关系图

-
任何函数都是 Function函数的实例对象,包括任何一个构造函数和Function函数本身
因此``Function.prototype
和Function.proto `指向的是同一个对象 -
Object是所有对象的父类,包括Function对象和任何一个原型对象
-
原型对象本质就是一个有
constructor属性的对象,其也是Object的实例 -
任何对象都有自己的隐式原型对象,任何函数对象都额外再有一个显示原型对象
-
原型链的尽头是
Object.prototype,Object.getPrototypeof(Object.prototype)的结果是null -
Object是Function的父类,但是Object又是Function的实例