Skip to content

ES5 中实现继承 ​

一、对象和函数的原型 ​

认识对象的原型 ​

JavaScript 中每个对象都有一个特殊的内置属性[[prototype]],这个特殊对象可以指向另外一个对象。

这个对象的作用:

  • 当通过引用对象的属性 key 来获取一个 value 时,它会触发[[Get]]的操作
  • 这个操作会首先检查该对象是否有对应的属性,如果有就使用它
  • 如果该对象中没有对应的属性,那么就会访问对象[[prototype]]内置属性指向的对象上的属性

那么如果通过字面量直接创捷一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性?

  • 答案是有的,只要是对象都会有这样的一个内置属性

获取的方式有两种:

  • 方式一:通过对象的__proto__属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题)
  • 方式二:通过Object.getPrototypeOf()可以获取到
javascript
var obj = {
	name: 'mohca',
	age: 18,
}

// 获取对象的原型

// 非标准方式
console.log(obj.__proto__)

// 标准方式
console.log(Object.getPrototypeOf(obj))

认识函数的原型 ​

将函数看成普通对象时,它具备__proto__(隐式原型)

javascript
var obj = {}

function foo() {}

console.log(obj.__proto__)
console.log(foo.__proto__)

将函数看成是函数时,它具备 prototype (显式原型)

javascript
var obj = {}

function foo() {}

console.log(obj.prototype) // 对象上是没有的
console.log(foo.prototype)

new 操作过程:

  1. 创建空对象 var obj = {}
  2. 将这个空对象复制给 this this = obj
  3. 将函数的显示原型赋值给这个对象作为它的隐式原型 obj.__proto__ = Person.prototype
  4. 执行函数体中的代码
  5. 将这个对象默认返回

重写原型对象 ​

javascript
function Person() {}

console.log(Person.prototype)

// 在原有的原型对象上添加新的属性
Person.prototype.message = 'Hello Person'
Person.prototype.info = { name: 'hahaha' }

// 直接赋值一个新的原型对象
Person.prototype = {
	message: 'Hello Person',
	info: { name: 'hahaha' },
	// constructor: Person
}

Object.defineProperty(Person.prototype, 'constructor', {
	configurable: true,
	writable: true,
	enumerable: false,
	value: Person,
})

console.log(111, Object.keys(Person.prototype))

// 新建实例对象
var p = new Person()
console.log(p.message)

二、原型链的查找顺序 ​

  1. 在obj上查找
  2. 在obj.__proto__ 上查找
  3. 在obj.__proto__.proto__上查找,查找不到返回 undefined
javascript
var obj = {
	name: 'mocha',
	age: 18,
}

三、原型链实现方法的继承 ​

面向对象有三大特性:封装、继承、多态

  • 封装:将属性和方法封装到有个类中,称之为封装的过程
  • 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态的前提
  • 多态:不同的对象在执行时表现出不同的形态

方式一 ​

父类的原型直接赋值给子类的原型

缺点:父类和子类共享一个原型对象,修改了任意一个,另外一个也会被修改

javascript
// 定义Person构造函数(类)
function Person(name, age) {
	this.name = name
	this.age = age
}

Person.prototype.running = function () {
	console.log(this.name + ' running~')
}

Person.prototype.eating = function () {
	console.log(this.name + ' eating~')
}

// 定义学生类
function Student(name, age, sno, score) {
	this.name = name
	this.age = age
	this.sno = sno
	this.score = score
}

Student.prototype = Person.prototype

Student.prototype.studying = function () {
	console.log(this.name + ' studying~')
}

var stu1 = new Student('mocha', 18, 111, 100)

stu1.running() // mocha running~
stu1.studying() // mocha studying~
console.log(stu1) // Student {name: 'mocha', age: 18, sno: 111, score: 100}

image-20230630174113599

方式二 ​

创建一个父类的实例对象,用这个实例对象作为子类的原型对象

缺点:原型上有重复多余的变量

javascript
// 定义Person构造函数(类)
function Person(name, age) {
	this.name = name
	this.age = age
}

Person.prototype.running = function () {
	console.log(this.name + ' running~')
}

Person.prototype.eating = function () {
	console.log(this.name + ' eating~')
}

// 定义学生类
function Student(name, age, sno, score) {
	this.name = name
	this.age = age
	this.sno = sno
	this.score = score
}

var p = new Person('latte', 18)
Student.prototype = p

Student.prototype.studying = function () {
	console.log(this.name + ' studying~')
}

var stu1 = new Student('mocha', 20, 111, 100)

stu1.running() // mocha running~
stu1.studying() // mocha studying~
console.log(stu1) // Student {name: 'mocha', age: 20, sno: 111, score: 100}

image-20230630174038235

四、借用构造函数实现属性的继承 ​

为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(借用构造函数或者称之为经典继承或者称之为伪造对象)

做法:在在子类型构造函数的内部调用父类型构造函数

  • 因为函数可以在任意的时刻被调用
  • 因此通过 apply()和 call()方法也可以在新创建的对象上执行构造函数
javascript
// 定义Person构造函数(类)
function Person(name, age) {
	this.name = name
	this.age = age
}

Person.prototype.running = function () {
	console.log(this.name + ' running~')
}

Person.prototype.eating = function () {
	console.log(this.name + ' eating~')
}

// 定义学生类
function Student(name, age, sno, score) {
	Person.call(this, name, age)
	this.sno = sno
	this.score = score
}

var p = new Person()
Student.prototype = p

Student.prototype.studying = function () {
	console.log(this.name + ' studying~')
}

var stu1 = new Student('mocha', 20, 111, 100)

stu1.running() // mocha running~
stu1.studying() // mocha studying~
console.log(stu1) // Student {name: 'mocha', age: 20, sno: 111, score: 100}

image-20230630174743307

弊端:

  • 会调用两次父类构造函数
    • 一次在创建子类原型的时候
    • 另一次再子类构造函数内部(也就是每次创建子类实例的时候)
  • 所有的子类实例事实上会拥有两份父类的属性
    • 一份在当前的实例自己里面(person 本身),另一份在子类对应的原型对象中(person.__proto__)
    • 两份属性不会出现访问问题,因为默认一定是访问实例本身的那部分属性

五、寄生组合实现继承 ​

方案一 ​

创建新的对象,让对象原型指向父类原型,最后将对象原型赋值给子类原型

javascript
var obj = {}
Object.setPrototypeOf(obj.Person.prototype)
Student.prototype = obj

方案二 ​

创建新的函数,让函数原型指向父类原型,最后将函数原型赋值给子类原型

javascript
function F() {}
F.prototype = Person.prototype
Student.prototype = new F()

方案三 ​

使用Object.create()

javascript
var obj = Object.create(Person.prototype)
Student.prototype = obj

最终方案 ​

javascript
function createObject(o) {
	function F() {}
	F.prototype = o
	return new F()
}

function inherit(Subtype, Supertype) {
	Subtype.prototype = createObject(Supertype.prototype)
	Object.defineProperty(Subtype.prototype, 'constructor', {
		enumerable: false,
		configurable: true,
		writable: true,
		value: Subtype,
	})
}

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

Person.prototype.running = function () {
	console.log(this.name + ' running~')
}

Person.prototype.eating = function () {
	console.log(this.name + ' eating~')
}

function Student(name, age, height, sno, score) {
	Person.call(this, name, age, height)
	this.sno = sno
	this.score = score
}

inherit(Student, Person)

Student.prototype.studying = function () {
	console.log(this.name + ' studying~')
}

var stu = new Student('mocha', 18, 1.88, 1001, 100)
console.log(stu)
stu.running()
stu.eating()
stu.studying()

image-20230704181539585

六、Object 是对象父类 ​

Object 是所有对象的父类

js
var info = {
	name: 'mocha',
	age: 18,
}

console.log(
	Object.getPrototypeOf(info).__proto__ === Object.prototype.__proto__
) // true

七、对象的其他方法补充 ​

hasOwnProperty ​

判断对象是否有某一个属于自己的属性(不是在原型上的属性)

js
function createObject(o) {
	function F() {}
	F.prototype = o
	return new F()
}

var obj = {
	name: 'mocha',
	age: 18,
}

var info = createObject(obj)
info.address = '广州'
info.intro = '广州很美丽'

console.log(info.hasOwnProperty('name')) // false
console.log(info.hasOwnProperty('address')) // true

in 操作符 ​

判断某个属性是否在某个对象或者对象的原型上

js
function createObject(o) {
	function F() {}
	F.prototype = o
	return new F()
}

var obj = {
	name: 'mocha',
	age: 18,
}

var info = createObject(obj)
info.address = '广州'
info.intro = '广州很美丽'

console.log('name' in info) // true
console.log('address' in info) // true

for in ​

遍历的不仅仅是自己身上的属性,也包括原型上的属性

js
function createObject(o) {
	function F() {}
	F.prototype = o
	return new F()
}

var obj = {
	name: 'mocha',
	age: 18,
}

var info = createObject(obj)
info.address = '广州'
info.intro = '广州很美丽'

for (var key in info) {
	console.log(key)
}
// name
// age
// address
// intro

instanceof 操作符 ​

用于检测构造函数的 prototype 是否出现在某个实例对象的原型链上

javascript
function createObject(o) {
	function F() {}
	F.prototype = o
	return new F()
}

function inherit(Subtype, Supertype) {
	Subtype.prototype = createObject(Supertype.prototype)
	Object.defineProperty(Subtype.prototype, 'constructor', {
		enumerable: false,
		configurable: true,
		writable: true,
		value: Subtype,
	})
}

function Person() {}
function Student() {}

inherit(Student, Person)

var stu = new Student()

console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true
console.log(stu instanceof Array) // false

isPrototypeOf ​

javascript
function createObject(o) {
	function F() {}
	F.prototype = o
	return new F()
}

function inherit(Subtype, Supertype) {
	Subtype.prototype = createObject(Supertype.prototype)
	Object.defineProperty(Subtype.prototype, 'constructor', {
		enumerable: false,
		configurable: true,
		writable: true,
		value: Subtype,
	})
}

var obj = {
	name: 'mocha',
	age: 18,
}

var info = createObject(obj)
info.address = '广州'
info.intro = '广州很美丽'

function Person() {}
function Student() {}

inherit(Student, Person)

var stu = new Student()

console.log(Student.prototype.isPrototypeOf(stu)) // true
console.log(Person.prototype.isPrototypeOf(stu)) // true

/* 可以用于判断对象之间的继承 */
console.log(obj.isPrototypeOf(info)) // true