文章目錄
- 本篇學習目標
- 1. vue基礎
- 1.0_vue基礎 v-for更新監測
- 1.1_vue基礎_v-for就地更新
- 1.2_vue基礎_虛擬dom
- 1.3_vue基礎_diff算法
- 情況1: 根元素變了, 刪除重建
- 情況2: 根元素沒變, 屬性改變, ==元素復用==, 更新屬性
- 1.4_vue基礎_diff算法-key
- 情況3: 根元素沒變, 子元素沒變, 元素內容改變
- 無key - 就地更新
- 有key - 值為索引
- 有key - 值為id
- 1.5_階段小結
- 1.6_vue基礎 動態class
- 1.7_vue基礎-動態style
- 2. vue過濾器
- 2.0_vue過濾器-定義使用
- 2.1_vue過濾器-傳參和多過濾器
- 2.2_案例-品牌管理(時間格式化)
- 3. vue計算屬性
- 3.0_vue計算屬性-computed
- 3.1_vue計算屬性-緩存
- 3.2_案例-品牌管理(總價和均價)
- 3.3_vue計算屬性-完整寫法
- 3.4_案例-小選影響全選
- 3.5_案例-全選影響小選
- 3.6_案例-反選
- 4. vue偵聽器
- 4.0_vue偵聽器-watch
- 4.1_vue偵聽器-深度偵聽和立即執行
- 4.2_案例-品牌管理(數據緩存)
- 今日總結
- 面試題
- 1. Vue 中怎么自定義過濾器
- 2. Vue中:key作用, 為什么不能用索引
- 3. 數組更新有的時候v-for不渲染
- 寫在最后
本篇學習目標
- 能夠了解key作用, 虛擬DOM, diff算法
- 能夠掌握設置動態樣式
- 能夠掌握過濾器, 計算屬性, 偵聽器
- 能夠完成品牌管理案例
1. vue基礎
1.0_vue基礎 v-for更新監測
目標: 當v-for遍歷的目標結構改變, Vue觸發v-for的更新
情況1: 數組翻轉
情況2: 數組截取
情況3: 更新值
口訣:
數組變更方法, 就會導致v-for更新, 頁面更新
數組非變更方法, 返回新數組, 就不會導致v-for更新, 可采用覆蓋數組或this.$set()
<template><div><ul><li v-for="(val, index) in arr" :key="index">{{ val }}</li></ul><button @click="revBtn">數組翻轉</button><button @click="sliceBtn">截取前3個</button><button @click="updateBtn">更新第一個元素值</button></div>
</template><script>
export default {data(){return {arr: [5, 3, 9, 2, 1]}},methods: {revBtn(){// 1. 數組翻轉可以讓v-for更新this.arr.reverse()},sliceBtn(){// 2. 數組slice方法不會造成v-for更新// slice不會改變原始數組// this.arr.slice(0, 3)// 解決v-for更新 - 覆蓋原始數組let newArr = this.arr.slice(0, 3)this.arr = newArr},updateBtn(){// 3. 更新某個值的時候, v-for是監測不到的// this.arr[0] = 1000;// 解決-this.$set()// 參數1: 更新目標結構// 參數2: 更新位置// 參數3: 更新值this.$set(this.arr, 0, 1000)}}
}
</script><style></style>
這些方法會觸發數組改變, v-for會監測到并更新頁面
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
這些方法不會觸發v-for更新
slice()
filter()
concat()
注意: vue不能監測到數組里賦值的動作而更新, 如果需要請使用Vue.set() 或者this.$set(), 或者覆蓋整個數組
總結: 改變原數組的方法才能讓v-for更新
1.1_vue基礎_v-for就地更新
v-for
的默認行為會嘗試原地修改元素而不是移動它們。
詳解v-for就地更新流程(可以看ppt動畫)
這種 虛擬DOM對比方式, 可以提高性能 - 但是還不夠高
1.2_vue基礎_虛擬dom
目標: 了解虛擬DOM的概念
.vue文件中的template里寫的標簽, 都是模板, 都要被vue處理成虛擬DOM對象, 才會渲染顯示到真實DOM頁面上
-
內存中生成一樣的虛擬DOM結構(本質是個JS對象)
因為真實的DOM屬性好幾百個, 沒辦法快速的知道哪個屬性改變了
比如template里標簽結構
<template><div id="box"><p class="my_p">123</p></div> </template>
對應的虛擬DOM結構
const dom = {type: 'div',attributes: [{id: 'box'}],children: {type: 'p',attributes: [{class: 'my_p'}],text: '123'} }
-
以后vue數據更新
- 生成新的虛擬DOM結構
- 和舊的虛擬DOM結構對比
- 利用diff算法, 找不不同, 只更新變化的部分(重繪/回流)到頁面 - 也叫打補丁
好處1: 提高了更新DOM的性能(不用把頁面全刪除重新渲染)
好處2: 虛擬DOM只包含必要的屬性(沒有真實DOM上百個屬性)
總結: 虛擬DOM保存在內存中, 只記錄dom關鍵信息, 配合diff算法提高DOM更新的性能
在內存中比較差異, 然后給真實DOM打補丁更新上
1.3_vue基礎_diff算法
vue用diff算法, 新虛擬dom, 和舊的虛擬dom比較
情況1: 根元素變了, 刪除重建
舊虛擬DOM
<div id="box"><p class="my_p">123</p>
</div>
新虛擬DOM
<ul id="box"><li class="my_p">123</li>
</ul>
情況2: 根元素沒變, 屬性改變, 元素復用, 更新屬性
舊虛擬DOM
<div id="box"><p class="my_p">123</p>
</div>
新虛擬DOM
<div id="myBox" title="標題"><p class="my_p">123</p>
</div>
1.4_vue基礎_diff算法-key
情況3: 根元素沒變, 子元素沒變, 元素內容改變
無key - 就地更新
v-for不會移動DOM, 而是嘗試復用, 就地更新,如果需要v-for移動DOM, 你需要用特殊 attribute key
來提供一個排序提示
<ul id="myUL"><li v-for="str in arr">{{ str }} <input type="text"></li>
</ul>
<button @click="addFn">下標為1的位置新增一個</button>
export default {data(){return {arr: ["老大", "新來的", "老二", "老三"]}},methods: {addFn(){this.arr.splice(1, 0, '新來的')}}
};
舊 - 虛擬DOM結構 和 新 - 虛擬DOM結構 對比過程
性能不高, 從第二個li往后都更新了
有key - 值為索引
- 還是就地更新
因為新舊虛擬DOM對比, key存在就復用此標簽更新內容, 如果不存在就直接建立一個新的
<ul id="myUL"><li v-for="(str, index) in arr" :key="index">{{ str }} <input type="text"></li>
</ul>
<button @click="addFn">下標為1的位置新增一個</button>
export default {data(){return {arr: ["老大", "新來的", "老二", "老三"]}},methods: {addFn(){this.arr.splice(1, 0, '新來的')}}
};
key為索引-圖解過程 (又就地往后更新了)
-
v-for先循環產生新的DOM結構, key是連續的, 和數據對應
-
然后比較新舊DOM結構, 找到區別, 打補丁到頁面上
最后補一個li, 然后從第二個往后, 都要更新內容
口訣: key的值有id用id, 沒id用索引
有key - 值為id
key的值只能是唯一不重復的, 字符串或數值
v-for不會移動DOM, 而是嘗試復用, 就地更新,如果需要v-for移動DOM, 你需要用特殊 attribute key
來提供一個排序提示
新DOM里數據的key存在, 去舊的虛擬DOM結構里找到key標記的標簽, 復用標簽
新DOM里數據的key存在, 去舊的虛擬DOM結構里沒有找到key標簽的標簽, 創建
舊DOM結構的key, 在新的DOM結構里沒有了, 則移除key所在的標簽
<template><div><ul><li v-for="obj in arr" :key="obj.id">{{ obj.name }}<input type="text"></li></ul><button @click="btn">下標1位置插入新來的</button></div>
</template><script>
export default {data() {return {arr: [{name: '老大',id: 50},{name: '老二',id: 31},{name: '老三',id: 10}],};},methods: {btn(){this.arr.splice(1, 0, {id: 19, name: '新來的'})}}
};
</script><style>
</style>
圖解效果:
總結: 不用key也不影響功能(就地更新), 添加key可以提高更新的性能
1.5_階段小結
v-for什么時候會更新頁面呢?
- 數組采用更新方法, 才導致v-for更新頁面
vue是如何提高更新性能的?
- 采用虛擬DOM+diff算法提高更新性能
虛擬DOM是什么?
- 本質是保存dom關鍵信息的JS對象
diff算法如何比較新舊虛擬DOM?
- 根元素改變 – 刪除當前DOM樹重新建
- 根元素未變, 屬性改變 – 更新屬性
- 根元素未變, 子元素/內容改變
- 無key – 就地更新 / 有key – 按key比較
1.6_vue基礎 動態class
目標: 用v-bind給標簽class設置動態的值
- 語法:
- :class="{類名: 布爾值}"
<template><div><!-- 語法::class="{類名: 布爾值}"使用場景: vue變量控制標簽是否應該有類名--><p :class="{red_str: bool}">動態class</p></div>
</template><script>
export default {data(){return {bool: true}}
}
</script><style scoped>.red_str{color: red;}
</style>
總結: 就是把類名保存在vue變量中賦予給標簽
1.7_vue基礎-動態style
目標: 給標簽動態設置style的值
- 語法
- :style="{css屬性: 值}"
<template><div><!-- 動態style語法:style="{css屬性名: 值}"--><p :style="{backgroundColor: colorStr}">動態style</p></div>
</template><script>
export default {data(){return {colorStr: 'red'}}
}
</script><style></style>
總結: 動態style的key都是css屬性名
2. vue過濾器
2.0_vue過濾器-定義使用
目的: 轉換格式, 過濾器就是一個函數, 傳入值返回處理后的值
過濾器只能用在, 插值表達式和v-bind表達式
Vue中的過濾器場景
- 字母轉大寫, 輸入"hello", 輸出"HELLO"
- 字符串翻轉, “輸入hello, world”, 輸出"dlrow ,olleh"
語法:
-
Vue.filter(“過濾器名”, (值) => {return “返回處理后的值”})
-
filters: {過濾器名字: (值) => {return “返回處理后的值”}
例子:
- 全局定義字母都大寫的過濾器
- 局部定義字符串翻轉的過濾器
<template><div><p>原來的樣子: {{ msg }}</p><!-- 2. 過濾器使用語法: {{ 值 | 過濾器名字 }}--><p>使用翻轉過濾器: {{ msg | reverse }}</p><p :title="msg | toUp">鼠標長停</p></div>
</template><script>
export default {data(){return {msg: 'Hello, Vue'}},// 方式2: 局部 - 過濾器// 只能在當前vue文件內使用/*語法: filters: {過濾器名字 (val) {return 處理后的值}}*/filters: {toUp (val) {return val.toUpperCase()}}
}
</script><style></style>
總結: 把值轉成另一種形式, 使用過濾器, Vue3用函數替代了過濾器.
全局注冊最好在main.js中注冊, 一處注冊到處使用
2.1_vue過濾器-傳參和多過濾器
目標: 可同時使用多個過濾器, 或者給過濾器傳參
- 語法:
- 過濾器傳參: vue變量 | 過濾器(實參)
- 多個過濾器: vue變量 | 過濾器1 | 過濾器2
<template><div><p>原來的樣子: {{ msg }}</p><!-- 1.給過濾器傳值語法: vue變量 | 過濾器名(值)--><p>使用翻轉過濾器: {{ msg | reverse('|') }}</p><!-- 2.多個過濾利使用語法: vue變量 | 過濾器1 | 過濾器2--><p :title="msg | toUp | reverse('|')">鼠標長停</p></div>
</template><script>
export default {data(){return {msg: 'Hello, Vue'}},filters: {toUp (val) {return val.toUpperCase()}}
}
</script><style></style>
總結: 過濾器可以傳參, 還可以對某個過濾器結果, 后面在使用一個過濾器
2.2_案例-品牌管理(時間格式化)
目標: 復制上個案例, 在此基礎上, 把表格里的時間用過濾器+moment模塊, 格式化成YYYY-MM-DD 格式
圖示:
-
下載moment處理日期的第三方工具模塊
moment官網文檔: http://momentjs.cn/docs/#/displaying/
yarn add moment
-
定義過濾器, 把時間用moment模塊格式化, 返回我們想要的格式
// 目標: 處理時間 // 1. 下載moment模塊 import moment from 'moment'// 2. 定義過濾器, 編寫內部代碼 filters: { formatDate (val){return moment(val).format('YYYY-MM-DD')} }<!-- 3. 使用過濾器 --> <td>{{ obj.time | formatDate }}</td>
3. vue計算屬性
3.0_vue計算屬性-computed
目標: 一個數據, 依賴另外一些數據計算而來的結果
語法:
-
computed: {"計算屬性名" () {return "值"} }
需求:
- 需求: 求2個數的和顯示到頁面上
<template><div><p>{{ num }}</p></div>
</template><script>
export default {data(){return {a: 10,b: 20}},// 計算屬性:// 場景: 一個變量的值, 需要用另外變量計算而得來/*語法:computed: {計算屬性名 () {return 值}}*/// 注意: 計算屬性和data屬性都是變量-不能重名// 注意2: 函數內變量變化, 會自動重新計算結果返回computed: {num(){return this.a + this.b}}
}
</script><style></style>
注意: 計算屬性也是vue數據變量, 所以不要和data里重名, 用法和data相同
總結: 一個數據, 依賴另外一些數據計算而來的結果
3.1_vue計算屬性-緩存
目標: 計算屬性是基于它們的依賴項的值結果進行緩存的,只要依賴的變量不變, 都直接從緩存取結果
<template><div><p>{{ reverseMessage }}</p><p>{{ reverseMessage }}</p><p>{{ reverseMessage }}</p><p>{{ getMessage() }}</p><p>{{ getMessage() }}</p><p>{{ getMessage() }}</p></div>
</template><script>
export default {data(){return {msg: "Hello, Vue"}},// 計算屬性優勢:// 帶緩存// 計算屬性對應函數執行后, 會把return值緩存起來// 依賴項不變, 多次調用都是從緩存取值// 依賴項值-變化, 函數會"自動"重新執行-并緩存新的值computed: {reverseMessage(){console.log("計算屬性執行了");return this.msg.split("").reverse().join("")}},methods: {getMessage(){console.log("函數執行了");return this.msg.split("").reverse().join("")}}
}
</script><style></style>
總結: 計算屬性根據依賴變量結果緩存, 依賴變化重新計算結果存入緩存, 比普通方法性能更高
3.2_案例-品牌管理(總價和均價)
目標: 基于之前的案例, 完成總價和均價的計算效果
此處只修改了變化的代碼
<tr style="background-color: #EEE"><td>統計:</td><td colspan="2">總價錢為: {{ allPrice }}</td><td colspan="2">平均價: {{ svgPrice }}</td>
</tr><script>
// 目標: 總價和均價顯示
// 1. 末尾補tr - 顯示總價和均價
export default {// ...源代碼省略// 2. 計算屬性computed: {allPrice(){// 3. 求總價return this.list.reduce((sum, obj) => sum += obj.price, 0)},avgPrice(){// 4. 求均價 - 保留2位小數return (this.allPrice / this.list.length).toFixed(2)}}
}
</script>
總結: 總價來源于所有數據計算而來的結果, 故采用計算屬性
3.3_vue計算屬性-完整寫法
目標: 計算屬性也是變量, 如果想要直接賦值, 需要使用完整寫法
語法:
computed: {"屬性名": {set(值){},get() {return "值"}}
}
需求:
- 計算屬性給v-model使用
頁面準備輸入框
<template><div><div><span>姓名:</span><input type="text" v-model="full"></div></div>
</template><script>
// 問題: 給計算屬性賦值 - 需要setter
// 解決:
/*完整語法:computed: {"計算屬性名" (){},"計算屬性名": {set(值){},get(){return 值}}}
*/
export default {computed: {full: {// 給full賦值觸發set方法set(val){console.log(val)},// 使用full的值觸發get方法get(){return "無名氏"}}}
}
</script><style></style>
總結: 想要給計算屬性賦值, 需要使用set方法
3.4_案例-小選影響全選
目標: 小選框都選中(手選), 全選自動選中
- 需求: 小選框都選中(手選), 全選自動選中
分析:
① 先靜態后動態, 從.md拿到靜態標簽和數據
② 循環生成復選框和文字, 對象的c屬性和小選框的選中狀態, 用v-model雙向綁定
③ 定義isAll計算屬性, 值通過小選框們統計c屬性狀態得來
圖示:
模板標簽和數據(直接復制在這基礎上寫vue代碼)
<template><div><span>全選:</span><input type="checkbox"/><button>反選</button><ul><li><input type="checkbox"/><span>任務名</span></li></ul></div>
</template><script>
export default {data() {return {arr: [{name: "豬八戒",c: false,},{name: "孫悟空",c: false,},{name: "唐僧",c: false,},{name: "白龍馬",c: false,},],};}
};
</script>
正確代碼,不可復制
<template><div><span>全選:</span><!-- 4. v-model 關聯全選 - 選中狀態 --><input type="checkbox" v-model="isAll"/><button>反選</button><ul><li v-for="(obj, index) in arr" :key="index"><!-- 3. 對象.c - 關聯 選中狀態 --><input type="checkbox" v-model="obj.c"/><span>{{ obj.name }}</span></li></ul></div>
</template><script>
// 目標: 小選框 -> 全選
// 1. 標簽+樣式+js準備好
// 2. 把數據循環展示到頁面上
export default {data() {return {arr: [{name: "豬八戒",c: false,},{name: "孫悟空",c: false,},{name: "唐僧",c: false,},{name: "白龍馬",c: false,},],};},// 5. 計算屬性-isAllcomputed: {isAll () {// 6. 統計小選框狀態 -> 全選狀態// every口訣: 查找數組里"不符合"條件, 直接原地返回falsereturn this.arr.every(obj => obj.c === true)}}
};
</script>
3.5_案例-全選影響小選
目標: 全選影響小選
- 需求1: 獲取到全選狀態 – 改裝isAll計算屬性
- 需求2: 全選狀態同步給所有小選框
分析:
①: isAll改成完整寫法, set里獲取到全選框, 勾選的狀態值
②: 遍歷數據數組, 賦給所有小選框v-model關聯的屬性
圖示:
正確代碼,不可以復制
<script>
export default {// ...其他代碼// 5. 計算屬性-isAllcomputed: {isAll: {set(val){// 7. 全選框 - 選中狀態(true/false)this.arr.forEach(obj => obj.c = val)},get(){// 6. 統計小選框狀態 -> 全選狀態// every口訣: 查找數組里"不符合"條件, 直接原地返回falsereturn this.arr.every(obj => obj.c === true)}}}
};
</script>
3.6_案例-反選
目標: 反選功能
- 需求: 點擊反選, 讓所有小選框, 各自取相反勾選狀態
分析:
①: 小選框的勾選狀態, 在對象的c屬性
②: 遍歷所有對象, 把對象的c屬性取相反值賦予回去即可
圖示:
正確代碼,不可以復制
<button @click="btn">反選</button><script>
export default {// ...其他代碼省略methods: {btn(){// 8. 讓數組里對象的c屬性取反再賦予回去this.arr.forEach(obj => obj.c = !obj.c)}}
};
</script>
4. vue偵聽器
4.0_vue偵聽器-watch
目標: 可以偵聽data/computed屬性值改變
語法:
-
watch: {"被偵聽的屬性名" (newVal, oldVal){} }
完整例子代碼:
<template><div><input type="text" v-model="name"></div>
</template><script>
export default {data(){return {name: ""}},// 目標: 偵聽到name值的改變/*語法:watch: {變量名 (newVal, oldVal){// 變量名對應值改變這里自動觸發}}*/watch: {// newVal: 當前最新值// oldVal: 上一刻值name(newVal, oldVal){console.log(newVal, oldVal);}}
}
</script><style></style>
總結: 想要偵聽一個屬性變化, 可使用偵聽屬性watch
4.1_vue偵聽器-深度偵聽和立即執行
目標: 偵聽復雜類型, 或者立即執行偵聽函數
-
語法:
watch: {"要偵聽的屬性名": {immediate: true, // 立即執行deep: true, // 深度偵聽復雜類型內變化handler (newVal, oldVal) {}} }
完整例子代碼:
<template><div><input type="text" v-model="user.name"><input type="text" v-model="user.age"></div>
</template><script>
export default {data(){return {user: {name: "",age: 0}}},// 目標: 偵聽對象/*語法:watch: {變量名 (newVal, oldVal){// 變量名對應值改變這里自動觸發},變量名: {handler(newVal, oldVal){},deep: true, // 深度偵聽(對象里面層的值改變)immediate: true // 立即偵聽(網頁打開handler執行一次)}}*/watch: {user: {handler(newVal, oldVal){// user里的對象console.log(newVal, oldVal);},deep: true,immediate: true}}
}
</script><style></style>
總結: immediate立即偵聽, deep深度偵聽, handler固定方法觸發
4.2_案例-品牌管理(數據緩存)
目標: 偵聽list變化, 同步到瀏覽器本地
- 需求: 把品牌管理的數據實時同步到本地緩存
分析:
? ① 在watch偵聽list變化的時候, 把最新的數組list轉成JSON字符串存入到localStorage本地
? ② data里默認把list變量從本地取值, 如果取不到給個默認的空數組
效果:
? 新增/刪除 – 刷新頁面 – 數據還在
在之前的案例代碼基礎上接著寫
正確代碼,不可復制
<script>
import moment from "moment";
export default {data() {return {name: "", // 名稱price: 0, // 價格// 3. 本地取出緩存listlist: JSON.parse(localStorage.getItem('pList')) || [],};},// ...其他代碼省略watch: {list: {handler(){// 2. 存入本地localStorage.setItem('pList', JSON.stringify(this.list))},deep: true}}
};
</script>
今日總結
- v-for能監測到哪些數組方法變化, 更新頁面
- key的作用是什么
- 虛擬dom好處, diff算法效果
- 動態設置class或style
- vue過濾器作用和分類
- vue計算屬性作用
- vue偵聽器的作用
面試題
1. Vue 中怎么自定義過濾器
? Vue.js允許自定義過濾器,可被用于一些常見的文本格式化。過濾器可以用在兩個地方:雙花括號插值和v-bind表達式
? 全局的用Vue.filter()
? 局部的用filters屬性
2. Vue中:key作用, 為什么不能用索引
? :key是給v-for循環生成標簽頒發唯一標識的, 用于性能的優化
? 因為v-for數據項的順序改變,Vue 也不會移動 DOM 元素來匹配數據項的順序,而是就地更新每個元素
? :key如果是索引, 因為索引是連續的, 如果刪除其中某一個, 會導致最后一個被刪除
? 當我們再刪除的時候, :key再根據數據來把新舊的dom對比時, 刪除:key不存在的對應的標簽(添加也是一樣的插入到指定位置, 別的都不會動)
3. 數組更新有的時候v-for不渲染
? 因為vue內部只能監測到數組順序/位置的改變/數量的改變, 但是值被重新賦予監測不到變更, 可以用 Vue.set() / vm.$set()
寫在最后
?原創不易,還希望各位大佬支持一下\textcolor{blue}{原創不易,還希望各位大佬支持一下}原創不易,還希望各位大佬支持一下
👍 點贊,你的認可是我創作的動力!\textcolor{green}{點贊,你的認可是我創作的動力!}點贊,你的認可是我創作的動力!
?? 收藏,你的青睞是我努力的方向!\textcolor{green}{收藏,你的青睞是我努力的方向!}收藏,你的青睞是我努力的方向!
?? 評論,你的意見是我進步的財富!\textcolor{green}{評論,你的意見是我進步的財富!}評論,你的意見是我進步的財富!