>[success] # Vue 响应式
1. 下面正常的js逻辑代码是否能像`vue`一样,当`price `发生改变期待着`total `也可以重新计算,但实际结果却是打印出来的结果为`10 `而不是`12`
2. 因此可以看出`JavaScript`是程序性的,**不是反应性**的,为了使total反应性,我们必须使用`JavaScript`使事物表现不同
~~~
let price = 5
let quantity = 2
let total = price * quantity
price++
console.log(`total is ${total}`) // 10
~~~
>[danger] ##### 让数据具备响应收集
1. 想让代码开始变成**反应性**,据需要在想让触发的节点重新运行指定逻辑代码,我们先收集在触发
2. 我们将收集和触发函数命名为`watchFn`,在指定位置触发
~~~
const reactiveFns = []
function watchFn(fn) {
reactiveFns.push(fn) // 收集后进行触发
fn()
}
// 统一触发所有收集
function replay() {
reactiveFns.forEach((run) => run())
}
let price = 5
let quantity = 2
let total = 0
watchFn(() => {
total = price * quantity
})
price++
// 每次执行完都要触发一下
replay()
console.log(total) // 可以得到触发后值
~~~
>[danger] ##### 收集更多数据怎么办
1. 在实际开发中,会有更多数据需要被收集触发,如果只是单纯的使用数组收集差生问题,下面案例产生问题**触发将不希望改变的name 也被触发**
~~~
const reactiveFns = []
function watchFn(fn) {
reactiveFns.push(fn) // 收集后进行触发
fn()
}
// 统一触发所有收集
function replay() {
reactiveFns.forEach((run) => run())
}
let name = 'w'
// 收集名字变化
watchFn(() => {
name += '1'
})
let price = 5
let quantity = 2
let total = 0
watchFn(() => {
total = price * quantity
})
price++
// 每次执行完都要触发一下,这次触发将不希望改变的name 也被触发
replay()
console.log(total) // 可以得到触发后值
~~~
2. 为每一个对象创建一个属于自己的收集对象这样,就不会相互忽然,创建一个 `Depend` 类,用来收集各自的响应触发
3. 让数据可以更加自由的触发对象的变化可以使用两种方案`Object.defineProperty`或`new Proxy`,代码思路是我们将每个需要响应式的数据定义属于自己的收集,触发时候触发属于自己的收集响应的方法
~~~
// 属于每个对象自己的收集
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(fn) {
if (fn) {
this.reactiveFns.push(fn)
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
// 设置一个专门执行响应式函数的一个函数
function watchFn(fn, dep) {
dep.addDepend(fn)
fn()
}
const info = { name: 'w', age: 18 }
const bookInfo = { name: '语文', price: 125 }
const InfoDep = new Depend()
const bookDep = new Depend()
// 使用Object.defineProperty 收集
function proxy(obj, dep) {
Object.keys(obj).forEach((key) => {
let value = obj[key]
Object.defineProperty(obj, key, {
set: function (newValue) {
value = newValue
// 进行设置值的时候进行触发收集
dep.notify()
},
get: function () {
return value
},
})
})
}
watchFn(() => {
console.log(`info 信息收集`)
}, InfoDep)
proxy(info, InfoDep)
watchFn(() => {
console.log(`bookInfo 信息收集`)
}, bookDep)
proxy(bookInfo, bookDep)
console.log('---------------')
info.age++
bookInfo.price++
~~~
>[danger] ##### 自定义收集依赖
1. 对象会有多个属性,不同属性可能需要的是不同的收集响应,因此需要一个新的收集数据解构 `{对象:{属性:Depend},对象:{属性:Depend]}}`对象能做为`key`需要使用`WeakMap`
2. `getDepend` 作为一个自动收集和使用响应的方法,主要是将数据可以存储在`WeakMap` 解构,并将每一个属性`key `都可以对应 属于自己`Depend`
3. `Object.defineProperty` 进行收集当触发`get` 时候进行收集,`set` 时候进行触发
4. `watchFn` 用来触发第一次响应,和给响应事件赋值,这里将`target ` 作为全局变量,当触发`watchFn` 收集的时候必须要触发一次收集对象对应属性的` get`,此时触发对应属性`get`后并且将对应属性响应方法收集(因为是全局定义所以方便收集),触发后`watchFn` 再将`target `置为`null` 等待下一次触发
~~~
// 属于每个对象自己的收集
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(fn) {
if (fn) {
this.reactiveFns.push(fn)
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
// 收集响应式数据解构
const objMap = new WeakMap()
// 定义一个收集方法
function getDepend(obj, key) {
// 判断响应式中是否收集了 对象所对应key
let map = objMap.get(obj)
if (!map) {
// 不存在 就创建一个{对象: Map}
map = new Map()
objMap.set(obj, map)
}
// 判断对象key 是否已经创建响应收集
const getDep = map.get(key)
if (!getDep) {
const dep = new Depend()
// 收集key依赖 和dep
map.set(key, dep)
}
// 将对应key 收集的依赖返回
return map.get(key)
}
let target = null
// 设置一个专门执行响应式函数的一个函数
function watchFn(fn) {
target = fn
fn()
target = null
}
function proxy(obj) {
Object.keys(obj).forEach((key) => {
let value = obj[key]
Object.defineProperty(obj, key, {
get() {
// 触发get 时候要收集响应依赖
const dep = getDepend(obj, key)
// 将依赖收集
dep.addDepend(target)
return value
},
set(nvalue) {
// 触发依赖
value = nvalue
const dep = getDepend(obj, key)
dep.notify()
},
})
})
}
const info = { name: 'w', age: 18 }
proxy(info)
watchFn(function foo() {
console.log('foo function')
console.log('foo:', info.name)
console.log('foo', info.age)
})
watchFn(function bar() {
console.log('bar function')
console.log('bar:', info.age + 10)
})
console.log('age发生变化-----------------------')
info.age = 20
~~~
* 如果我们将`proxy` 定义的名字改为`reactive`,将里面的`Object.defineProperty` 换成 `new Proxy` 就是vue3的一个模型
~~~
// 封装一个函数: 负责通过obj的key获取对应的Depend对象
const objMap = new WeakMap()
function getDepend(obj, key) {
// 1.根据对象obj, 找到对应的map对象
let map = objMap.get(obj)
if (!map) {
map = new Map()
objMap.set(obj, map)
}
// 2.根据key, 找到对应的depend对象
let dep = map.get(key)
if (!dep) {
dep = new Depend()
map.set(key, dep)
}
return dep
}
function reactive(obj) {
const objProxy = new Proxy(obj, {
set: function(target, key, newValue, receiver) {
// target[key] = newValue
Reflect.set(target, key, newValue, receiver)
const dep = getDepend(target, key)
dep.notify()
},
get: function(target, key, receiver) {
const dep = getDepend(target, key)
dep.depend()
return Reflect.get(target, key, receiver)
}
})
return objProxy
}
// ========================= 业务代码 ========================
const obj = reactive({
age: 18,
address: "广州市"
})
watchFn(function() {
console.log(obj.name)
console.log(obj.age)
console.log(obj.age)
})
// 修改name
console.log("--------------")
obj.age = 20
console.log("=============== user =================")
const user = reactive({
nickname: "abc",
level: 100
})
watchFn(function() {
console.log("nickname:", user.nickname)
console.log("level:", user.level)
})
user.nickname = "cba"
~~~
- 工程化 -- Node
- vscode -- 插件
- vscode -- 代码片段
- 前端学会调试
- 谷歌浏览器调试技巧
- 权限验证
- 包管理工具 -- npm
- 常见的 npm ci 指令
- npm -- npm install安装包
- npm -- package.json
- npm -- 查看包版本信息
- npm - package-lock.json
- npm -- node_modules 层级
- npm -- 依赖包规则
- npm -- install 安装流程
- npx
- npm -- 发布自己的包
- 包管理工具 -- pnpm
- 模拟数据 -- Mock
- 页面渲染
- 渲染分析
- core.js && babel
- core.js -- 到底是什么
- 编译器那些术语
- 词法解析 -- tokenize
- 语法解析 -- ast
- 遍历节点 -- traverser
- 转换阶段、生成阶段略
- babel
- babel -- 初步上手之了解
- babel -- 初步上手之各种配置(preset-env)
- babel -- 初步上手之各种配置@babel/helpers
- babel -- 初步上手之各种配置@babel/runtime
- babel -- 初步上手之各种配置@babel/plugin-transform-runtime
- babel -- 初步上手之各种配置(babel-polyfills )(未来)
- babel -- 初步上手之各种配置 polyfill-service
- babel -- 初步上手之各种配置(@babel/polyfill )(过去式)
- babel -- 总结
- 各种工具
- 前端 -- 工程化
- 了解 -- Yeoman
- 使用 -- Yeoman
- 了解 -- Plop
- node cli -- 开发自己的脚手架工具
- 自动化构建工具
- Gulp
- 模块化打包工具为什么出现
- 模块化打包工具(新) -- webpack
- 简单使用 -- webpack
- 了解配置 -- webpack.config.js
- webpack -- loader 浅解
- loader -- 配置css模块解析
- loader -- 图片和字体(4.x)
- loader -- 图片和字体(5.x)
- loader -- 图片优化loader
- loader -- 配置解析js/ts
- webpack -- plugins 浅解
- eslit
- plugins -- CleanWebpackPlugin(4.x)
- plugins -- CleanWebpackPlugin(5.x)
- plugin -- HtmlWebpackPlugin
- plugin -- DefinePlugin 注入全局成员
- webapck -- 模块解析配置
- webpack -- 文件指纹了解
- webpack -- 开发环境运行构建
- webpack -- 项目环境划分
- 模块化打包工具 -- webpack
- webpack -- 打包文件是个啥
- webpack -- 基础配置项用法
- webpack4.x系列学习
- webpack -- 常见loader加载器
- webpack -- 移动端px转rem处理
- 开发一个自己loader
- webpack -- plugin插件
- webpack -- 文件指纹
- webpack -- 压缩css和html构建
- webpack -- 清里构建包
- webpack -- 复制静态文件
- webpack -- 自定义插件
- wepack -- 关于静态资源内联
- webpack -- source map 对照包
- webpack -- 环境划分构建
- webpack -- 项目构建控制台输出
- webpack -- 项目分析
- webpack -- 编译提速优护体积
- 提速 -- 编译阶段
- webpack -- 项目优化
- webpack -- DefinePlugin 注入全局成员
- webpack -- 代码分割
- webpack -- 页面资源提取
- webpack -- import按需引入
- webpack -- 摇树
- webpack -- 多页面打包
- webpack -- eslint
- webpack -- srr打包后续看
- webpack -- 构建一个自己的配置后续看
- webpack -- 打包组件和基础库
- webpack -- 源码
- webpack -- 启动都做了什么
- webpack -- cli做了什么
- webpack - 5
- 模块化打包工具 -- Rollup
- 工程化搭建代码规范
- 规范化标准--Eslint
- eslint -- 扩展配置
- eslint -- 指令
- eslint -- vscode
- eslint -- 原理
- Prettier -- 格式化代码工具
- EditorConfig -- 编辑器编码风格
- 检查提交代码是否符合检查配置
- 整体流程总结
- 微前端
- single-spa
- 简单上手 -- single-spa
- 快速理解systemjs
- single-sap 不使用systemjs
- monorepo -- 工程
- Vue -- 响应式了解
- Vue2.x -- 源码分析
- 发布订阅和观察者模式
- 简单 -- 了解响应式模型(一)
- 简单 -- 了解响应式模型(二)
- 简单 --了解虚拟DOM(一)
- 简单 --了解虚拟DOM(二)
- 简单 --了解diff算法
- 简单 --了解nextick
- Snabbdom -- 理解虚拟dom和diff算法
- Snabbdom -- h函数
- Snabbdom - Vnode 函数
- Snabbdom -- init 函数
- Snabbdom -- patch 函数
- 手写 -- 虚拟dom渲染
- Vue -- minVue
- vue3.x -- 源码分析
- 分析 -- reactivity
- 好文
- grpc -- 浏览器使用gRPC
- grcp-web -- 案例
- 待续