Vue实现(三)

Vue中,我们访问的数据都是直接通过App.dataName,而不是App.data.dataName,因此我们需要对参数进行代理

// 对data 进行代理
function agency(vm) {
    let keys = Object.keys( vm.$data )
    for( let i = 0; i < keys.length; i++ ) {
        let key = keys[i]
        Object.defineProperty(vm, key, {
            configurable: true,
            enumerable: true,
            get() {
                return vm.$data[key]
            },
            set( newVal ) {
                vm.$data[key] = newVal
            }
        })
    }
}

发布订阅模式

采取用一种类似预购的模式

  1. 商家提供一个数据
  2. 根据需求订购所需商品
  3. 等待,做其他的事
  4. 当货到了,查看数据,通知取货

我们在代码中需要使用事件模型:

​ 需要一个 event对象

  1. on 类似预定货物
  2. off 类似取消预定
  3. emit 类似通知取货
let event = (function(){
    options = []
    return {
        on: function( type, fn ) {
            options[type] = (options[type] ||  [])
            options[type].push(fn)
        },
        off:function( type, fn ) {
            if( arguments.length  === 0 ) {
                options = []
            }else if( arguments.length === 1) {
                if( options[type] ) indexof(options[type]) = []
                else return 
            }else {
                for( let i = 0; i < options[type].length ; i++ ) {
                    if( options[type][i] === fn ) {
                        options[type].splice( i, 1)
                    }              
                }
            }
        },
        emit: function( type ) {
            let datas = Array.prototype.splice.call( arguments, 1 )           
            console.log(datas)
            for( let i = 0; i < options[type].length ; i++ ) {
                options[type][i].apply( this, datas ) 
            }
        }     
    }
}())

Vue中实现的逻辑
在这里插入图片描述
在这里插入图片描述
DEP

let depId = 0
class Dep {
    constructor() {
        this.id = depId++
        this.sub = []
    }
    addSub( target ) {
        this.sub.push( target )
    }
    depend() {
        if( Dep.target ) {
            this.addSub( Dep.target )
            Dep.target.addDep( this ) //将Dep 和 渲染watcher 关联起来
        }
    }
    removeSub( target ) {
        if( this.sub.length ) {
            let index = this.sub.indexOf(target)
            if( index > -1 ) {
                this.sub.splice( index, 1 )
            }
        }
    }
    notify() {
        let deps = this.sub
        let that = this
        deps.forEach( function(watcher) {
            watcher.update()
            that.removeSub(watcher)
        });
    }
}

Dep.target = null

let targetStack = []

function enterStack( target ) {
    targetStack.unshift( Dep.target )
    Dep.target = target
}

function popStack() {
    targetStack.shift(Dep.target)
}

watcher

/**
 * 观察者
 */
let watcherId = 0
class Watcher {
    /**
     * @param {object} vm PFVue实例
     * @param {string|function} expOrFun 如果是渲染watcher 传入 function 如果是计算watcher 传入路径
     */
    constructor( vm, expOrFun ) {
        this.vm = vm
        this.getter = expOrFun

        this.id = watcherId++
        this.deps = [] // 依赖项
        this.depIds = {} // 是一个 Set 类型, 用以保护 依赖项的唯一性 

        this.get()
    }
    get() {
        enterStack( this )
        console.log( typeof this.getter )
        if( typeof this.getter === 'function' ) {
            this.getter.call( this.vm, this.vm )
        }

        popStack()
    }
    run() {
        this.get()
    }
    update() {
        this.run()
    }
    /**将 当前dep 和 watcher 关联 */
    addDep(dep) {
        this.deps.push( dep )
    }
}

其他部分,这一部分前面已经写过了

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="root">
    <div class="aaa">{{name}}</div>
    <div>{{age}}</div>
    <div>{{gender}}</div>
    <div>
      <li></li>
      <li></li>
      <li></li>
    </div>
    <button onclick="add()">增加</button>
  </div>
  <script src="./js/Dep.js"></script>
  <script src="./js/watcher.js"></script>
  <script>
    /** 创建Vue类 */
    class pfVue {
      constructor(obj) {
        this.$el = document.querySelector(obj.el)
        this.$parent = this.$el.parentNode
        this.$data = obj.data
        this.mount()
        agency(this)
      }
    }
    // Vue的初始化方法
    pfVue.prototype.mount = function () {
      this.render = this.renderCreater()
      this.mountComponent()
    }
    // 初始化组件
    pfVue.prototype.mountComponent = function () {
      let mount = function () {// Vue 中使用 watcher 来监控变化
        console.log( 'updates')
        this.update(this.render())
      }
      new Watcher( this, mount )

    }
    /**
    * 通过柯里化来存储 Vnode
    */
    pfVue.prototype.renderCreater = function () {
      let vnode = readNode(this.$el)
      return function () {
        return complier(vnode, this.$data)
      }
    }
    /** 将虚拟DOM 渲染到页面中: diff算法比较其差别*/
    pfVue.prototype.update = function (newVnode) {
      observe(this.$data, this)
      mountNode.call(this, newVnode)

    }
    /**
    * 对虚拟DOM进行渲染
    */
    function complierNode(v) {
      let $nodeType = v.nodeType
      let node = {}
      if ($nodeType === 1) {
        node = document.createElement(v.nodeName)
        node.nodeType = $nodeType
        for (let item in v.data) {
          node.setAttribute(item, v.data[item])
        }
        if (v.childNodes.length > 0) {
          let _children = v.childNodes //这里曾少写过一个Let导致递归错误 
          for (let i = 0; i < _children.length; i++) {
            let child = complierNode(_children[i])
            node.appendChild(child)
          }
        }
      }
      else if ($nodeType === 3) {
        node = document.createTextNode(v.nodeValue)
      }
      return node
    }
    // 挂载DOM 
    function mountNode(newVnode) {
      let $compliedDOM = complierNode(newVnode)
      this.$parent.replaceChild($compliedDOM, this.$el)
      this.$el = $compliedDOM
    }
    // 创建VNode类
    // nodeName data content nodeType  children
    class VNode {
      constructor(nodeName, data, nodeValue, nodeType) {
        this.nodeName = nodeName && nodeName.toLowerCase();
        this.data = data;
        this.nodeValue = nodeValue;
        this.nodeType = nodeType;
        this.childNodes = [];
      }
      appendChildren(vnode) {
        this.childNodes.push(vnode)
      }
    }
    /**
     * 把节点转换成虚拟节点
     */
    function readNode(node) {
      let comNode;
      const _nodeType = node.nodeType
      if (_nodeType === 1) {
        // 元素节点
        const _nodeAttrs = node.attributes
        const _nodeName = node.nodeName
        let data = {}
        for (let i = 0; i < _nodeAttrs.length; i++) {
          data[_nodeAttrs[i].nodeName] = _nodeAttrs[i].nodeValue
        }
        comNode = new VNode(_nodeName, data, undefined, _nodeType)

        let childNodes = node.childNodes
        for (let i = 0; i < childNodes.length; i++) {
          comNode.appendChildren((readNode(childNodes[i])))
        }
      } else if (_nodeType === 3) {
        // 文本节点
        comNode = new VNode(undefined, undefined, node.nodeValue, _nodeType)
      }
      return comNode
    }
    //对存在的Dom进行替换
    function complier(template, data) {
      // 用于替换{{}}中数据的正则
      const ruleForSupport = /\{\{(.+?)\}\}/g
      let children = template.childNodes
      let _vnode = null
      let type = template.nodeType
      //对文本标签中的{{}}中的数据替换
      if (type == 3) {
        let value = template.nodeValue
        value = value.replace(ruleForSupport, (_, g) => {
          let arr = g.trim().split('.')
          let getValueByKeli = createGetValueByPath(arr)
          let arrValue = getValueByKeli(data)
          return arrValue
        })
        _vnode = new VNode(template.nodeName, template.data, value, template.nodeType)
      } else {

        _vnode = new VNode(template.nodeName, template.data, template.nodeValue, template.nodeType)
        // 对于非文本标签进行遍历
        for (let i = 0; i < children.length; i++) {
          _vnode.appendChildren(complier(children[i], data))
        }
      }

      return _vnode
    }
    // 利用函数的柯里化
    function createGetValueByPath(path) {
      let res = path
      return function (value) {
        let prop
        while (prop = res.shift()) {
          value = value[prop]
        }
        return value
      }
    }
    function defineReactive(obj, key, value, enumerable) {
      let that = this
      let dep = new Dep();
      //通过传入的value实习闭包 这样set和get方法就无需创建其他的中间变量
      Object.defineProperty(obj, key, {
        configurable: true,
        enumerable: !!enumerable,
        set(newValue) {
          value = newValue
          observe( obj, that )
          dep.notify()
          typeof that.mountComponent === 'function' && that.mountComponent()
        },
        get() {//在其中进行通知
          console.log(1111)
          dep.depend()
          return value
        }
      })
    }
    /** 
    * 将对象进行响应式处理
    */
    function observe(obj, vm) {
      if (Array.isArray(obj)) {
      console.log( 1 )
        obj.__proto__ = array_methods
        for (let i = 0; i < obj.length; i++) {
          observe(obj[i], vm)
        }
      } else {
        console.log(typeof obj)
        console.log( 2 )
        let keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          let prop = keys[i]
          defineReactive.call(vm, obj, prop, obj[prop], true)
          if( typeof obj[prop] === 'object' )
            observe( obj[prop], vm )
        }
      }
    }
    // 对data 进行代理
    function agency(vm) {
      let keys = Object.keys(vm.$data)
      for (let i = 0; i < keys.length; i++) {
        let key = keys[i]
        proxy(vm, '$data', key)
      }
    }
    function proxy(o, prop, key) {
      Object.defineProperty(o, key, {
        configurable: true,
        enumerable: true,
        get() {
          return o[prop][key]
        },
        set(newVal) {
          o[prop][key] = newVal
        }
      })
    }
    // 定义需要重写的方法
    let ARRAY_METHODS = [
      'push',
      'pop',
      'splice',
      'shift',
      'unshift',
      'sort',
      'reverse'
    ]
    // 对array_methods中方法进行重写赋值
    let array_methods = Object.create(Array.prototype)
    ARRAY_METHODS.forEach(method => {
      array_methods[method] = function () {
        for (let i = 0; i < arguments.length; i++) {
          observe(arguments[i])
        }
        let res = Array.prototype[method].apply(this, arguments)
        return res
      }
    })
    let app = new pfVue({
      el: '#root',
      data: {
        name: 'pf',
        age: 13,
        gender: 'man',
        father: '11'
      },
    })

    function add() {
      app.$data.age = app.$data.age + 1
    }
  </script>
</body>

</html>

推荐一个发布订阅者模式的分析

https://segmentfault.com/a/1190000016208088
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值