大前端 -vue - 模拟 Vue.js 响应式原理

代码地址:
目标:

  1. 模拟一个最小版本的vue
  2. 响应式原理在面试常常问到的问题
  3. 学习别人优秀的经验,转换成自己的经验
  4. 实际项目中出问题的原理层面的解决
  5. 为学习vue源码做铺垫

1.数据驱动

数据响应式:数据响应式中的【数据】指的是数据模型,基于vue开发的时候,我们的数据模型就是普通的javascript对象。
数据响应式的核心是:当我们改变数据的时候,试图会进行更新,避免了频繁的dom操作,提高开发效率。
双向绑定:当数据发生改变,视图同时会发生更新;视图改变,数据也会发生改变。
在双向绑定中包含了数据响应式。双向绑定一般指可以和用户交互的表单元素。我们可以使用v-model在表单元素上创建双向数据绑定。
数据驱动:就是一个开发的过程,开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图。
现在主流的mvvm都帮我们实现了,数据响应式和双向绑定。
是vue开发过程中最独特的特性之一。

2.响应式核心原理 vue2.x

响应式核心原理
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

Object.defineProperty

  • 演示Object.defineProperty如何使用 - 单个数据
    Object.defineProperty()参数:第一个参数:对象,第二个参数:向对象中添加的属性,第三个参数:属性描述符,给当前的属性添加了enumerable,configurable属性。和一些方法,get和set方法,其实就是getter访问器和setter设置器。在get方法:是访问数据。在set中是更新数据。
<!DOCTYPE html>
<html lang="cn">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>defineProperty</title>
</head>
<body>
  <div id="app">
    hello
  </div>
  <script>
    // 模拟 Vue 中的 data 选项
    let data = {
   
      msg: 'hello'
    }

    // 模拟 Vue 的实例
    let vm = {
   }

    // 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作
   
    Object.defineProperty(vm, 'msg', {
   
      // 可枚举(可遍历)
      enumerable: true,
      // 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)
      configurable: true,
      // 当获取值的时候执行
      get () {
   
        console.log('get: ', data.msg)
        return data.msg
      },
      // 当设置值的时候执行
      set (newValue) {
   
        console.log('set: ', newValue)
        if (newValue === data.msg) {
   
          return
        }
        data.msg = newValue
        // 数据更改,更新 DOM 的值
        document.querySelector('#app').textContent = data.msg
      }
    })

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>
</body>
</html>

演示Object.defineProperty如何使用 - 多个数据

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>defineProperty 多个成员</title>
</head>
<body>
  <div id="app">
    hello
  </div>
  <script>
    // 模拟 Vue 中的 data 选项
    let data = {
   
      msg: 'hello',
      count: 10
    }

    // 模拟 Vue 的实例
    let vm = {
   }

    proxyData(data)

    function proxyData(data) {
   
      // 遍历 data 对象的所有属性
      Object.keys(data).forEach(key => {
   
        // 把 data 中的属性,转换成 vm 的 setter/setter
        Object.defineProperty(vm, key, {
   
          enumerable: true,
          configurable: true,
          get () {
   
            console.log('get: ', key, data[key])
            return data[key]
          },
          set (newValue) {
   
            console.log('set: ', key, newValue)
            if (newValue === data[key]) {
   
              return
            }
            data[key] = newValue
            // 数据更改,更新 DOM 的值
            document.querySelector('#app').textContent = data[key]
          }
        })
      })
    }

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>
</body>
</html>

3.响应式核心原理 vue3.x

数据劫持使用的是:Proxy
Proxy:是直接监听对象而非属性,因此再把多个对象转换成getter和setter的时候不需要循环。这个对象是es6中新增的。ie不支持,性能由浏览器优化。

Proxy的使用:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Proxy</title>
</head>
<body>
  <div id="app">
    hello
  </div>
  <script>
    // 模拟 Vue 中的 data 选项
    let data = {
   
      msg: 'hello',
      count: 0
    }

   
    /**
    Proxy是一个构造函数,因此通过new Proxy创建一个代理对象,当我们想要访问data中的属性的时候,我们使用vm这个代理对象来访问,
    例如想要获取msg,就是vm.msg。当我们使用vm.msg的时候会触发代理中的get方法,当我们给msg赋值的时候会触发set方法。

    Proxy有2个参数:
    第一个参数:我们要代理的对象,
    第二个参数:是一个对象,这个对象中的成员是执行代理行为时候的函数,也就是:当我们要访问vm.msg的时候就会触发get方法,当我们给msg赋值的时候会触发set方法。
    get:get方法跟之前的方法不同,需要传递参数,get方法中需要传递target和key。target就是代理的目标对象当前的是data,key:是我们要访问的哪个属性,这2个参数我们不需要关系如何实现的,这个是由系统帮我们实现的。只需要传递target,和key。返回的target[key]就是我们获取到的值。
    set方法有3个参数:
    target:我们代理的目标对象, 
    key:我们设置的属性,例如:msg,count。  
    newValue:新设置的值。

    Proxy的好处
    1.性能好,因为浏览器做了优化
    2.不用循环
    */

     // 模拟 Vue 实例
    let vm = new Proxy(data, {
   
      // 执行代理行为的函数
      // 当访问 vm 的成员会执行
      get (target, key) {
   
        console.log('get, key: ', key, target[key])
        return target[key]
      },
      // 当设置 vm 的成员会执行
      set (target, key, newValue) {
   
        console.log('set, key: ', key, newValue)
        if (target[key] === newValue) {
   
          return
        }
        target[key] = newValue
        document.querySelector('#app').textContent = target[key]
      }
    })

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>
</body>
</html>

4.发布订阅模式

vue中的自定义事件事件模式,nodejs中的事件都是基于发布订阅模式的。
订阅者,发布者,信号中心

我们假设,存在一个信号中心,某个任务执行完成,就向信号中心发布(publish)一个信号,其他任务可以向信号中心订阅(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫发布订阅模式。

发布订阅模式概念:

举例:假设你是学生的家长,学校每次考完试,都要获取小孩子的成绩,现在假设可以到孩子所在的班级去订阅孩子的成绩,一旦考试成绩出来,相当于触发了一个事件,就会由班级的老师把孩子的成绩以短信的形式发送给家长,不需要天天再问孩子成绩出来没。
在这个案例中:家长就是事件的订阅者,老师就是事件的发布者,孩子所在的班级可以假设为事件的中心,家长订阅班级中孩子的考试成绩,老师发布班级中孩子的考试成绩。

vue自定义事件是基于发布订阅模式

注册事件:注册事件的时候并不执行。eventType: 事件类型,handler:处理函数

触发事件:触发事件。eventType:事件类型

  • vue自定义事件:
<!DOCTYPE html>
<html lang&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值