obaa源码加注

这个是dntzhang写的用于监听变量更改的库obaa,加上一点注释方便理解~

传送门

/* obaa 1.0.0
 * By dntzhang
 * Github: https://github.com/Tencent/omi
 * MIT Licensed.
 */
; (function (win) {

  var obaa = function (target, arr, callback) {
    var _observe = function (target, arr, callback) {
      //if (!target.$observer) target.$observer = this
      // $observer指向这个要返回的对象
      var $observer = this

      // 被监听的属性列表
      var eventPropArr = []

      // 如果是数组先监听数组的变异操作
      if (obaa.isArray(target)) {
        if (target.length === 0) {
          target.$observeProps = {}
          target.$observeProps.$observerPath = '#'
        }
        $observer.mock(target)
      }

      // 遍历非继承的key值
      for (var prop in target) {
        if (target.hasOwnProperty(prop)) {
          // 传入三个参数时(指定了要监听的key值)
          if (callback) {
            // 以array方式指定
            if (obaa.isArray(arr) && obaa.isInArray(arr, prop)) {
              eventPropArr.push(prop)
              $observer.watch(target, prop)
            }
            // 以string方式指定
            else if (obaa.isString(arr) && prop == arr) {
              eventPropArr.push(prop)
              $observer.watch(target, prop)
            }
          }
          // 传入两个参数时(代表监听所有的key值)
          else {
            eventPropArr.push(prop)
            $observer.watch(target, prop)
          }
        }
      }
      // 留一个原始对象的引用(后面返回的时候用到)
      $observer.target = target

      // 如果没有 handler 队列,初始化一个
      if (!$observer.propertyChangedHandler)
        $observer.propertyChangedHandler = []
      
      // 根据参数个数赋值回调函数
      var propChanged = callback ? callback : arr

      // 把回调放入 handler 队列中
      // 下面的all代表了监听所有的key
      $observer.propertyChangedHandler.push({
        all: !callback,
        propChanged: propChanged,
        eventPropArr: eventPropArr
      })
    }

    _observe.prototype = {

      // 当有值修改时的的响应函数
      // 这个方法定义在原型上了,所有对象都共用这个方法
      onPropertyChanged: function (prop, value, oldValue, target, path) {

        // 如果值有变化且当前对象有 Handler
        if (value !== oldValue && this.propertyChangedHandler) {
          // 根的名字?
          var rootName = obaa._getRootName(prop, path)
          // 遍历当前对象所有的 Handler
          for (
            var i = 0, len = this.propertyChangedHandler.length;
            i < len;
            i++
          ) {
            var handler = this.propertyChangedHandler[i]
            // .all - 当前监听所有变化
            // isInArray - 当前节点在 handler 监听列表中
            // rootName 以 Array- 开头
            if (
              handler.all ||
              obaa.isInArray(handler.eventPropArr, rootName) ||
              rootName.indexOf('Array-') === 0
            ) {
              // 调用 handler 的 propChanged 方法
              // 并把 上下文设为 this.target
              handler.propChanged.call(this.target, prop, value, oldValue, path)
            }
          }
        }
        // 如果属性不以 Array- 开头(不是数组的操作方法) && 值为 object(数组、对象或者null)
        if (prop.indexOf('Array-') !== 0 && typeof value === 'object') {
          // 监听这个属性
          // 应该是为了保证新加入的属性也能被监听到?
          this.watch(target, prop, target.$observeProps.$observerPath)
        }
      },

      // 模拟数组操作(覆盖原有的数组操作方法)
      // 作用是监听数组的操作方法
      mock: function (target) {
        var self = this
        // 修改操作数组的方法,这里的item是push、splice这些方法名字符串
        obaa.methods.forEach(function (item) {
          target[item] = function () {
            var old = Array.prototype.slice.call(this, 0)//拷贝一份数组
            var result = Array.prototype[item].apply(
              this,
              Array.prototype.slice.call(arguments)
            )//调用时先执行原来应该有的效果

            // 如果触发列表中有的话(下面有一个触发的方法列表)
            // 其实这里直接只遍历触发列表就可以了吧?
            // 还是说这样就可以实现类数组对象的操作了?
            if (new RegExp('\\b' + item + '\\b').test(obaa.triggerStr)) {
              // 遍历target的所有非继承得到的、非可执行函数的属性
              // 对这个属性调用watch方法
              for (var cprop in this) {
                if (
                  this.hasOwnProperty(cprop) &&
                  !obaa.isFunction(this[cprop])
                ) {
                  self.watch(this, cprop, this.$observeProps.$observerPath)
                }
              }
              //todo
              // 以 Array- 开头加上 方法名,调用 onPropertyChanged 方法
              self.onPropertyChanged(
                'Array-' + item,
                this,
                old,
                this,
                this.$observeProps.$observerPath
              )
            }
            return result
          }

          // 把原来的数组操作方法用 purePush 这种命名暴露出去
          // 大概是为了提供一个能够直接操作但不被监听到的方法
          target[
            'pure' + item.substring(0, 1).toUpperCase() + item.substring(1)
          ] = function () {
            return Array.prototype[item].apply(
              this,
              Array.prototype.slice.call(arguments)
            )
          }
        })
      },

      watch: function (target, prop, path) {

        // 不监听用来监听的属性(大概是为了避免循环中修改了存储用的属性)
        if (prop === '$observeProps' || prop === '$observer') return

        // 不监听函数
        if (obaa.isFunction(target[prop])) return

        // 没有的话初始化监听属性
        if (!target.$observeProps) target.$observeProps = {}

        // 如果传入path,把path存入observeProps
        // 如果未传path(代表是根节点),把path设为 ‘#’
        if (path !== undefined) {
          target.$observeProps.$observerPath = path
        } else {
          target.$observeProps.$observerPath = '#'
        }

        var self = this

        // 把target的属性值复制到 $observeProps 里,
        // 用 currentValue 表示当前复制的值
        var currentValue = (target.$observeProps[prop] = target[prop])

        // 使用getter和setter对属性进行监听 
        // 操作时使用$observeProps进行操作
        // 当被赋值时调用对象的 onPropertyChanged 方法(让对象自己处理响应)
        Object.defineProperty(target, prop, {
          get: function () {
            return this.$observeProps[prop]
          },
          set: function (value) {
            var old = this.$observeProps[prop]
            this.$observeProps[prop] = value
            self.onPropertyChanged(
              prop,
              value,
              old,
              this,
              target.$observeProps.$observerPath
            )
          }
        })

        // 如果当前遍历到的值是对象(数组)的话
        if (typeof currentValue == 'object') {

          if (obaa.isArray(currentValue)) {
            // 递归模拟数组的操作方法
            this.mock(currentValue)

            // 如果是空数组
            // 初始化对象的 $observeProps
            // 初始化对象的 $observerPath
            if (currentValue.length === 0) {
              if (!currentValue.$observeProps) currentValue.$observeProps = {}
              if (path !== undefined) {
                currentValue.$observeProps.$observerPath = path
              } else {
                currentValue.$observeProps.$observerPath = '#'
              }
            }
          }

          // 遍历那些非继承得到的属性
          for (var cprop in currentValue) {
            if (currentValue.hasOwnProperty(cprop)) {
              // 对这些属性进行监听
              this.watch(
                currentValue,
                cprop,
                target.$observeProps.$observerPath + '-' + prop
              )
            }
          }
        }
      }
    }
    // 在这个omi-mp-create里未用到这个返回值
    return new _observe(target, arr, callback)
  }

  // 数组被重新修改了的方法列表
  // (或者说被监听了)
  obaa.methods = [
    'concat',
    'copyWithin',
    'entries',
    'every',
    'fill',
    'filter',
    'find',
    'findIndex',
    'forEach',
    'includes',
    'indexOf',
    'join',
    'keys',
    'lastIndexOf',
    'map',
    'pop',
    'push',
    'reduce',
    'reduceRight',
    'reverse',
    'shift',
    'slice',
    'some',
    'sort',
    'splice',
    'toLocaleString',
    'toString',
    'unshift',
    'values',
    'size'
  ]
  // 数组方法中会触发事件的方法列表(最后被join了一下成一个字符串)
  obaa.triggerStr = [
    'concat',
    'copyWithin',
    'fill',
    'pop',
    'push',
    'reverse',
    'shift',
    'sort',
    'splice',
    'unshift',
    'size'
  ].join(',')

  obaa.isArray = function (obj) {
    return Object.prototype.toString.call(obj) === '[object Array]'
  }

  obaa.isString = function (obj) {
    return typeof obj === 'string'
  }

  obaa.isInArray = function (arr, item) {
    for (var i = arr.length; --i > -1;) {
      if (item === arr[i]) return true
    }
    return false
  }

  obaa.isFunction = function (obj) {
    return Object.prototype.toString.call(obj) == '[object Function]'
  }

  // 得到根的名字
  // 如果已经是根则返回当前属性名字(现在这个就是根)
  // 否则返回path中最上级的那一层
  obaa._getRootName = function (prop, path) {
    if (path === '#') {
      return prop
    }
    return path.split('-')[1]
  }

  // 暴露的方法,作用是监听obj的某个已存在属性
  obaa.add = function (obj, prop) {
    var $observer = obj.$observer
    $observer.watch(obj, prop)
  }

  // 暴露的方法,用于为obj赋值一个属性并监听这个属性
  obaa.set = function (obj, prop, value, exec) {
    if (!exec) {
      obj[prop] = value
    }
    var $observer = obj.$observer
    $observer.watch(obj, prop)
    if (exec) {
      obj[prop] = value
    }
  }

  // 额外的size方法,使用size修改数组长度而非直接赋值
  // 避免js不能监听数组.length属性的问题
  Array.prototype.size = function (length) {
    this.length = length
  }

  // 判断运行环境,把obaa对象暴露出去
  if (
    typeof module != 'undefined' &&
    module.exports
  ) {
    module.exports = obaa
  } else if (typeof define === 'function' && define.amd) {
    define(obaa)
  } else {
    win.obaa = obaa
  }

})(Function('return this')())
// 这里使用Function是为了取到全局变量的this,也就是window对象
// 见MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function
posted @ 2018-12-28 14:13  黄zzzz  阅读(289)  评论(0编辑  收藏  举报