基础知识

前言

  • 设计模式是在某种场合下对某个问题的一种解决方案。
  • 在我们试图解决一些问题的时候,说不定别人也遇到过一样的问题,并且把他们整理成了模式,提供了一种通用的解决方案。
  • 设计模式实际上是解决某一种问题的一种思想,与具体的语言无关。
  • 除了主流的面向对象语言,在函数式的语言中,设计模式依然存在。
  • 一些设计模式的实现会因为语言的不同而不同。
  • 软件的成本并非全部在开发阶段,虽然设计模式可能会增加复杂度,或带来一些额外的代码,但它会让人们写出可复用和可维护性高的程序。
  • 所有设计模式的实现遵循一条原则:“找出程序中变化的地方,并将变化封装起来”。
  • 很多模式的类图和结构很相似但不重要,辨别模式的关键是这个模式出现的场景,以及帮助我们解决了什么问题。

一、面向对象的 JavaScript

JavaScript 没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承。 ES6 的 class 是语法糖。

编程语言类型和鸭子类型

  • 编程语言按照数据类型大体可以分为两类,一类是**_静态类型语言_**,另一类是**_动态类型语言_**。**_静态类型语言_**在编译时便以确定变量的类型,而**_动态类型语言_**要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。**
  • **静态类型语言**的优点是可以提早发现错误,编译器可以针对这些数据类型的信息做一些优化。缺点是强契约不够灵活。
  • _动态类型语言_的优点是简洁代码量少,缺点是有些类型相关的错误可能到运行的时候才能知道。
  • JS 是_**动态类型语言。**_
  • _鸭子类型_强调关注对象的行为,而不是对象本身。动态类型语言 不必借助超类型的帮助,假如一个对象正确实现了 push 和 pop 方法,那它就可以被当作栈来使用。

多态

  • 多态的思想是把“做什么”和“谁去做”分离开来。下面代码,我们不关注传进来的是那种对象,只要它有 makeSound 方法。
// 不变的部分
function makeSound (animal) {
  animal.sound()
}

// 可变的部分
const Duck = function () {}
Duck.prototype.sound = function () {
  console.log('嘎嘎嘎')
}

const Chicken = function () {}
Chicken.prototype.sound = function () {
  console.log('咯咯咯')
}

makeSound(new Duck())
makeSound(new Chicken())
  • 将行为分布在各个对象中,让这些对象负责自己的行为,这是面向对象设计的优点。

封装

  • 封装一般指封装数据封装实现,更广义的,还包括封装类型封装变化
  • 比如 each 函数内部封装了迭代器的实现。
  • 封装变化将容易变化的封装起来,能最大程度保证对象的稳定性和扩展性。

原型模式

原型编程思想中,类不是必须的,对象未必从类创建而来,一个对象时通过克隆另一个对象所得到的。 原型模式不单是一种设计模式,也被称为一种编程泛型。

  • 原型模式是通过克隆来创建对象的,如果需要一个跟某个对象一模一样的的对象,就可以使用原型模式。
  • 原型模式的实现关键,是语言是否提供了 clone 方法,ES5 提供了 Object.create 方法,可以用来克隆对象。
Object.create = Object.create || function (obj) {
  var F = function () {}
  F.prototype = obj
  return new F()
}

JS 中的原型继承

  • 基于原型链的委托机制就是原型继承的本质。
  • 要得到一个对象,不是通过实例化,而是找到一个对象作为原型,并克隆它。
  • JS 的根对象是 Object.prototype ,是个空的对象。
  • JS 中没有类的概念,只有函数构造器,JS 的函数除了可以作为普通函数被调用,当使用 new 运算符来调用函数时,此时的函数就是一个构造器。
  • 对象会记住他的原型,更准确的是,记住对象构造器的原型。
  • 除了根对象,任何对象都有一个原型,而通过 Object.create(null) 可以创建出没有原型的对象。

二、this、call 和 apply

三、闭包和高阶函数

虽然 JS 是一门完整的面向对象语言的编程语言,但这门语言同时也拥有许多函数式语言的特性。

闭包的一些作用

  1. 封装变量:将一些不需要暴露在全局的变量封装成“私有变量”,比如用于缓存计算的变量。
  2. 延续局部变量的寿命:一些低版本浏览器会在 img 变量销毁的时候,丢失掉请求。可以将 img 用闭包封闭起来。
  3. 闭包和面向对象设计:通常用面向对象思想能实现的功能,闭包也能实现。
  4. 闭包实现命令模式

闭包与内存管理

  • 将变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,不能说是内存泄漏。
  • 跟闭包和内存泄漏有关系的是,使用闭包的同时比较容易形成循环引用,但本身并非闭包或者 JS 的问题。原因主要是 IE 浏览器涉及到 COM 对象的垃圾收集机制是计数策略,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。

高阶函数

高阶函数至少满足下列条件之一的函数。

  • 函数可以作为参数被传递。比如回调函数、各种数组方法。
  • 函数可以作为返回值输出。

高阶函数实现 AOP和其它应用

AOP(面向切面编程)的主要作用是把跟核心业务逻辑模块无关的功能抽离出来,通常包括日志统计、安全控制、异常处理等。这样能保持业务逻辑模块的纯净和高内聚性。

  • 函数柯里化(function curring),又称_部分求值。_
  • uncurrying,类数组通常会借用数组的方法,uncurrying 可以将他们变成通用的函数。
  • 函数节流
  • 分时函数
  • 惰性加载函数
// 节流函数
function throttle (fn, interval = 500) {
  let timer;
  
  return function (...args) {
    if (timer) {
      return false
    }

    let timer = setTimeout(() => {
      clearTimeout(timer);
      timer = null;
      fn(...args)
    }, interfal);
  }
}

// 惰性加载函数
var addEvent = (elem, type, handler) => {
  if (window.addEventListener) {
    addEvent = (elem, type, handler) => elem.addEventListener(type, handler, false)
  } else if (window.attachEvent) {
    addEvent = (elem, type, handler) => elem.attachEvent('on' + type, handler)
  }
}