面向对象编程/原型及原型链
面向对象
为什么要面向对象?
- 优势:简化我们对行为岔路的预备。
- 特点:逻辑迁移更加灵活,代码复用性高,高度模块化的体现。
对象是什么?
- 对单个物体的简单抽象。
- 对象是一个容器,封装了属性和方法,属性代表对象的状态,方法代表对象的行为。
- 模块、接口 => 对象
简单对象
对外开放,属性和方法可以被直接修改
const Course = {
teacher: '小明',
startCourse(name) {
return `开始${name}课`
},
}
函数对象
构造函数的实例化对象
- 需要一个模板,表征了一个类物品的共同特征,从而生成对象。
- 构造函数即对象模板,构造函数可以理解为类。
- js 本质上并不是直接基于类,基于构造函数 + 原型链 => constructor + prototype
- 需要使用 new 来实例化对象。
- 构造函数体内使用的 this 指向所要生成的实例。
- 可以做初始化传参。
function Course(name) {
this.name = name
this.teacher = '小明'
this.teachestartCourse = function () {
return `开始${this.name}课`
}
}
const 语文 = new Course('语文') // 实例化一个对象
面试提问 1:如果构造函数不实例化,可以作为对象生成器使用吗?
答案:不可以
function Course(name) {
this.name = name
this.teacher = '小明'
this.teachestartCourse = function () {
return `开始${this.name}课`
}
}
console.log(Course.teacher) // undefined
面试提问 2:如果项目需要使用,又不想被外界感知,通常如何解决?
答案: 单例模式 => 全局维护统一实例 => 解决对象实例化生成问题
function Course() {
const isClass = this instanceof Course
if (!isClass) {
return new Course()
}
this.name = '语文'
this.teacher = '小明'
this.teachestartCourse = function () {
return `开始${this.name}课`
}
}
console.log(Course().name) // 语文
面试提问 3:new 是什么? / new 原理 / new 的时候做了什么?
- 结构上:创建一个空的对象,作为返回的对象实例
- 属性上:将空对象的
__proto__
指向constructor
的prototype
属性 - 关系上:将当前实例对象赋给内部的 this
- 生命上周期:执行了构造函数的初始化代码
手写代码模拟 new
function _new() {
const obj = {} // 创建一个空的对象,作为返回的对象实例
const constructor = Array.prototype.shift.call(arguments)
obj.__proto__ = constructor.prototype // 将空对象的原型指向构造函数的 prototype属性
const result = constructor.apply(obj, arguments) // 把构造函数的 this 指向刚刚创建的对象实例,同时执行构造函数初始化代码
// result || obj 这里这么写考虑了构造函数显示返回 null 的情况
return typeof result === 'object' ? result || obj : obj
}
function person(name, age) {
this.name = name
this.age = age
}
const p = _new(person, '小明', 10)
console.log(p)
面试提问 4:constructor
是什么? - 构造器
- 每个对象实例化时,都会自动拥有一个
constructor
属性 constructor
属性继承自原型对象,并指向当前构造函数constructor
代表了实例化对象是被谁生成的
面试提问 5:使用构造函数继承属性就没有问题吗? / 会有什么性能问题?
- 构造函数中的方法实例化后会存在于每个生成的实例内,
- 而方法往往需要统一模板化,重复挂载只会浪费资源。
- 如何进行优化:把方法挂在构造函数的
prototype
属性上,让实例对象自动通过自身的__proto__
属性去引用方法
面试提问 6:前面提到的原型对象又是什么? - prototype
function Course() {}
const course1 = new Course()
/**
1. 构造函数:用来初始化创建对象的函数 - Course
2. 自动给构造函数赋予了一个属性 prototype(原型对象),该属性等于实例对象的 __proto__
Course.prototype === course1.__proto__ // true
3. 每个对象实例化时,都会自动拥有一个 constructor 属性
4. constructor属性继承自原型对象,并指向当前构造函数
ourse1.constructor => course1.__proto__.constructor => Course.prototype.constructor => Course
*/
PS: 只有构造函数有 prototype 属性
面试提问 7:原型对象有自己的原型吗? - 有
function Course() {}
const course1 = new Course()
// Course.prototype.__proto__ => Object.prototype
继承
1. 重写 原型对象 继承
- 在原型对象上的属性和方法都能被实例共享
- 本质:重写原型对象,将父类实例对象的属性和方法,作为子对象原型的属性和方法
- 缺点:1. 子类的 prototype 来自父类的一个实例对象,其中一个子类实例对象改了继承而来属性或方法,其他子类实例对象继承而来的属性或方法也就被修改了。2. 实例化子类时,无法向父类传参。
function Course() {
this.name = 'English'
}
Course.prototype.getName = function () {
return this.name
}
function English() {}
English.prototype = new Course()
English.prototype.constructor = English
const english = new English()
const name = english.getName()
console.log(name) // English
2. 构造函数继承/拷贝继承
- 在子类的内部直接调用父类继承
- 缺点:无法通过原型链继承原型对象上共享的方法
function Course(name) {
this.name = name
}
Course.prototype.getName = function () {
return this.name
}
function English(name) {
Course.call(this, name)
}
const english = new English('新概念英语')
console.log(english.name) // 新概念英语
const name = english.getName() // Uncaught TypeError: english.getName is not a function
3. 组合继承
- 组合继承:构造函数继承 + 重写原型对象继承
- 缺点:父类被调用了两次
function Course(name) {
this.name = name
}
Course.prototype.getName = function () {
return this.name
}
function English(name) {
Course.call(this, name)
}
English.prototype = new Course()
English.prototype.constructor = English
const english = new English('新概念英语')
const name = english.getName()
console.log(name)
4. 寄生组合继承
- 以
Course.prototype
为原型,通过Object.create()
创建一个方新的对象赋值给English.prototype
function Course(name) {
this.name = name
}
Course.prototype.getName = function () {
return this.name
}
function English(name) {
Course.call(this, name)
}
English.prototype = Object.create(Course.prototype)
English.prototype.constructor = English
const english = new English('新概念英语')
const name = english.getName()
console.log(name)
5. 多重继承
- 通过
Object.assign()
合并多个父类的原型对象给当前子类的原型对象
function School(school) {
this.school = school
}
Course.prototype.getSchool = function () {
return this.school
}
function Course(name) {
this.name = name
}
Course.prototype.getName = function () {
return this.name
}
function English(school, name) {
School.call(this, school)
Course.call(this, name)
}
English.prototype = Object.create(School.prototype)
Object.assign(English.prototype, Course.prototype)
English.prototype.constructor = English
const english = new English('胜利小学', '新概念英语')
const school = english.getSchool()
const name = english.getName()
console.log(school, name)