我要写个Vue

一、作为一个MVVM框架,应该实现什么:

数据响应式:监听数据变化并能及时在视图中更新(defineProperty,Proxy)

模板引擎:提供描述视图的模板语法(插值,指令)

渲染:将模板转化为html(vdom => dom)

二、定义响应式拦截

// 拦截定义
// 处理新增对象属性
function defineReactive (obj, key, val) {
  // 处理初始化时的深层对象
  observe(val)
  Object.defineProperty(obj, key, {
    get() {
      console.log('get: ', key)
      return val
    },
    set(newval) {
      if(newval === val) return
      // 处理深层对象的重新赋值
      observe(val)
      val = newval
      console.log('set: ', key, newval)
    }
  })
}

function observe(obj) {
  if(typeof obj !== "object" || obj === null) return obj
  Object.keys(obj).forEach(k => defineReactive(obj, k, obj[k]))
}

let obj = { foo: { name: { deep: 'foo' } } }
observe(obj)
console.log('val: ', obj.foo.name.deep )
obj.foo.name.deep = 'foooooooooo'
console.log('val: ', obj.foo.name.deep )

三、简单实现一下


function defineReactive (obj, key, val) {
  // 处理初始化时的深层对象 let a = { obj: {...} }
  observe(val)
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    get() {
      console.log('get', key, val)
      if(Dep.target) dep.add(Dep.target)
      return val
    },
    set(newval) {
      if(newval === val) return
      console.log('set', key, newval)
      // 处理深层对象的重新赋值 a.obj = {...}
      observe(val)
      val = newval

      dep.notify()
    }
  })
}

function observe(obj) {
  if(typeof obj !== "object" || obj === null) return obj
  new Oberver(obj)
}

function proxy(vm, key) {
  Object.keys(vm[key]).forEach(datakey => {
    Object.defineProperty(vm, datakey, {
      get() {
        return vm[key][datakey]
      },
      set(newval) {
        vm[key][datakey] = newval
      }
    })
  })
}

class Oberver{
  constructor(obj) {
    this.value = obj

    // 拦截
    this.walk(this.value)
  }
  walk(obj) {
    Object.keys(obj).forEach(k => defineReactive(obj, k, obj[k]))
  }
}

class vVue{
  constructor(options) {
    this.$options = options
    this.$data = options.data

    // $data 响应式
    observe(this.$data)

    // $data的值代理到新对象
    proxy(this, '$data')

    // 编译模板
    new Compile(options.el, this)
  }
}

class Compile{
  constructor(el, vm) {
    this.$el = document.querySelector(el)
    this.$vm = vm

    if(this.$el) {
      this.compile(this.$el)
    }
  }
  compile(el) {
    // 判断元素类型
    el.childNodes.forEach(node => {
      if(node.nodeType === 1) {
        // 节点元素
        this.compileElement(node)
        console.log('节点元素', node.nodeName)
      } else if(this.isInner(node)) {
        this.compileText(node)
        console.log('模板元素', node.textContent, RegExp.$1.trim())
      }

      if(node.childNodes && node.childNodes.length > 0) {
        this.compile(node)
      }
    })
  }
  isInner(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }

  compileText(node) {
    this.update(node, RegExp.$1.trim(), 'text')
  }

  compileElement(node) {
    const attrs = node.attributes
    Array.from(attrs).forEach(attr => { // k-text = "count"
      const attrName = attr.name // k-text
      const attrValue = attr.value // count
      if(this.isDirective(attrName)) {
        const direct = attrName.substr(3)
        this[direct] && this[direct](node, attrValue)
      }
    })
  }
  isDirective(name) {
    return name.startsWith('vv-')
  }

  // 实现vv-text
  text(node, exp) {
    this.update(node, exp, 'text')
  }

  textUpdate(node, val) {
    node.textContent = val
  }

  update(node, exp, dir) {
    const fn = this[dir + 'Update']
    fn && fn(node, this.$vm[exp])

    new Watcher(this.$vm, exp, val => {
      fn && fn(node, val)
    })
  }
}


class Watcher{
  constructor(vm, exp, updatefn) {
    this.vm = vm
    this.exp = exp
    this.updatefn = updatefn

    Dep.target = this
    this.vm[exp]
    Dep.target = null
  }

  update() {
    this.updatefn.call(this.vm, this.vm[this.exp])
  }
}

class Dep {
  constructor() {
    this.watchers = []
  }
  add(watcher) {
    this.watchers.push(watcher)
  }
  notify() {
    this.watchers.forEach(w => w.update())
  }
}

这里是流程解析:

new vVue() -> observe -> 为data里的数据做响应式 -> 每一个属性defineReactive的时候,会利用闭包保存一个Dep -> 定义get, set -> 把data里的数据映射到vVue实例上(proxy更优雅) -> 编译app节点 -> Compile(感觉改成函数更好理解一点)-> 判断节点类型 -> 递归处理直到遇到指令和文本节点(这里当一种情况了)-> 执行指令对应的方法 -> 执行update(初始化页面) -> 生成watcher,用来保存对应属性和对应的更新函数 -> 将自身保存到全局变量(Dep.target)里去,并主动调用对应属性的get方法 -> 在get方法中将Dep 和watcher 建立关系(其实就是dep 实例保存watcher) -> 下次data属性值更新会调用set方法,将dep中保存的watcher执行watcher里保存的更新方法

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值