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