面向对象编程
面向过程:关注的重点在动词。分析出解决问题的步骤,然后用函数来实现这些步骤,使用的时候一次调用。
面向对象:关注的重点在主谓。把构成问题的事物分解成各个对象,构建对象的目的不是为了完成步骤,而是为了描述事物在整个解决问题的过程中的行为。
面向对象的特点是什么?
封装:部分属性和行为是对象私有的,不提供给对象访问,只提供部分对外接口,防止对象核心数据被篡改。可以在一定程度上保证对象的完整性和功能的健全。
继承:为了代码的复用,子类可以从父类继承一些属性和方法。当然子类也可以有自己独有的额外属性和方法。
多态:不同对象 作用的 同一行为 产生的 不同的效果。把 做什么 和 谁去做 分开了。不同对象复用了同一行为。
比如下棋的过程:
面向过程的:开局 - 黑方下棋 — 展示画布 — 检查胜负 - 白方下棋 - 展示画布 - 检查胜负 -------- 循环
用代码表示:
init()
blackPlay()
repaint()
check()
whitePlay()
repaint()
check()
...
面向对象的:棋盘、玩家
棋盘.开局 - 棋手.下棋 - 棋盘.重新展示 - 棋盘.检查胜负 - 棋手.下棋 -----
const checkerBoard = new CheckerBoard()
// 对象在新建的时候会自动去完成初始化,开局等操作
const wPlayer = new Player('white')
const bPlayer = new Player('black')
wPlayer.play()
bPlayer.play()
// 棋手在操作 play 之后,棋盘可能会监听到相应的事件,并自动去重新展示、检查胜负等,无需手动操作。这里再次可以看出面向对象中心是在描述所涉及到的对象的行为,不是为了某一个目的而去做的操作,更符合事物的特点。
面向对象的特性在这个例子中的表现
封装:Player, CheckerBoard 类的使用者只需要关注对象暴露的接口,无需关心类的内部实现。
继承
多态:白方和黑方选手,下棋,白棋/黑棋;白方胜利/黑方胜利
什么时候适合使用面向对象的编程思想?
在比较复杂/参与方较多的问题,面向对象可以更好地简化问题,更方便维护和扩展。
在简单的问题面前,这两种思想没什么差别,甚至面向过程更方便一些。
JS 中的面向对象
方法
属性
Object Array Date Function RegExp
创建对象
- 普通方式(创建空对象,补上属性和方法)。无复用
const Player = new Object()
player.name = 'ss'
player.play = function () {
}
工厂模式。无法判断实例的类型
function createPlayer(name) {
const Player = new Object()
player.name = name
player.play = function () {
}
return player
}
- 构造函数/实例
function Player (name) {
this.name = name
this.start = function () {
}
}
const p1 = new Player('w')
const p2 = new Player('b')
- 原型
function Player(name) {
this.name = name
}
Player.prototype.start = function () {
}
将方法放到原型链上优化了内存占用
- 静态属性/方法 实例属性/方法
原型及原型链
怎么找到 Player 的原型对象?怎么找到更上级的原型对象?
function Player (name) {
this.name = name
}
Player.prototype.start = function () {
console.log('下棋')
}
const p1 = new Player('1')
const p2 = new Player('2')
console.log(Player.prototype)
// { start: [Function (anonymous)] }
console.log(p1.__proto__)
// { start: [Function (anonymous)] }
console.log(Player.prototype === p1.__proto__)
// true
console.log(Player.prototype.constructor === Player)
// true
实例.proto === 构造函数.prototype
console.log(Player.__proto__)
// {}
console.log(Player.__proto__ === Function.prototype)
// true
构造函数的上游原型是一个对象
到现在为止我们并没有一个直接的对象是指向某个原型链上游的对象的,这就是他们为什么看起来那么神秘,其实就只是个普通的对象而已。
new 关键字做了什么
- 创建一个空对象
- 把该对象的
__proto__
指向构造函数的prototype
- 把构造函数的
this
显示指向新创建的对象并调用构造函数 - 返回值
- 如果构造函数没有显示的返回值,返回
this
- 如果构造函数的返回值是 number string boolean 这种基本数据类型,返回
this
- 如果构造函数的返回值是一个对象,那就返回这个对象
- 如果构造函数没有显示的返回值,返回
function mockNew() {
const obj = new Object()
const constructor = Array.prototype.shift.call(arguments, 1)
obj.__proto__ = constructor.prototype
// const obj = Object.create(constructor.prototype)
const result = constructor.apply(obj, arguments)
return typeof result === 'object' ? result : obj
}
function Player(name) {
this.name = name
}
const p = mockNew(Player, 'ss')
console.log(p) // Player { name: 'ss' }
console.log(p instanceof Player) // true