一:對 SPA 單⻚⾯的理解,優缺點是什么?
SPA( single-page application )僅在 Web ⻚⾯初始化時加載相應的 HTML、JavaScript 和 CSS。⼀旦⻚⾯加載完成,SPA 不會因為⽤戶的操作⽽進⾏⻚⾯的重新加載或跳轉;取⽽代之的是利⽤路由機制實現 HTML 內容的變換,UI 與⽤戶的交互,避免⻚⾯的重新加載。
優點:
1)⽤戶體驗好、快,內容的改變不需要重新加載整個⻚⾯,避免了不必要的跳轉和重復渲染;
2)SPA 相對對服務器壓⼒⼩;
3)前后端職責分離,架構清晰,前端進⾏交互邏輯,后端負責數據處理;
缺點:
1)⾸屏(初次)加載慢:為實現單⻚ Web 應⽤功能及顯示效果,需要在加載⻚⾯的時候將JavaScript、CSS 統⼀加載,部分⻚⾯按需加載;
2)不利於 SEO:由於所有的內容都在⼀個⻚⾯中動態替換顯示,所以在 SEO 上其有着天然的弱勢。
二:new Vue() 發生了什么?
1)結論:new Vue()是創建Vue實例,它內部執行了根實例的初始化過程。
2)具體包括以下操作:
-
選項合並
-
$children,$refs,$slots,$createElement等實例屬性的方法初始化
-
自定義事件處理
-
數據響應式處理
-
生命周期鈎子調用 (beforecreate created)
-
可能的掛載
3)總結:new Vue()創建了根實例並准備好數據和方法,未來執行掛載時,此過程還會遞歸的應用於它的子組件上,最終形成一個有緊密關系的組件實例樹
源碼地址:src/core/instance/init.js
三:Vue.use是干什么的?原理是什么?
核心答案:
vue.use 是用來使用插件的,我們可以在插件中擴展全局組件、指令、原型方法等。
1、檢查插件是否注冊,若已注冊,則直接跳出;
2、處理入參,將第一個參數之后的參數歸集,並在首部塞入 this 上下文;
3、執行注冊方法,調用定義好的 install 方法,傳入處理的參數,若沒有 install 方法並且插件本身為 function 則直接進行注冊;
1) 插件不能重復的加載
install 方法的第一個參數是vue的構造函數,其他參數是Vue.set中除了第一個參數的其他參數; 代碼:args.unshift(this)
2) 調用插件的install 方法 代碼:typeof plugin.install === 'function'
3) 插件本身是一個函數,直接讓函數執行。 代碼:plugin.apply(null, args)
4) 緩存插件。 代碼:installedPlugins.push(plugin)
源碼地址:src/core/global-api/use.js
四:請說一下響應式數據的理解?
根據數據類型來做不同處理,數組和對象類型當值變化時如何劫持。
1) 對象內部通過defineReactive方法,使用 Object.defineProperty() 監聽數據屬性的 get 來進行數據依賴收集,再通過 set 來完成數據更新的派發;
2) 數組則通過重寫數組方法來實現的。擴展它的 7 個變更⽅法,通過監聽這些方法可以做到依賴收集和派發更新;( push/pop/shift/unshift/splice/reverse/sort )
這里在回答時可以帶出一些相關知識點 (比如多層對象是通過遞歸來實現劫持,順帶提出vue3中是使用 proxy來實現響應式數據)
補充回答:
內部依賴收集是怎么做到的,每個屬性都擁有自己的dep屬性,存放他所依賴的 watcher,當屬性變化后會通知自己對應的 watcher去更新。
響應式流程:
1、defineReactive 把數據定義成響應式的;
2、給屬性增加一個 dep,用來收集對應的那些watcher;
3、等數據變化進行更新
dep.depend() // get 取值:進行依賴收集
dep.notify() // set 設置時:通知視圖更新
這里可以引出性能優化相關的內容:1)對象層級過深,性能就會差。2)不需要響應數據的內容不要放在data中。3)object.freeze() 可以凍結數據。
源碼地址:src/core/observer/index.js 158
五:Vue如何檢測數組變化?
數組考慮性能原因沒有用defineProperty對數組的每一項進行攔截,而是選擇重寫數組 方法以進行重寫。當數組調用到這 7 個方法的時候,執行 ob.dep.notify() 進行派發通知 Watcher 更新;
* 重寫數組方法:push/pop/shift/unshift/splice/reverse/sort
補充回答:
在Vue中修改數組的索引和長度是無法監控到的。需要通過以下7種變異方法修改數組才會觸發數組對應的wacther進行更新。數組中如果是對象數據類型也會進行遞歸劫持。
說明:那如果想要改索引更新數據怎么辦?
可以通過Vue.set()來進行處理 =》 核心內部用的是 splice 方法。
// 取出原型方法; const arrayProto = Array.prototype // 拷貝原型方法; export const arrayMethods = Object.create(arrayProto) // 重寫數組方法; def(arrayMethods, method, function mutator (...args) { } ob.dep.notify() // 調用方法時更新視圖;
源碼地址:src/core/observer/array.js 15
六:Vue.set 方法是如何實現的?
核心答案:
為什么$set可以觸發更新,我們給對象和數組本身都增加了dep屬性,當給對象新增不存在的屬性則觸發對象依賴的watcher去更新,當修改數組索引時我們調用數組本身的splice方法去更新數組。
補充回答:
官方定義 Vue.set(object, key, value)
1) 如果是數組,調用重寫的splice方法 (這樣可以更新視圖 )
代碼:target.splice(key, 1, val)
2) 如果不是響應式的也不需要將其定義成響應式屬性。
3) 如果是對象,將屬性定義成響應式的 defineReactive(ob.value, key, val)
通知視圖更新 ob.dep.notify()
源碼地址:src/core/observer/index.js 202
七:Vue中模板編譯原理?
核心答案:
如何將template轉換成render函數(這里要注意的是我們在開發時盡量不要使用template,因為將template轉化成render方法需要在運行時進行編譯操作會有性能損耗,同時引用帶有complier包的vue體積也會變大) 默認.vue文件中的 template處理是通過vue-loader 來進行處理的並不是通過運行時的編譯。
1) 將 template 模板轉換成 ast 語法樹 - parserHTML
2) 對靜態語法做靜態標記 - markUp
3) 重新生成代碼 - codeGen
補充回答:
模板引擎的實現原理就是new Function + with來進行實現的。
vue-loader中處理template屬性主要靠的是 vue-template-compiler
vue-loader
// template => ast => codegen => with+function 實現生成render方法 let {ast, render } = VueTemplateCompiler.compile(`<div>{{aaa}}</div>`) console.log(ast, render) // 模板引擎的實現原理 with + new Function console.log(new Function(render).tostring()) // render方法執行完畢后生成的是虛擬 dom // with(this){return _c('div',[_s(aaa)])} // 代碼生成
源碼設置:
const ast = parse(template.trim(), options) // 將代碼解析成ast語法樹 if (options.optimize !== false) { optimize(ast, options) // 優化代碼 標記靜態點 標記樹 } const code = generate(ast, options) // 生成代碼
源碼地址:src/compiler/index.js
八:Proxy 與 Object.defineProperty 優劣對比
核心答案:
Proxy 的優勢如下:
1)Proxy 可以直接監聽對象而非屬性;
2)Proxy 可以直接監聽數組的變化;
3)Proxy 有多達 13 種攔截方法,不限於 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具備的;4)Proxy 返回的是一個新對象,我們可以只操作新的對象達到目的,而 Object.defineProperty 只能遍歷對象屬性直接修改;
5)Proxy 作為新標准將受到瀏覽器廠商重點持續的性能優化,也就是傳說中的新標准的性能紅利;
Object.defineProperty 的優勢如下:
兼容性好,支持 IE9,而 Proxy 的存在瀏覽器兼容性問題,而且無法用 polyfill 磨平,因此 Vue 的作者才聲明需要等到下個大版本( 3.0 )才能用 Proxy 重寫。
九:Vue3.x響應式數據原理
Vue3.x改用Proxy替代Object.defineProperty。因為Proxy可以直接監聽對象和數組的變化,並且有多達13種攔截方法。並且作為新標准將受到瀏覽器廠商重點持續的性能優化。
Proxy只會代理對象的第一層,那么Vue3又是怎樣處理這個問題的呢?
判斷當前Reflect.get的返回值是否為Object,如果是則再通過reactive方法做代理, 這樣就實現了深度觀測。
監測數組的時候可能觸發多次get/set,那么如何防止觸發多次呢?
我們可以判斷key是否為當前被代理對象target自身屬性,也可以判斷舊值與新值是否相等,只有滿足以上兩個條件之一時,才有可能執行trigger。
來自VUE中文社區公眾號 https://mp.weixin.qq.com/s/60HI-CM1GhqDG5zeTFSOrw