【系列 1】手写vue响应式原理

2023-11-09

手写vue响应式原理

首先我们看看原生 vue 做了什么

<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
<script>
    const vm = new Vue({
        data: {
            name: '小米',
            arr:[{a:2}] // 检测深度响应式
        }
    })
    console.log('vm', vm)
</script>

可见 vm 第一层与 _data 内都能获取到 data 数据, 并且其数值都进行了 get set 响应式处理
在这里插入图片描述
在这里插入图片描述
接下来我们动手实现 vue 的这个步骤!

vue 响应式原理

目录结构:

├── dist   // 打包存放的目录
├── public  // 静态资源文件
│   └── index.html
├── src
│   ├── observe
│   │  ├── array.js  // 操作数组数据响应式
│   │  └── index.js  // 数据响应式
│   ├── index.js     // 导出 vue 构造函数
│   ├── init.js      // 初始化vue状态
│   └── state.js     // 据不同属性进行初始化操作
├── .babelrc         // babel打包配置
├── package.json
└── rollup.config.js 

使用Rollup搭建开发环境-目录说明

/src/index.js (导出vue构造函数)

import {initMixin} from './init';

function Vue(options) {
    this._init(options);
}
initMixin(Vue); // 给原型上新增_init方法
export default Vue;

/src/init.js (init方法中初始化vue状态)

import { initState } from "./state";

export function initMixin(Vue) {
    Vue.prototype._init = function(options) {
        const vm = this;
        vm.$options = options // 所有后续的扩展方法都有一个$options选项可以获取用户的所有选项
        // 对于实例的数据源 props data methods computed watch
        initState(vm);
    }

/src/state.js (根据不同属性进行初始化操作)

这里开始对数据进行响应式处理

import { observe } from "./observe/index";

export function initState(vm) {
    const options = vm.$options

    // 后续实现计算属性 、 watcher 、 props 、methods
    if (options.data) {
        initData(vm);
    }
}

function proxy(vm, source, key) {
    Object.defineProperty(vm, key, {
        get() {
            return vm[source][key]
        },
        set(newValue) {
            vm[source][key] = newValue;
        }
    })
}

function initData(vm) {
    let data = vm.$options.data;
    // 如果是函数就拿到函数的返回值 否则就直接采用data作为数据源
    data = vm._data = typeof data === 'function' ? data.call(vm) : data

    // 属性劫持 采用defineProperty将所有的属性进行劫持

    // 我期望用户可以直接通过 vm.xxx 获取值, 也可以这样取值 vm._data.xxx
    for (let key in data) {
        proxy(vm, '_data', key)
    }
    observe(data)
}

src/observe/index.js (实现数据响应式)

对数据递归操作实现所有数据都响应式

import arrayPrototype from "./array";

class Observer{
    constructor(data){
        // 如果是数组的话也是用defineProperty会浪费很多性能 很少用户会通过arr[1000] = 1234
        // vue3 中的 polyfill 直接就给数组做代理了
        // 改写数组的方法,如果用户调用了可以改写数组方法的api 那么我就去劫持这个方法
        // 变异方法 push pop shift unshift reverse sort splice 
        Object.defineProperty(data,'__ob__',{
            value:this,
            enumerable:false
        })
         // 如果有__ob__属性 说明被观测过了
        // 修改数组的索引和长度是无法更新视图的
        if(Array.isArray(data)){
            // 需要重写这7个方法
            data.__proto__ = arrayPrototype; 
            // 直接将属性赋值给这个对象
            // 如果数组里面放的是对象类型 我期望他也会被变成响应式的
            this.observeArray(data);
        }else{
            this.walk(data)
        }
    }
    observeArray(data){
        data.forEach(item=> observe(item)); //如果是对象我才进行观测了  
    }
    walk(data){ // 循环对象 尽量不用for in (会遍历原型链)
        let keys = Object.keys(data); // [0,1,2]
        keys.forEach(key=> { //没有重写数组里的每一项
            defineReactive(data,key,data[key])
        })
    }
}
// 性能不好的原因在于 所有的属性都被重新定义了一遍
// 一上来需要将对象深度代理 性能差
function defineReactive(data,key,value){ //  闭包
    // 属性会全部被重写增加了get和set
    observe(value); // 递归代理属性
    Object.defineProperty(data,key,{
        get(){ // vm.xxx
            return value;
        },
        set(newValue){ // vm.xxx = {a:1} 赋值一个对象的话 也可以实现响应式数据
            if(newValue === value) return
            observe(newValue)
            value = newValue;
        }
    })
}
export function observe(data) {
    if(typeof data !== 'object' || data == null){
        return ; // 如果不是对象类型,那么不要做任何处理
    }
    if(data.__ob__){
        // 说明这个属性已经被代理过了
        return data
    }

    // 我稍后要区分 如果一个对象已经被观测了,就不要再次被观测了
    // __ob__ 标识是否有被观测过

    return new Observer(data)
};

src/observe/array.js (操作数组数据响应式)

目的就是对数组内新增的数据再次进行观测 避免里面出现没有监听到的对象数据

let oldArrayPrototype = Array.prototype;
// arrayProptotype.__proto__ = Array.prototype;

let arrayPrototype = Object.create(oldArrayPrototype);
let methods = [
    'push',
    'pop',
    'shift',
    'unshift',
    'reverse',
    'sort',
    'splice'
]
// 重写数组 7 方法 (目的就是对新增的数据再次进行观测 避免里面出现没有监听到的对象数据)
// 如 arr[1000] = 1234 更改数组会响应式是通过 $set 实现的
methods.forEach(method => { // 用户调用push方法会先经历我自己重写的方法,之后调用数组原来的方法
    arrayPrototype[method] = function(...args) {
        let inserted;
        let ob = this.__ob__;
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args; // 数组
                break;
            case 'splice': // arr.splice(1,1,xxx)
                inserted = args.slice(2); // 接去掉前两个参数
            default:
                break
        }
        if (inserted) {
            // 对新增的数据再次进行观测
            ob.observeArray(inserted)
        }
        return oldArrayPrototype[method].call(this, ...args)
    }
})
export default arrayPrototype
手写 vue 代码仓库 链接
GitHub https://github.com/shunyue1320/vue-resolve/tree/vue-01
Gitee https://gitee.com/shunyue/vue-resolve/tree/vue-01/
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【系列 1】手写vue响应式原理 的相关文章

  • 如何在bootstrap中默认隐藏侧边栏?

    我在这里有一个很好的参考 作为 Bootstrap 在设计 Web 表单应用程序时的侧边栏 http startbootstrap com template overviews simple sidebar http startbootst
  • 如何使用 JavaScript 选择预节点/块中的文本?

    我了解不允许 JS 将任意文本复制到剪贴板背后的安全原因 但是是否有一种方法可以通过单击按钮来选择预节点中的文本 类似于 select 函数在输入中的工作方式 我不是在寻找复制到剪贴板的 jQuery 插件 我只想突出显示预块中的文本 以便
  • 雅虎 OAuth2 隐式授予流程不适用于新的雅虎应用程序

    我有现有的网络应用程序和专用雅虎应用程序 https developer yahoo com apps 在职的 它用OAuth2 隐式授权流程 https developer yahoo com oauth2 guide flows imp
  • 实现悬停信息框

    我有一个日历 当用户将鼠标悬停在单元格上时 会出现一个很大的信息框 其中包含该日期的详细信息 虽然当用户离开时使信息框消失 但我遇到了一些麻烦 我基本上想要它 这样当鼠标光标移出信息框隐藏的日历单元格时 它就会消失 但我遇到了麻烦 因为mo
  • 如何强制折断不可折断的字符串?

    我有一个根据数据库中包含的数据生成的 HTML 页面 数据库有时包含浏览器无法分解的长字符串 因为这些字符串不包含可分解的字符 空格 点 逗号等 有没有办法使用 html css 甚至 javascript 来解决这个问题 看到这个link
  • NestJS e2e 测试模拟会话装饰器

    我正在尝试使用 supertest 编写一个 e2e 测试 其中我的控制器实际上使用了 Session 装饰师 然而 我不想承担使用数据库连接等启动会话的全部负担 因此测试中的我的应用程序实际上并未初始化会话 相反 我想首先模拟掉装饰器提供
  • 无法读取未定义的“触及”属性

    为什么我会收到此错误无法读取未定义的属性 为什么无法读取formName controls email touched但它能够阅读formName get custDetails touched
  • 如何在php中使用一张图像绘制形状

    我需要使用图像的一部分来创建帧图像 例如 用户将从后端上传图像片段 现在我需要根据前端用户的要求在前端创建一个框架 用户将选择框架的高度和宽度 然后他将选择该图像片段 如下所示 我没有办法做到这一点 我尝试通过 css 和 html can
  • window.open:是否可以打开一个新窗口并修改其 DOM

    我想打开一个新窗口 var my window open iframe html blank height 600 width 600 但当我打开它时 我想修改它的DOM 我尝试过 var div my document createEle
  • html canvas动画卡顿

    谁能解释为什么提供的画布动画断断续续 我创建了一个测试存根来演示该问题 我在桌面上的 FF Chrome IE 以及 Android 上的 FF 和 Chrome 中看到了卡顿现象 口吃是由于垃圾收集造成的吗 似乎 raf 在每次调用时都会
  • “|”是什么意思(单管道)在 JavaScript 中做什么?

    console log 0 5 0 0 console log 1 0 1 console log 1 0 1 为什么0 5 0返回零 但任何整数 包括负数 都返回输入整数 单管道 有什么作用 这是一个按位或 https developer
  • 窗口大小调整触发的 DOM 事件

    我有一个布局相当复杂的页面 最初打开页面时 某些元素的对齐存在问题 但是 可以通过更改浏览器窗口的大小来 永久 解决此问题 显然 我不希望用户必须调整浏览器窗口的大小才能使页面正确显示 所以我想知道是否有一种方法可以在页面首次加载时以编程方
  • ReactCSSTransitionGroup 组件WillLeave 未调用

    我尝试使用 ReactCssTransition 但不知何故该事件没有被调用 componentWillLeave 这是我的组件 import React Component from react import TransitionGrou
  • 将 onclick 事件应用于页面加载时不存在的元素

    我将列表样式设置为看起来像选择框 并且当用户单击列表中的元素时我想触发一个函数 但是该元素是通过加载的AJAX因此 当页面加载并且我无法绑定时不存在onclick事件到它onDomReady 如果我把它作为一个普通的选择列表 我可以只标记一
  • Rails:找不到 JavaScript 运行时。有关可用运行时的列表,请参阅 https://github.com/sstephenson/execjs。 (ExecJS::运行时不可用)

    自从几周前 Dreamhost 升级了服务器以来 我的网站就被破坏了 我一直在努力解决它并取得了一些进展 但我仍然坚持希望是最后的问题 我在 Ruby 1 8 7 上使用 Rails 3 1 1 并收到来自 PhusionPassenger
  • 尝试使用 Firebug 查找 JavaScript 文件中的函数

    我试图找到这个函数调用 myFooBar 该函数在某些 HTML 中内联引用 但页面加载了大量 JavaScript 并且在每个文件中搜索该函数需要相当多的工作 如何使用 Firebug 找到此函数所在的 JavaScript 文件 打开脚
  • 是否可以将请求标头添加到 CORS 预检请求中?

    我有一个从外部服务器 不是服务器 访问 API 的网站 为网站提供服务 通过简单的XmlHttpRequest 见下文 那个API 需要将用于访问服务的 API 密钥添加为请求标头 然而 正如这些CORS https developer m
  • 如何为 Imagus 悬停缩放扩展开发自定义过滤器?

    当我读到关于悬停缩放是邪恶的 http www reddit com r YouShouldKnow comments 1wjrc8 ysk that the hover zoom extension is spyware 哎呀 有两篇文章
  • Node.js 和 Passport 对象没有 validPassword 方法

    我正在使用 Node js Express Passport 创建一个简单的身份验证 本地 到目前为止我所达到的效果是 当输入错误的用户名或密码时 用户将被重定向到错误页面 但是当用户输入正确的用户名和密码时 我收到此错误 node mod
  • 用javascript调用外部网页(跨域)

    我正在尝试使用以下网络服务来验证提要这个问题 https stackoverflow com questions 11996430 check if a url is a valid feed 但浏览器不允许我向另一台服务器发送 ajax

随机推荐

  • 基于Qt开发的游戏手柄小程序例子

    以前做过一个项目 用游戏手柄链接上位机软件 控制下位机执行一些机械动作 现在我将手柄控制的功能单独拿出来做了一个手柄检测的小程序 供开发者们拿去移植到自己的项目中用 这个程序是用 Qt creator5 12开发环境开发的 不过移植到VS
  • idea配置hibernate环境-零基础入门-详细版

    idea配置hibernate环境 下载hibernate所需jar包 官网链接 高速链接 用idea创建一个web项目 Create New Project gt 选择Java Enterprise gt 勾选Web Applicatio
  • 嵌入式毕设分享 STM32与wifi的天气预报网时钟系统

    文章目录 0 前言 1 设计内容 2 软件设计 3 关键代码 4 最后 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目缺少创新和亮点 往往达不到毕业答辩的要求 这两年不断有学弟学妹告诉学长自己做的项目系统达不到老
  • 基于FPGA的正弦波发生器设计与实现

    基于FPGA的正弦波发生器设计与实现 摘要 本文介绍了一种基于FPGA的正弦波发生器的设计与实现 通过使用FPGA的数字信号处理功能 可以实现高精度 高性能的正弦波生成 文章首先介绍了DDS Direct Digital Synthesis
  • 《如何为Android Studio安装HAXM》

    Preface 1 Intel HAXM Hardware Accelerated Execution Manager 即英特尔硬件加速执行管理器 Intel HAXM 是一款硬件辅助虚拟引擎 管理程序 使用基于 Intel R Virtu
  • Python|excel表格数据一键转json格式小工具|支持xlsx、xls格式转json|【源码+解析】

    背景 最近在使用JavaScript编写一些浏览器RPA脚本 脚本使用过程中遇到一些问题 脚本使用的数据往往存放在excel表 但运行时只能读取json数据 导致频繁人工excel转json 效率低下 遇到问题后赶紧搜索excel转json
  • Selenium+Webdriver被检测识别出来的应对方案

    在写爬虫 面对很多js 加载的页面 很多人束手无策 更多的人喜欢用Senlenium Webdriver 古语有云 道高一尺魔高一丈 已淘宝为首 众多网站都针对 Selenium的js监测机制 比如 window navigator web
  • 转载:python 文件读写(追加、覆盖)

    with open file txt w as f f write content content 要放入文件的内容 要进行utf 8转码 可在pycharm中打开文件进行转码 x 创建一个新文件并打开它进行写入 b 二进制模式 t 文本模
  • 汇总下关于安全的13款必备工具

    汇总下关于安全的几款必备工具 1 burpsuite Burp Suite 是用于攻击web 应用程序的集成平台 http协议分析神器 里面包括了不少安全必备的功能 重放 爆破 扫描并且支持自定义脚本 实现自己想要的功能 Burp Suit
  • MacBook Big Sur 完美解决外接显示器 字体模糊、边缘不清 HIDPI 解决办法

    该文章转发MacBook Big Sur 完美解决外接显示器 字体模糊 边缘不清 HIDPI 解决办法 毕扬博客
  • error: invalid conversion from ‘void*‘ to ‘char*‘ [-fpermissive]

    include
  • 【计算机视觉】InvaSpread 讲解

    任何的书写错误 排版错误 概念错误等 希望大家包含指正 在阅读本篇之前建议先学习 机器学习 噪声对比估计 NCE 计算机视觉 MoCo 讲解 计算机视觉 InstDis 讲解 InvaSpread 在 InvaSpread 中 负样本的个数
  • 点云数据学习总结之一:点云数据存储格式

    最近研究点云数据 找了下相关的资料 看了许多的博文 下面总结了我认为比较写的比较好的博文链接 有需要的可以看下 刚刚才研究 所以可能不全 欢迎大家补充 共同学习 同时 未避免转载的博客链接失效 所以对博客内容进行了截图 如果原博主觉得受到侵
  • RestHighLevelClient封装使用,Java调用ES客户端 [支持ES6.x]

    前言 之前做项目的时候 需要用到Es的操作 本来想使用EsJpa的 即SpringDataElasticsearch 结果项目采用的是SpringBoot1 x版本 不得已 只要自己封装RestHighLevelClient来使用 不过网上
  • 蓝桥杯python技能升级

    只通过了百分之40 后面超时了 给没有头绪的伙计们一个思路吧 也请大佬给一个更好的解题思路 import math n m map int input split max 0 此为加的最大点 当加的次数小于0的时候 return0 if m
  • el-select下拉框只回显value不回显label的原因以及解决方法

    el select的采用的是map的key value结构 因此只显示value而不显示label的原因是 value的类型不正确 只需要在回显之前加上一行代码 将这个value转换成对应的类型即可 我这个里面需要的int类型 因此转成in
  • 集合相似度(PAT)

    题目链接 https www patest cn contests gplt L2 005 一开始用map超时了 总是有一组数据超时 当时觉得很纳闷 后来学到了 其实set也是可以开数组的 map也是 include
  • ubuntu 下安装chrome浏览器

    1 将google chrome stable current amd64软件复制移动到家目录下 2 打开终端 路径在家目录下 3 依次运行下面三条命令 sudo apt get install google chrome stable s
  • Swift的几种传值方式

    传值方式 在进行页面跳转过程中无法避免需要进行值的传递 那么值的传递可以分为正向传值和反向传值 例如在SourceViewController跳转至DestinationViewController的过程中需把前者的属性值传递给后者称为正向
  • 【系列 1】手写vue响应式原理

    手写vue响应式原理 首先我们看看原生 vue 做了什么 可见 vm 第一层与 data 内都能获取到 data 数据 并且其数值都进行了 ge