实现一个简单的vue双向绑定

<div id="app">
    <input type="text" id="origin" @change="changeInput" v-model="text">
    <span id="output">{{text}}</span>
</div>
<script type="text/javascript" src="vue.js"></script>
<script type="text/javascript">
  new Vue({
     el: '#app',
     data () {
          return {
            text: '333'
          }
      },
      methods: {
        changeInput (event) {
          console.log(this, event, 'event')
        }
      }
  })
</script>

vue.js

//收集依赖
class Dep {
  static target = null
  constructor() {
    this.childDeps = []
  }
  add (childDep) {
    const nodes = this.childDeps.map(child => child.node)
    if (!nodes.includes(childDep.node)) {
      this.childDeps.push(childDep)
    }
  }
  notify () {
    this.childDeps.map(child => {
      child.update()
    })
  }
}
//订阅器
class Watcher {
  constructor (vm, node, key) {
    this.vm = vm
    this.node = node
    this.key = key
    this.update()
  }
  get () {
    Dep.target = this
    this.value = this.vm._data[this.key]
    Dep.target = null
  }
  update () {
    this.get()
    this.node.innerText = this.value
  }
}
// vue
class Vue {
  constructor(options) {
    this.$options = options
    //初始化data
    if (this.$options.data) {
      this._data = this.initData()
      this.observe(this._data)
    }
    //初始化methods
    if (this.$options.methods) {
      this.$methods = this.$options.methods
      this.proxyData(this.$methods)
    }
    if (this.$options.el) {
      //获取dom节点并添加到文档碎片中
      let domNode = document.querySelector(this.$options.el)
      this.$el = this.createFragment(domNode)
      //插入真实节点中
      document.querySelector(this.$options.el).appendChild(this.$el)
    }
  }
  //初始化获取data参数
  initData () {
    let data = {}
    if (typeof this.$options.data === 'function') {
      data = this.$options.data()
      if (typeof data !== 'object') {
        console.error('data must be a object')
      }
    }
    return data
  }
  //将深层的数据代理到this上,可以用this.xx直接访问
  proxyData (data) {
    Object.keys(data).map(key => {
      Object.defineProperty(this, key, {
        get () {
          return data[key]
        },
        set (newVal) {
          data[key] = newVal
        }
      })
    })
  }
  //创建一个文档碎片
  createFragment (node) {
    let fragment = document.createDocumentFragment()
    let childNode
    while (childNode = node.firstChild) {
      this.compileNode(childNode)
      fragment.appendChild(childNode)
      childNode = node.firstChild
    }
    return fragment
  }
  //编译节点  将上面的属性解析
  compileNode (node) {
    const reg = /\{\{(\S*)\}\}/
    const regEventType = /(@|v-bind:|v-on:)(\S+)/
    //如果是元素节点
    if (node.nodeType === 1) {
      let attributes = [...node.attributes]
      attributes.map(attr => {
        if (attr.nodeName === 'v-model') {
          const key = attr.nodeValue //获取双向绑定的值
          node.addEventListener('keyup', (event) => {
            this._data[key] = event.target.value
          })
          node.value = this._data[key]
          node.removeAttribute('v-model')
        }
        if (regEventType.test(attr.nodeName)) {
          const eventType = RegExp.$2
          const eventName = attr.nodeValue
          node.addEventListener(eventType, () => {
            this[eventName].call(this, event)
          })
        }
      })
      if (reg.test(node.innerText)) {
        const key = RegExp.$1 //获取双向绑定的值
        //添加订阅
        new Watcher(this, node, key)
      }
    } else if (node.nodeType === 3) { //文本节点
      if (reg.test(node.nodeValue)) {
        const key = RegExp.$1 //获取双向绑定的值
        //添加订阅
        new Watcher(this, node, key)
      }
    }
  }
  //给data内的属性添加响应式
  observe (data) {
    if (typeof data !== 'object' || data === null) {
      return
    }
    Object.keys(data).map(key => {
      this.defineReactive(data, key, data[key])
      this.observe(data[key])
    })
  }
  //动态监控对象
  defineReactive (obj, key, value) {
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      get () {
        //收集依赖
        if (Dep.target) {
          dep.add(Dep.target)
        }
        return value
      },
      set (newVal) {
        if (newVal === value) return
        value = newVal
        //数据发生改变通知dom更新   发布
        dep.notify()
      }
    })
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容