[切图仔救赎]炒冷饭--在线手撸vue2响应式原理
--图片来源vue2.6正式版本(代号:超时空要塞)发布时,尤雨溪推送配图。
前言
其实这个冷饭我并不想炒,毕竟vue3马上都要出来。我还在这里炒冷饭,那明显就是搞事情。
起因:
作为切图仔搬砖汪,长期切图jq一把梭。重复繁琐的切图,让自己陷入了一个无限的围城。想出去切图这个围城看一看,但是又害怕因为切图时间久了,自己会的也只有切图了。
为了后面能够继续搬砖恰饭,帮助自己跳出切图仔的围城。也去看了vue相关文档,当时记忆深刻觉得还行。可是G胖这个时候发动小紫本和打折魔咒,不知不觉又沉迷于DOTA小本子上面了。关于vue响应式原理很快忘得一塌糊涂,只记得一个属性Object.defindProperty,然后就没有然后了......
为了避免自己后面再次忘记,所以这里炒一个冷饭加深记忆。
响应式vue
在讲解vue响应式的原理之前,让我们来一段Vue代码作为示例:
<div id="app">
<div>主食: {{ food }}</div>
<div>饮料: {{ drink }}</div>
<div>菜单: {{ menu }}</div>
</div>
<script>
let vue = new Vue({
el: '#app',
data: {
food: '煎饼果子',
drink: '热豆浆'
},
computed: {
menu() {
return this.food + this.drink
}
}
})
</script>
当food和drink发生变化后,Vue会做两件事:
在页面上更新
food和drink的值。再次调用
menu, 重新计算food + drink的值, 并在页面上面更新。
更新值+计算值做的事情其实很简单,几行代码的事情。问题是当food或者drink变化时,Vue是怎么知道谁变化,然后马上响应其行为,去执行那"简单的几行代码"?
所以,当看到Vue案例时,词穷的我当时第一反应就是牛皮。
之所以发出感叹,是因为通常的JavaScript代码是实现不了这样的功能的。话不多说,让我们直接上代码来说明:
let food = "煎饼果子"
let drink = "热豆浆"
let menu = null
menu = food + drink
food = '炸鸡汉堡'
drink = '快乐水'
console.log(menu)
最终控制台打印结果:
煎饼果子热豆浆
如果是在Vue当中,food和drink发生了变化,那么Vue会跟着做出响应动作,从而在控制台输出我们想要的结果:
炸鸡汉堡快乐水
菜单响应
这里就出现第一个问题,当food或者drink 发生变化之后,menu并不会响应其变化。这个时候就需要我们来解决这个问题,满足menu响应。
借鉴Vue一样,我们先把menu的计算方法。也写成一个函数,取名为target。然后每次food或者drink变化的时候调用target函数
let food = "煎饼果子"
let drink = "热豆浆"
let menu = null
let target = () => {
menu = food + drink
}
target() // 初始化菜单menu
food = '炸鸡汉堡'
drink = '快乐水'
target()
console.log(menu)
控制台输出:
炸鸡汉堡快乐水
浴室沉思
前面一把梭直接调用的满足menu响应的问题,但是也间接留下一个新的疑惑点。这里针对一个菜单,就写了一个target。假设有多个菜单需要响应呢?
例如:
单人早餐 = 煎饼果子 + 热豆浆豪华套餐: 煎饼果子加两鸡蛋 + 热豆浆 + 油条一根午餐- ......
如果这个时候切换成:
单人午餐 = 炸鸡汉堡 + 快乐水豪华套餐: 双层炸鸡汉堡 + 快乐水 + 快乐薯条一包- ......
按照前面的逻辑, 估计得写N个target。这个时候响应式又是一个麻烦事情,可是有句话说的好。梭哈一时爽,一直梭哈一直爽。既然前面直接采用target一把梭完成,所以针对N个target方法,我也可以直接来个for循环一把梭能完成响应式问题。
for循环一把梭
- 定义一个数组,每定义了一个target函数。就存储到数组当中。
let storge = [] // 用来存储target
function record (){ //
storge.push(target)
}
- 定义循环函数,每次
data有变更。就调用这个函数,进行一把for循环.
function replay (){
storge.forEach(run => run())
}
- 合并成完整的代码:
let food = "煎饼果子"
let drink = "热豆浆"
let menu = null
food = '炸鸡汉堡'
drink = '快乐水'
let target = () => {
menu = food + drink
}
let storge = []; //用来存储更多的target
function record(target) {
storge.push(target)
}
function replay() {
storge.forEach(run => run())
}
record(target)
replay()
food = '炸鸡汉堡'
drink = '快乐水'
replay()
console.log(menu)
最后控制台成功输出:
炸鸡汉堡快乐水
Dep依赖类
通过一把梭实现功能,那么接下来就开始思考优化部分了。继续记录target这类的代码,这样有点怪怪的。为了后面方便管理,我们把代码进行简单的优化,封装成一个类:
class Dep {
constructor() {
this.subs = []
}
// 收集依赖
depend(sub) {
if (sub && !this.subs.includes(sub)) { // 做一个判断
this.subs.push(sub)
}
}
notify() {
console.log("暗号:下雨啦,收衣服啦!")
this.subs.forEach(sub => sub()) // 运行我们的target
}
}
就这样target函数存储在类的subs中,record也变成了depend,使用notify来代替replay
封装成类之后,每次当data数据更新的时候,就会发出一个暗号下雨啦,收衣服啦! 然后就开始遍历运行相应的target依赖了。
新的调用代码就更加清晰明了:
let dep = new Dep()
let food = "煎饼果子"
let drink = "热豆浆"
let menu = null
let target = () => {
menu = food + drink
}
dep.depend(target)
target() // 完成menu第一次初始化
console.log(menu)
food = '炸鸡汉堡'
drink = '快乐水'
dep.notify()
console.log(menu)
控制台输出:
煎饼果子热豆浆
暗号:'下雨啦,收衣服啦!'
炸鸡汉堡快乐水
观察者亮相
当前的代码,是确定一个依赖事件,就定义target,然后调用依赖类dep.depend将其存储起来。
let target = () => { menu = food + drink }
dep.depend(target)
target()
这个时候又新来一个target事件又该如何做:
新添加一个target事件?
let target2 = () => { 新的依赖事件 }
dep.depend(target2)
target2()
要是有几百个依赖,那还不得上天。我估计要是这样写代码,估计你的同事要说你写代码像CXK
观察者函数
借鉴观察者模式,封装一个watcher函数. 帮你观察记录相关target事件,避免多次声明变量。
function watcher(myFun) {
target = myFun
dep.depend(target)
target()
target = null
}
watcher(() => {
menu = food + drink
})
正如你所看到的,watcher函数接受myFunc参数,将其赋给全局的target上,调用dep.depend()将其添加到数组里,之后调用并重置target。
既然又封装一个新的函数,那么验证又将是必不可少的了。这里我们修改一下drink来试试:
drink = "快乐水"
console.log(menu)
dep.notify()
console.log(menu)
控制台输出结果:
煎饼果子热豆浆
暗号:下雨啦,收衣服啦!
煎饼果子快乐水
Object.defineProperty()
基本用法
铺垫了这么久,一个关键性角色这个时候也登场了。
该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys 方法), 这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。
--《MDN文档》
不明觉厉? 那就先热身一下,进入快乐的举例子环节:
let data = {
food: '煎饼果子',
drink: '热豆浆'
}
Object.defineProperty(data, 'food', {
get() {
console.log(`触发get方法`)
},
set(newVal) {
console.log(`设置food为${newVal}`)
}
})
data.food
data.food = 炸鸡汉堡
控制台输出:
触发get方法
设置food为炸鸡汉堡
简单封装
但是仅仅凭借object.defineProperty是无法完成当一个数据更新了,完成数据响应。而且代码这里也是只是对food做了一个处理, 还有drink没有处理,所以为了完成data所以属性都做相应的处理。接下来就是对于Object.defineProperty()进行简单的封装处理了:
Object.keys(data).forEach(key => {
let value = data[key]
Object.defineProperty(data, key, {
get() {
return value
},
set(newVal) {
value = newVal
}
})
})
遍历了data每个属性,然后对每个属性进行侦听。这样data的属性一旦改变,就会自动发出通知.
代码整合
前面零零散散分别讲了 Dep、watcher和object.defineProperty, 那么接下来就让我们把这个几个部分整合到一起,完整查看整个代码:
let data = {
food: '煎饼果子',
drink: '热豆浆'
}
class Dep {
constructor() {
this.subs = []
}
// 收集依赖
depend(sub) {
if (sub && !this.subs.includes(sub)) { // 做一个判断
this.subs.push(sub)
}
}
notify() {
console.log("暗号:下雨啦,收衣服啦!")
this.subs.forEach(sub => sub()) // 运行我们的target
}
}
Object.keys(data).forEach(key => {
let value = data[key]
let dep = new Dep()
Object.defineProperty(data, key, {
get() {
dep.depend(target)
return value
},
set(newVal) {
value = newVal
dep.notify()
}
})
})
function watcher(myFun) {
target = myFun
// dep.depend(target) 这里修改,移动到Object.defineProperty当中去
target()
target = null
}
watcher(() => {
data.menu = data.food + data.drink
})
console.log(data.menu)
data.food = "炸鸡汉堡"
data.drink = "快乐水"
console.log(data.menu)
控制台输出:
煎饼果子热豆浆
暗号:下雨啦,收衣服啦!
炸鸡汉堡快乐
这里完全实现了文章开头所提出的需求,每当food或drink更新时,我们的menu也会跟着响应并更新。
这时候Vue文档的插图的意义就很明显了:
免责声明
以上就是我的炒冷饭内容,怕忘记重写总结一下,有说错的地方多担待。(特拿前端劝退师骚声明一份,窥伺好久了。)
意思就是写得略粗糙,别喷我。。。
我是车大棒,我为我自己插眼。
[切图仔救赎]炒冷饭--在线手撸vue2响应式原理的更多相关文章
- 一个切图仔的 CSS 笔记
1,flexbox~注意,设为 Flex 布局以后,子元素的float.clear和vertical-align属性将失效. 在ios8上要加上前缀 display: -webkit-box; dis ...
- 一个切图仔的 JS 笔记
1,常用数据操作 Math.round(mum,2);num.toFixed(2);两位数 Math.floor(); 返回不大于的最大整数 Math.ceil(); 则是不小于他的最小整数 Math ...
- 一个切图仔的HTML笔记
1,href="javascript:history.back(-1)" //页面返回上一步 2,meta信息设置 360浏览器就会在读取到这个标签后,立即切换对应的极速核. &l ...
- 手写实现vue的MVVM响应式原理
文中应用到的数据名词: MVVM ------------------ 视图-----模型----视图模型 三者与 Vue 的对应:view 对应 te ...
- 手摸手带你理解Vue响应式原理
前言 响应式原理作为 Vue 的核心,使用数据劫持实现数据驱动视图.在面试中是经常考查的知识点,也是面试加分项. 本文将会循序渐进的解析响应式原理的工作流程,主要以下面结构进行: 分析主要成员,了解它 ...
- APP切图标记PS的外挂神器-Assistor PS(转)
目前APP设计师们对Assistor PS 可是好评连连,说是切图仔的福音或救星.确实是这样的. 与其他切图标记软件不同的是,Assistor PS 是完全独立于 PS 本身的,说是一个外挂更加合适, ...
- Sketch 和 PS中的设计图如何实现“自动切图”?
切图是很多UI设计师的一项日常工作.平时做完设计图,要将设计稿切成便于制作成页面的图片,并标注好尺寸和间距,交付给前端来完成html+css布局的静态页面,有利于交互,形成良好的视觉感. 但有的认为前 ...
- .NET手撸绘制TypeScript类图——上篇
.NET手撸绘制TypeScript类图--上篇 近年来随着交互界面的精细化,TypeScript越来越流行,前端的设计也越来复杂,而类图正是用简单的箭头和方块,反映对象与对象之间关系/依赖的好方式. ...
- .NET手撸绘制TypeScript类图——下篇
.NET手撸绘制TypeScript类图--下篇 在上篇的文章中,我们介绍了如何使用.NET解析TypeScript,这篇将介绍如何使用代码将类图渲染出来. 注:以防有人错过了,上篇链接如下:http ...
随机推荐
- 【转】 Pro Android学习笔记(六五):安全和权限(2):权限和自定义权限
目录(?)[-] 进程边界 声明和使用权限 AndroidManifestxml的许可设置 自定义权限 运行安全通过两个层面进行保护.进程层面:不同应用运行在不同的进程,每个应用有独自的user ID ...
- JAVA 1.5 并发之 BlockingQueue
1.BlockingQueue 顾名思义就是阻塞队列 最经典的使用场合就是 生产者 - 消费者 模型啦,其优点是队列控制已经处理好,用户只需要存(满了会阻塞),取(空了会阻塞) 可以更多的关心核心逻辑 ...
- 问题:webservice浏览后 无法输入参数;结果:调试Web Service时不能输入参数的解决办法
使用.NET 开发Web Service,有一个很方便的功能就是可以通过IE直接测试Web Service.当你的Web Service的参数都是元数据类型,那么只要你使用IE浏览Web Servic ...
- C语言学习笔记--单引号和双引号
(1)C 语言中单引号用来表示字符字面量(是个数值)被编译为对应的 ASCII 码 (2)C 语言中双引号用来表示字符串字面量(是个指针)被编译为对应的内存地址 例如:'a'表示字符字面量(97),在 ...
- qt数据库sql语句使用c++中的变量
void SerialWidget::on_btnMysql_clicked() { qDebug()<<QSqlDatabase::drivers()<<endl; /*列出 ...
- T-SQL操作XML 数据类型方法 "modify" 的参数 1 必须是字符串文字。
----删除关键字的同时也清理AP表中所有关联这个ID的数据 create trigger Trg_UpdateAppWordOnDelKeyWord on [dbo].[tbl_KeyWord] f ...
- 获取服务器IP,客户端IP
客户端IP相关的变量 1. $_SERVER['REMOTE_ADDR']; 客户端IP,有可能是用户的IP,也有可能是代理的IP. 2. $_SERVER['HTTP_CLIENT_IP']; 代理 ...
- SQL标量值函数:小写金额转大写
我们日常开发业务系统中,作为统计报表中,特别是财务报表,显示中文金额经常遇到. 转换大小写的方法有很多,以下是从数据库函数方面解决这一问题. 效果如图: 调用:SELECT dbo.[Fn_Conve ...
- R: which(查询位置)、%in% (是否存在)、ifelse(判断是否):
################################################### 问题:ifelse.which.%in% 18.4.27 解决方案: > x < ...
- Windows form UI skinEngine的使用方法
1.安装SkinEngine(这里安装的是3.4.7) 链接: https://pan.baidu.com/s/1-kZ5KgYclshWc17jbuke5w 提取码: bp7n 复制这段内容后打开百 ...