zoukankan      html  css  js  c++  java
  • 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
    
  • 相关阅读:
    图片上下左右居中
    点击滚动指定高度 屏幕滚动事件
    实例16 验证登录信息的合法性
    实例15 判断某一年是否为闰年
    实例14 实现两个变量的互换(不借助第3个变量)
    实例13 不用乘法运算符实现2*16
    实例12 用三元运算符判断奇数和偶数
    实例11 加密可以这样简单(位运算)
    实例10 自动类型转换与强制类型转换
    实例9 重定向输入流实现程序日志
  • 原文地址:https://www.cnblogs.com/wozho/p/10190272.html
Copyright © 2011-2022 走看看