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 操作过程:
- 创建空对象
var obj = {} - 将这个空对象复制给 this
this = obj - 将函数的显示原型赋值给这个对象作为它的隐式原型
obj.__proto__ = Person.prototype - 执行函数体中的代码
- 将这个对象默认返回
重写原型对象
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)二、原型链的查找顺序
- 在
obj上查找 - 在
obj.__proto__上查找 - 在
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}
方式二
创建一个父类的实例对象,用这个实例对象作为子类的原型对象
缺点:原型上有重复多余的变量
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}
四、借用构造函数实现属性的继承
为了解决原型链继承中存在的问题,开发人员提供了一种新的技术: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}
弊端:
- 会调用两次父类构造函数
- 一次在创建子类原型的时候
- 另一次再子类构造函数内部(也就是每次创建子类实例的时候)
- 所有的子类实例事实上会拥有两份父类的属性
- 一份在当前的实例自己里面(person 本身),另一份在子类对应的原型对象中(
person.__proto__) - 两份属性不会出现访问问题,因为默认一定是访问实例本身的那部分属性
- 一份在当前的实例自己里面(person 本身),另一份在子类对应的原型对象中(
五、寄生组合实现继承
方案一
创建新的对象,让对象原型指向父类原型,最后将对象原型赋值给子类原型
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()
六、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')) // truein 操作符
判断某个属性是否在某个对象或者对象的原型上
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) // truefor 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
// introinstanceof 操作符
用于检测构造函数的 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) // falseisPrototypeOf
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