手写vue(二)响应式实现
创始人
2025-05-30 12:52:05

名词解释:

vm:指Vue实例

一、目标效果

  1. vue定义

(1)新建vm时,可以通过一个data对象,或者data函数,其属性可以通过vm直接访问,而data对象可以通过vm._data获取

(2)修改vm._data.xxx时,等价于修改this.xxx

  1. 响应式对象

(1)能够监听到data对象属性的修改

(2)能够监听到data下对象属性的属性

  1. 响应式数组

(1)能够监听到通过数组方法修改数组

(2)数组中保存的对象,也能被监测修改

(3)不监听数组通过下标修改

测试先行:

    

二、Vue实例定义

  1. 参考Vue源码,使用构造函数的方式,通过创建传入一个配置对象生成实例

  1. option传给vue之后,直接通过this.$option挂载到示例上,方便后面,读取配置项进行初始化

  1. 初始化单独在一个init文件中进行

import init from "./init"function Vue(option) {// 挂载到vms上this.$option = optioninit(this)
};export default Vue

三、在Vue实例中定义data属性

data中的值,可以通过多个地方修改,但修改的结果时同步的,此时就不能直接粗暴地挂载在vm上:例如:

要把data.foo挂载到this.foo,如果直接使用this.foo = data.foo,则执行this.foo = newVal之后,data.foo还是旧的值。

由上面的反例可得,我们要挂载的内容不是foo的值,而是data的foo这个属性,通过this.foo修改值,应该同步到data.foo上去

通过Object.defineProperty可以动态地给对象添加属性,读取的值和返回的值都可以通过getter、setter自己定义,所以我们获取、修改值时,直接去操作data对象。

// init.js
function initData(vm) {const opt = vm.$optionlet data = opt.dataif (typeof data === 'function') {data = data()}// 在vm上定义_data和data中的数据defineGlobal(vm, '_data', { _data: data })Object.keys(data).forEach(key => {defineGlobal(vm, key, data)})
}/*** 在vm上定义代理对象* @param {*} vm vue实例* @param {*} key 需要在实例上定义的key* @param {*} source 需要被代理的源对象*/
function defineGlobal(vm, key, source) {Object.defineProperty(vm, key, {configurable: true,enumerable: true,get() {return source[key]},set(v) {source[key] = v}})
}

四、对象监听

与vue挂载data类似,可以使用Object.defineProperty去代理data中的所有属性,并且递归地去代理所有的子属性,通过setter就可以监听属性的修改。

具体实现:创建一个Observer类,需要监听的对象作为参数传入构造函数,然后在这个类的构造函数中去给对象添加响应式。添加响应式监听之后,把类实例对象挂到监听对象中去,作为一个已经被监听的标志位,也直接使用监听的相关方法,否则,其实直接通过方法处理就足够了,并不需要作为一个类。

// init.js
function initData(vm) {...// 观察data中内容observe(data)...
}// observe/index.js
class Observer {constructor(obj) {if (Array.isArray(obj)) {...} else {this.observeObj(obj)}// 在被监听的对象上,定义已被监听的标志位,指向Observer对象自身// 在其它文件中,也方便直接使用监听的相关方法Object.defineProperty(obj, '__ob__', {value: this})}/*** 监听对象* @param {Object} obj 需要观察的对象*/observeObj(obj) {Object.keys(obj).forEach(key => {const val = obj[key]// 尝试监听对象的属性observe(val)// 定义响应式defineReactive(obj, key)})}
}// 导出的方法/*** * @param {Object} obj 需要监听的对象* @returns */
export function observe(obj) {if (typeof obj !== 'object') {return null}if (obj.__ob__) {return obj.__ob__}return new Observer(obj)
}/*** 给对象添加响应式* @param {Object} obj 需要定义响应式的对象* @param {String} key 对象的属性key*/
export function defineReactive(obj, key) {let value = obj[key]Object.defineProperty(obj, key, {configurable: true,enumerable: true,get() {return value},set(val) {console.log('set data', obj, key, val);value = val}})
}

五、数组监听

由于数组长度可能比较大,如果对数组的每个对象都进行响应式监听,则性能损耗太高,比如数组长度为1万,则需要对一万个属性的进行响应式监听:Object.defineProperty(arr, i, {})

由于数组对数组进行操作,主要时通过push、pop、splice等等方法去修改的,通过下标直接修改比较少,因此只监听数组的方法调用进行监听。

具体实现:

举例,我们要监听对象arr的push方法,不能直接重写原型对象方法arr.__proto__.push = XXX,因为arr._proto指向的是Array.prototype, 直接修改后,所有数组对象的push方法都会被修改。

利用原型链中会逐级查找属性的特点,我们可以创建一个对象,在这个对象中重新定义push等方法,然后,需要监听的数组,只需要原型对象指我们创建的数组原型重写对象,就可以实现监听

而这个对象的原型对象应该指向Array对象,因为我们并不需要重写所有的数组方法,而是只需要重写一部分会修改原数组的方法,并且,我们重新定义的方法,最后也是要调用Array中的原方法是实现方法逻辑的,相当于对方法进行了一层代理,每次调用时,能够通知到我们,做一些操作。

// array.js
// 需要监听的方法
const observeMethods = ['push','pop','shift','unshift','splice','sort','reverse'
]
const arrayProto = Array.prototype
// 创建一个空对象,原型对象指向Array的原型属性上
const obArrayProto = Object.create(arrayProto)// 重写需要监听的方法
observeMethods.forEach(methodName => {const originalMethod = arrayProto[methodName]obArrayProto[methodName] = function (...args) {let inserted// 新增元素的方法,需要给新元素添加响应式switch (methodName) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)breakdefault:break;}const observe = this.__ob__if (inserted) {observe.observeArray(inserted)}console.log(methodName, args);// 需要使用call,因为originalMethod方法没有通过XXX对象去调用,因此,如果直接执行,方法中的this会指向window,在严格模式下会指向undefined// 通过call方法调用,让originalMethod方法内的this指向数组对象,因为这个方法是通过arr.XXX()调用的return originalMethod.call(this, ...args)}})/*** 监听数组方法* @param {Array} arr */
export function observeArrayMethod(arr) {// 数组的原型对象指向重写了部分方法的新的原型对象Object.setPrototypeOf(arr, obArrayProto)
}// ********************** observe/index.js ********************
import { observeArrayMethod } from "./array"class Observer {constructor(obj) {if (Array.isArray(obj)) {this.observeArray(obj)} else {...}...}/*** 监听数组* @param {Array} arr 需要观察的数组*/observeArray(arr) {// 监听数组的修改方法observeArrayMethod(arr)// 尝试监听数组内的所有元素arr.forEach(observe)}
}

总结:

目录结构:

gitee源码地址:

https://gitee.com/ZepngLin/my-vue/tree/%EF%BC%88%E4%BA%8C%EF%BC%89%E5%93%8D%E5%BA%94%E5%BC%8F%E5%AE%9E%E7%8E%B0

相关内容

热门资讯

跨界锂业等待腾飞,金圆股份补税... 本报(chinatimes.net.cn)记者胡雅文 北京报道金圆环保股份有限公司(下称“金圆股份”...
29日白银,最新持仓变化 新浪期货 根据交易所数据,截至5月29日收盘主力合约白银2508,涨跌+0.00%,成交量65.95...
县政府收到上级转办的信访后,不... 5月30日,吉林省纪委监委公开通报4起形式主义、官僚主义典型问题。 其中包括:农安县政府办公室不作为...
智慧医院智能化系统设计方案(6... 本方案聚焦智慧医院智能化系统建设,涵盖总体规划、设计方案及造价估算等内容,旨在打造高效、安全、便捷的...
Nordic nRF开发环境搭... 文章目录前言说明SDK说明什么是NCS什么是Zephyr下载安装Windows安装VS Code安装...
ARM 在Unity3D 中的... (注意:虽然我们选择了渐进 CPU 光照贴图,但我们鼓励你...
【Java】try-catch... 文章目录 try-catch多个 single-catch 块multi-catchfinallyf...
一季度收入超33亿,霸王茶姬将... 5月30日晚,霸王茶姬(NASDAQ: CHA)发布了上市之后的第一份季度财报。财报显示,今年第一季...
搭建SFTP服务安全共享文件,... 文章目录1.前言2.本地SFTP服务器搭建2.1.SFTP软件的下载和安装2.2.配置SFTP站点2...
Specifications 1.适用范围 动态构建查询语句支持所有的查询条件支持子查询、连接查询、排序、分页不支持自定义模型&#...
0 ROS1学习笔记 ROS1学习笔记目录参考内容代码仓库地址 写在最前 本栏目是ROS1,在Ubuntu...
拒绝转保!34年编外员工办不了... 临近退休了,突然被强制“自愿转保”,河北井陉县税务局的冯爱文拒绝签字,导致办理不了退休,到底发生了什...
队列实现及leetcode相关... 上一篇写的是栈这一篇分享队列实现及其与队列相关OJ题 文章目录一、队列概念及实现二、队列源码三、l...
REITs再破局:存量资产盘活... 21世纪经济报道记者唐韶葵 上海报道公募REITs近期呈现发行加速迹象。5月30日,据证监会网站披露...
9.0 自定义SystemUI... 1.前言 在进行9.0的系统rom产品定制化开发中,在9.0中针对systemui下拉状态栏和通知...
收评:沪指震荡调整跌0.47%... 【收评:沪指震荡调整跌0.47% 全市场超4100只个股下跌】财联社5月30日电,市场全天震荡调整,...
谁是车圈恒大,魏建军发对脾气了... “整体来看,中国主流车企的资产负债情况要好于国外车企,中国主流车企根本不存在所谓的‘车圈恒大’。”继...
split()详解 需求:js根据字符串中逗号取出各个元素并且放在数组中 split() 方法使用指定的...
工作十几年,第一次在线上遇到死... 概述 最近一直在为系统的稳定性努力着,但凡线上有一些问题,都不轻易放过...
每日学术速递3.21 CV - 计算机视觉 |  ML - 机器学习 |  RL - 强化学习 | NLP 自然语言处理 ...
国家医保局开展药师“挂证”全国... 本文来源:时代周报 作者:林昀肖近日,国家医保局发布《关于对定点零售药店药师“挂证”等情况开展核查的...
148.网络安全渗透测试—[C... 我认为:无论是学习安全还是从事安全的人,多多少少都有些许的情怀和使命感&...
【每日一题Day153】LC2... 温度转换【LC2469】 给你一个四舍五入到两位小数的非负浮点数 celsius 来表示温度&#x...
娇牛马的新饲料:领导喂的炼乳小... 出品 | 虎嗅青年文化组作者 | 阿珂可编辑、题图 | 渣渣郡本文首发于虎嗅年轻内容公众号“那個NG...
样式声明——溢出属性 系列文章目录 前端系列文章——传送门 CSS系列文章——传送门 文章目录系列文章目录1.溢出ove...
用水量测系统-灌区用水精细化管... 政策背景 2021年1月,水利部办公厅和财政部办公厅联合印发《全国中型灌区续建配套与节...
云南铜业资源整合 拟收购凉山矿... 中经记者 陈家运 北京报道近日,云南铜业(000878.SZ)发布重大资产重组预案,拟通过发行股份的...
计讯物联二次供水水池泵站监测方... 方案背景 水质污染、设施故障率高、供水压力、安防缺失、故障反馈周期长等城市高楼大厦高层供水问题层出不...
记录上一次视频播放的位置 (一)用到的知识点 (1)ontimeupd...
【错误记录】串口不存在或者被其... 文章目录一、报错信息二、解决方案 一、报错信息 使用串口工具调试 设备的 串口 数据 ,...