w字总结《JavaScript设计模式与开发实践》(设计模式)(上)

10,078 阅读14分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

系列文章

w字总结《JavaScript设计模式与开发实践》(基础篇)

w字总结《JavaScript设计模式与开发实践》(设计模式)(下)

w字总结《JavaScript设计模式与开发实践》(设计原则和编程技巧)

设计模式(上)

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点

实现单例模式

举个最常用的例子,我们登录的时候,页面中会出现一个登录浮窗,而它是唯一的,无论点多少次登录按钮,浮窗只会被创建一次,不会每次点击都创建新的实例。

简易的单例模式实现起来并不复杂,无非就是用一个变量来记录当前是否已为该类创建过对象,是则在下次获取该类实例时直接返回之前创建的对象。

var Singleton = function( name ){ 
    this.name = name; 
    this.instance = null; 
}; 
Singleton.prototype.getName = function(){ 
    alert ( this.name ); 
}; 
Singleton.getInstance = function( name ){ 
    if ( !this.instance ){ 
        this.instance = new Singleton( name ); 
    } 
    return this.instance; 
}; 
var a = Singleton.getInstance( 'sven1' ); 
var b = Singleton.getInstance( 'sven2' ); 
console.log( a === b ); // true

代码很简单,但是这样的写法意义不大,因为类是“不透明”的,使用者必须知道这是一个单例类,且需要使用Singleton.getInstance获取对象。

透明的单例模式(代理)

我们需要做的是能够实现一个透明的单例类,使用方式像其他任何普通类一样且足够灵活。

var CreateDiv = function( html ) {
    this.html = html
    this.init()
}
CreateDiv.prototype.init = function() {
    var div = document.createElement('div')
    div.innerHTML = this.html
    document.body.appendChild(div)
}

// 代理类
var ProxySingletonCreateDiv = (function() {
    var instance
    return function(html) {
        if(!instance) {
            instance = new CreateDiv(html)
        }
        return instance
    }
})()

var a = new ProxySingletonCreateDiv('a')
var b = new ProxySingletonCreateDiv('b')

console.log(a === b)

上述代码中我们通过代理类ProxySingletonCreateDiv实现了对CreateDiv的单例化(代理模式,在后面介绍),与之前不同的是,CreateDiv变成了普通的类,如果业务场景中需要创建多个实例时,我们可以直接使用CreateDiv创建。二者组合达到了单例模式的效果。

惰性单例

顾名思义,惰性单例只有在需要的时候才创建对象实例。实现思路如最开始,用一个变量来标志是否创建过对象,是则返回创建好的对象。

var getSingle = function(fn) {
    var result 
    return function() {
        return result || (result = fn.apply(this,arguments))
    }
}

这里结合了基础篇中的许多知识,我们可以传入任何方法,之后让getSingle返回一个新的函数,并用result保存fn的计算结果,因为result在闭包中,它永远不会被销毁,以后再请求时,如果result存在,那么它将返回这个值。

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。策略模式的目的就是将算法的使用与算法的实现分离。

计算奖金

书里举了一个计算奖金的例子。

很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为 S 的人年终奖有 4 倍工资,绩效为 A的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。

在这个例子中,算法的使用方法不变,目的都是为了取得计算后的奖金,而算法的实现不一定相同,每种绩效对应不同计算规则。

基于策略模式的程序由两部分组成,第一部分是策略类,其中封装具体算法,并负责具体计算过程。第二部分是环境类,负责接受客户请求,并把请求委托给某个策略类。

var strategies = {
    'S': function(num) {
        return num * 4
    },
    'A': function(num) {
        return num * 3
    },
    'B': function(num) {
        return num * 2
    },
}
// 
var calculate = function(level , num) {
    return strategies[level](num)
} 

console.log(calculate('S' , 4));
console.log(calculate('A' , 3));

多态在策略模式中的体现

通过使用策略模式重构代码,我们消除了原程序中大片的条件分支语句。所有跟计算奖金有关的逻辑不再放在Context中,而是分布在各个策略对象中。Context并没有计算奖金的能力,而是把这个职责委托给了某个策略对象。每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出“计算奖金”的请求时,它们会返回各自不同的计算结果,这正是对象多态性的体现,也是“它们可以相互替换”的目的。替换 Context 中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果。

策略模式优缺点

  • 优点

    • 策略模式利用组合、委托、多态等技术及思想,避免多重条件选择语句。
    • 策略模式完美支持开放-封闭原则,将算法封装在strategy中,易于切换、理解和扩展。
    • 策略模式中的算法也可以复用在其他地方。
    • 策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
  • 缺点

    • 在程序中添加许多策略类或策略对象
    • 必须了解所有的 strategy,必须了解各个 strategy 之间的不同点,这样才能选择一个合适的 strategy、

代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

送花问题

在四月一个晴朗的早晨,小明遇见了他的百分百女孩,我们暂且称呼小明的女神为A。两天之后,小明决定给A送一束花来表白。刚好小明打听到 A 和他有一个共同的朋友B,于是内向的小明决定让B来代替自己完成送花这件事情。当A在心情好的时候收到花,小明表白成功的几率有60%,而当 A 在心情差的时候收到花,小明表白的成功率无限趋近于0。

这时候需要用到代理模式,小明无法得知A的心情,而B却了解,把花交给B,B会监听A的心情变化,选择心情好的时候再转交。

var Flower = function(){}

var xiaoming = {
    sendFlower: function(target) {
        var flower = new Flower()
        target.receiveFlower(flower)
    }
}

var B = {
    receiveFlower: function(flower) {
        A.listenGoodMood(function() {
            A.receiveFlower(flower)
        })
    }
}

var A = {
    receiveFlower: function(flower) {
        console.log('收到花', flower)
    },
    listenGoodMood: function( fn ) {
        setTimeout(function() {
            fn()
        },1000)
    }
}

xiaoming.sendFlower(B)

保护代理和虚拟代理

保护代理:代理B可以帮助A过滤掉一些请求,不符合要求的请求在代理B处被拒绝掉,如上文的例子。

虚拟代理:把操作交给代理B去执行,代理B决定何时去执行操作。虚拟操作把一些开销很大的对象延迟到真正需要的时候再创建。

var B = {
    receiveFlower: function(flower) {
        A.listenGoodMood(function() {
            var flower = new Flower()
            A.receiveFlower(flower)
        })
    }
}

缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。

var mult = function(){ 
    console.log( '开始计算乘积' ); 
    var a = 1; 
    for ( var i = 0, l = arguments.length; i < l; i++ ){ 
        a = a * arguments[i]; 
    } 
    return a; 
}; 
mult( 2, 3 ); // 输出:6 
mult( 2, 3, 4 ); // 输出:24 
var proxyMult = (function(){ 
    var cache = {}; 
    return function(){ 
        var args = Array.prototype.join.call( arguments, ',' ); 
        if ( args in cache ){ 
            return cache[ args ]; 
        } 
        return cache[ args ] = mult.apply( this, arguments ); 
    } 
})(); 
proxyMult( 1, 2, 3, 4 ); // 输出:24 
proxyMult( 1, 2, 3, 4 ); // 输出:24

高阶函数动态创建代理

/**************** 计算乘积 *****************/ 
var mult = function(){ 
    var a = 1; 
    for ( var i = 0, l = arguments.length; i < l; i++ ){ 
        a = a * arguments[i]; 
    } 
    return a; 
}; 
/**************** 计算加和 *****************/ 
var plus = function(){ 
    var a = 0; 
    for ( var i = 0, l = arguments.length; i < l; i++ ){ 
        a = a + arguments[i]; 
    } 
    return a; 
}; 
/**************** 创建缓存代理的工厂 *****************/ 
var createProxyFactory = function( fn ){ 
    var cache = {}; 
    return function(){ 
        var args = Array.prototype.join.call( arguments, ',' ); 
        if ( args in cache ){ 
            return cache[ args ]; 
        } 
        return cache[ args ] = fn.apply( this, arguments ); 
    } 
}; 
var proxyMult = createProxyFactory( mult ), 
proxyPlus = createProxyFactory( plus ); 
console.log( proxyMult( 1, 2, 3, 4 ) ); // 输出:24 
console.log( proxyMult( 1, 2, 3, 4 ) ); // 输出:24 
console.log( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10 
console.log( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10

迭代器模式

提供一种方法顺序访问一个聚合对象中的各元素,而又不需要暴露该对象的内部表示。

迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。简单来说就是统一“集合”型数据结构的遍历接口,实现可循环遍历获取集合中各数据项(不关心数据项的数据结构)。

实现自己的迭代器

接受两个参数:被循环数组、循环每一步后触发的回调

var each = function(ary, callback) {
    for(var i = 0,l = ary.length;i< l;i++) {
        callback.call(ary[i] , i , ary[i])
    }
}

var arr = [1,2,3]
each(arr , function(i , n)) {
    console.log([i , n])
}

内部迭代器和外部迭代器

  1. 内部迭代器

我们刚刚编写的 each 函数属于内部迭代器,each函数的内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用。内部迭代器在调用的时候非常方便,外界不用关心迭代器内部的实现,跟迭代器的交互也仅仅是一次初始调用,但这也刚好是内部迭代器的缺点。由于内部迭代器的迭代规则已经被提前规定,上面的each函数就无法同时迭代2个数组了。比如现在有个需求,要判断 2 个数组里元素的值是否完全相等, 如果不改写each函数本身的代码,我们能够入手的地方似乎只剩下each的回调函数了。

var compare = function( ary1, ary2 ){ 
    if ( ary1.length !== ary2.length ){ 
        throw new Error ( 'ary1 和 ary2 不相等' ); 
    } 
    each( ary1, function( i, n ){ 
        if ( n !== ary2[ i ] ){ 
            throw new Error ( 'ary1 和 ary2 不相等' ); 
        } 
    }); 
    alert ( 'ary1 和 ary2 相等' ); 
}; 
compare( [ 1, 2, 3 ], [ 1, 2, 4 ] ); // throw new Error ( 'ary1 和 ary2 不相等' );
  1. 外部迭代器

外部迭代器必须显式地请求迭代下一个元素。 外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序。外部迭代器虽然调用方式复杂,但适用面更广,更能满足多变的需求。

var Iterator = function( obj ){ 
    var current = 0; 
    var next = function(){ 
        current += 1; 
    }; 
    var isDone = function(){ 
        return current >= obj.length; 
    }; 
    var getCurrItem = function(){ 
        return obj[ current ]; 
    }; 
    return { 
        next: next, 
        isDone: isDone, 
        getCurrItem: getCurrItem 
    } 
};

// 改写compare函数

var compare = function( iterator1, iterator2 ){ 
    while( !iterator1.isDone() && !iterator2.isDone() ){ 
        if ( iterator1.getCurrItem() !== iterator2.getCurrItem() ){ 
            throw new Error ( 'iterator1 和 iterator2 不相等' ); 
        } 
        iterator1.next(); 
        iterator2.next(); 
    } 
    alert ( 'iterator1 和 iterator2 相等' ); 
}

var iterator1 = Iterator( [ 1, 2, 3 ] ); 
var iterator2 = Iterator( [ 1, 2, 3 ] ); 
compare( iterator1, iterator2 ); // 输出:iterator1 和 iterator2 相等

迭代类数组对象和字面量对象

迭代器模式不仅可以迭代数组,还可以迭代一些类数组的对象。比如arguments、{"0":'a',"1":'b'}等。通过上面的代码可以观察到,无论是内部迭代器还是外部迭代器,只要被迭代的聚合对象拥有 length 属性而且可以用下标访问,那它就可以被迭代。在 JavaScript 中,for in 语句可以用来迭代普通字面量对象的属性。

倒序迭代器

var reverseEach = function(ary , callback) {
    for(var l = ary.length - 1;l >= 0; l--) {
        callback(l,ary[l])
    }
}

reverseEach([0,1,2] , function(i, n) {
    console.log(n)  
})

中止迭代器

var each = function( ary, callback ){ 
    for ( var i = 0, l = ary.length; i < l; i++ ){ 
        if ( callback( i, ary[ i ] ) === false ){ // callback 的执行结果返回 false,提前终止迭代
            break; 
        } 
    } 
}; 
each( [ 1, 2, 3, 4, 5 ], function( i, n ){ 
    if ( n > 3 ){ // n 大于 3 的时候终止循环
        return false; 
    } 
    console.log( n ); // 分别输出:1, 2, 3 
});

发布-订阅模式

它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。


/* 通用发布订阅 */
// 时间上解耦、对象之间解耦,对应的会消耗时间和内存
var Event = (function () {
    var clientList = {},
        listen, trigger, remove
    listen = function (key, fn) {
        if (!clientList[key]) {
            clientList[key] = []
        }
        clientList[key].push(fn)
    }
    trigger = function () {
        var key = Array.prototype.shift.call(arguments)
        var fns = clientList[key]
        if (!fns || fns.length === 0) {
            return false
        }
        for (var i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    }
    remove = function (key, fn) {
        var fns = clientList[key]
        if (!fns) {
            return false
        }
        if (!fn) {
            fns && (fns.length = 0)
        } else {
            for (var i = fns.length - 1; i >= 0; i--) {
                var _fn = fns[i]
                if (_fn === fn) {
                    fns.splice(i, 1)
                }
            }
        }

    }
    return {
        listen, trigger, remove
    }
})()

Event.listen('square88' , function(price) {
    console.log('price:' , price);
})

Event.trigger('square88' , 2000000)

发布-订阅模式的应用

书中讲的在这就不多说了,说点书里没有的。

想要对发布-订阅模式有更加深入的学习,推荐自己动手实现一个vue,vue2的响应式就是通过该模式实现的,接下来我们看看这个特性是如何使用的。

vue响应式原理

var v = new Vue({
    data() {
        return {
            a:'a'
        }
    }
})

image

官网有一张关于响应式的图,我们来结合图片分析一下。

数据劫持

我们都知道,数据劫持的核心是Object.defineProperty将属性转化成对应的getter\setter(vue不支持ie8以下的原因)。在数据传递变更的时候,会进入Dep和Watcher中处理。

walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; ++i) {
        defineReactive(obj, keys[i], obj[keys[i]])
    }
}

劫持相关函数及订阅发布


/**
 * Define a reactive property on an Object.
 */
export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  /*在闭包中定义一个dep对象*/
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  /*如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。*/
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  /*对象的子对象递归进行observe并返回子节点的Observer对象*/
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      /*如果原本对象拥有getter方法则执行*/
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        /*进行依赖收集*/
        dep.depend()
        if (childOb) {
          /*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          /*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      /*通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作*/
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        /*如果原本对象拥有setter方法则执行setter*/
        setter.call(obj, newVal)
      } else {
        val = newVal
      }

      /*新的值需要重新进行observe,保证数据响应式*/
      childOb = observe(newVal)

      /*dep对象通知所有的观察者*/
      dep.notify()
    }
  })
}

在初始化时对data内数据开始劫持监听,初始化时调用observe,返回的是Observer实例

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
/*
 尝试创建一个Observer实例(__ob__),如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。
 */
export function observe(value: any, asRootData: ?boolean): Observer | void {
  /*判断是否是一个对象*/
  if (!isObject(value)) {
    return
  }
  let ob: Observer | void

  /*这里用__ob__这个属性来判断是否已经有Observer实例,如果没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性,如果已有Observer实例则直接返回该Observer实例*/
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    /*这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等情况。*/
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    /*如果是根数据则计数,后面Observer中的observe的asRootData非true*/
    ob.vmCount++
  }
  return ob
}
Dep与Watcher

在数据劫持时,数据的获取和修改都会做出对应操作。操作目的就是为了通知“中转站”,它主要是对数据变更起通知作用及存放依赖这些数据的地方。

Dep

Dep用来收集依赖,通知对应的订阅者,让它执行自己的操作。

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher
  id: number
  subs: Array<Watcher>

  constructor() {
    this.id = uid++
    this.subs = []
  }

  /*添加一个观察者对象*/
  addSub(sub: Watcher) {
    this.subs.push(sub)
  }

  /*移除一个观察者对象*/
  removeSub(sub: Watcher) {
    remove(this.subs, sub)
  }

  /*依赖收集,当存在Dep.target的时候添加观察者对象*/
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  /*通知所有订阅者*/
  notify() {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/
const targetStack = []
export function pushTarget(_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  // 改变目标指向
  Dep.target = _target
}

export function popTarget() {
  // 删除当前目标,重算指向
  Dep.target = targetStack.pop()
}

上述代码主要进行了两个动作:

  • 定义subs数组,用来搜集订阅者Watcher。
  • 劫持数据变更时,通知Watcher进行update操作。
Watcher

Watcher是订阅者,主要作用是订阅Dep,当Dep发出消息notify的时候,所有订阅了Dep的Watcher执行自己的update操作。

export default class Watcher {
  vm: Component
  expression: string
  cb: Function
  id: number
  deep: boolean
  user: boolean
  lazy: boolean
  sync: boolean
  dirty: boolean
  active: boolean
  deps: Array<Dep>
  newDeps: Array<Dep>
  depIds: ISet
  newDepIds: ISet
  getter: Function
  value: any

  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    /*_watchers存放订阅者实例*/
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression =
      process.env.NODE_ENV !== 'production' ? expOrFn.toString() : ''
    // parse expression for getter
    /*把表达式expOrFn解析成getter*/
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function() {}
        process.env.NODE_ENV !== 'production' &&
          warn(
            `Failed watching path: "${expOrFn}" ` +
              'Watcher only accepts simple dot-delimited paths. ' +
              'For full control, use a function instead.',
            vm
          )
      }
    }
    this.value = this.lazy ? undefined : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  /*获得getter的值并且重新进行依赖收集*/
  get() {
    /*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
    pushTarget(this)
    let value
    const vm = this.vm

    /*
    执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。
    在将Dep.target设置为自生观察者实例以后,执行getter操作。
    譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c,
    那么在执行getter的时候就会触发a跟c两个数据的getter函数,
    在getter函数中即可判断Dep.target是否存在然后完成依赖收集,
    将该观察者对象放入闭包中的Dep的subs中去。
    */
    if (this.user) {
      try {
        value = this.getter.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      }
    } else {
      value = this.getter.call(vm, vm)
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    /*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/
    if (this.deep) {
      /*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/
      traverse(value)
    }

    /*将观察者实例从target栈中取出并设置给Dep.target*/
    popTarget()
    this.cleanupDeps()
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  /*添加一个依赖关系到Deps集合中*/
  addDep(dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  /*清理依赖收集*/
  cleanupDeps() {
    /*移除所有观察者对象*/
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  /*
  调度者接口,当依赖发生改变的时候进行回调。
  */
  update() {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      /*同步则执行run直接渲染视图*/
      this.run()
    } else {
      /*异步推送到观察者队列中,由调度者调用。*/
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  /*
        调度者工作接口,将被调度者回调。
        */
  run() {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        /*
          即便值相同,拥有Deep属性的观察者以及在对象/数组上的观察者应该被触发更新,因为它们的值可能发生改变。
        */
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        /*设置新的值*/
        this.value = value

        /*触发回调渲染视图*/
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  /*获取观察者的值*/
  evaluate() {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  /*收集该watcher的所有deps依赖*/
  depend() {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  /*将自身从所有依赖收集订阅列表删除*/
  teardown() {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      /*从vm实例的观察者列表中将自身移除,由于该操作比较耗费资源,所以如果vm实例正在被销毁则跳过该步骤。*/
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

命令模式

命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。

命令模式是一种松耦合的方式,使请求发送者和接收者消除彼此的耦合关系。

/* 
    本质是对命令封装,拆分发出命令的责任和执行命令的责任
    优点:降低对象耦合度,易扩展组合命令,调用同一方法实现不同功能
*/

// 命令
var CreateCommand = function (receiver) {
    this.receiver = receiver
}
CreateCommand.prototype.execute = function () {
    this.receiver.action()
}

// 接收者
var TVOn = function () { }
TVOn.prototype.action = function () {
    console.log('TV on now');
}

var TVOff = function () { }
TVOff.prototype.action = function () {
    console.log('TV off now');
}

// 调用者
var Invoker = function (tvOnCommand, tvOffCommand) {
    this.tvOnCommand = tvOnCommand
    this.tvOffCommand = tvOffCommand
}
Invoker.prototype.tvOn = function () {
    this.tvOnCommand.execute()
}
Invoker.prototype.tvOff = function () {
    this.tvOffCommand.execute()
}

var tvOnCommand = new CreateCommand(new TVOn())
var tvOffCommand = new CreateCommand(new TVOff())

var myInvoker = new Invoker(tvOnCommand, tvOffCommand)
myInvoker.tvOn()
myInvoker.tvOff()

组合模式

组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的。

组合模式用途

组合模式将对象组合成树型结构,以表示“部分-整体”的层级结构。组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。

以宏命令为例,请求从树最顶端的对象往下传递,如果当前处理请求的对象是叶对象(普通子命令),叶对象自身会对请求作出相应的处理;如果当前处理请求的对象是组合对象(宏命令),组合对象则会遍历它属下的子节点,将请求继续传递给这些子节点。

请求从上至下传递直到尽头,用户只需要关心最顶层的组合对象,请求该对象请求便会向下传递。

角色: (1)子对象 (2)组合对象 (3)抽象类:主要定义了参与组合的对象的公共接口,也可以直接在组合对象中定义

举例

github上原有例子

场景:组织内有各种员工,员工有不同姓名、工资,可进行添加操作。

// 场景 以员工为例。这里我们有不同的员工类型
// 开发者
class Developer {
    constructor(name, salary) {
        this.name = name
        this.salary = salary
    }
    getName() {
        return this.name
    }
    setSalary(salary) {
        this.salary = salary
    }
    getSalary() {
        return this.salary
    }
    getRoles() {
        return this.roles
    }
    develop() {
        /* */
    }
}
// 设计师
class Designer {
    constructor(name, salary) {
        this.name = name
        this.salary = salary
    }
    getName() {
        return this.name
    }
    setSalary(salary) {
        this.salary = salary
    }
    getSalary() {
        return this.salary
    }
    getRoles() {
        return this.roles
    }
    design() {
        /* */
    }
}
// 一个由几种不同类型的员工组成的组织
class Organization {
    constructor(){
        this.employees = []
    }
    // 追加元素
    addEmployee(employee) {
        this.employees.push(employee)
    }
    //  叶对象都有一样的getSalary方法。在根对象执行的时候,可以使用leaf.execute的模式来调用对象的方法。
    getNetSalaries() {
        let netSalary = 0
        this.employees.forEach(employee => {
            netSalary += employee.getSalary()
        })
        return netSalary
    }
}
// 调用
// Prepare the employees
const john = new Developer('John Doe', 12000)
const jane = new Designer('Jane', 10000)
// Add them to organization 优势:无论多少员工类型 对整个组合对象只调用一次
const organization = new Organization()
organization.addEmployee(john)
organization.addEmployee(jane)
console.log("Net salaries: " , organization.getNetSalaries()) // Net Salaries: 22000

需要注意的地方

  • 组合模式不是父子关系
  • 对叶对象操作的一致性
  • 双向映射关系
  • 用职责链模式提高组合模式性能

使用场景

  • 含有某种层级结构的对象集合(具体结构在开发过程中无法确定)
  • 希望对这些对象或者其中的某些对象执行某种操作

缺点:因为组合对象的任何操作都会对所有的子对象调用同样的操作,所以当组合的结构很大时会有性能问题。