>[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 -- 案例
- 待續