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文件操作系统实例详解 Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,它可以实现服务器端的JavaScript代码执行。Node.js提供了丰富的内置模块,其中包含文件操作系统模块,可以帮助我们对文件进行操作。下面就是Node.js文件操作系统实例的详细攻略。 1. 引入文件操作系统模块 要对文件进行操作,我们需…

    node js 2023年6月8日
    00
  • 使用express搭建一个简单的查询服务器的方法

    下面是使用express搭建一个简单的查询服务器的方法的完整攻略: 准备工作 安装Node.js和npm 在终端中执行以下命令安装express和body-parser依赖: npm install express body-parser –save 搭建服务器 创建一个新的Node.js项目,并创建一个名为app.js的文件。打开该文件并加入以下代码: …

    node js 2023年6月8日
    00
  • 在Windows上安装和配置 Jupyter Lab 作为桌面级应用程序教程

    以下是在Windows上安装和配置 Jupyter Lab 作为桌面级应用程序的完整攻略: 安装 Python 首先,你需要安装 Python。可以从Python官网下载最新版本的Python安装包,选择合适的版本并下载。 下载完成后,双击安装包,按照提示完成安装。 安装完成后,在命令行运行以下命令,验证Python是否安装成功: bash python -…

    node js 2023年6月8日
    00
  • 关于Node.js的events.EventEmitter用法介绍

    关于Node.js的events.EventEmitter用法介绍,我们可以从以下几个方面进行详细讲解。 一、events.EventEmitter介绍 在 Node.js 中,events 模块是 Node.js 模块库的核心之一,它提供了一个简单的事件发射和监听器模式的实现。通过 events 模块,可以方便地进行异步事件的处理。 events.Even…

    node js 2023年6月8日
    00
  • Node.js学习之地址解析模块URL的使用详解

    下面是“Node.js学习之地址解析模块URL的使用详解”的完整攻略。 概述 在Node.js中,可以通过地址解析模块URL来解析URL地址,获取其中的协议、主机名、路径等信息,从而方便地处理URL相关的业务逻辑。本攻略将详细介绍URL模块的相关属性和方法,以及如何结合实际应用场景进行使用。 URL模块的基本属性 在使用URL模块之前,需要将其进行引入: c…

    node js 2023年6月8日
    00
  • nodejs模块系统源码分析

    来一篇关于 “nodejs模块系统源码分析” 的完整攻略吧! 什么是模块 总体来说,在Node.js中,每个文件都被视为一个模块,而模块是 Node.js 的核心概念之一。 模块系统是 Node.js 的一个重要组成部分,它是 Node.js 的一个基本特性。从它的名称我们可以知道,模块系统有助于将一个程序分解为更小、更易于维护的部分,这可以让开发者更容易地…

    node js 2023年6月8日
    00
  • Express实现前端后端通信上传图片之存储数据库(mysql)傻瓜式教程(一)

    OK,这里是 “Express实现前端后端通信上传图片之存储数据库(mysql)傻瓜式教程(一)”的完整攻略: 攻略概览 本攻略主要介绍如何使用 Express 实现前后端之间的图片上传,以及如何将上传的图片存储到 MySQL 数据库中。攻略包含以下主要内容: 前端页面的开发,包括上传图片的界面和相应的 JS 代码; Express 后端的开发,包括上传图片…

    node js 2023年6月8日
    00
  • Nodejs 发布自己的npm包并制作成命令行工具的实例讲解

    下面将详细讲解如何发布自己的npm包并制作成命令行工具的步骤: 准备工作 安装Node.js环境 注册npm账号或者使用已有的npm账号 发布npm包 创建一个文件夹,命名为my-package(名字可以自己定义)。 在my-package文件夹下创建一个package.json文件。 { "name": "my-package…

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