面向对象编程
面向对象编程:Object-Oriented Programming
,程序的主要组织单位是对象
在 JS 中的对象定义为:无序属性的集合,其属性可以为基本值、对象或函数
行话:单个物体的抽象
面向过程编程
在面向过程编程中,程序的主要组织单位是函数。函数接收输入(参数),经过一系列处理,产生输出(返回值)。面向过程编程通常将问题分解为一系列的步骤,每个步骤由一个函数实现
函数式编程
鼓励使用纯函数(Pure Functions),即对于相同的输入,始终产生相同的输出,并且没有副作用(没有改变外部状态的行为)
对象
基础概念
对象由一组属性组成,每个属性都包括一个键(字符串或 Symbol)和一个值(任意数据类型)。
属性的键是唯一的,不同属性之间用逗号分隔。属性的值可以是任何数据类型,包括基本数据类型和其他对象。
对象创建
Object
const obj = new Object()
obj.name = 'xx'
obj.age = '23'
obj.sayAge = function() {
console.log(this.age)
}
字面量
const obj = {
name: 'xx',
age: '23',
sayAge(){
console.log(this.age)
}
}
Object.create()
创建一个新对象,该对象的原型(即__proto__
属性)指向传入的参数对象
const obj = Object.create({})
// create 实现原理
Object.create = (_obj) => {
if(typeof _obj !== 'object'){
return {}
}
// 创建一个空函数
function F() {}
// 将空函数的原型设置为传入的proto对象
F.prototype = _obj
// 返回一个新对象,该对象的原型指向传入的proto对象
return new F()
}
对象存储
对象的存储为:对象的内容是存储在堆中,变量在栈中存储对象的引用
构造函数
用来创建对象的特殊函数,通常以大写字母开头。
使用 new 关键字调用构造函数可以创建新对象,并将构造函数内部的属性和方法添加到新对象上。
function Person (age){
this.age = age
}
const my = new Person(29)
console.log(my.age) // 29
new 做的事情
- 在堆里面建一个新的空对象
- 将这个新对象的
__proto__
指向构造函数的prototype
,以便实例可以继承构造函数原型上的属性和方法。--可用Object.setPrototypeOf(obj, prototype)
- 执行构造函数,其中 this 关键字指向新创建的空对象上,这样构造函数内部的代码可以操作这个新对象。--可用
call()
- 如果构造函数没有显式返回一个对象,那么会返回这个新对象的引用地址
工厂模式
在工厂模式中,不直接调用构造函数来创建对象,而是使用一个工厂函数(或者方法)来创建对象。
这种模式封装了对象的创建过程(不让外部感知),使得代码更具灵活性和可维护性
function Person (...args) {
// 判断 this 是否为实例
// 是:表明不是函数了,已经 new,所以执行下面的 this.xx = args[x]
// 否:表明当前还是函数,未 new
const _isClass = this instanceof Person
if(!_isClass) return new Person(...args)
this.name = args[0]
this.age = args[1]
}
// Person 使用
const myself1 = Person('zhangsan', 58)
console.log(myself1) // { name: 'zhangsan', age: 58 }
const myself2 = new Person('lisi', 69)
console.log(myself2) // { name: 'lisi', age: 69 }
Person 函数既可以被当作构造函数使用(通过 new 关键字调用),也可以被当作工厂函数使用(直接调用)
单例模式
是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点以访问该实例。
常用于:路由、全局状态等
function Person (...args) {
// 判断是否已经存在实例
if(typeof Person.instance === 'object'){
return Person.instance
}
// 正常的构造逻辑
this.name = args[0]
this.age = args[1]
// 将实例保存在静态属性中
Person.instance = this
}
// Person 使用
const myself1 = new Person('xxx', 12)
const myself2 = new Person('zzz', 21)
console.log(myself1 === myself2) // true,因为它们是同一个实例
原型与原型链
原型
对象的原型指的就是__proto__
属性,但它不是标准的 JavaScript API,不建议直接使用
原型链
当在对象上面找不到属性时,就会通过__proto__
属性一层层网上找,这就是原型链
最终找不到就返回 undefined
function Parent(name){
this.name = name
}
Parent.prototype.sayName = function(){
console.log(`my name is ${this.name}`)
}
const obj = new Parent('张三')
obj.getName() // my name is 张三
继承
通过构造函数的原型对象来实现实例的继承
普通继承
将父类实例作为子构造函数的原型对象
function Parent(...args){
this.address = '成都'
this.name = '张三'
this.like = ['钓鱼', '洗碗']
this.args0 = args[0]
}
Parent.prototype.sayName = function(){
console.log(`my name is ${this.name}`)
}
function Child(){}
// 重写原型对象---start
Child.prototype = new Parent()
Child.prototype.constructor = Child // 重写构造函数
// 重写原型对象---end
const child1 = new Child()
const child2 = new Child()
child1.sayName() // my name is 张三
child2.like.push('喝酒') // 通过 child2 去改 like
child1.like // ['钓鱼', '洗碗', '喝酒'],child1 的 like 也会被改
优点
- 完成基础的继承功能:子类实例将会完全继承父类实例的属性、原型对象
缺点
- 父构造函数将不支持传参
- 子类实例的原型对象是共享的,那如果直接改原型对象的值后,影响所有的子类实例
构造函数继承(经典继承)
在子构造函数中调用父构造函数来实现继承
function Parent(...args){
this.address = '成都'
this.name = '张三'
this.like = ['钓鱼', '洗碗']
this.args0 = args[0]
}
Parent.prototype.sayName = function(){
console.log(`my name is ${this.name}`)
}
function Child(...args){
// 调用父构造函数,将其属性继承到子类实例上
Parent.call(this, ...args)
}
const child1 = new Child()
const child2 = new Child()
child2.like.push('喝酒')
child2.like // ['钓鱼', '洗碗', 喝酒']
child1.like // ['钓鱼', '洗碗']
child1.sayName() // 报错:child1.sayName is not a function
优点
- 父构造函数将支持传递参数
- 子类实例的原型不会共享,避免了原型继承中的共享问题。
缺点
- 子类实例将无法继承父构造函数的
prototype
属性
组合继承
结合普通继承与经典继承,完全弥补这两个继承的缺点
function Parent(...args){
this.address = '成都'
this.name = '张三'
this.like = ['钓鱼', '洗碗']
this.args0 = args[0]
}
Parent.prototype.sayName = function(){
console.log(`my name is ${this.name}`)
}
function Child(...args){
// 调用父构造函数,将其属性继承到子类实例上
Parent.call(this, ...args)
}
Child.prototype = new Parent()
Child.prototype.constuctor = Child // constuctor 修正
const child1 = new Child()
const child2 = new Child()
child2.like.push('喝酒')
child2.like // ['钓鱼', '洗碗', 喝酒']
child1.like // ['钓鱼', '洗碗']
child1.sayName() // my name is 张三
优点
- 子类实例将能继承父构造函数的
prototype
属性
缺点
- 会调用两次父构造函数
-
Child.prototype = new Parent()
new Child()
- 原型对象上多了不必要的属性
-
- 因为
Child.prototype = new Parent();
这行代码会创建一个父类的实例,所以子类的原型对象上会多出一些不必要的属性,尽管它们在子类的构造函数中被覆盖了。
- 因为
寄生组合继承
基于组合继承,解决两次调用父类构造函数问题。
function Parent(...args){
this.address = '成都'
this.name = '张三'
this.like = ['钓鱼', '洗碗']
this.args0 = args[0]
}
Parent.prototype.sayName = function(){
console.log(`my name is ${this.name}`)
}
function Child(...args){
// 调用父构造函数,将其属性继承到子类实例上
Parent.call(this, ...args)
}
// 手动将子构造函数的 prototype 指向 父构造函数的 prototype
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constuctor = Child // constuctor 修正
const child1 = new Child()
const child2 = new Child()
child2.like.push('喝酒')
child2.like // ['钓鱼', '洗碗', 喝酒']
child1.like // ['钓鱼', '洗碗']
child1.sayName() // my name is 张三
寄生组合继承其实就是ES6
的 class Child extends Parent
的ES5
代码
多重继承
指的是一个类(或对象)同时继承了多个父类(或对象),从而可以拥有多个父类的属性和方法
function Parent1(...args){
this.address1 = '成都'
this.name1 = '张三'
this.like1 = ['钓鱼', '洗碗']
this.args10 = args[0]
}
Parent1.prototype.sayName = function(){
console.log(`my name is ${this.name}`)
}
function Parent2(...args){
this.address2 = '上海'
this.name2 = '李四'
this.like2 = ['游泳']
this.args20 = args[0]
}
Parent2.prototype.sayLike = function(){
console.log(`my like is ${this.like}`)
}
function Child(...args){
// 调用父1构造函数,将其属性继承到子类实例上
Parent1.call(this, ...args)
// 调用父2构造函数,将其属性继承到子类实例上
Parent2.call(this, ...args)
}
// 手动将子构造函数的 prototype 指向 父构造函数的 prototype
Child.prototype = Object.create(Object.assign(Parent1.prototype, Parent2.prototype))
Child.prototype.constuctor = Child // constuctor 修正
const child1 = new Child()
const child2 = new Child()
child2.like.push('喝酒')
child2.like // ['钓鱼', '洗碗', 喝酒']
child1.like // ['钓鱼', '洗碗']
child1.sayName() // my name is 张三
其他补充知识
in、hasOwnProperty、instanceof
- in:检查属性是否在对象上(自身以及原型链上)
-
- "name" in my // true || false
- hasOwnProperty:检查属性是否在对象上(仅自身不涉及原型链上)
-
- my.hasOwnProperty('name') // true || false
- instanceof:检查对象是否属于某个构造函数的实例
-
- my instanceof Object // true || false
- 实现原理:检查对象的原型链上是否包含构造函数的 prototype 属性,即判断 实例.proto===构造函数.prototype
对象分类
对象分为 2 类:实例对象、函数对象
实例对象:通过 [new 构造函数()] 生成的
函数对象:通过 [new Function()] 生成的
- 每个对象(包含函数)都有
__proto__
属性,其指向等于其构造函数的prototype
指向 - 每个函数都有
prototype
属性,指向一个普通对象,该对象具有__proto__
、constructor
属性
-
__proto__
指向等于其构造函数(Object)的prototype
指向constructor
指向函数本身
图解:实例、构造函数、Function、Object、null 的关系
person.__proto__===Person.prototype // true === person instanceof Person(true)
Person.__proto__===Function.prototype // true === Person instanceof Function(true)
Function.__proto__===Function.prototype // true === Function instanceof Function(true)
Object.__proto__===Function.prototype // true === Object instanceof Function(true)
Object.__proto__.__proto__===Object.prototype // true === Object.__proto__ instanceof Object(true)
Function.__proto__===Object.prototype // false !== Function instanceof Object(true)
Object 与 Function 的关系
Function 与 Object 的__proto__
都指向同一个原型对象(Function.prototype
)
Object.__proto__ === Function.__proto__ === Function.prototype // true
Object instanceof Function // true,表明 Object 是 Function 的实例
Function instanceof Object // true,表明 Function 是 Object 的实例
Function instanceof Function // true,表明 Function 是 Function 的实例
Object instanceof Object // true,表明 Object 是 Object 的实例
更改对象的原型
- 粗暴(不推荐):obj.proto===newObj;
- 优雅(推荐):Object.setPrototypeOf(obj, newObj) 等价于 1
- 到位:const obj = Object.create(newObj) 等价于两步 const obj ={}; obj.proto=newObj;
面试题
1. 手写 new
// new 做的事情
function Person(age){
this.age = age
}
const my = myNew(Person, 29)
function myNew = function (context, ...args) {
// 补充相关代码
}
// 答案如下:
function myNew = function (context, ...args) {
// 补充相关代码
// 1. 创建一个空对象
const obj = {};
// 2. 更改 obj 的原型
Object.setPrototypeOf(obj, context.prototype); // 等价于 obj.__proto__ = context.prototype;
// 1和2等价于 const obj = Object.create(context.prototype);
// 3. 将函数里面的 this 指向该对象 并 执行函数代码
const res = context.apply(obj, args);
// 4. 返回结果:函数自身结果或新对象
return res instanceof Object ? res : obj;
}
2. 基础判断题
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = new Person("xh", 29);
console.log(person.__proto__ === Person.prototype)
console.log(person.constructor === Person.constuctor)
console.log(Person.constructor === Function)
console.log(person.constructor === Person)
console.log(Person === Person.prototype.constructor)
console.log(person.__proto__.constructor === Object)
console.log(Person.prototype.__proto__ === Object.prototype)
console.log(person.__proto__.__proto__.constructor === Object)
console.log(person.__proto__.__proto__.__proto__ === null)
console.log(Function.constructor === Object)
console.log(Object.constructor === Function)
// 问题:以上打印结果
// 答案:
// true
// false
// true
// true
// true
// false
// true
// true
// true
// false
// true