什么是面向对象编程
编程思想
面向过程:关注的是动词,分析出解决问题所需要的所有步骤,针对所有步一一实现一些函数,按照顺序来调用函数
面向对象:关注的是主谓,把构成这个问题的事务,拆解成一个个对象,这一个个对象是为了描述这个对象在当前问题中的各种行为方式
面向对象的特性
封装:让使用对象的人不用去考虑内部的实现,对象对外暴露出一些api,提供给使用方使用。
继承:为了代码的复用,从父类继承他允许继承的属性和方法
多态:不同的对象作用于同一个操作,然后产生不同的结果。多态的思想实际上把“想做什么”和“谁去做”这两者分离开。
面向对象特性的表现
五子棋为例:
面向过程:开局 -> 下白棋 -> 判断胜负 -> 下黑棋 -> 判断胜负
面向对象:棋盘、玩家(黑方,白方)
什么场景下适合面向对象编程
面向过程:简单的场景下,协同人员较少
面向对象:中型或者大型项目,协同人员较多,迭代频繁。
JS中的面向对象
方法:
属性
JS中的一些内置对象
// Array Date Function RegExp
// 多态
[].toString()
{}.toString()
// 继承
const o = new Object()
o.toString()
// 封装
const date = new Date()
date.getTime()
JS 中怎么创建对象
// 普通方法
const player = new Object()
player.color = 'red'
player.start = function () {
console.log('player')
}
// 工厂模式 缺陷:无法判断类型
function CreatePlayer (color) {
const player = new Object()
player.color = color
player.start = function () {
console.log('player')
}
return player
}
// 构造函数和实例 缺陷:每生成一个实例,构造函数内部的方法都会重新开辟一块内容
function Player (color) {
this.color = color
this.start = function () {}
}
const p1 = new Player('red')
console.log(p1.constructor) // [Function: Player]
// 原型 方法定义在原型上
function Player (color) {
this.color = color
}
Player.prototype.start = function () {}
// 静态属性 提供给构造函数调用的属性
为什么要在原型链上添加属性或者方法
在构造函数内通过this添加方法的话,每生成一个对象,都会重新开辟一块内存空间。造成内存浪费。prototype的方式,可以多个对象共用一块空间
怎么找到一个原型对象
function Player (color) {
this.color = color
this.start = function () {}
}
const p1 = new Player('red')
p1.__proto__
=== Player.prototype
=== Object.getPrototypeOf(p1) // 原型对象
p1.__proto__.constructor === Player
new 关键字到底做了什么
- 创建一个空对象p
- 将p的__proto__ 指向构造函数的prototype
- 将this指向p
- 返回这个对象p
4.1. 构造函数未return,返回p
4.2. 构造函数 return this 返回对象p
4.3. 构造函数return基本数据类型,返回对象p
4.4. 构造函数return对象类型 返回改对象
function myNew () {
let o = new Object()
let FunctionConstructor = [].shift.call(arguments)
o.__proto__ = FunctionConstructor.prototype
let resultObj = FunctionConstructor.apply(o, arguments)
return typeof resultObj === 'object' ? resultObj : o
}
原型链是什么
__ proto__ 连起来的链 最终值是null
function Player() {}
Player.prototype.name = 'player'
const p1 = new Player()
console.log(p1.name) // player
p1.name = 'p1'
console.log(p1.name) // p1
继承
原型链继承
function Parent () {
this.name = 'parent'
this.actions = ['sing', 'jump', 'rap']
}
function Child () {}
Child.prototype = new Parent()
Child.prototype.constructor = Child
// 问题1 如果父类有引用类型的属性,其中一个实例改变了该属性 所有实例该属性都会改变
// 问题2 无法传参给父类
构造函数继承
function Parent (name) {
this.name = name
this.actions = ['sing', 'jump', 'rap']
this.eat = function () {}
}
function Child () {
Parent.apply(this, arguments)
}
// 缺点 浪费内存
组合继承
结合原型链继承和构造函数继承
function Parent (name) {
this.name = name
this.actions = ['sing', 'jump', 'rap']
}
Parent.prototype.getName = function () {
return this.name
}
function Child () {
Parent.apply(this, arguments)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
// 缺点: 调用两次构造函数生成多余属性 还是多占用内存
寄生组合式继承
function Parent (name) {
this.name = name
this.actions = ['sing', 'jump', 'rap']
}
Parent.prototype.getName = function () {
return this.name
}
function Child () {
Parent.apply(this, arguments)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
Object.create内部实现
// 使用中间桥梁连接
let TempFunction = function () {}
TempFunction.prototype = Parent.prototype
Child.prototype = new TempFunction()
为什么不能直接 Child.prototype = Parent.prototype ?
子的原型对象调用父的原型对象,一旦子的原型上增加方法,父的原型也会改变
class继承
class Parent {
constructor () {}
}
class Child extends Parent {
constructor () {
super()
}
}