Skip to content
本页目录

JavaScript 设计模式精讲

1.设计模式的黑话

  1. MVC、MVP、MVVM: 用于系统分层,降低层间耦合;
  2. 模块模式: 将内部的属性和方法隐藏,仅暴露需要暴露的部分;
  3. 链模式: 通过在对象方法中将当前对象返回,实现对同一个对象的多个方法的链式调用;
  4. 中间件: 处于操作系统和应用程序之间的软件,来完成对任何数据的预处理和后处理;

主要的三种设计模式方式:

  • 创建型模式
  • 结构型模式
  • 行为型模式

创建型模式

创建型模式关注如何创建对象,主要特点是将对象的创建和使用分离。

1. 单例模式: 保证一个类只有一个实例,并提供一个访问它的全局访问点;
2. 工厂模式: 根据输入的不同返回不同类的实例,一般用来创建同一类对象;
3. 抽象工厂模式: 通过对类的工厂抽象,使其业务用于对产品类簇的创建;
4. 建造者模式: 分步构建一个复杂对象,使得同样的构建过程可以采用不同的表示;

结构型模式

结构型模式关注如何将对象按某种布局组成更大的结构

  1. 代理模式: 为目标对象创造一个代理对象,以控制对目标对象的访问;
  2. 享元模式: 运用共享技术来有效地支持大量细粒度对象的复用,减少创建的对象的数量;
  3. 适配器模式: 解决两个软件实体间接口不兼容的问题;
  4. 装饰者模式: 向一个现有的对象添加新的功能,同时又不改变其结构;
  5. 外观模式: 为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问;
  6. 组合模式: 用小的子对象构建更大的对象,使得对单个对象和组合对象具有一致的访问性;
  7. 桥接模式: 将类的抽象部分与实现部分分离,使它们可以独立地变化;

行为型模式

行为型模式关注对象之间的通信,描述对象之间怎样相互协作,以及怎样分配职责。包括下面几个小节:

  1. 发布-订阅模式: 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为;

  2. 策略模式: 定义了一系列算法,并将每个算法封装起来,使它们可以相互替换;

  3. 状态模式: 允许一个对象在其内部状态发生改变时改变其行为能力;

  4. 模板方法模式: 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤;

  5. 迭代器模式: 提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示;

  6. 命令模式: 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开;

  7. 职责链模式: 把请求从链中的一个对象传到下一个对象,直到请求被响应为止;

  8. 中介者模式: 定义一个中介对象来简化原有对象之间的复杂耦合关系;

2.this、new、bind、call、apply

this 是在函数被调用时确定的,它的指向完全取决于函数调用的地方,而不是它被声明的地方

当一个函数被调用时,会创建一个执行上下文,它包含函数在哪里被调用(调用栈)、函数的调用方式、传

入的参数等信息, this 就是这个记录的一个属性,它会在函数执行的过程中被用到。 this 在函数的指向有以下几种场景

  1. 作为构造函数被 new 调用;
  2. 作为对象的方法使用;
  3. 作为函数直接调用;
  4. 被 call 、 apply 、 bind 调用;
  5. 箭头函数中的 this ;

1.new绑定

javascript
function Foo() {
 console.log(this)
}
var bar = new Foo() // 输出: Foo 实例,this 就是 bar

new调用构造函数的时候

  1. 创建一个新对象;

  2. 构造函数的 prototype 被赋值给这个新对象的 proto

  3. 将新对象赋给当前的 this ;

  4. 执行构造函数;

  5. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象,如果返回的不是对象将被忽略;

如果我们只是调用这个函数 就相当于在这个整个window中new了一个

2.显示绑定

通过 call applybind 我们可以修改函数绑定的 this ,使其成为我们指定的对象。通过这些方法的第一个参数我们可以显式地绑定 this

javascript
function foo(name, price) {
this.name = name
this.price = price
}
function Food(category, name, price) {
 foo.call(this, name, price) // call 方式调用
// foo.apply(this, [name, price]) // apply 方式调用
this.category = category
}
new Food('食品', '汉堡', '5块钱')
// 浏览器中输出: {name: "汉堡", price: "5块钱", category: "食品"}

如果不加call调用,那么就返回

image-20230518143729664

那么foo函数把数据绑定到哪里去了.答案是全局对象window上

image-20230518143906694

总的来说最大的区别就是 new时候 在函数里的this会指向其新创建的对象,

没有new 就是直接执行的时候,this指向全局对象

恶补call apply bind的知识点

call和apply的区别
javascript
func.call(thisArg, arg1, arg2, ...) // call 用法
func.apply(thisArg, [arg1, arg2, ...]) // apply 用法
bind是返回新函数,传入新的对象并利用原来的对象的方法
javascript
var food = {
 name: '汉堡',
 price: '5块钱',
 getPrice: function(place) {
 console.log(place + this.price)
 }
}
food.getPrice('KFC ') // 浏览器中输出: "KFC 5块钱"
var getPrice1 = food.getPrice.bind({ name: '鸡腿', price: '7块钱' }, '肯打鸡 ')
getPrice1() // 浏览器中输出: "肯打鸡 7块钱"
手写bind
javascript
// ES5 方式
Function.prototype.bind = Function.prototype.bind || function() {
 var self = this
 var rest1 = Array.prototype.slice.call(arguments)
 var context = rest1.shift()
 return function() {
 var rest2 = Array.prototype.slice.call(arguments)
 return self.apply(context, rest1.concat(rest2))
 }
}
// ES6 方式
Function.prototype.bind = Function.prototype.bind || function(...rest1) {
 const self = this
 const context = rest1.shift()
 return function(...rest2) {
 return self.apply(context, [...rest1, ...rest2])
 }
}

注意:

如果你把 null 或 undefined 作为 this 的绑定对象传入 call 、 apply 、 bind ,这些值在调用时会被忽

略,实际应用的是默认绑定规则。

javascript
var a = 'hello'
function foo() {
 console.log(this.a)
}
foo.call(null) // 浏览器中输出: "hello" 相当于foo()

3.隐式绑定

函数是否在某个上下文对象中调用,如果是的话 this 绑定的是那个上下文对象。

javascript
var a = 'hello'
var obj = {
 a: 'world',
 foo: function() {
 console.log(this.a)
 }
}
obj.foo() // 浏览器中输出: "world"

上面代码中, foo 方法是作为对象的属性调用的,那么此时 foo 方法执行时, this 指向 obj 对象。也就是说,

此时 this 指向调用这个方法的对象,如果嵌套了多个对象,那么指向最后一个调用这个方法的对象:

实际上 this就是不断在找对象 找离他最近的对象 最远的就是window
javascript
var a = 'hello'
var obj = {
 a: 'world',
 b:{
     a:'China',
     foo: function() {
     	console.log(this.a)
     }
  	}
}
obj.b.foo() // 浏览器中输出: "China"

4.默认绑定

函数独立调用,直接使用不带任何修饰的函数引用进行调用,也是上面几种绑定途径之外的方式。非严格模式下 t

his 绑定到全局对象(浏览器下是 winodw ,node 环境是 global ),严格模式下 this 绑定到 undefined (因

为严格模式不允许 this 指向全局对象)。

javascript
var a = 'hello'
function foo() {
 var a = 'world'
 console.log(this.a)
 console.log(this)
}
foo() // 相当于执行 window.foo()
// 浏览器中输出: "hello"
// 浏览器中输出: Window 对象

变量 a 被声明在全局作用域,成为全局对象 window 的一个同名属性。函数 foo 被执行时, this

此时指向的是全局对象,因此打印出来的 a 是全局对象的属性。

特殊的情况 函数是对象的一个方法

javascript
var a = 'hello'
var obj = {
 a: 'world',
 foo: function() {
 console.log(this.a)
 }
}
var bar = obj.foo
bar() // 浏览器中输出: "hello"

此时 bar 函数,也就是 obj 上的 foo 方法为什么又指向了全局对象呢,是因为 bar 方法此时是作为函数独立调

用的,所以此时的场景属于默认绑定,而不是隐式绑定

特殊情况 把对象的方法作为回调函数执行

javascript
var a = 'hello'
var obj = {
 a: 'world',
 foo: function() {
 console.log(this.a)
 }
}
function func(fn) {
 fn()
}
func(obj.foo) // 浏览器中输出: "hello"

这种场景我们遇到的比较多的是 setTimeout 和 setInterval ,如果回调函数不是箭头函数,那么其中的 this 指向的就是全局对象.

其实我们可以把默认绑定当作是隐式绑定的特殊情况,比如上面的 bar() ,我们可以当作是使用 window.bar() 的

方式调用的,此时 bar 中的 this 根据隐式绑定的情景指向的就是 window 。

5.this绑定的优先级

优先级:

new 绑定 > 显示绑定 > 隐式绑定**>** 默认绑定

6.箭头函数的this

箭 头 函 数 的 this 绑定是无法通过 call 、 apply 、 bind 被修改的,且因为箭头函数没有构造函数constructor ,所以也不可以使用 new 调用,即不能作为构造函数,否则会报错。

javascript
var a = 'hello'
var obj = {
 a: 'world',
 foo: () => {
 console.log(this.a)
 }
}
obj.foo() // 浏览器中输出: "hello"

this的小练习

javascript
var a = 20
var obj = {
 a: 40,
 foo:() => {
 console.log(this.a)
 
 function func() {
 this.a = 60
 console.log(this.a)
 }
 
 func.prototype.a = 50
 return func
 }
}
var bar = obj.foo() // 浏览器中输出: 20
bar() // 浏览器中输出: 60 
new bar() // 浏览器中输出: 60
  1. var a = 20 这句在全局变量 window 上创建了个属性 a 并赋值为 20;

  2. 首先执行的是 obj.foo() ,这是一个箭头函数,箭头函数不创建新的函数作用域直接沿用语句外部的作用域,因此 obj.foo() 执行时箭头函数中 this 是全局 window,首先打印出 window 上的属性 a 的值 20,箭头函数返回了一个原型上有个值为 50 的属性 a 的函数对象 func 给 bar ;

  3. 继续执行的是 bar() ,这里执行的是刚刚箭头函数返回的闭包 func ,其内部的 this 指向 window,因此 this.a 修改了 window.a 的值为 60 并打印出来;

  4. 然后执行的是 new bar() ,根据之前的表述,new 操作符会在 func 函数中创建一个继承了 func 原型的实例对象并用 this 指向它,随后 this.a = 60 又在实例对象上创建了一个属性 a ,在之后的打印中已经在实例上找到了属性 a ,因此就不继续往对象原型上查找了,所以打印出第三个 60;

如果把其中的箭头函数替换成普通函数的话

javascript
var a = 20
var obj = {
 a: 40,
 foo: function() {
 console.log(this.a)
 
 function func() {
 this.a = 60
 console.log(this.a)
 }
 
 func.prototype.a = 50
 return func
 }
}
var bar = obj.foo() // 浏览器中输出: 40
bar() // 浏览器中输出: 60
new bar() // 浏览器中输出: 60