Vue2双向数据绑定的原理

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调

Vue双向数据绑定主要有以下几个步骤:
模板解析 事件添加 数据劫持 双向绑定

模板解析

文本解析编译

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue双向数据绑定的原理</title>
  <script src="vue01.js"></script>
</head>

<body>
  <div id="app">
    {{str}}
    <h1>{{str}}</h1>
  </div>

</body>
<script>
  // 创建一个Vue实例
  new Vue({
    el: "#app",
    data: {
      str: '前端小白学习中!'
    }
  });
</script>

</html>

<!-- 模板解析 事件添加  数据劫持 双向绑定 -->
<!-- 当我们没有写Vue的时候,浏览器是不能帮助我们解析{{}} 里面的文字的 
目的: 让str 正常输出文字-->

<!-- 在data里面定义一些自己的文本数据,进行挂载以{{}}形式进行展示 -->
// 为了让str正常显示,我们要创建Vue这个类
class Vue {
  // options 选择 在构造函数中传入参数 这个参数就是创建的Vue 传递过来的对象 ,所以我们通过el可以获取div这个DOM
  constructor(options) {
    console.log(options.el)
    this.$el = document.querySelector(options.el)
    this.$data = options.data  //new Vue data 里面的数据
    console.log('Html页面创建的Vue实例里面的data数据', this.$el)
    // 第一步:成功获取模板
    // 第二步:解析编译模板——【我们需要使用到一个方法】并且把DOM传进去
    this.compile(this.$el)

  }
  // 将我们获取的DOM 元素传递进去,看一下里面的节点
  compile (node) {
    node.childNodes.forEach((item, index) => {
      // 怎么获取文本节点:通过nodeType  案例中节点为3的是文本节点 1元素节点
      console.log(item.nodeType)  // 这里输出的是子节点,以及对应的下标
      // 第三步:我们获取文本节点的目的是将它替换成为data中的数据
      // 使用===进行判断
      if (item.nodeType === 1) {
        // 这里考虑到标签没有内容的情况获取单标签的情况
        if (item.childNodes.length > 0) {
          this.compile(item)
        }
      }
      if (item.nodeType === 3) {
        // 是文本节点 使用正则来匹配这个格式
        let reg = /\{\{(.*?)\}\}/g
        // 获取节点的文本内容
        let text = item.textContent  //{{str}}
        item.textContent = text.replace(reg, (match, vmKey) => {
          // vnKey 这里我们获取到了一个str,通过str 我们可以获取这边的值
          // 因为对象作为参数已经进行了传递了
          // 对 {{}}里面可能存在的空格进行处理 
          vmKey=vmKey.trim()
          console.log("reg:", reg, "match:", match, "vmKey:", vmKey,)
          return this.$data[vmKey]
          // 按照正则的格式找到{{}}里面的文本,替换成为data里面的数据,也就是data中的字符串
          // 再把返回的值赋值给item的textContent,就是item文本节点里的内容已经发生改变了
          // 我们发现返回之后只有第一个节点进行了替换,因为h1是元素节点,这里只判断文本节点
          // 在上面的第21行进行递归调用 之后就获取数据成功了
        })
      }
    })
  }
}




// 问题: Vue 创建类加构造函数的意义
// 当 new 一个对象的时候会自动调用这个对象的构造函数
// compile ——是什么??
// compile——【编译】是我们自己定义的一个方法 用来将我们获取的模板进行解析
// 问题3: forEach 的使用 里面的两个参数都是什么?
// 我们上面将node里面的节点进行了遍历,item 是node里面的每一个节点 index 是节点里面对应的下标


// 

是元素节点 我们是不需要动的
// 我们需要获取的是文本节点 // 怎么获取文本节点:通过nodeType // 获取文本节点之后进行匹配 使用到了正则表达式 【对于个别关键字不是很了解】转义字符的作用

事件绑定

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>02Vues双向数据绑定的原理——事件绑定</title>
  <script src="./vue02.js"></script>
</head>

<body>
  <div id="app">
    {{str}}
    <h1>{{str}}</h1>
    <button @click="cli">按钮</button>
  </div>

</body>
<script>
  // 创建一个Vue实例
  new Vue({
    el: "#app",
    data: {
      str: '前端小白学习中_双向数据绑定的原理——事件绑定'
    },
    // 在methods进行方法的调用
    methods: {
      cli () {
        console.log('方法调用了')
      }
    }
  });
</script>

</html>

<!-- 事件绑定:我们使用了点击事件之后需要对数据进行处理 之后才可以实现点击的功能
2. 我们的点击事件是写在元素标签的里面
 2.1 我们需要找到元素节点 根据之前的判断元素类型为1就是元素节点
 2.2 找到所有的元素节点之后对节点进行判断,看一下里面有没有包含点击事件

3.拿到方法名称之后通过名称拿到里面的整个方法,当我们点击的时候再让它进行执行就可以了
-->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>02Vues双向数据绑定的原理——事件绑定</title>
  <script src="./vue02.js"></script>
</head>

<body>
  <div id="app">
    {{str}}
    <h1>{{str}}</h1>
    <button @click="cli">按钮</button>
  </div>

</body>
<script>
  // 创建一个Vue实例
  new Vue({
    el: "#app",
    data: {
      str: '前端小白学习中_双向数据绑定的原理——事件绑定'
    },
    // 在methods进行方法的调用
    methods: {
      cli () {
        console.log('方法调用了')
      }
    }
  });
</script>

</html>

<!-- 事件绑定:我们使用了点击事件之后需要对数据进行处理 之后才可以实现点击的功能
2. 我们的点击事件是写在元素标签的里面
 2.1 我们需要找到元素节点 根据之前的判断元素类型为1就是元素节点
 2.2 找到所有的元素节点之后对节点进行判断,看一下里面有没有包含点击事件

3.拿到方法名称之后通过名称拿到里面的整个方法,当我们点击的时候再让它进行执行就可以了
-->

数据劫持

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>03Vue双向数据绑定的原理——数据劫持</title>
  <script src="./vue03.js"></script>
</head>

<body>
  <div>

    <!--  
核心:对数据进行拦截
获取设置属性的时候对属性进行拦截,再另外执行操作
-->
</body>
<script>
  let obj = {
    name: '沐舒',
    age: 19,
    // other 是一个子对象 
    other: {
      like: '健身'
    }
  }
  // 第一个参数:要操作的对象  第二个参数:哪一个属性 第三个参数:也是一个对象里面的get set 方法
  Object.defineProperty(obj, 'name', {
    // 这里defineProperty这个方法已经給name属性劫持掉了,把它控制住了,和原本的name属性没有关系了
    // 后面再要获取值的时候就要触发get方法了,后面能获取到什么值就取决于get方法里面的return 返回值
    // 企图获取属性的时候再来执行这个方法

    get () {
      return name               //这里返回的是一个空的字符串,因为我们还没有对它进行设置
    },
    // 去设置修改属性的时候触发的 
    set (val) {
      name = val  // 这里在你设置值的时候,就已经把你输入的值赋值给name
    }
  })
</script>

</html>

<!--
  这里属于编译解析——最后它对视图进行更新
  这里的重点是:definedProperty 是Vue2里面实现数据劫持的方法
get: 不是用来获取属性值的,而是在企图获取属性值的时候要触发的方法
set: 设置修改属性的时候触发d

eg: 在控制台输入obj.name='jack'  触发了修改设置属性的这个行为,触发行为就会执行set
obj.name '沐舒'
这里必须要有设置的操作,设置成为什么不重要:重要的是进行触发
set(val){
  name=val  //set 方法将name设置成为amy 这样get就可以获取name为 沐舒
}

获取到的值就是修改之后的值吗??
在set 方法中传入一个参数  ,将参数设置为属性就可以了
set(val){
  name=val  这个参数可以获取到当我们设置修改这个属性的时候输入的值
}


使用 definedProperty 将数据进行劫持之后,这个属性已经和原本设置的值没有关系了
后面需要进行值的获取的时候,
① 明确在get 方法里面给它一个返回值
get () {
      return '小红'
    },
    虽然有set 方法进行设置但是获取的时候触发的还是get,get方法中有具体的返回值的时候set方法中的值就不会返回

②
-->
// Vue03
// 为了让str正常显示,我们要创建Vue这个类
class Vue {
  constructor(options) {
    this.$el = document.querySelector(options.el)
    this.$data = options.data
    // ① 把传过来的参数的对象也存储一下 {el...data,methods}
    this.$options = options
    console.log(" this.$options", this.$options)

    this.compile(this.$el)

  }

  compile (node) {
    node.childNodes.forEach((item, index) => {
      if (item.nodeType === 1) {
        //         2. 我们的点击事件是写在元素标签的里面
        //  2.1 我们需要找到元素节点 根据之前的判断元素类型为1就是元素节点
        //  2.2 找到所有的元素节点之后对节点进行判断,看一下里面有没有包含点击事件
        if (item.hasAttribute("@click")) {
          // 通过getAttribute这个方法看一下里面的属性值是什么 
          let vmKey = item.getAttribute("@click")
          console.log("vmKey", vmKey)  // cli 可以拿到事件绑定的方法名称
          vmKey = vmKey.trim()          // 对vmKey 进行空格处理
          // 3.拿到方法名称之后通过名称拿到里面的整个方法,当我们点击的时候再让它进行执行就可以了

          item.addEventListener('click', () => {
            console.log(this.$options.methods[vmKey])
            // 4. 让方法执行 获取了里面的数据 
            this.$options.methods[vmKey]()
          })
        }
        if (item.childNodes.length > 0) {
          this.compile(item)
        }
      }
      if (item.nodeType === 3) {
        let reg = /\{\{(.*?)\}\}/g
        let text = item.textContent  //{{str}}
        item.textContent = text.replace(reg, (match, vmKey) => {
          vmKey = vmKey.trim()
          return this.$data[vmKey]
        })
      }
    })
  }
}

Vue2双向数据绑定的原理_第1张图片请添加图片描述

请添加图片描述
请添加图片描述
Vue2双向数据绑定的原理_第2张图片
Vue2双向数据绑定的原理_第3张图片

你可能感兴趣的:(前端,前端)