原文有 36 道 vue 經常使用面試題,考慮到太多一次也看不完,因此分爲 上、中、下三篇,若是你能讀完這三篇文章,相信你在面試中 vue 的問題你不會怕了。html
答案:在 beforeDestroy 中銷燬定時器。前端
① 爲何銷燬它:vue
在頁面 a 中寫了一個定時器,好比每隔一秒鐘打印一次 1,當我點擊按鈕進入頁面 b 的時候,會發現定時器依然在執行,這是很是消耗性能的。node
② 解決方案 1:es6
mounted(){ this.timer = setInterval(()=>{ console.log(1) },1000)},beforeDestroy(){ clearInterval(this.timer)}
方案 1 有兩點很差的地方,引用尤大的話來講就是:面試
它須要在這個組件實例中保存這個 timer,若是能夠的話最好只有生命週期鉤子能夠訪問到它。這並不算嚴重的問題,可是它能夠被視爲雜物。vue-router
咱們的創建代碼獨立於咱們的清理代碼,這使得咱們比較難於程序化的清理咱們創建的全部東西。express
方案 2(推薦):該方法是經過$once 這個事件偵聽器在定義完定時器以後的位置來清除定時器後端
mounted(){ const timer = setInterval(()=>{ console.log(1) },1000) this.$once('hook:beforeDestroy',()=>{ clearInterval(timer) })}
官網參考連接:https://cn.vuejs.org/v2/guide...api
① 先說,父組件如何主動獲取子組件的數據?
方案 1:$children
$children 用來訪問子組件實例,要知道一個組件的子組件多是不惟一的,因此它的返回值是數組。
如今,咱們定義 Header,HelloWorld 兩個組件
<template> <div class="index"> <Header></Header> <HelloWorld :message="message"></HelloWorld> <button @click="goPro">跳轉</button> </div></template>mounted(){ console.log(this.$children)}
打印的是一個數組,能夠用 foreach 分別獲得所須要的的數據
缺點:
沒法肯定子組件的順序,也不是響應式的。若是你確切的知道要訪問子組件建議使用$refs。
方案 2 :$refs
<HelloWorld ref="hello" :message="message"></HelloWorld>
調用 helloworld 子組件的時候直接定義一個 ref,這樣就能夠經過 this.$refs 獲取所須要的的數據。
this.$refs.hello.屬性this.$refs.hello.方法
② 子組件如何主動獲取父組件中的數據?
經過 :$parent
用來訪問父組件實例,一般父組件都是惟一肯定的,跟children 相似
this.$parent.屬性this.$parent.方法
父子組件通訊除了以上三種,還有 props 和 attrs
③inheritAttrs
這是@2.4 新增的屬性和接口。inheritAttrs 屬性控制子組件 html 屬性上是否顯示父組件的提供的屬性。
若是咱們將父組件 Index 中的屬性 desc、keysword、message 三個數據傳遞到子組件 HelloWorld 中的話,以下
父組件 Index 部分
<HelloWorld ref="hello" :desc="desc" :keysword="keysword" :message="message"></HelloWorld>
子組件:HelloWorld,props 中只接受了 message
props: { message: String},
實際狀況,咱們只須要 message,那其餘兩個屬性則會被當作普通的 html 元素插在子組件的根元素上。
如圖這樣作會使組件預期功能變得模糊不清,這個時候,在子組件中寫入,inheritAttrs:false ,這些沒用到的屬性便會被去掉,true 的話,就會顯示。
若是,父組件中沒被須要的屬性,跟子組件原本的屬性衝突的時候,則依據父組件
<HelloWorld ref="hello" type="text" :message="message"></HelloWorld>
子組件:HelloWorld
<template> <input type="number"></template>
這個時候父組件中 type=「text」,而子組件中 type=」number」,而實際中最後顯示的是 type=」text」,這並非咱們想要的,因此只要設置:inheritAttrs:false,type 便會成爲 number上述這些沒被用到的屬性,如何被獲取呢?這就用到了$attrs
③$attrs
做用:能夠獲取到沒有使用的註冊屬性,若是須要,咱們在這也能夠往下繼續傳遞。
就上上述沒有被用到的 desc 和 keysword 就能經過$attrs 獲取到。
經過$attrs 的這個特性能夠父組件傳遞到孫組件,免除父組件傳遞到子組件,再從子組件傳遞到孫組件的麻煩
代碼以下 父組件 Index 部分
<div class="index"> <HelloWorld ref="hello" :desc="desc" :keysword="keysword" :message="message"></HelloWorld></div> data(){ return{ message:'首頁', desc:'首頁描述', keysword:'我是關鍵詞key' }},
子組件 HelloWorld 部分
<div class="hello"> <sunzi v-bind="$attrs"></sunzi> <button @click="aa">獲取父組件的數據</button></div>
孫子組件 sunzi 部分
<template> <div class="header"> {{$attrs}} <br> </div></template>
能夠看出經過 v-bind=」$attrs」將數據傳到孫組件中
除了以上,provide / inject 也適用於 隔代組件通訊,尤爲是獲取祖先組件的數據,很是方便。簡單的說,當組件的引入層次過多,咱們的子孫組件想要獲取祖先組件的資源,那麼怎麼辦呢,總不能一直取父級往上吧,並且這樣代碼結構容易混亂。這個就是 provide / inject 要乾的事情。
<template> <div><childOne></childOne> </div></template><script> import childOne from '../components/test/ChildOne' export default { name: "Parent", provide: { for: "demo" }, components:{ childOne } }
在這裏咱們在父組件中 provide for 這個變量,而後直接設置三個組件(childOne、childTwo 、childThird)而且一層層不斷內嵌其中, 而在最深層的 childThird 組件中咱們能夠經過 inject 獲取 for 這個變量
<template> <div> {{demo}} </div></template><script> export default { name: "", inject: ['for'], data() { return { demo: this.for } } }</script>
經過 Vue.directive() 來定義全局指令
有幾個可用的鉤子(生命週期), 每一個鉤子能夠選擇一些參數. 鉤子以下:
bind: 一旦指令附加到元素時觸發
inserted: 一旦元素被添加到父元素時觸發
update: 每當元素自己更新(可是子元素還未更新)時觸發
componentUpdate: 每當組件和子組件被更新時觸發
unbind: 一旦指令被移除時觸發。
bind 和 update 也許是這五個裏面最有用的兩個鉤子了
每一個鉤子都有 el, binding, 和 vnode 參數可用.
update 和 componentUpdated 鉤子還暴露了 oldVnode, 以區分傳遞的舊值和較新的值.
el 就是所綁定的元素.
binding 是一個保護傳入鉤子的參數的對象. 有不少可用的參數, 包括 name, value, oldValue, expression, arguments, arg 及修飾語.
vnode 有一個更不尋常的用例, 它可用於你須要直接引用到虛擬 DOM 中的節點.
binding 和 vnode 都應該被視爲只讀.
如今,自定義一個指令,添加一些樣式,表示定位的距離
Vue.directive('tack',{ bind(el,binding){ el.style.position='fixed'; el.style.top=binding.value + 'px' }})<div class="header" v-tack="10" >我是header</div>
假設咱們想要區分從頂部或者左側偏移 70px, 咱們能夠經過傳遞一個參數來作到這一點
Vue.directive('tack', { bind(el, binding, vnode) { el.style.position = 'fixed'; const s = (binding.arg === 'left' ? 'left' : 'top'); el.style[s] = binding.value + 'px'; }})
也能夠同時傳入不止一個值
Vue.directive('tack', { bind(el, binding, vnode) { el.style.position = 'fixed'; el.style.top = binding.value.top + 'px'; el.style.left = binding.value.left + 'px'; }})<div class="header" v-tack="{left:’20’,top:’20’}" >我是header</div>
breforeCreate():實例建立前,這個階段實例的 data 和 methods 是讀不到的。
created():實例建立後,這個階段已經完成數據觀測,屬性和方法的運算,watch/event 事件回調,mount 掛載階段尚未開始。$el 屬性目前不可見,數據並無在 DOM 元素上進行渲染。
created 完成以後,進行 template 編譯等操做,將 template 編譯爲 render 函數,有了 render 函數後纔會執行 beforeMount()
beforeMount():在掛載開始以前被調用:相關的 render 函數首次被調用
mounted():掛載以後調用,el 選項的 DOM 節點被新建立的 vm.$el 替換,並掛載到實例上去以後調用今生命週期函數,此時實例的數據在 DOM 節點上進行渲染
後續的鉤子函數執行的過程都是須要外部的觸發纔會執行
有數據的變化,會調用 beforeUpdate,而後通過 Virtual Dom,最後 updated 更新完畢,當組件被銷燬的時候,會調用 beforeDestory,以及 destoryed。
computed:
① 有緩存機制;② 不能接受參數;③ 能夠依賴其餘 computed,甚至是其餘組件的 data;④ 不能與 data 中的屬性重複
watch:
① 可接受兩個參數;② 監聽時可觸發一個回調,並作一些事情;③ 監聽的屬性必須是存在的;④ 容許異步
watch 配置:handler、deep(是否深度)、immeditate (是否當即執行)
總結:
當有一些數據須要隨着另一些數據變化時,建議使用 computed
當有一個通用的響應數據變化的時候,要執行一些業務邏輯或異步操做的時候建議使用 watch
① computed 中能夠分紅 getter(讀取) 和 setter(設值)
② 通常狀況下是沒有 setter 的,computed 預設只有 getter ,也就是隻能讀取,不能改變設值。
1、默認只有 getter 的寫法
<div id="demo">{{ fullName }}</div>var vm = new Vue({ el: '#demo', data: { firstName: 'Foo', lastName: 'Bar' }, computed: { fullName: function () { return this.firstName + ' ' + this.lastName } }})//其實fullName的完整寫法應該是以下:fullName: { get(){ return this.firstName + ' ' + this.lastName }}
注意:不是說咱們更改了 getter 裏使用的變量,就會觸發 computed 的更新,前提是 computed 裏的值必需要在模板裏使用才行。若是將{{fullName}}去掉,get()方法是不會觸發的。
2、setter 的寫法,能夠設值
<template> <div id="demo"> <p> {{ fullName }} </p> <input type="text" v-model="fullName"> <input type="text" v-model="firstName"> <input type="text" v-model="lastName"> </div></template>var vm = new Vue({ el: '#demo', data: { firstName: 'zhang', lastName: 'san' }, computed: { fullName: { //getter 方法 get(){ console.log('computed getter...') return this.firstName + ' ' + this.lastName }, //setter 方法 set(newValue){ console.log('computed setter...') var names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] return this.firstName + ' ' + this.lastName } } }})
在這裏,咱們修改 fullName 的值,就會觸發 setter,同時也會觸發 getter。
注意:並非觸發了 setter 也就會觸發 getter,他們兩個是相互獨立的。咱們這裏修改了 fullName 會觸發 getter 是由於 setter 函數裏有改變 firstName 和 lastName 值的代碼,這兩個值改變了,fullName 依賴於這兩個值,因此便會自動改變。
① 全局導航守衛
前置守衛
router.beforeEach((to, from, next) => { // do someting});
後置鉤子(沒有 next 參數)
router.afterEach((to, from) => { // do someting});
② 路由獨享守衛
cont router = new VueRouter({ routes: [ { path: '/file', component: File, beforeEnter: (to, from ,next) => { // do someting } } ]});
順便看一下路由裏面的參數配置:
③ 組件內的導航鉤子
組件內的導航鉤子主要有這三種:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他們是直接在路由組件內部直接進行定義的
beforeRouteEnter
data(){ return{ pro:'產品' }},beforeRouteEnter:(to,from,next)=>{ console.log(to) next(vm => { console.log(vm.pro) })}
注:beforeRouteEnter 不能獲取組件實例 this,由於當守衛執行前,組件實例被沒有被建立出來,咱們能夠經過給 next 傳入一個回調來訪問組件實例。在導航被確認時,會執行這個回調,這時就能夠訪問組件實例了
僅僅是 beforRouteEnter 支持給 next 傳遞迴調,其餘兩個並不支持,由於剩下兩個鉤子能夠正常獲取組件實例 this
如何經過路由將數據傳入下一個跳轉的頁面呢?
答:params 和 query
params
傳參this.$router.push({ name:"detail", params:{ name:'xiaoming', }});接受this.$route.params.name
query
傳參this.$router.push({ path:'/detail', query:{ name:"xiaoming" } })接受 //接收參數是this.$routethis.$route.query.id
那 query 和 params 什麼區別呢?
① params 只能用 name 來引入路由,query 既能夠用 name 又能夠用 path(一般用 path)
② params 相似於 post 方法,參數不會再地址欄中顯示query 相似於 get 請求,頁面跳轉的時候,能夠在地址欄看到請求參數
那剛纔提到的 this.和route 有何區別?
先打印出來看一下 router.push 方法
$route 爲當前 router 跳轉對象,裏面能夠獲取 name、path、query、params 等
es6 新增的主要的特性:
① let const 二者都有塊級做用域
② 箭頭函數
③ 模板字符串
④ 解構賦值
⑤ for of 循環
⑥ import 、export 導入導出
⑦ set 數據結構
⑧ ...展開運算符
⑨ 修飾器 @
⑩ class 類繼承
⑪ async、await
⑫ promise
⑬ Symbol
⑭ Proxy 代理
操做數組經常使用的方法:
es5:concat 、join 、push、pop、shift、unshift、slice、splice、substring 和 substr 、sort、 reverse、indexOf 和 lastIndexOf 、every、some、filter、map、forEach、reduce
es6:find、findIndex、fill、copyWithin、Array.from、Array.of、entries、values、key、includes
經過 Object.defineProperty()來劫持各個屬性的 setter,getter,在數據變更時發佈消息給訂閱者,觸發相應的監聽回調
vue-router 有兩種模式,hash 模式和 history 模式
hash 模式
url 中帶有#的即是 hash 模式,#後面是 hash 值,它的變化會觸發 hashchange 這個事件。
經過這個事件咱們就能夠知道 hash 值發生了哪些變化。而後咱們即可以監聽 hashchange 來實現更新頁面部份內容的操做:
window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1); document.body.style.color = hash;}
另外,hash 值的變化,並不會致使瀏覽器向服務器發出請求,瀏覽器不發出請求,也就不會刷新頁面。
history 模式
history api 能夠分爲兩大部分,切換和修改
① 切換歷史狀態
包括 back,forward,go 三個方法,對應瀏覽器的前進,後退,跳轉操做
history.go(-2);//後退兩次history.go(2);//前進兩次history.back(); //後退hsitory.forward(); //前進
② 修改歷史狀態
包括了 pushState,replaceState 兩個方法,這兩個方法接收三個參數:stateObj,title,url
history.pushState({color:'red'}, 'red', 'red'})window.onpopstate = function(event){ console.log(event.state) if(event.state && event.state.color === 'red'){ document.body.style.color = 'red'; }}history.back();history.forward();
經過 pushstate 把頁面的狀態保存在 state 對象中,當頁面的 url 再變回這個 url 時,能夠經過 event.state 取到這個 state 對象,從而能夠對頁面狀態進行還原,這裏的頁面狀態就是頁面字體顏色,其實滾動條的位置,閱讀進度,組件的開關的這些頁面狀態均可以存儲到 state 的裏面。
history 缺點:
1:hash 模式下,僅 hash 符號以前的內容會被包含在請求中,如http://www.a12c.com,所以對於...,即便沒有作到對路由的全覆蓋,也不會返回 404 錯誤。
2:history 模式下,前端的 URL 必須和實際向後端發起請求的 URL 一致。如http://www.a12c.com/book/a。若是後端缺乏對/book/a 的路由處理,將返回 404 錯誤
往期
127個經常使用的JS代碼片斷,每段代碼花30秒就能看懂(三)
127個經常使用的JS代碼片斷,每段代碼花30秒就能看懂(四)
[
](http://mp.weixin.qq.com/s?__b...