總結一些vue中必知必會的知識點,讓我們在面試之前能夠胸有成竹一點~
一、Vue元件通訊
1、父元件向子元件通訊
方法一:props
<template>
<child :msg="message"></child>
</template>
複製程式碼
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
複製程式碼
方法二:使用$children
使用$children
可以在父元件中訪問子元件。比如呼叫子元件的方法,並傳入值等。
2、子元件向父元件通訊
方法一:使用vue事件
父元件向子元件傳遞事件方法,子元件通過$emit
觸發事件,回撥給父元件。
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
methods: {
func (msg) {
console.log(msg); // ssss
}
}
}
</script>
複製程式碼
<template>
<button @click="handleClick">點我</button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {
handleClick () {
//........
this.$emit('msgFunc', 'ssss');
}
}
}
</script>
複製程式碼
方法二:使用$parent
使用$parent
可以訪問父元件的例項,當然也就可以訪問父元件的屬性和方法了。
3、非父子元件、兄弟元件之間的資料傳遞
非父子元件通訊,Vue官方推薦使用一個Vue例項作為中央事件匯流排。
意思就是將一個公共狀態儲存在一個這些元件共用的一個父元件上,這樣就可以使用子元件通訊父元件的方式來間接的完成通訊了。
二、new Vue()之後都發生了什麼?
vue入口原始碼如下:
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
// initMixin給Vue.prototype新增:
// _init函式,
stateMixin(Vue)
/*
stateMixin給Vue.prototype新增:
$data屬性,
$props屬性,
$set函式,
$delete函式,
$watch函式,
...
*/
eventsMixin(Vue)
/*
eventsMixin給Vue.prototype新增:
$on函式,
$once函式,
$off函式,
$emit函式,
$watch方法,
...
*/
lifecycleMixin(Vue)
/*
lifecycleMixin給Vue.prototype新增:
_update方法:私有方法,用於更新dom,其中呼叫_patch產生跟新後的dom,
$forceUpdate函式,
$destroy函式,
...
*/
renderMixin(Vue)
/*
renderMixin給Vue.prototype新增:
$nextTick函式,
_render函式,
...
*/
export default Vue
複製程式碼
vue原始碼載入的時候,也就是在new Vue()之前做了一些初始化工作。
new Vue的時候會執行一個_init方法。程式碼如下:
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
複製程式碼
從上面的程式碼我們看見_init
很清淅的幹了幾件事, 合併相關配置, 初始化生命週期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等
三、元件data為什麼必須是函式?
因為元件可能被多處使用,但它們的data是私有的,所以每個元件都要return一個新的data物件,如果共享data,修改其中一個會影響其他元件。
四、vue 為什麼採用Virtual DOM?
-
建立真實DOM的代價高:真實的 DOM 節點 node 實現的屬性很多,而 vnode 僅僅實現一些必要的屬性,相比起來,建立一個 vnode 的成本比較低。
-
觸發多次瀏覽器重繪及迴流:使用 vnode ,相當於加了一個緩衝,讓一次資料變動所帶來的所有 node 變化,先在 vnode 中進行修改,然後 diff 之後對所有產生差異的節點集中一次對 DOM tree 進行修改,以減少瀏覽器的重繪及迴流。
-
虛擬dom由於本質是一個js物件,因此天生具備跨平臺的能力,可以實現在不同平臺的準確顯示。
-
Virtual DOM 在效能上的收益並不是最主要的,更重要的是它使得 Vue 具備了現代框架應有的高階特性。
五、Vue建立的一個大致過程
首先例項化,然後資料繫結,元件掛在、內容替換。
結合上例子分析Vue的生命週期:
<!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>vue生命週期學習</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<h1>{{message}}</h1>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Vue的生命週期'
},
beforeCreate: function() {
console.group('------beforeCreate建立前狀態------');
console.log("%c%s", "color:red" , "el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //undefined
console.log("%c%s", "color:red","message: " + this.message)
},
created: function() {
console.group('------created建立完畢狀態------');
console.log("%c%s", "color:red","el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeMount: function() {
console.group('------beforeMount掛載前狀態------');
console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
mounted: function() {
console.group('------mounted 掛載結束狀態------');
console.log("%c%s", "color:red","el : " + this.$el); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeUpdate: function () {
console.group('beforeUpdate 更新前狀態===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
updated: function () {
console.group('updated 更新完成狀態===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
beforeDestroy: function () {
console.group('beforeDestroy 銷燬前狀態===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
destroyed: function () {
console.group('destroyed 銷燬完成狀態===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message)
}
})
</script>
</html>
複製程式碼
[1] 在beforeCreate和created鉤子函式之間的生命週期
在這個階段開始初始化事件、並且對資料進行了檢測(劫持),這時候還沒有el。[2] created鉤子函式和beforeMount間的生命週期
- 首先會判斷物件是否有el選項。如果有的話就繼續向下編譯,如果沒有el選項,則停止編譯,也就意味著停止了生命週期,直到在該vue例項上呼叫vm.$mount(el)。此時註釋掉程式碼中:
el: '#app',
- 然後,我們往下看,template引數選項的有無對生命週期的影響。
(1).如果vue例項物件中有template引數選項,則將其作為模板編譯成render函式。 (2).如果沒有template選項,則將外部HTML作為模板編譯。 (3).可以看到template中的模板優先順序要高於outer HTML的優先順序。
<!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>vue生命週期學習</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<!--html中修改的-->
<h1>{{message + '這是在outer HTML中的'}}</h1>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
template: "<h1>{{message +'這是在template中的'}}</h1>", //在vue配置項中修改的
data: {
message: 'Vue的生命週期'
}
</script>
</html>
複製程式碼
那麼將vue物件中template的選項註釋掉後列印如下資訊:
這下就可以想想什麼el的判斷要在template之前了~是因為vue需要通過el找到對應的outer template。
在vue物件中還有一個render函式,它是以createElement作為引數,然後做渲染操作,而且我們可以直接嵌入JSX.
new Vue({
el: '#app',
render: function(createElement) {
return createElement('h1', 'this is createElement')
}
})
複製程式碼
所以綜合排名優先順序: render函式選項 > template選項 > outer HTML.
總之在這個階段就是判斷是否有el,或者template。然後進行處理
[3] beforeMount和mounted 鉤子函式間的生命週期
這段時間就是替換$el,將{{message}}轉為真正的內容。
[4] mounted
在mounted之前h1中還是通過{{message}}進行佔位的,因為此時掛在到頁面上的還是JavaScript中的虛擬DOM形式。在mounted之後可以看到h1中的內容發生了變化。
[5] beforeUpdate鉤子函式和updated鉤子函式間的生命週期
當vue發現data中的資料發生了改變,會觸發對應元件的重新渲染,先後呼叫beforeUpdate和updated鉤子函式。
[6] beforeDestroy和destroyed鉤子函式間的生命週期
beforeDestroy鉤子函式在例項銷燬之前呼叫。在這一步,例項仍然完全可用。 destroyed鉤子函式在Vue 例項銷燬後呼叫。呼叫後,Vue 例項指示的所有東西都會解繫結,所有的事件監聽器會被移除,所有的子例項也會被銷燬。
六、自定義指令
在程式碼的例項中
指令鉤子函式會被傳入以下引數:
el:指令所繫結的元素,可以用來直接操作 DOM 。
binding:一個物件,包含以下屬性:
- name:指令名,不包括 v- 字首。
- value:指令的繫結值,例如:v-my-directive="1 + 1" 中,繫結值為 2。
- oldValue:指令繫結的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
- expression:字串形式的指令表示式。例如 v-my-directive="1 + 1" 中,表示式為 "1 + 1"。
- arg:傳給指令的引數,可選。例如 v-my-directive:foo 中,引數為 "foo"。
- modifiers:一個包含修飾符的物件。例如:v-my-directive.foo.bar 中,修飾符物件為 { foo: true, bar: true }。
- vnode:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
- oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。
複製程式碼
七、Vue.js 外掛開發詳解
八、元件style的scoped
問題:在元件中用js動態建立的dom,新增樣式不生效。
複製程式碼
<template>
<div class="test"></div>
</template>
<script>
let a=document.querySelector('.test');
let newDom=document.createElement("div"); // 建立dom
newDom.setAttribute("class","testAdd" ); // 新增樣式
a.appendChild(newDom); // 插入dom
</script>
<style scoped>
.test{
background:blue;
height:100px;
width:100px;
}
.testAdd{
background:red;
height:100px;
width:100px;
}
</style>
複製程式碼
- 結果
// test生效 testAdd 不生效
<div data-v-1b971ada class="test"><div class="testAdd"></div></div>
.test[data-v-1b971ada]{ // 注意data-v-1b971ada
background:blue;
height:100px;
width:100px;
}
複製程式碼
原因
當 <style>
標籤有 scoped 屬性時,它的 CSS 只作用於當前元件中的元素。
它會為元件中所有的標籤和class
樣式新增一個scoped
標識,就像上面結果中的data-v-1b971ada
。
所以原因就很清楚了:因為動態新增的dom沒有scoped
新增的標識,沒有跟testAdd
的樣式匹配起來,導致樣式失效。
- 解決辦法:
去掉scoped即可。
九、Vue 陣列/物件更新 檢視不更新
data() { // data資料
return {
arr: [1,2,3],
obj:{
a: 1,
b: 2
}
};
},
// 資料更新 陣列檢視不更新
this.arr[0] = 'OBKoro1';
this.arr.length = 1;
console.log(arr);// ['OBKoro1'];
// 資料更新 物件檢視不更新
this.obj.c = 'OBKoro1';
delete this.obj.a;
console.log(obj); // {b:2,c:'OBKoro1'}
複製程式碼
由於js的限制,Vue 不能檢測以上陣列的變動,以及物件的新增/刪除,很多人會因為像上面這樣操作,出現檢視沒有更新的問題。
解決方式:
- 1、this.$set(你要改變的陣列/物件,你要改變的位置/key,你要改成什麼value)
this.$set(this.arr, 0, "OBKoro1"); // 改變陣列
this.$set(this.obj, "c", "OBKoro1"); // 改變物件
複製程式碼
- 2、陣列原生方法觸發檢視更新:
Vue可以監測到陣列變化的,陣列原生方法:
splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
複製程式碼
意思是使用這些方法不用我們再進行額外的操作,檢視自動進行更新。
十、Vue 過濾器的作用
過濾器,通常用於後臺管理系統,或者一些約定型別,過濾。Vue過濾器用法是很簡單,但是很多朋友可能都沒有用過。
<!-- 在雙花括號中 -->
{{ message | filterTest }}
<!-- 在 `v-bind` 中 -->
<div :id="message | filterTest"></div>
複製程式碼
export default {
data() {
return {
message:1
}
},
filters: {
filterTest(value) {
// value在這裡是message的值
if(value===1){
return '最後輸出這個值';
}
}
}
}
複製程式碼
用法就是上面講的這樣,可以自己在元件中試一試就知道了,很簡單很好用的。
十一、列表渲染相關
1、v-for迴圈繫結model:
input在v-for中可以像如下這麼進行繫結,我敢打賭很多人不知道。
// 資料
data() {
return{
// 這裡設定 key / value
obj: {
ob: "OB",
koro1: "Koro1"
},
// model 儲存
model: {
ob: "預設ob",
koro1: "預設koro1"
}
}
},
// html模板
<div v-for="(value,key) in obj">
<input type="text" v-model="model[key]">
</div>
// input就跟資料繫結在一起了,那兩個預設資料也會在input中顯示
複製程式碼
2、v-if儘量不要與v-for在同一節點使用:
v-for
的優先順序比 v-if
更高,如果它們處於同一節點的話,那麼每一個迴圈都會執行一遍v-if。
如果你想根據迴圈中的每一項的資料來判斷是否渲染,那麼你這樣做是對的:
<li v-for="todo in todos" v-if="todo.type===1">
{{ todo }}
</li>
複製程式碼
如果你想要根據某些條件跳過迴圈,而又跟將要渲染的每一項資料沒有關係的話,你可以將v-if放在v-for的父節點:
// 陣列是否有資料 跟每個元素沒有關係
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
複製程式碼
如上,正確使用v-for與v-if優先順序的關係,可以為你節省大量的效能。
十二、深度watch和watch立即觸發回撥
watch很多人都在用,但是這watch中的這兩個選項deep、immediate,或許不是很多人都知道,我猜。
- deep
在選項引數中指定 deep: true,可以監聽物件中屬性的變化。
- immediate
在選項引數中指定 immediate: true
, 將立即以表示式的當前值觸發回撥,也就是立即觸發一次。
watch: {
obj: {
handler(val, oldVal) {
console.log('屬性發生變化觸發這個回撥',val, oldVal);
},
deep: true // 監聽這個物件中的每一個屬性變化
},
step: { // 屬性
//watch
handler(val, oldVal) {
console.log("預設立即觸發一次", val, oldVal);
},
immediate: true // 預設立即觸發一次
},
},
複製程式碼
十三、這些情況下不要使用箭頭函式:
- 不應該使用箭頭函式來定義一個生命週期方法
- 不應該使用箭頭函式來定義 method 函式
- 不應該使用箭頭函式來定義計算屬性函式
- 不應該對 data 屬性使用箭頭函式
- 不應該使用箭頭函式來定義 watcher 函式
箭頭函式繫結了父級作用域的上下文,this 將不會按照期望指向 Vue 例項。 也就是說,你不能使用this來訪問你元件中的data資料以及method方法了。 this將會指向undefined。
十四、路由懶載入寫法
// 我所採用的方法,個人感覺比較簡潔一些,少了一步引入賦值。
const router = new VueRouter({
routes: [
path: '/app',
component: () => import('./app'), // 引入元件
]
})
// Vue路由文件的寫法:
const app = () => import('./app.vue') // 引入元件
const router = new VueRouter({
routes: [
{ path: '/app', component: app }
]
})
複製程式碼
如果我們的路由比較多的話,是不是要在路由上方引入十幾行元件?
第一種跟第二種方法相比就是把引入賦值的一步,直接寫在component上面,本質上是一樣的。兩種方式都可以的,只是第一種是懶載入的方式。
十五、專案啟動路由和404
export default new Router({
routes: [
{
path: '/', // 專案啟動頁
redirect:'/login' // 重定向到下方宣告的路由
},
{
path: '*', // 404 頁面
component: () => import('./notFind') // 或者使用component也可以的
},
]
})
複製程式碼