vue 用key拿对象value_Vue 基本实现

本文详细探讨了Vue的响应式原理,从数据代理、Object响应式、Observer、Dep、Watcher等方面逐步剖析,实现了数据的代理及响应式系统。接着介绍了Array响应式,通过拦截器实现对数组操作的响应式,最后讲解了模板编译的过程,包括v-text、v-html、v-bind、v-model和v-on等指令的解析。文章总结了响应式的优缺点,以及模板编译的完整流程,加深了对Vue响应式机制的理解。
摘要由CSDN通过智能技术生成

前言

学习前端到了一定的程度,尤其看了部分 jQuery 源码后,我想尝试着自己去动手造轮子。

但是我的方向或者参考貌似只能是 Vue 。毕竟它的 MVVM(Model-View-ViewModel)架构实在是好用,我们只需要关注自己的数据,去改变数据后视图会自动的进行更新。所以我决定自己动手实现一遍 Vue 的响应式原理。进而更深刻的理解 MVVM 这种模式及其好处。

所以在本文开始之前你必须已经对 Vue 的基本使用有过了解
若您在观看图中对代码的高亮要求较高,也可直接去 语雀 平台查看

首先附上本文所要讲解的内容范围,我会对图中所有知识点进行讲解并逐步分析实现。

b800f8db81c86a6ab0004a06f9984569.png

基本使用

下面是我们使用 Vue 的时候的一段代码:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue 响应式原理</title>
</head>
<body>
  <div id="app">
      <input type="text" v-model="sentence">{
    { sentence }}
        <p>{
    { name }} --- {
    { age }}</p>
    </div>
    <script type="module">
        import Vue from './Vue.js'
        const vm = new Vue({
    
            el: '#app',
            data() {
    
                return {
    
                    name: 'Jerry',
                    age: 20,
                    sentence: 'Hello MVVM',
          info: {
    
            height: 180
          }
                }
            }
        })
    console.log(vm)
    window.vm = vm // 便于浏览器中使用 vm 变量测试
    </script>
</body>
</html>

我们在使用 Vue 的时候向其传入了一个对象。并且在 data 中的数据可以实现响应式。

接下来我们先来实现数据的代理,即当我们使用 vm.name 的时候也可以拿到 Jerry

数据代理

数据代理之前我们先创建 Vue 这个类:

// Vue.js
export default class Vue {
    
    constructor(options) {
    
    this.$el = document.querySelector(options.el)
    if (typeof options.data === 'function') {
    
      this.$data = options.data()
    } else if (typeof options.data === 'object' && options.data !== null) {
    
      this.$data = options.data
    } else {
    
      console.error('data is must be a function or object')
    }
    this.$options = options

    // 数据代理
    }
}

接下来实现通过 vm 也可以拿到 vm.$data 上的属性,并且可以同步修改,原理其实就是 Object.defineProperty()

// Vue.js
export default class Vue {
    
    constructor(options) {
    
    this.$el = document.querySelector(options.el)
    if (typeof options.data === 'function') {
    
      this.$data = options.data()
    } else if (typeof options.data === 'object' && options.data !== null) {
    
      this.$data = options.data
    } else {
    
      console.error('data is must be a function or object')
    }
    this.$options = options

    // 数据代理
    this.proxyData()
  }

  proxyData() {
    
    for (const key in this.$data) {
    
      Object.defineProperty(this, key, {
    
        enumerable: true,
        configurable: false,
        get() {
    
          return this.$data[key]
        },
        set(newVal) {
    
          this.$data[key] = newVal
        }
      })
    }
  }
}

通过代理,我们就可以在 Vue 的示例对象上访问到 data 中的数据了,并且实现了同步,测试如下:

c414de86f0148ee241498618411c6d0a.png

到这里我们的数据代理已经实现好了,接下来我们实现响应式系统。

Object 响应式

Observer

首先,我们来使 data 中的数据变成响应式的,要变成响应式的就必须被观测,所以我们创建一个 Observer 类对数据进行观测:

// Observer.js
export default class Observer {
    
  constructor(data) {
    
    this.observe(data)
  }

  observe(data) {
    

  }
}

并且我们要在 new Vue 的时候就要对数据进行观测,所以在 Vue 的构造方法中我们需要实例化 Observer

// Vue.js
import Observer from "./Observer.js"

export default class Vue {
    
    constructor(options) {
    
    this.$el = document.querySelector(options.el)
    if (typeof options.data === 'function') {
    
      this.$data = options.data()
    } else if (typeof options.data === 'object' && options.data !== null) {
    
      this.$data = options.data
    } else {
    
      console.error('data is must be a function or object')
    }
    this.$options = options

    this.proxyData()

    // 数据观测/劫持
    new Observer(this.$data) // 新增
  }

  proxyData() {
    
    for (const key in this.$data) {
    
      Object.defineProperty(this, key, {
    
        enumerable: true,
        configurable: false,
        get() {
    
          return this.$data[key]
        },
        set(newVal) {
    
          this.$data[key] = newVal
        }
      })
    }
  }
}

接着我们就来看一下 Observer 这个类的具体实现,我们都知道 Vue 的响应式的核心是使用了 Object.defineProperty 来将对象的属性变成 gettersetter 形式来实现的,所以我们一起来实现一下:

// Observer.js
export default class Observer {
    
  constructor(data) {
    
    this.observe(data)
  }

  observe(data) {
    
    for (const key in data) {
    
      const value = data[key]
      defineReactive(data, key, value)
    }
  }
}

function defineReactive(obj, key, value) {
    
  Object.defineProperty(obj, key, {
    
    enumerable: true,
    configurable: true,
    get() {
    
      return value
    },
    set(newVal) {
    
      value = newVal
    }
  })
}

我们这样设置后会有两个问题:

  • 我们现在只对 vm 的第一层属性添加了响应式并没有对其子属性添加响应式
  • 试想如果改变了 vm.info = {color: 'red'} 那么新添加的对象 {color: 'red'} 并不具备响应式

对于子属性我们需要递归添加响应式,对于新赋的值我们一应该让其具备响应式:

// Observer.js
export default class Observer {
    
  constructor(data) {
    
    this.observe(data)
  }

  observe(data) {
    
    for (const key in data) {
    
      const value = data[key]
      defineReactive(data, key, value)
    }
  }
}

function defineReactive(obj, key, value) {
    
  // 新增  递归子属性添加响应式
  if (typeof value === 'object' && value !== null) {
    
    new Observer(value)
  }
  Object.defineProperty(obj, key, {
    
    enumerable: true,
    configurable: true,
    get() {
    
      return value
    },
    set(newVal) {
    
      if (value !== newVal) {
    
        value = newVal
        // 为赋的新值添加响应式
        new Observer(value)
      }
    }
  })
}

不过我们如果只是向这样简单的将对象的属性变成 gettersetter 的形式那么也没有任何意义。

试想我们的目的是为了收集页面中使用了我们的 data 中数据的地方,即收集依赖。而页面中只要有对数据的依赖,那么必定会走 get 方法获取数据,所以我们应该在 get 方法中收集依赖。

当我们收集到这些依赖后,要在数据发生变化时,要通知这些依赖进行更新视图。而数据的变化又必定会触发 set 方法,所以我们要在这里去通知依赖。

所以分析到这里我们发现,我们需要有一个类来帮助我们收集依赖,并且具有通知依赖去更新视图的功能,假设这个类是 Dep ,那么我们还需要在下面新增的两个位置进行操作:

// Observer.js
export default class Observer {
    
  constructor(data) {
    
    this.observe(data)
  }

  observe(data) {
    
    for (const key in data) {
    
      const value = data[key]
      defineReactive(data, key, value)
    }
  }
}

function defineReactive(obj, key, value) {
    
  if (typeof value === 'object' && value !== null) {
    
    new Observer(value)
  }
  Object.defineProperty(obj, key, {
    
    enumerable: true,
    configurable: true,
    get() {
    
      // 收集依赖    新增
      return value
    },
    set(newVal) {
    
      if (value !== newVal) {
    
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值