日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

(四)Vue原理

發布時間:2023/12/31 vue 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (四)Vue原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Vue原理

  • Vue原理(大廠必考)
      • 面試為何會考察原理
      • 面試中如何考察?以何種方式
      • Vue原理包括哪些?
  • 如何理解MVVM
      • 組件化基礎
        • “很久以前”就有組件化
        • 數據驅動視圖(MVVM,setState)
      • Vue MVVM
      • 總結
  • 監聽data變化的核心API是什么
      • Vue響應式
      • Proxy有兼容性問題
      • Object.defineProperty基本用法
      • Object.defineProperty實現響應式
  • 如何深度監聽data變化、數組變化
      • Object.defineProperty缺點
      • 總結
  • 虛擬DOM-面試里的網紅
      • 虛擬DOM(Virtual DOM)和diff
      • 解決方案-vdom
      • 通過snabbdom學習vdom
  • 用過虛擬DOM嗎?
      • snabbdom重點總結
      • vdom總結
  • 虛擬DOM-diff算法概述
      • 樹diff的時間復雜度O(n^3)
      • 優化時間復雜度到O(n)
  • 深入diff算法源碼
      • 生成vnode
      • patch函數
      • patchVnode函數(vnode對比)
      • updateChildren函數
  • 虛擬DOM-考點總結和復習
      • diff算法總結
      • vdom和diff-總結
  • 模板編譯前置知識點-with語法
      • 模板編譯
      • with語法
  • vue模板被編譯成什么?
      • 編譯模板
      • vue組件中使用render代替template
      • 總結
  • 回顧和復習已學的知識點
      • 總結組件 渲染/更新過程
      • 回顧學過的知識
      • 組件 渲染/更新過程
  • vue組件是如何渲染和更新的
      • 初次渲染過程
      • 執行render函數會觸發getter
      • 更新過程
      • 完成流程圖
      • 異步渲染
      • 總結1
      • 總結2
  • 如何用JS實現hash路由
      • 前端路由原理
      • hash的特點
  • 如何用JS實現H5 history路由
      • H5 history
      • 正常頁面瀏覽
      • 改造成H5 history模式
      • 總結
      • 兩者選擇
  • vue原理-考點總結和復習
      • 組件化
      • 響應式
      • vdom和diff
      • 模板編譯
      • 渲染過程
      • 前端路由

Vue原理(大廠必考)

面試為何會考察原理

面試中如何考察?以何種方式

考察重點,而不是考察細節,掌握好2/8原則
和使用相關聯的原理,例如vdom、模板渲染
整體流程是否全面?熱門技術是否有深度?

Vue原理包括哪些?

組件化
響應式
vdom和diff
模板編譯
渲染過程
前端路由

如何理解MVVM

組件化基礎

“很久以前”就有組件化

asp jsp php已經有組件化了
nodejs中也有類似的組件化

數據驅動視圖(MVVM,setState)

傳統組件,只是靜態渲染,更新還要依賴于操作DOM
數據驅動視圖–Vue MVVM
數據驅動視圖–React setState(暫時先不看)

Vue MVVM


總結

組件化
數據驅動視圖
MVVM

監聽data變化的核心API是什么

Vue響應式

組件data的數據一旦變化,立刻觸發視圖的更新
實現數據驅動視圖的第一步
考察Vue原理的第一題
核心API-Object.defineProperty
如何實現響應式,代碼演示
Object.defineProperty的一些缺點(Vue3.0啟用Proxy)

Proxy有兼容性問題

Proxy兼容性不好,且無法polyfill
Vue2.x還會存在一段時間,所以都得學
Vue3.0相關知識,下一章將,這里只是先提一下

Object.defineProperty基本用法

Object.defineProperty實現響應式

監聽對象,監聽數組
復雜對象,深度監聽
幾個缺點

如何深度監聽data變化、數組變化

Object.defineProperty缺點

深度監聽,需要遞歸到底,一次性計算量大
無法監聽新增屬性/刪除屬性(Vue.set Vue.delete)
無法原生監聽數組,需要特殊處理

// 觸發更新視圖 function updateView() {console.log('視圖更新') }// 重新定義數組原型 const oldArrayProperty = Array.prototype // 創建新對象,原型指向 oldArrayProperty ,再擴展新的方法不會影響原型 const arrProto = Object.create(oldArrayProperty); ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {arrProto[methodName] = function () {updateView() // 觸發視圖更新oldArrayProperty[methodName].call(this, ...arguments)// Array.prototype.push.call(this, ...arguments)} })// 重新定義屬性,監聽起來 function defineReactive(target, key, value) {// 深度監聽observer(value)// 核心 APIObject.defineProperty(target, key, {get() {return value},set(newValue) {if (newValue !== value) {// 深度監聽observer(newValue)// 設置新值// 注意,value 一直在閉包中,此處設置完之后,再 get 時也是會獲取最新的值value = newValue// 觸發更新視圖updateView()}}}) }// 監聽對象屬性 function observer(target) {if (typeof target !== 'object' || target === null) {// 不是對象或數組return target}// 污染全局的 Array 原型// Array.prototype.push = function () {// updateView()// ...// }if (Array.isArray(target)) {target.__proto__ = arrProto}// 重新定義各個屬性(for in 也可以遍歷數組)for (let key in target) {defineReactive(target, key, target[key])} }// 準備數據 const data = {name: 'zhangsan',age: 20,info: {address: '北京' // 需要深度監聽},nums: [10, 20, 30] }// 監聽數據 observer(data)// 測試 // data.name = 'lisi' // data.age = 21 // // console.log('age', data.age) // data.x = '100' // 新增屬性,監聽不到 —— 所以有 Vue.set // delete data.name // 刪除屬性,監聽不到 —— 所有已 Vue.delete // data.info.address = '上海' // 深度監聽 data.nums.push(4) // 監聽數組

總結

基礎API-Object.defineProperty
如何監聽對象(深度監聽),監聽數組
Object.defineProperty的缺點

虛擬DOM-面試里的網紅

虛擬DOM(Virtual DOM)和diff

vdom是實現vue和React的重要基石
diff算法是vdom中最核心、最關鍵的部分
vdom是一個熱門話題,也是面試中的熱門話題
DOM操作非常耗費性能
以前用jQuery,可以自行控制DOM操作的時機,手動調整
Vue和React是數據驅動視圖,如何有效控制DOM操作?

解決方案-vdom

有了一定復雜度,想減少計算次數比較難
能不能把計算,更多的轉移為JS計算?因為JS執行速度很快
vdom-用JS模擬DOM結構,計算出最小的變更,操作DOM

通過snabbdom學習vdom

簡潔強大的vdom庫,易學易用
Vue參考它實現的vdom和diff
https://github.com/snabbdom/snabbdom
Vue3.0重寫了vdom的代碼,優化了性能
但vdom的基本理念不變,面試考點也不變
React vdom具體實現和Vue也不同,但不妨礙統一學習

用過虛擬DOM嗎?

snabbdom重點總結

h函數
vnode數據結構
patch函數

<!DOCTYPE html> <html> <head><meta charset="UTF-8"><title>Document</title> </head> <body><div id="container"></div><button id="btn-change">change</button><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script><script src="./demo1.js"></script> </body> </html>const snabbdom = window.snabbdom// 定義 patch const patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners ])// 定義 h const h = snabbdom.hconst container = document.getElementById('container')// 生成 vnode const vnode = h('ul#list', {}, [h('li.item', {}, 'Item 1'),h('li.item', {}, 'Item 2') ]) patch(container, vnode)document.getElementById('btn-change').addEventListener('click', () => {// 生成 newVnodeconst newVnode = h('ul#list', {}, [h('li.item', {}, 'Item 1'),h('li.item', {}, 'Item B'),h('li.item', {}, 'Item 3')])patch(vnode, newVnode) }) //table-without-vdom.html <!DOCTYPE html> <html> <head><meta charset="UTF-8"><title>Document</title> </head> <body><div id="container"></div><button id="btn-change">change</button><script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script><script type="text/javascript">const data = [{name: '張三',age: '20',address: '北京'},{name: '李四',age: '21',address: '上海'},{name: '王五',age: '22',address: '廣州'}]// 渲染函數function render(data) {const $container = $('#container')// 清空容器,重要!!!$container.html('')// 拼接 tableconst $table = $('<table>')$table.append($('<tr><td>name</td><td>age</td><td>address</td>/tr>'))data.forEach(item => {$table.append($('<tr><td>' + item.name + '</td><td>' + item.age + '</td><td>' + item.address + '</td>/tr>'))})// 渲染到頁面$container.append($table)}$('#btn-change').click(() => {data[1].age = 30data[2].address = '深圳'// re-render 再次渲染render(data)})// 頁面加載完立刻執行(初次渲染)render(data)</script> </body> </html> //table-with-vdom.html <!DOCTYPE html> <html> <head><meta charset="UTF-8"><title>Document</title> </head> <body><div id="container"></div><button id="btn-change">change</button><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script><script type="text/javascript">const snabbdom = window.snabbdom// 定義關鍵函數 patchconst patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners])// 定義關鍵函數 hconst h = snabbdom.h// 原始數據const data = [{name: '張三',age: '20',address: '北京'},{name: '李四',age: '21',address: '上海'},{name: '王五',age: '22',address: '廣州'}]// 把表頭也放在 data 中data.unshift({name: '姓名',age: '年齡',address: '地址'})const container = document.getElementById('container')// 渲染函數let vnodefunction render(data) {const newVnode = h('table', {}, data.map(item => {const tds = []for (let i in item) {if (item.hasOwnProperty(i)) {tds.push(h('td', {}, item[i] + ''))}}return h('tr', {}, tds)}))if (vnode) {// re-renderpatch(vnode, newVnode)} else {// 初次渲染patch(container, newVnode)}// 存儲當前的 vnode 結果vnode = newVnode}// 初次渲染render(data)const btnChange = document.getElementById('btn-change')btnChange.addEventListener('click', () => {data[1].age = 30data[2].address = '深圳'// re-renderrender(data)})</script> </body> </html>

vdom總結

用JS模擬DOM結構(vnode)
新舊vnode對比,得出最小的更新范圍,最后更新DOM
數據驅動視圖的模式下,有效控制DOM操作

虛擬DOM-diff算法概述

diff算法是vdom中最核心、最關鍵的部分
diff算法能在日常使用vue React中體現出來(如key)
diff算法是前端熱門話題,面試“寵兒”
diff即對比,是一個廣泛的感念,如linux diff命令、git diff等
兩個js對象也可以做diff,如https://github.com/cujojs/jiff
兩棵樹做diff,如這里的vdom diff

樹diff的時間復雜度O(n^3)

第一,遍歷tree1;第二,遍歷tree2
第三,排序
1000個節點,要計算1億次,算法不可用

優化時間復雜度到O(n)

只比較同一層級,不跨級比較
tag不相同,則直接刪掉重建,不再深度比較
tag和key,兩者都相同,則認為是相同節點,不再深度比較

深入diff算法源碼

生成vnode

h函數

patch函數

執行pre hook
第一個參數不是vnode-創建一個空的vnode(emptyNodeAt),關聯到這個DOM元素
判斷vnode是否相同(sameVnode)-key和sel都相等
相同執行patchVnode
不相同,直接刪掉重建

patchVnode函數(vnode對比)

  • 執行prepatch hook(生命周期的鉤子)

  • 設置vnode.elem

  • vnode.text===undefined(vnode.children一般有值)

    新舊都有children,updateChildren;新children有,舊children無(舊text有),清空text,添加children(addVnodes);舊children有,新children無,移除children(removeVnodes);舊text有,清空
  • vnode.text!==undefined(vnode.children一般無值)

    新舊text不一樣,移除舊children,設置新text

updateChildren函數

  • 開始和開始對比
    patchVnode()
    累加累減
  • 結束和結束對比
    patchVnode()
    累加累減
  • 開始和結束對比
    patchVnode()
    累加累減
  • 結束和開始對比
    patchVnode()
    累加累減
  • 以上四個都未命中
    拿新節點key,能否對應上oldCh中的某個節點的key
    沒對應上,New element
    對應上,拿到對應上key的節點,判斷sel是否相等,不相等New element,相等patchVnode()



不使用key全部刪掉然后插入,使用key直接移動過來,不用做銷毀然后重新渲染的過程

虛擬DOM-考點總結和復習

diff算法總結

patchVnode
addVnodes removeVnodes
updateChildren(key的重要性)

vdom和diff-總結

細節不重要,updateChildren的過程也不重要,不要深究
vdom核心概念很重要:h、vnode、patch、diff、key等
vdom存在的價值更加重要:數據驅動視圖,控制DOM操作

模板編譯前置知識點-with語法

模板編譯

模板是vue開發中最常用的部分,即與使用相關聯的原理
它不是html,有指令、插值、JS表達式,到底是什么
面試不會直接問,但會通過“組件渲染和更新過程”考察
前置知識:JS的with語法
vue template complier將模板編譯為render函數
執行render函數生成vnode

with語法

改變{}內自由變量的查找規則,當做obj屬性來查找
如果找不到匹配的obj屬性,就會報錯
with要慎用,它打破了作用域規則,易讀性變差

const obj = {a:100,b:200} console.log(obj.a) console.log(obj.b) console.log(obj.c) //undefined//使用with,能改變{}內自由變量的查找方式 //使用{}內自由變量,當做obj的屬性來查找 with(obj) {console.log(a)console.log(b)console.log(c) //會報錯!!! }

vue模板被編譯成什么?

模板不是html,有指令、插值JS表達式,能實現判斷、循環
html是標簽語言,只有JS才能實現判斷、循環(圖靈完備的:能實現順序執行、判斷、循環)
因此,模板一定是轉換為某種JS代碼,即編譯模板

const compiler = require('vue-template-compiler')// 插值 // const template = `<p>{{message}}</p>` // with(this){return _c('p',[_v(_s(message))])} // with(this){return createElement('p',[createTextVNode(toString(message))])} //this->new Vue({....}) // h -> vnode // createElement -> vnode// // 表達式 // const template = `<p>{{flag ? message : 'no message found'}}</p>` // // with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}// // 屬性和動態屬性 // const template = ` // <div id="div1" class="container"> // <img :src="imgUrl"/> // </div> // ` // with(this){return _c('div', // {staticClass:"container",attrs:{"id":"div1"}}, // [ // _c('img',{attrs:{"src":imgUrl}})])}// // 條件 // const template = ` // <div> // <p v-if="flag === 'a'">A</p> // <p v-else>B</p> // </div> // ` // with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}// 循環 // const template = ` // <ul> // <li v-for="item in list" :key="item.id">{{item.title}}</li> // </ul> // ` // with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}// 事件 // const template = ` // <button @click="clickHandler">submit</button> // ` // with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}// v-model const template = `<input type="text" v-model="name">` // 主要看 input 事件 // with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}// render 函數 // 返回 vnode // patch// 編譯 const res = compiler.compile(template) console.log(res.render)// ---------------分割線--------------// // 從 vue 源碼中找到縮寫函數的含義 // function installRenderHelpers (target) { // target._o = markOnce; // target._n = toNumber; // target._s = toString; // target._l = renderList; // target._t = renderSlot; // target._q = looseEqual; // target._i = looseIndexOf; // target._m = renderStatic; // target._f = resolveFilter; // target._k = checkKeyCodes; // target._b = bindObjectProps; // target._v = createTextVNode; // target._e = createEmptyVNode; // target._u = resolveScopedSlots; // target._g = bindObjectListeners; // target._d = bindDynamicKeys; // target._p = prependModifier; // }

編譯模板

模板編譯為render函數,執行render函數返回vnode
基于vnode再執行patch和diff(后面會講)
使用webpack vue-loader,會在開發環境下編譯模板(重要)

vue組件中使用render代替template

講完模板編譯,再講這個render,就比較好理解了
在有些復雜情況中,不能用template,可以考慮用render
React一直都用render(沒有模板),和這里一樣

總結

with語法
模板到render函數,再到vnode,再到渲染和更新
vue組件可以用render代替template

回顧和復習已學的知識點

總結組件 渲染/更新過程

一個組件渲染到頁面,修改data觸發更新(數據驅動視圖)
其背后原理是什么,需要掌握哪些要點
考察對流程了解的全面程度

回顧學過的知識

響應式:監聽data屬性getter setter(包括數組)
模板編譯:模板到render函數,再到vnode
vdom:patch(elem,vnode)和patch(vnode,newVnode)

組件 渲染/更新過程

初次渲染過程
更新過程
異步渲染

vue組件是如何渲染和更新的

初次渲染過程

解析模板為render函數(或在開發環境已完成,vue-loader)
觸發響應式,監聽data屬性getter setter
執行render函數,生成vnode,patch(elem,vnode)

執行render函數會觸發getter

<p>{{message}}</p> <script> export default {data(){return {message:'hello',//會觸發getcity:'北京'//不會觸發get,因為模板沒用到,即和視圖沒關系}} } </script>

更新過程

修改data,觸發setter(此前在getter中已被監聽)
重新執行render函數,生成newVnode
patch(vnode,newVnode)

完成流程圖

異步渲染

回顧$nextTick
匯總data的修改,一次性更新視圖
減少DOM操作次數,提高性能

總結1

渲染和響應式的關系
渲染和模板編譯的關系
渲染和vdom的關系

總結2

初次渲染過程
更新過程
異步渲染

如何用JS實現hash路由

前端路由原理

稍微復雜一點的SPA,都需要路由
vue-router也是vue全家桶的標配之一
屬于“和日常使用相關聯的原理”,面試常考
回顧vue-router的路由模式
hash
H5 history

hash的特點

hash變化會觸發網頁跳轉,即瀏覽器的前進、后退
hash變化不會刷新頁面,SPA必須的特點
hash永遠不會提交到server端(前端自生自滅)

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>hash test</title> </head> <body><p>hash test</p><button id="btn1">修改 hash</button><script>// hash 變化,包括:// a. JS 修改 url// b. 手動修改 url 的 hash// c. 瀏覽器前進、后退window.onhashchange = (event) => {console.log('old url', event.oldURL)console.log('new url', event.newURL)console.log('hash:', location.hash)}// 頁面初次加載,獲取 hashdocument.addEventListener('DOMContentLoaded', () => {console.log('hash:', location.hash)})// JS 修改 urldocument.getElementById('btn1').addEventListener('click', () => {location.href = '#/user'})</script> </body> </html>

如何用JS實現H5 history路由

H5 history

用url規范的路由,但跳轉不刷新頁面
history.pushState
window.onpopstate

正常頁面瀏覽

https://github.com/xxx 刷新頁面
https://github.com/xxx/yyy 刷新頁面
https://github.com/xxx/yyy/zzz 刷新頁面

改造成H5 history模式

https://github.com/xxx 刷新頁面
https://github.com/xxx/yyy 前端跳轉,不刷新頁面
https://github.com/xxx/yyy/zzz 前端跳轉,不刷新頁面

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>history API test</title> </head> <body><p>history API test</p><button id="btn1">修改 url</button><script>// 頁面初次加載,獲取 pathdocument.addEventListener('DOMContentLoaded', () => {console.log('load', location.pathname)})// 打開一個新的路由// 【注意】用 pushState 方式,瀏覽器不會刷新頁面document.getElementById('btn1').addEventListener('click', () => {const state = { name: 'page1' }console.log('切換路由到', 'page1')history.pushState(state, '', 'page1') // 重要!!})// 監聽瀏覽器前進、后退window.onpopstate = (event) => { // 重要!!console.log('onpopstate', event.state, location.pathname)}// 需要 server 端配合,可參考// https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90</script> </body> </html>

總結

hash–window.onhashchange
H5 history–history.pushState和window.onpopstate
H5 history需要后端支持

兩者選擇

to B的系統推薦用hash,簡單易用,對url規范不敏感
to C的系統,可以考慮選擇H5 history,但需要服務端支持
能選擇簡單的,就別用復雜的,要考慮成本和收益

vue原理-考點總結和復習

組件化

組件化的歷史
數據驅動視圖
MVVM

響應式

Object.defineProperty
監聽對象(深度),監聽數組
Object.defineProperty的缺點(Vue3用Proxy,后面會講)

vdom和diff

應用背景
vnode結構
snabbdom使用:vnode h patch

模板編譯

with語法
模板編譯為render函數
執行render函數生成vnode

渲染過程

初次渲染過程
更新過程
異步渲染

前端路由

hash
H5 history
兩者對比

總結

以上是生活随笔為你收集整理的(四)Vue原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。