Vue数据双向绑定原理(Object.defineProperty和发布订阅模式)

1.主文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./eventEmitter.js"></script>
  <script src="./vue.js"></script>
  <script src="./observer.js"></script>
  <script src="./compiler.js"></script>
</head>
<body>
    <div id="app">
      <p v-text='age'></p>
      <span>{{msg}}</span>
      <div>
        <span>{{age}}</span>
        <span>{{age}}</span>
      </div>
      <input type="text" v-model="msg">
    </div>
    <script>
      // 实例化Vue对象
      const vm = new Vue({
        el:'#app',
        data: {
          msg:'abc',
          age:10
        }  
      });
      console.log(vm)
    </script>
</body>
</html>

2.vue.js(模拟vue实例,进行数据代理)

// 模拟vue.js文件
// 初始化工作
// 1. 代理数据:把data的数据挂载到vm上,同时设置get/set
// 2. 数据劫持:为data的属性提供get/set,同时更新DOM元素
// 3. 编译模板:处理视图中的DOM
// 3.1 如el中的子节点的{{}}
// 3.2 如el中的子节点的指令属性



function Vue(options) {
  this.$options = options
  this.$data = options.data || {}
  // el的值可以是DOM元素,也可以是字符串
  const el = options.el
  this.$el = typeof el === 'string' ? document.querySelector(el) : el

  // 1. 代理数据:把data的数据挂载到vm上,同时设置get/set
  this.proxyData()

  // 2. 数据劫持:为data的属性提供get/set,同时更新DOM元素
  // 第一种方法 直接写
  // 第二种方法 原型方法
  // 第三种方法 利用构造函数

  new Observer(this.$data)

  // 3. 编译模板
  new Compiler(this)
}

Vue.prototype.proxyData = function() {
  // 把对象的的key放到数组
  Object.keys(this.$data).forEach(key => {
    Object.defineProperty(this,key,{
      enumerable: true,
      configurable: false,
      get() {
        return this.$data[key]
      },
      set(value) {
        if(value === this.$data[key]) return 
        this.$data[key] = value
      }
    })
  })
  
}

3.observer.js(数据劫持)

 // 2. 数据劫持:为data的属性提供get/set,同时更新DOM元素
function  Observer(data) {
  this.$data = data
  // 处理data
  Object.keys(data).forEach(key => {
    this.defineReactive(this.$data,key,this.$data[key])
  })
 
}

Observer.prototype.defineReactive = function(data, key, value) {
  
      Object.defineProperty(data,key,{
        enumerable: true,
        configurable: false,
        get() {
          // 下面的写法会造成内存溢出(死循环):get里面调用了get
          // return data[key]
          return value

        },
        set(val) {
          if(val === value) return 
          value = val
          // 更新DOM
          // v-text -> msg abc -> xyz
          // v-model -> age 10 -> 20
          // {{}} -> size: 18->19
          // bus  来自eventEmitter.js  执行事件
          bus.$emit(key)
        }
      })
}

4.eventEmitter.js(发布订阅操作)


function EventEmitter() {
  this.subs = {}
}


// 注册事件
EventEmitter.prototype.$on = function(EventType, handler) {
  this.subs[EventType] = this.subs[EventType] || []
  this.subs[EventType].push(handler)
}



// 触发事件
EventEmitter.prototype.$emit = function(EventType,...rest) {
  if(this.subs[EventType]) {
    this.subs[EventType].forEach(handler => {
      handler.call(this,...rest)
    });
  }
}

const bus = new EventEmitter()

5.compiler.js (编译模板)

// 3. 编译模板:处理视图中的DOM
// 3.1 如el中的子节点的{{mag}}
// <p>{{msg}}</p>
// 3.2 如el中的子节点的指令属性
// <p v-text="msg"></p>
// 3.3 处理data数据

// 要el data

function Compiler(vm) {
  this.$vm = vm
  this.compile(this.$vm.$el)
}

// 编译模板
Compiler.prototype.compile = function (el) {
  // 编译类型
  // console.log(el.childNodes) 
  // Array.from 把伪数组转化数组
  Array.from(el.childNodes).forEach(node => {
    if (this.isTextNode(node)) {
      this.compilerTextNode(node)
    }
    if(this.isElementNode(node)) {
      this.compilerElementNode(node)
      // 如果下面还有子元素
      this.compile(node)
    }
  })

  
}


// 核心函数
// 编译文本节点 

// <p>{{msg}}</p>  -> <p>abc</p>
// 1. node.textContent   {{msg}}
// 2. 判断{{}}
// 3. 提取msg
// 4. 赋值
Compiler.prototype.compilerTextNode = function (node) { 
    const txt = node.textContent //"{{msg}}"
    // + 一个或者多个
    // . 匹配到连续的字符
    const reg = /\{\{(.+)\}\}/
    if(reg.test(txt)) {
      //RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配bai的第一du个 子匹配(以括号为标志)字符串,以此类推,RegExp.$2,RegExp.$3,..RegExp.$99总共可以有99个匹配
      const key = RegExp.$1.trim()
      // 第一次默认赋值
      node.textContent = this.$vm.$data[key]

      // 注册事件 bus  来自eventEmitter.js
      // 改数据时为修改dom值注册事件
      bus.$on(key , ()=> {
        node.textContent = this.$vm.$data[key]
      })
    }
}

// 编译元素节点
// <p v-text='msg'></p>   ->  <p>abc</p>
// <input v-model='msg'>  ->  input的value值
// 1. 找到所有的属性attrbutes
// 2. 判断指令属性
// 3. 处理具体的某个指令
Compiler.prototype.compilerElementNode = function (node) {
  // console.dir(node)
  Array.from(node.attributes).forEach( attr => {

    const attrName = attr.name //v-model  v-text
    const value = attr.value   //msg  age
    if(this.isDirective(attrName)) {

       if(attrName === 'v-text') {
          node.textContent = this.$vm.$data[value]
          // 注册事件
          bus.$on(value , ()=> {
            node.textContent = this.$vm.$data[value]
          })
       }

      // v-model -> :value=""  @input='fn'  
       if(attrName === 'v-model') {
        node.value = this.$vm.$data[value]
        // 注册事件 
        bus.$on(value , ()=> {
          node.value = this.$vm.$data[value]
        })

        // node -> inputDOM -> 绑定input事件
        node.oninput = () => {
          this.$vm.$data[value] = node.value
        }
       }
    }
  })
}


// 辅助函数
// 判断不同类型的节点
// 文本节点
Compiler.prototype.isTextNode = function (node) {
    return node.nodeType === 3
}

// 元素节点
Compiler.prototype.isElementNode = function (node) {
  return node.nodeType === 1
}

// 判断属性名字是否是指令
Compiler.prototype.isDirective = function (attrName) {
  return attrName.startsWith('v-')
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值