Vue

Vue

1. Vue原理
  • Vue是采用數據劫持配合發(fā)布者-訂閱者模式,通過Object.defineProperty()來劫持各個屬性的gettersetter。

  • 在數據發(fā)生變化的時候,發(fā)布消息給依賴收集器,去通知觀察者,做出對應的回調函數去更新視圖。

  • 具體執(zhí)行流程:

1.MVVM作為綁定入口,整合Observe,CompilWatcher三者,通過Observe來監(jiān)聽model的變化。

2.通過Compil來解析編譯模版指令,最終利用Watcher搭起ObserveCompil之前的通信橋梁。

3.從而達到數據變化 => 更新視圖,視圖交互變化(input) => 數據model變更的雙向綁定效果。

2. Vue的生命周期
  • 單個組件的生命周期

1.beforeCreated

2.created

3.beforeMounted

4.mounted

5.activated

6.beforeUpdated

7.updated

8.deactivated

9.beforeDestory

10.destoryed

  • 父子組件的生命周期順序

1.父組件先執(zhí)行(beforeCreated -> created -> beforeMounted)函數

2.父組件掛載前,子組件再執(zhí)行(beforeCreated -> created -> beforeMounted -> mounted )。

3.子組件掛載完成之后,最后執(zhí)行父組件掛載函數mounted。

4.接著是下面的三種情況:

(1)更新

只更新父或子: 局部更新,父或子beforeUpdate -> updated

同時更新父和子: 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

(2)銷毀父組件

父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

(3)激活父組件

子activated -> 父activated -> 停止 -> 子deactivated -> 父deactivated

3. Vue響應式原理
  • 使用 defineReactive 函數將深度遍歷一個對象(或循環(huán)遍歷數組),將對象構建成響應式式對象。明顯的標志就是 ob 屬性 實質是通過 Object.defineProperty對屬性(深度遍歷)進行 settergetter 攔截。

  • get中主要做依賴收集dep.depend() 【子屬性也收集該依賴】

  • set中主要做派發(fā)更新 (新的值才更新)dep.notify()調用dep數組中每個渲染watcherupdate方法更新DOM

  • 響應式對象使用應該注意哪些點

1.對象的新增屬性,數組的新增元素,因為不是響應式的,所以不會觸發(fā)視圖渲染。此時應該使用 $set

2.改變某一下標的元素,因為Vue未實現(xiàn)監(jiān)聽,所以不會觸發(fā)視圖渲染。此時應該使用 $set

3.刪除對象的屬性,數組下標的某一元素,確保刪除屬性能觸發(fā)視圖渲染。此時應該使用 $delete

4. data為什么必須是函數而不是對象?
  • 首先舉個栗子

var option = {

  data: {

a: 1

  }

}

class component {

  constructor(opt) {

this.data = opt.data;

Object.defineProperty(this.data, `a`, {

  get: function () {

console.log('get val');

return this._a;

  },

  set: function (newVal) {

console.log('set val:' + newVal);

this._a = newVal;

  }

});

  }

}

var c1 = new component(option);

var c2 = new component(option);

c1.data.a = 3;

c2.data.a = 5;

console.log(`c1 : ` + c1.data.a);//c1 : 5

console.log(`c2 : ` + c2.data.a);//c2 : 5

示例代碼中 Object.defineProperty 把傳進來組件中的dataa 屬性轉化為 gettersetter,可以實現(xiàn) data.a的數據監(jiān)控。這個轉化是Vue.js 響應式的基石。

這樣就不難理解data為什么不能是對象了,如果傳進來是對象,new出來的兩個實例同時引用一個對象,那么當你修改其中一個屬性的時候,另外一個實例也會跟著改。

總結:

1.對象是對于內存地址的引用。直接定義個對象的話,組件之間都會使用這個對象,這樣會造成組件之間數據相互影響。

2.組件就是為了抽離開來提高復用的, 如果組件之間數據默認存在關系,就違背了組件的意義。

3.函數 return 一個新的對象,其實還是為 data 定義了一個對象, 只不過這個對象是內存地址的單獨引用了,這樣組件之間就不會存在數據干擾的問題。

5. v-model基本原理
  • 首先在編譯階段,v-model被當成普通指令解析到el.directives,然后在解析v-model的時候,會根據節(jié)點的不同請求去執(zhí)行不同的邏輯。

1.如果節(jié)點是selectcheckbox, radio,則監(jiān)聽的是change事件

2.如果節(jié)點是input,textarea,則監(jiān)聽一般是input事件,在.lazy下的情況下是change事件。

3.如果節(jié)點是組件,則是使用自定義的回調函數

  • 在運行的時候,通過相應事件的監(jiān)聽函數去更改數據

v-model實質是一種語法糖,換成模板寫法如下:


<input :value="sth" @input="sth = $event.target.value" />

  • 組件中使用v-model

// 自定義屬性名和事件名需要一致

export default {

  model: {

prop: 'num', // 自定義屬性名

event: 'addNum' // 自定義事件名

  },

  props: {

num: Number,

  },

  methods: {

add() {

  this.$emit('addNum', this.num + 1)

}

  }

}

6. vue2.0響應式的缺陷
  • Object.defineProperty無法監(jiān)控到數組下標的變化,導致通過數組下標添加元素,不能實時響應

  • Object.defineProperty本身是可以監(jiān)控到數組下標的變化的,但是在 Vue 中,從性能/體驗的性價比考慮,棄用了這個特性。

  • Object.defineProperty只能劫持對象的屬性,從而需要對每個對象,每個屬性進行遍歷,如果,屬性值是對象,還需要深度遍歷

7. Vue3.0為什么使用Proxy實現(xiàn)響應式
  • Proxy可以劫持整個對象,并返回一個新的對象。

  • Proxy不僅可以代理對象,還可以代理數組。還可以代理動態(tài)增加的屬性。

8. Vue的通信方式
  • props$emit

  • $parent$children

  • vueBus: 中央事務總線,一個發(fā)布訂閱中心

  • vuex:狀態(tài)樹管理(單一的)

  • refrefs

  • $attr$listener: v-bind="$attrs" v-on="$listeners"

  • provideinject: 實質就是遞歸父組件幫你尋找對應的provider

  • provideinject 綁定并不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監(jiān)聽的對象,那么其對象的屬性還是可響應的。

9. Vue.nextTick的原理
  • Vue.nextTick是在執(zhí)行render渲染后運行的,即在render渲染后的下一次tickevent loop最開始的時候執(zhí)行)

  • Vue.nextTikc的降級順序(優(yōu)先使用) Promise.then(microtask) , MutationObserver(microtask) , setImmediate(task) , setTimeout(fn, 0)(task)

  • Vue在修改數據后,視圖不會立刻更新,而是等同一事件循環(huán)中的所有數據變化完成之后,再統(tǒng)一進行視圖更新。

  • 應用場景

1.在Vue生命周期的created()鉤子函數進行DOM操作一定要放到Vue.nextTick()的回調函數中。

2.在數據變化后要執(zhí)行的某個操作,而這個操作需要使用隨數據改變而改變的DOM結構的時候,這個操作都應該放進Vue.nextTick()的回調函數中。

  • 10. new Vue會做什么操作

Vue.prototype._init = function (options) {

const vm = this

// ...忽略,從第45行看起

if (process.env.NODE_ENV !== 'production') {

  initProxy(vm) // 作用域代理,攔截組件內訪問其它組件的數據

} else {

  vm._renderProxy = vm

}

// expose real self

vm._self = vm

initLifecycle(vm) // 建立父子組件關系,在當前實例上添加一些屬性和生命周期標識。

initEvents(vm) // 用來存放除 @hook:生命周期鉤子名稱="綁定的函數"事件的對象。如:$on、 $emit等

initRender(vm) // 用于初始化 $slots、 $attrs、 $listeners

callHook(vm, 'beforeCreate')

initInjections(vm) // resolve injections before data/props  // 初始化 inject,一般用于更深層次的組件通信,相當于加強版子組件的 props。用于組件庫開發(fā)較多

initState(vm) // 是很多選項初始化的匯總,包括:props、methods、data、computed和watch 等。

initProvide(vm) // resolve provide after data/props  // 初始化 provide

callHook(vm, 'created')

// ...忽略

if (vm.$options.el) {

  vm.$mount(vm.$options.el)  // 掛載實例

}

  }

11. Vue的diff原理
  • 主要執(zhí)行的是patch函數。主要流程如下:

function patch (oldVnode, vnode, hydrating, removeOnly)

1.如果oldVnode不存在,即是新添加的節(jié)點,則創(chuàng)建vnode的DOM

2.如果不是真實的節(jié)點且是相同類型的節(jié)點,則進入結點diff,即patchVnode函數。否則會用新的節(jié)點替換老的。這里的相同類型指的是具有相同的key值和一些其他條件,例如標簽相同等等。

3.如果oldVnode === vnode,則認為沒有變化, 如果oldVnodeisAsyncPlaceholder屬性為true時,跳過檢查異步組件,return;

4.如果oldVnodevnode都是靜態(tài)節(jié)點(實例不會發(fā)生變化),且具有相同的key,并且當vnode是克隆節(jié)點或是v-once指令控制的節(jié)點時,則把oldVnode.elmoldVnode.child都復制到vnode上;

5.如果vnode不是文本節(jié)點或注釋節(jié)點

(1)如果vnodeoldVnode都有子節(jié)點并且兩者的子節(jié)點不一致時,就調用updateChildren更新子節(jié)點

(2)如果只有vnode有子節(jié)點,則調用addVnodes創(chuàng)建子節(jié)點

(3)如果只有oldVnode有子節(jié)點,則調用removeVnodes把這些子節(jié)點都刪除

(4)如果vnode文本為undefined,則清空vnode.elm文本;

6.如果vnode是文本節(jié)點但是vnode.text != oldVnode.text時只需要更新vnode.elm的文本內容就可以。

7.在updateChildren主要是子節(jié)點數組對比,思路是通過首尾兩端對比,如果是相同類型的節(jié)點也會使用patchVnode函數。

  • 在做對比中key 的作用 主要是

1.決定節(jié)點是否可以復用

2.建立key-index的索引,主要是替代遍歷,提升性能

12. computed 和 watcher
  • computed是計算屬性,依賴其他屬性計算,并且computed的值有緩存,只有當計算值發(fā)生變化才會返回內容。所以,對于任何復雜邏輯,你都應當使用計算屬性。

  • watch主要用于監(jiān)控vue實例的變化,它監(jiān)控的變量當然必須在data里面聲明才可以,它可以監(jiān)控一個變量,也可以是一個對象。比較適合的場景是一個數據影響多個數據。

  • watch支持異步。

  • watcher的分類

1.內部-watcher vue組件上的每一條數據綁定指令(例如{{myData}})和computed屬性,通過compile最后都會生成一個對應的 watcher 對象。

2.user--watcherwatch屬性中,由用戶自己定義的,都屬于這種類型,即只要監(jiān)聽的屬性改變了,都會觸發(fā)定義好的回調函數

3.render-watcher每一個組件都會有一個 render-watcher, function () {vm._update(vm._render(), hydrating);}, 當 data / computed中的屬性改變的時候,會調用該 render-watcher 來更新組件的視圖

watcher 也有固定的執(zhí)行順序,分別是:內部-watcher -> user-watcher -> render-watcher

13. Vue指令

// 全局

Vue.directive('my-click', config)

// 局部

new Vue({

directives:{

focus: config // v-focus

}

}})

  • 配置參數

1.一個指令定義對象可以提供如下幾個鉤子函數 (均為可選):

(1)bind:只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。

(2)inserted:被綁定元素插入父節(jié)點時調用 (僅保證父節(jié)點存在,但不一定已被插入文檔中)。

(3)update:所在組件的 VNode 更新時調用,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新。

(4)componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調用。

(5)unbind:只調用一次,指令與元素解綁時調用。

每個鉤子函數都有四個參數el、binding、vnode 和 oldVnode

14. 混入 (mixin)
  • 混入 (mixin) 提供了一種非常靈活的方式,來分發(fā) Vue 組件中的可復用功能。

  • 全局和局部mixin


var mixin = {

  data: function () {

return {

  message: 'hello',

  foo: 'abc'

}

  }

}

<!-- 全局mixin -->

Vue.mixin(mixin)

<!-- 局部mixin -->

new Vue({

  mixins: [mixin],

})

  • 合并策略

1.鉤子函數將合并成數組,且混入的函數先執(zhí)行

2.其他的值為對象的將被合并為同一個對象。兩個對象鍵名沖突時,取組件對象的鍵值對。

3.默認的合并策略可以使用下面的方面更改


Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {

  // 返回合并后的值

}

15. vue-router
  • Vue RouterVue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,讓構建單頁面應用變得易如反掌

<router-link>和<router-view>和<keep-alive>

  • 路由模式

1.HashHistory模式:實質是監(jiān)聽onhashchange事件 (window.location API - location.hash

2.HTML5History模式:實質是使用h5的 window.history API, 監(jiān)聽popstate事件(pushState, replaceState)。使用該模式,服務器和前端需要做好頁面404的處理

3.AbstractHistory模式:在不支持上面兩種方式的環(huán)境下使用,如node環(huán)境,實際是使用數組模擬路由歷史棧

  • 導航守衛(wèi)

// 全局守衛(wèi)

// 在項目中,一般在beforeEach這個鉤子函數中進行路由跳轉的一些信息判斷。

判斷是否登錄,是否拿到對應的路由權限等等。

router.beforeEach((to, from, next) => {

  to: Route:  // 即將要進入的目標 路由對象

  from: Route: // 當前導航正要離開的路由

  next: Function: // 一定要調用該方法來 resolve 這個鉤子。

})

router.afterEach((to, from) => {})

router.beforeResolve((to, from) => {}) 

// 與afterEach類似, 區(qū)別是在導航被確認之前,同時在所有組件內守衛(wèi)和異步路由組件被解析之后,解析守衛(wèi)就被調用

// 路由獨享守衛(wèi)

const router = new VueRouter({

  routes: [

{

  path: '/foo',

  component: Foo,

  beforeEnter: (to, from, next) => {},

  ...

}

  ]

})

// 組件內守衛(wèi)

const Foo = {

  template: `...`,

  beforeRouteEnter (to, from, next) {

// 在渲染該組件的對應路由被 confirm 前調用

// 不!能!獲取組件實例 `this`

// 因為當守衛(wèi)執(zhí)行前,組件實例還沒被創(chuàng)建

  },

  beforeRouteUpdate (to, from, next) {

// 在當前路由改變,但是該組件被復用時調用

// 舉例來說,對于一個帶有動態(tài)參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,

// 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調用。

// 可以訪問組件實例 `this`

  },

  beforeRouteLeave (to, from, next) {

// 導航離開該組件的對應路由時調用

// 可以訪問組件實例 `this`

  }

16. VueRouter
  • 基于vue的插件機制,全局混入beforeCreateddestroyed的生命鉤子

  • 查找根實例上的route,注入到每個組件上,監(jiān)聽current變化


Vue.util.defineReactive(this, '_route', this._router.history.current)

  • vue原型上添加兩個屬性$router$route, 攔截get操作,限制set操作

Object.defineProperty(Vue.prototype, '$router', {

get () { return this._routerRoot._router }

})

  • 注冊全局組件RouterView 和 RouterLink
17. Vuex
  • Vue.js 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應用的所有組件的狀態(tài),并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化。

  • 核心概念

1.state:使用單一狀態(tài)樹,用一個對象就包含了全部的應用層級狀態(tài)。

2.getter:可看成數據的computed計算屬性

3.mutation:唯一更改數據的方法 通過 store.commit 使用相應的 mutation方法

4.Action:支持異步的提交mutation 通過 store.dispatch 使用相應的Action方法

5.module:數據分模塊。如moduleA.xx

  • 如何注入

在使用 Vue.use(vuex)的時候會執(zhí)行install 方法在(vue插件機制)。這個方法會混入一個minxin


Vue.mixin({

beforeCreate() {

const options = this.$options

// store injection

// 非根組件指向其父組件的$store,使得所有組件的實例,都指向根的store對象

if (options.store) {

  this.$store = typeof options.store === 'function'

? options.store()

: options.store

} else if (options.parent && options.parent.$store) {

  this.$store = options.parent.$store

}

}

})

  • 如何實現(xiàn)響應式

通過添加到data中實現(xiàn)響應式


store._vm = new Vue({

  data: {

$$state: state

  },

  computed // 這里是store的getter

})

18. 首屏加載慢的優(yōu)化方案
  • webpack來打包Vue項目,vendor.js過大問題解決

1.造成過大的原因是因為在main.js導入第三庫太多時,webpack合并js時生成了vendor.js(我們習慣把第三方庫放在vendor里面)造成的,js文件過多,拖慢加載速度,所以:首先在index.html中,使用CDN的資源


<body>

<script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>

<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>

<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>

<script src="https://unpkg.com/vue-awesome-swiper@2.6.7/dist/vue-awesome-swiper.js"></script>

</body>

2.在bulid/webpack.base.conf.js文件中,添加externals,導入index.html下所需的資源模塊:


module.exports = {

  context: path.resolve(__dirname, '../'),

  entry: {

app: ['babel-polyfill', 'lib-flexible', './src/main.js']

  },

  externals: { // <-添加

vue: 'Vue',

vuex: 'Vuex',

'vue-router': 'VueRouter',

VueAwesomeSwiper: 'VueAwesomeSwiper'

  },

3.在main.js里將以下 import 注釋 替換 require 引入模塊


// import Vue from 'vue'

// import VueAwesomeSwiper from 'vue-awesome-swiper'

const Vue = require('vue')

const VueAwesomeSwiper = require('VueAwesomeSwiper')

Vue.use(VueAwesomeSwiper)

4.當然可以在生產環(huán)境當中刪除掉不必要的console.log,打開build/webpack.prod.conf.jsplugins里添加以下代碼


plugins: [

new webpack.optimize.UglifyJsPlugin({ //添加-刪除console.log

  compress: {

warnings: false,

drop_debugger: true,

drop_console: true

  },

  sourceMap: true

}),

5.執(zhí)行npm run build之后,會發(fā)現(xiàn)文件的體積明顯小了很多,如果把一些Ui庫也替換成CDN的方式,可能體積會更小,渲染解析更快。

  • Vue-cli開啟打包壓縮 和后臺配合 gzip訪問開啟打包壓縮 和后臺配合 gzip訪問

1.首先打開 config/index.js,找到 build 對象中的productionGzip ,改成 true

2.打開 productionGzip: true 之前,先要安裝依賴 compression-webpack-plugin ,官方推薦的命令是:


npm install --save-dev compression-webpack-plugin

//(此處有坑) 如果打包報錯,應該是版本問題 ,先卸載之前安裝的此插件 ,然后安裝低版本

npm install --save-dev compression-webpack-plugin@1.1.11

3.等安裝好了,重新打包 npm run build,此時打包的文件會 新增 .gz文件。是不是比原來的js文件小很多呢,之后項目訪問的文件就是這個.gz文件

4.后臺nginx開啟gzip模式訪問,瀏覽器訪問項目,自動會找到 .gz的文件。加載速度明顯提高。


http {  //在 http中配置如下代碼,

  gzip on;

  gzip_disable "msie6";

  gzip_vary on;

  gzip_proxied any;

  gzip_comp_level 8; #壓縮級別

  gzip_buffers 16 8k;

  #gzip_http_version 1.1;

  gzip_min_length 100; #不壓縮臨界值

  gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;

}

19. Vue核心之虛擬DOM
  • 真實DOM和其解析流程,瀏覽器渲染引擎工作流程都差不多,大致分為5步,創(chuàng)建DOM樹-->創(chuàng)建StyleRules-->創(chuàng)建Render樹-->布局Layout-->繪制Painting。

1.用HTML分析器,創(chuàng)建DOM樹。

2.用CSS分析器,生成樣式規(guī)則表。

3.關聯(lián)DOM樹和規(guī)則表,生成渲染樹。

4.通過渲染樹計算節(jié)點屬性。

5.通過計算好的節(jié)點屬性,渲染頁面

DOM樹的構建是文檔加載完成開始的?構建DOM數是一個漸進過程,為達到更好用戶體驗,渲染引擎會盡快將內容顯示在屏幕上。它不必等到整個HTML文檔解析完畢之后才開始構建render數和布局。

Render樹是DOM樹和CSSOM樹構建完畢才開始構建的嗎?這三個過程在實際進行的時候又不是完全獨立,而是會有交叉。會造成一邊加載,一遍解析,一遍渲染的工作現(xiàn)象。

CSS的解析是從右往左逆向解析的(從DOM樹的下-上解析比上-下解析效率高),嵌套標簽越多,解析越慢。

  • JS操作真實DOM的代價

用我們傳統(tǒng)的開發(fā)模式,原生JSJQ操作DOM時,瀏覽器會從構建DOM樹開始從頭到尾執(zhí)行一遍流程。在一次操作中,我需要更新10個DOM節(jié)點,瀏覽器收到第一個DOM請求后并不知道還有9次更新操作,因此會馬上執(zhí)行流程,最終執(zhí)行10次。例如,第一次計算完,緊接著下一個DOM更新請求,這個節(jié)點的坐標值就變了,前一次計算為無用功。計算DOM節(jié)點坐標值等都是白白浪費的性能。即使計算機硬件一直在迭代更新,操作DOM的代價仍舊是昂貴的,頻繁操作還是會出現(xiàn)頁面卡頓,影響用戶體驗。

  • 虛擬DOM有什么好處?虛擬DOM,其實是一個大對象

1.Web界面由DOM樹(樹的意思是數據結構)來構建,當其中一部分發(fā)生變化時,其實就是對應某個DOM節(jié)點發(fā)生了變化。

2.虛擬DOM就是為了解決瀏覽器性能問題而被設計出來的。如前,若一次操作中有10次更新DOM的動作,虛擬DOM不會立即操作DOM,而是將這10次更新的diff內容保存到本地一個JS對象中,最終將這個JS對象一次性attchDOM樹上,再進行后續(xù)操作,避免大量無謂的計算量。所以,用JS對象模擬DOM節(jié)點的好處是,頁面的更新可以先全部反映在JS對象(虛擬DOM)上,操作內存中的JS對象的速度顯然要更快,等更新完成后,再將最終的JS對象映射成真實的DOM,交由瀏覽器去繪制。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容