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

yizhihongxing

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日

相关文章

  • 解决新建一个vue项目过程中遇到的问题

    当我们在新建一个vue项目的过程中,有可能会遇到一些问题,这里提供一些解决这些问题的攻略。 问题1:无法使用vue-cli 问题描述 在使用vue-cli新建项目时,可能会遇到以下错误提示: ‘vue’ 不是内部或外部命令,也不是可运行的程序或批处理文件。 解决方法 出现上述错误,通常是因为在命令行中找不到vue命令,需要安装vue-cli。我们可以通过以下…

    node js 2023年6月8日
    00
  • nodejs代码执行绕过的一些技巧汇总

    标题:Node.js代码执行绕过的一些技巧汇总 一、概述 Node.js是一款非常流行的JavaScript运行环境,但在代码执行过程中可能也会出现漏洞使得攻击者可以执行一些不受欢迎的代码。本文将探讨几种绕过代码执行漏洞的技巧。 二、技巧汇总 绕过输入过滤 当从前端获取用户输入时,很重要的一步就是对数据进行输入检查。但只是检查数据的类型是不够的,因为攻击者可…

    node js 2023年6月8日
    00
  • Node.js 学习笔记之简介、安装及配置

    Node.js 学习笔记之简介、安装及配置 简介 Node.js 是一种基于 Chrome V8 引擎的 JavaScript 运行时环境。Node.js 运行在服务端,并采用事件驱动、非阻塞式 I/O 模型,使其轻量又高效。Node.js 的优势在于能够把 JavaScript 语言用于服务端编程,与前端相比,它能够更好地处理 I/O 操作,更高效地开发高…

    node js 2023年6月8日
    00
  • node.js回调函数之阻塞调用与非阻塞调用

    当我们在node.js中执行一个耗时操作时,例如读取文件、请求网络数据等,会出现执行时间较长的情况,这会导致整个程序阻塞,影响程序的性能。为了解决这个问题,Node.js采用了回调函数的机制来实现非阻塞调用。 阻塞调用 阻塞调用是指应用程序在执行一个函数时,必须等待该函数执行完成,才能继续执行后面的代码。当我们在node.js中进行文件读取时,如果使用阻塞调…

    node js 2023年6月8日
    00
  • Node.js 制作实时多人游戏框架

    Node.js是一款基于V8引擎的JavaScript运行环境,Node.js的出现极大地推动了JavaScript在后端开发领域的普及和应用。下面,我将使用Markdown格式为大家讲解如何使用Node.js制作实时多人游戏框架。 环境搭建 首先,我们需要_node.js_的安装环境。这里以Mac OS X系统为例进行安装。在终端中输入以下命令进行安装: …

    node js 2023年6月8日
    00
  • nodejs 使用 js 模块的方法实例详解

    介绍如何在node.js中使用js模块,以下是详细的攻略: 1. js模块的引入 在node.js中,通常使用require语句来引入js模块。 require语句的格式如下: var module = require(‘module_name’); 其中,module_name是需要引入的js模块的名称路径。如果是自己创建的模块,可以使用相对路径名称来引用…

    node js 2023年6月8日
    00
  • 基于nodejs+express(4.x+)实现文件上传功能

    实现文件上传功能是Web开发中常见的需求之一。本文档将详细讲解如何使用nodejs+express(4.x+)实现文件上传功能。 1. 安装express(4.x+)和multer 在开始使用express和multer之前,需要先确保它们已经安装在你的电脑上。 可以通过npm来进行安装: npm install express multer –save …

    node js 2023年6月8日
    00
  • 浅谈Node Inspector 代理实现

    浅谈Node Inspector 代理实现 什么是Node Inspector? Node Inspector是一个基于Chrome DevTools协议的调试器,它允许调试Node.js应用程序,使用它可以轻松地查看和编辑源代码、检查变量和执行调试、设置断点以及调用控制台,等等。 什么是Node Inspector 代理? Node Inspector 代…

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