Vue源码学习之响应式是如何实现的

Vue源码学习之响应式是如何实现的

响应式是Vue的核心特性之一,它使得数据和视图之间能够自动同步更新。在Vue中,我们只需要修改数据,视图就会自动更新,这大大提高了开发效率。那么,响应式是如何实现的呢?

响应式实现原理

Vue通过Object.defineProperty()方法对数据对象进行劫持,当数据被修改时,会触发setter方法通知所有依赖于该数据的视图进行更新。具体实现步骤如下:

  1. 定义Observer类。遍历数据对象的所有属性,为每个属性添加getter和setter方法,getter方法用于收集依赖(Watcher),setter用于通知依赖进行更新。

  2. 定义Dep类。Dep类用于管理依赖(Watcher),每个响应式数据都会对应一个Dep实例,当数据发生变化时,Dep实例会通知依赖进行更新。

  3. 定义Watcher类。Watcher类用于存储依赖(Dep实例)和更新函数,更新函数会在数据发生变化时被调用。

  4. 在解析模板过程中,对模板中所有需要响应式更新的数据进行依赖收集,即创建Watcher实例,并把Watcher实例加入对应的Dep实例的依赖列表中。

  5. 当数据发生变化时,触发setter方法,setter会通知所有依赖的Watcher进行更新。

示例说明

下面通过两个示例,分别介绍响应式是如何实现的。

示例1

假设我们有一个数据对象:

const data = {name: 'Alice', age: 18}

我们对name属性进行依赖收集,代码如下:

class Dep {
  constructor() {
    this.subs = []
  }
  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target)
    }
  }
  notify() {
    this.subs.forEach(sub => sub.update())
  }
}

class Observer {
  constructor(value) {
    this.value = value
    if (!Array.isArray(value)) {
      this.walk(value)
    }
  }

  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
}

function defineReactive(obj, key, val) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      dep.depend()
      return val
    },
    set(newVal) {
      if (val === newVal) {
        return
      }
      val = newVal
      dep.notify()
    }
  })
}

const watcher = {
  update() {
    console.log('更新啦')
  }
}

function observe(value) {
  return new Observer(value)
}

observe(data)

Dep.target = watcher
data.name
Dep.target = null

在上面的代码中,我们对name属性进行了依赖收集,即创建了一个Dep实例,并把Watcher实例加入到其依赖列表中。接下来获取name属性的值,会触发getter方法并执行dep.depend(),此时会将Watcher实例加入到dep的依赖列表中。当name属性发生变化时,会触发setter方法并执行dep.notify(),此时会遍历dep的依赖列表,调用每个Watcher实例的update方法。

示例2

假设我们有一个模板:

<div>
  <p>姓名: {{name}}</p>
  <p>年龄: {{age}}</p>
  <p>性别: {{gender}}</p>
</div>

我们需要解析模板,并对其中的name、age、gender属性进行依赖收集,以实现响应式更新。代码如下:

class Watcher {
  constructor(vm, exp, cb) {
    this.vm = vm
    this.exp = exp
    this.cb = cb
    Dep.target = this
    this.value = this.get()
    Dep.target = null
  }

  get() {
    const value = this.vm
      ? this.vm.$data[this.exp]
      : undefined
    return value
  }

  update() {
    const value = this.vm
      ? this.vm.$data[this.exp]
      : undefined
    const oldValue = this.value
    if (value !== oldValue) {
      this.value = value
      this.cb && this.cb.call(this.vm, value, oldValue)
    }
  }
}

class Dep {
  constructor() {
    this.subs = []
  }

  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target)
    }
  }

  notify() {
    this.subs.forEach(sub => sub.update())
  }
}

class Observer {
  constructor(value) {
    this.value = value
    if (!Array.isArray(value)) {
      this.walk(value)
    }
  }

  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
}

function defineReactive(obj, key, val) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      dep.depend()
      return val
    },
    set(newVal) {
      if (val === newVal) {
        return
      }
      val = newVal
      dep.notify()
    }
  })
}

function observe(value) {
  return new Observer(value)
}

function compile(el) {
  const node = document.querySelector(el)
  const vm = new Vue({
    el,
    data: {
      name: 'Alice',
      age: 18,
      gender: '女'
    }
  })

  const watcherStore = []
  node.childNodes.forEach(child => {
    const reg = /\{\{(.*)\}\}/
    const text = child.textContent.trim()
    if (child.nodeType === 3 && reg.test(text)) {
      const exp = reg.exec(text)[1]
      watcherStore.push(new Watcher(vm, exp, function(value, oldValue) {
        child.textContent = text.replace(reg, value)
      }))
    }
  })
  return watcherStore
}

const watchers = compile('#app')

在上面的代码中,我们首先创建了Vue实例,并在模板中对name、age、gender属性进行了依赖收集。接下来我们遍历模板中的所有节点,对包含{{}}的文本节点进行Watcher实例的创建,并把Watcher实例加入到对应的Dep实例的依赖列表中。当name、age、gender属性发生变化时,会触发对应Dep实例的notify方法,并执行Watcher实例的update方法,从而实现对文本节点的更新。

总结:

响应式是Vue的核心特性,其实现原理是通过Object.defineProperty()方法对数据对象进行劫持,当数据被修改时,会触发setter方法通知所有依赖于该数据的视图进行更新。在解析模板过程中,需要对模板中所有需要响应式更新的数据进行依赖收集,即创建Watcher实例,并把Watcher实例加入对应的Dep实例的依赖列表中。当数据发生变化时,会触发setter方法并执行依赖列表中所有Watcher实例的update方法,从而实现对数据的更新以及视图的更新。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Vue源码学习之响应式是如何实现的 - Python技术站

(0)
上一篇 2023年6月8日
下一篇 2023年6月8日

相关文章

  • Node.js实现简单聊天服务器

    我来详细讲解一下“Node.js实现简单聊天服务器”的完整攻略。 1. 安装Node.js 首先需要安装Node.js,可以去官网下载安装包安装,或者使用命令行工具,在命令行中输入以下命令: $ sudo apt-get update $ sudo apt-get install nodejs 安装完成后,可以通过在命令行中输入以下命令来检验是否安装成功: …

    node js 2023年6月8日
    00
  • javascript实现双端队列

    下面是详细讲解 JavaScript 实现双端队列的完整攻略,包含以下内容: 双端队列的介绍 实现双端队列的方法 示例说明 1. 双端队列的介绍 双端队列是一种特殊的队列,它允许从两端进行数据的插入和删除操作。与普通队列相比,双端队列拥有更加丰富的操作,可以满足更多的需求。 2. 实现双端队列的方法 实现双端队列的方法有多种,其中最常见的方法是使用数组来实现…

    node js 2023年6月8日
    00
  • 浅析Node.js的Stream模块中的Readable对象

    浅析Node.js的Stream模块中的Readable对象 前言 在Node.js中,Stream是一个基础模块之一,负责处理数据流。它主要分为可写流(Writable)、可读流(Readable)以及双工流(Duplex)和转换流(Transform)四种类型。其中,我们今天将会重点探讨可读流(Readable)的属性和方法,以及如何使用它从流中读取数据…

    node js 2023年6月8日
    00
  • Node.js中的模块化,npm包管理器详解

    Node.js中的模块化 Node.js中模块化的核心思想是将代码段封装起来,使得模块与模块之间彼此独立,提高了代码的可重用性,并且使得代码更加易维护。Node.js的模块化分为两类:核心模块和文件模块。 核心模块 Node.js自带了一些核心模块,例如http、fs、path等,这些模块可以直接在代码中使用,无需安装任何第三方模块,也无需指定路径。 以下是…

    node js 2023年6月8日
    00
  • node.js基础知识小结

    Node.js基础知识小结 什么是Node.js? Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,可以让JavaScript在后端服务器端运行。它的最大特点是采用非阻塞方式,而传统的服务器都使用阻塞模式,也就是一个请求一个请求地处理,如果请求很多,性能会急剧下降。Node.js采用事件驱动、非阻塞I/O的模型,使得它非常适合…

    node js 2023年6月8日
    00
  • Async/Await替代Promise的6个理由

    Async/Await替代Promise的6个理由 在JavaScript中,我们经常使用Promise来解决异步编程问题,但是ES2017引入了async/await语法,使异步编程更加简单和直观。以下是async/await替代Promise的6个理由: 1.更容易处理错误 使用Promise时,我们需要使用.then()和.catch()方法来处理成功…

    node js 2023年6月8日
    00
  • TypeScript使用vscode监视代码编译的过程

    下面是详细的讲解: 1. 安装TypeScript和vscode 首先确保你已经安装了最新版本的Node.js和npm,可前往官网下载安装。安装完成后,进入命令行窗口,使用以下命令安装TypeScript: npm install -g typescript 安装完成后,我们需要安装vscode。可前往官网下载安装,或使用命令行工具安装: brew cask…

    node js 2023年6月9日
    00
  • Node.js中环境变量process.env的一些事详解

    Node.js中环境变量process.env的一些事详解 什么是环境变量 环境变量是操作系统中一个全局的key-value存储机制,用来存储和传递一些配置信息、设置和其他可变的值。在运行某些程序时,系统会根据不同的环境变量来影响应用行为。在Node.js中,我们可以通过process.env对象来访问环境变量。 如何设置环境变量 在Windows下,用户可…

    node js 2023年6月8日
    00
合作推广
合作推广
分享本页
返回顶部