一、active-class是哪一個組件的屬性?嵌套路由怎麼定義?
答:vue-router模塊的router-link組件。php
二、怎麼定義vue-router的動態路由?怎麼獲取傳過來的動態參數?
答:在router目錄下的index.js文件中,對path屬性加上/:id。 使用router對象的params.id css
三、vue-router有哪幾種導航鉤子?
答:三種,一種是全局導航鉤子:router.beforeEach(to,from,next),做用:跳轉前進行判斷攔截。第二種:組件內的鉤子;第三種:單獨路由獨享組件html
四、scss是什麼?安裝使用的步驟是?有哪幾大特性?
答:預處理css,把css當前函數編寫,定義變量,嵌套。 先裝css-loader、node-loader、sass-loader等加載器模塊,在webpack-base.config.js配置文件中加多一個拓展:extenstion,再加多一個模塊:module裏面test、loader前端
4.一、scss是什麼?在vue.cli中的安裝使用步驟是?有哪幾大特性?
答:css的預編譯。vue
使用步驟:html5
第一步:用npm 下三個loader(sass-loader、css-loader、node-sass)java
第二步:在build目錄找到webpack.base.config.js,在那個extends屬性中加一個拓展.scssnode
第三步:仍是在同一個文件,配置一個module屬性react
第四步:而後在組件的style標籤加上lang屬性 ,例如:lang=」scss」jquery
有哪幾大特性:
一、能夠用變量,例如($變量名稱=值);
二、能夠用混合器,例如()
三、能夠嵌套
五、mint-ui是什麼?怎麼使用?說出至少三個組件使用方法?
答:基於vue的前端組件庫。npm安裝,而後import樣式和js,vue.use(mintUi)全局引入。在單個組件局部引入:import {Toast} from ‘mint-ui’。組件一:Toast(‘登陸成功’);組件二:mint-header;組件三:mint-swiper
六、v-model是什麼?怎麼使用? vue中標籤怎麼綁定事件?
答:能夠實現雙向綁定,指令(v-class、v-for、v-if、v-show、v-on)。vue的model層的data屬性。綁定事件:<input @click=doLog() />
七、axios是什麼?怎麼使用?描述使用它實現登陸功能的流程?
答:請求後臺資源的模塊。npm install axios -S裝好,而後發送的是跨域,需在配置文件中config/index.js進行設置。後臺若是是Tp5則定義一個資源路由。js中使用import進來,而後.get或.post。返回在.then函數中若是成功,失敗則是在.catch函數中
八、axios+tp5進階中,調用axios.post(‘api/user’)是進行的什麼操做?axios.put(‘api/user/8′)呢?
答:跨域,添加用戶操做,更新操做。
九、什麼是RESTful API?怎麼使用?
答:是一個api的標準,無狀態請求。請求的路由地址是固定的,若是是tp5則先路由配置中把資源路由配置好。標準有:.post .put .delete
十、vuex是什麼?怎麼使用?哪一種功能場景使用它?
答:vue框架中狀態管理。在main.js引入store,注入。新建了一個目錄store,….. export 。場景有:單頁應用中,組件之間的狀態。音樂播放、登陸狀態、加入購物車
十一、mvvm框架是什麼?它和其它框架(jquery)的區別是什麼?哪些場景適合?
答:一個model+view+viewModel框架,數據模型model,viewModel鏈接兩個
區別:vue數據驅動,經過數據來顯示視圖層而不是節點操做。
場景:數據操做比較多的場景,更加便捷
十二、自定義指令(v-check、v-focus)的方法有哪些?它有哪些鉤子函數?還有哪些鉤子函數參數?
答:全局定義指令:在vue對象的directive方法裏面有兩個參數,一個是指令名稱,另一個是函數。組件內定義指令:directives
鉤子函數:bind(綁定事件觸發)、inserted(節點插入的時候觸發)、update(組件內相關更新)
鉤子函數參數:el、binding
1三、說出至少4種vue當中的指令和它的用法?
答:v-if:判斷是否隱藏;v-for:數據循環出來;v-bind:class:綁定一個屬性;v-model:實現雙向綁定
1四、vue-router是什麼?它有哪些組件?
答:vue用來寫路由一個插件。router-link、router-view
1五、導航鉤子有哪些?它們有哪些參數?
答:導航鉤子有:a/全局鉤子和組件內獨享的鉤子。b/beforeRouteEnter、afterEnter、beforeRouterUpdate、beforeRouteLeave
參數:有to(去的那個路由)、from(離開的路由)、next(必定要用這個函數才能去到下一個路由,若是不用就攔截)最經常使用就這幾種
1六、Vue的雙向數據綁定原理是什麼?
答:vue.js 是採用數據劫持結合發佈者-訂閱者模式的方式,經過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變更時發佈消息給訂閱者,觸發相應的監聽回調。
具體步驟:
第一步:須要observe的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter和getter
這樣的話,給這個對象的某個值賦值,就會觸發setter,那麼就能監聽到了數據變化
第二步:compile解析模板指令,將模板中的變量替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,更新視圖
第三步:Watcher訂閱者是Observer和Compile之間通訊的橋樑,主要作的事情是:
一、在自身實例化時往屬性訂閱器(dep)裏面添加本身
二、自身必須有一個update()方法
三、待屬性變更dep.notice()通知時,能調用自身的update()方法,並觸發Compile中綁定的回調,則功成身退。
第四步:MVVM做爲數據綁定的入口,整合Observer、Compile和Watcher三者,經過Observer來監聽本身的model數據變化,經過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通訊橋樑,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據model變動的雙向綁定效果。
ps:16題答案一樣適合」vue data是怎麼實現的?」此面試題。
1七、請詳細說下你對vue生命週期的理解?
答:總共分爲8個階段建立前/後,載入前/後,更新前/後,銷燬前/後。
建立前/後: 在beforeCreated階段,vue實例的掛載元素$el和數據對象data都爲undefined,還未初始化。在created階段,vue實例的數據對象data有了,$el尚未。
載入前/後:在beforeMount階段,vue實例的$el和data都初始化了,但仍是掛載以前爲虛擬的dom節點,data.message還未替換。在mounted階段,vue實例掛載完成,data.message成功渲染。
更新前/後:當data變化時,會觸發beforeUpdate和updated方法。
銷燬前/後:在執行destroy方法後,對data的改變不會再觸發周期函數,說明此時vue實例已經解除了事件監聽以及和dom的綁定,可是dom結構依然存在
1八、請說下封裝 vue 組件的過程?
答:首先,組件能夠提高整個項目的開發效率。可以把頁面抽象成多個相對獨立的模塊,解決了咱們傳統項目開發:效率低、難維護、複用性等問題。
而後,使用Vue.extend方法建立一個組件,而後使用Vue.component方法註冊組件。子組件須要數據,能夠在props中接受定義。而子組件修改好數據後,想把數據傳遞給父組件。能夠採用emit方法。
1九、你是怎麼認識vuex的?
答:vuex能夠理解爲一種開發模式或框架。好比PHP有thinkphp,java有spring等。
經過狀態(數據源)集中管理驅動組件的變化(比如spring的IOC容器對bean進行集中管理)。
應用級的狀態集中放在store中; 改變狀態的方式是提交mutations,這是個同步的事物; 異步邏輯應該封裝在action中。
20、vue-loader是什麼?使用它的用途有哪些?
答:解析.vue文件的一個加載器,跟template/js/style轉換成js模塊。
用途:js能夠寫es六、style樣式能夠scss或less、template能夠加jade等
2一、請說出vue.cli項目中src目錄每一個文件夾和文件的用法?
答:assets文件夾是放靜態資源;components是放組件;router是定義路由相關的配置;view視圖;app.vue是一個應用主組件;main.js是入口文件
2二、vue.cli中怎樣使用自定義的組件?有遇到過哪些問題嗎?
答:第一步:在components目錄新建你的組件文件(smithButton.vue),script必定要export default {
第二步:在須要用的頁面(組件)中導入:import smithButton from ‘../components/smithButton.vue’
第三步:注入到vue的子組件的components屬性上面,components:{smithButton}
第四步:在template視圖view中使用,<smith-button> </smith-button>
問題有:smithButton命名,使用的時候則smith-button。
2三、聊聊你對Vue.js的template編譯的理解?
答:簡而言之,就是先轉化成AST樹,再獲得的render函數返回VNode(Vue的虛擬DOM節點)
詳情步驟:
首先,經過compile編譯器把template編譯成AST語法樹(abstract syntax tree 即 源代碼的抽象語法結構的樹狀表現形式),compile是createCompiler的返回值,createCompiler是用以建立編譯器的。另外compile還負責合併option。
而後,AST會通過generate(將AST語法樹轉化成render funtion字符串的過程)獲得render函數,render的返回值是VNode,VNode是Vue的虛擬DOM節點,裏面有(標籤名、子節點、文本等等)
挑戰一下:
一、vue響應式原理?
1.把一個普通 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter。
2.組件實例的 watcher 實例對象,
初步
最近一段時間在閱讀Vue源碼,從它的核心原理入手,開始了源碼的學習,而其核心原理就是其數據的響應式,講到Vue的響應式原理,咱們能夠從它的兼容性提及,Vue不支持IE8如下版本的瀏覽器,由於Vue是基於 Object.defineProperty 來實現數據響應的,而 Object.defineProperty 是 ES5 中一個沒法 shim 的特性,這也就是爲何 Vue 不支持 IE8 以及更低版本瀏覽器的緣由;Vue經過Object.defineProperty的 getter/setter 對收集的依賴項進行監聽,在屬性被訪問和修改時通知變化,進而更新視圖數據;
受現代JavaScript 的限制 (以及廢棄 Object.observe),Vue不能檢測到對象屬性的添加或刪除。因爲 Vue 會在初始化實例時對屬性執行 getter/setter 轉化過程,因此屬性必須在 data 對象上存在才能讓Vue轉換它,這樣才能讓它是響應的。 <a id=「more」></a>
咱們這裏是根據Vue2.3源碼進行分析,Vue數據響應式變化主要涉及 Observer, Watcher , Dep 這三個主要的類;所以要弄清Vue響應式變化須要明白這個三個類之間是如何運做聯繫的;以及它們的原理,負責的邏輯操做。那麼咱們從一個簡單的Vue實例的代碼來分析Vue的響應式原理
var vue = new Vue({
el: "#app",
data: {
name: 'Junga'
},
created () {
this.helloWorld()
},
methods: {
helloWorld: function() {
console.log('my name is' + this.name)
}
}
...
})
Vue初始化實例
根據Vue的生命週期咱們知道,Vue首先會進行init初始化操做;源碼在src/core/instance/init.js中
/*初始化生命週期*/
initLifecycle(vm)
/*初始化事件*/
initEvents(vm)Object.defineProperty
/*初始化render*/
initRender(vm)
/*調用beforeCreate鉤子函數而且觸發beforeCreate鉤子事件*/
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
/*初始化props、methods、data、computed與watch*/
initState(vm)
initProvide(vm) // resolve provide after data/props
/*調用created鉤子函數而且觸發created鉤子事件*/
callHook(vm, 'created')
以上代碼能夠看到 initState(vm) 是用來初始化props,methods,data,computed和watch;
src/core/instance/state.js
/*初始化props、methods、data、computed與watch*/
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
/*初始化props*/
if (opts.props) initProps(vm, opts.props)
/*初始化方法*/
if (opts.methods) initMethods(vm, opts.methods)
/*初始化data*/
if (opts.data) {
initData(vm)
} else {
/*該組件沒有data的時候綁定一個空對象*/
observe(vm._data = {}, true /* asRootData */)
}
/*初始化computed*/
if (opts.computed) initComputed(vm, opts.computed)
/*初始化watchers*/
if (opts.watch) initWatch(vm, opts.watch)
}
...
/*初始化data*/
function initData (vm: Component) {
/*獲得data數據*/
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}defi
...
//遍歷data中的數據
while (i--) {
/*保證data中的key不與props中的key重複,props優先,若是有衝突會產生warning*/
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(keys[i])) {
/*判斷是不是保留字段*/
/*這裏是咱們前面講過的代理,將data上面的屬性代理到了vm實例上*/
proxy(vm, `_data`, keys[i])
}
}
// observe data
/*這裏經過observe實例化Observe對象,開始對數據進行綁定,asRootData用來根數據,用來計算實例化根數據的個數,下面會進行遞歸observe進行對深層對象的綁定。則asRootData爲非true*/
observe(data, true /* asRootData */)
}
一、initData
如今咱們重點分析下initData,這裏主要作了兩件事,一是將_data上面的數據代理到vm上,二是經過執行 observe(data, true / asRootData /)將全部data變成可觀察的,即對data定義的每一個屬性進行getter/setter操做,這裏就是Vue實現響應式的基礎;observe的實現以下 src/core/observer/index.js
/*嘗試建立一個Observer實例(__ob__),若是成功建立Observer實例則返回新的Observer實例,若是已有Observer實例則返回現有的Observer實例。*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value)) {
return
}
let ob: Observer | void
/*這裏用__ob__這個屬性來判斷是否已經有Observer實例,若是沒有Observer實例則會新建一個Observer實例並賦值給__ob__這個屬性,若是已有Observer實例則直接返回該Observer實例,這裏能夠看Observer實例化的代碼def(value, '__ob__', this)*/
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
/*這裏的判斷是爲了確保value是單純的對象,而不是函數或者是Regexp等狀況。並且該對象在shouldConvert的時候纔會進行Observer。這是一個標識位,避免重複對value進行Observer
*/
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
/*若是是根數據則計數,後面Observer中的observe的asRootData非true*/
ob.vmCount++
}
return ob
}
這裏 new Observer(value) 就是實現響應式的核心方法之一了,經過它將data轉變能夠成觀察的,而這裏正是咱們開頭說的,用了 Object.defineProperty 實現了data的 getter/setter 操做,經過 Watcher 來觀察數據的變化,進而更新到視圖中。
二、Observer
Observer類是將每一個目標對象(即data)的鍵值轉換成getter/setter形式,用於進行依賴收集以及調度更新。
src/core/observer/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
/* 將Observer實例綁定到data的__ob__屬性上面去,以前說過observe的時候會先檢測是否已經有__ob__對象存放Observer實例了,def方法定義能夠參考/src/core/util/lang.js*/
def(value, '__ob__', this)
if (Array.isArray(value)) {
/*若是是數組,將修改後能夠截獲響應的數組方法替換掉該數組的原型中的原生方法,達到監聽數組數據變化響應的效果。這裏若是當前瀏覽器支持__proto__屬性,則直接覆蓋當前數組對象原型上的原生數組方法,若是不支持該屬性,則直接覆蓋數組對象的原型。*/
const augment = hasProto
? protoAugment /*直接覆蓋原型的方法來修改目標對象*/
: copyAugment /*定義(覆蓋)目標對象或數組的某一個方法*/
augment(value, arrayMethods, arrayKeys)
/*若是是數組則須要遍歷數組的每個成員進行observe*/
this.observeArray(value)
} else {
/*若是是對象則直接walk進行綁定*/
this.walk(value)
},
walk (obj: Object) {
const keys = Object.keys(obj)
/*walk方法會遍歷對象的每個屬性進行defineReactive綁定*/
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
首先將Observer實例綁定到data的ob屬性上面去,防止重複綁定;
若data爲數組,先實現對應的變異方法(這裏變異方法是指Vue重寫了數組的7種原生方法,這裏不作贅述,後續再說明),再將數組的每一個成員進行observe,使之成響應式數據;
不然執行walk()方法,遍歷data全部的數據,進行getter/setter綁定,這裏的核心方法就是 defineReative(obj, keys[i], obj[keys[i]])
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
/*在閉包中定義一個dep對象*/
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/*若是以前該對象已經預設了getter以及setter函數則將其取出來,新定義的getter/setter中會將其執行,保證不會覆蓋以前已經定義的getter/setter。*/
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/*對象的子對象遞歸進行observe並返回子節點的Observer對象*/
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/*若是本來對象擁有getter方法則執行*/
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/*進行依賴收集*/
dep.depend()
if (childOb) {
/*子對象進行依賴收集,其實就是將同一個watcher觀察者實例放進了兩個depend中,一個是正在自己閉包中的depend,另外一個是子元素的depend*/
childOb.dep.depend()
}
if (Array.isArray(value)) {
/*是數組則須要對每個成員都進行依賴收集,若是數組的成員仍是數組,則遞歸。*/
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
/*經過getter方法獲取當前值,與新值進行比較,一致則不須要執行下面的操做*/
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
/*若是本來對象擁有setter方法則執行setter*/
setter.call(obj, newVal)
} else {
val = newVal
}
/*新的值須要從新進行observe,保證數據響應式*/
childOb = observe(newVal)
/*dep對象通知全部的觀察者*/
dep.notify()
}
})
}
其中getter方法:
先爲每一個data聲明一個 Dep 實例對象,被用於getter時執行dep.depend()進行收集相關的依賴;
根據Dep.target來判斷是否收集依賴,仍是普通取值。Dep.target是在何時,如何收集的後面再說明,先簡單瞭解它的做用,
那麼問題來了,咱們爲啥要收集相關依賴呢?
new Vue({
template:
`<div>
<span>text1:</span> {{text1}}
<span>text2:</span> {{text2}}
<div>`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});
咱們能夠從以上代碼看出,data中text3並無被模板實際用到,爲了提升代碼執行效率,咱們沒有必要對其進行響應式處理,所以,依賴收集簡單點理解就是收集只在實際頁面中用到的data數據,而後打上標記,這裏就是標記爲Dep.target。
在setter方法中:
獲取新的值而且進行observe,保證數據響應式;
經過dep對象通知全部觀察者去更新數據,從而達到響應式效果。
在Observer類中,咱們能夠看到在getter時,dep會收集相關依賴,即收集依賴的watcher,而後在setter操做時候經過dep去通知watcher,此時watcher就執行變化,咱們用一張圖描述這三者之間的關係:
從圖咱們能夠簡單理解:Dep能夠看作是書店,Watcher就是書店訂閱者,而Observer就是書店的書,訂閱者在書店訂閱書籍,就能夠添加訂閱者信息,一旦有新書就會經過書店給訂閱者發送消息。
三、Watcher
Watcher是一個觀察者對象。依賴收集之後Watcher對象會被保存在Dep的subs中,數據變更的時候Dep會通知Watcher實例,而後由Watcher實例回調cb進行視圖的更新。
src/core/observer/watcher.js
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/*_watchers存放訂閱者實例*/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
/*把表達式expOrFn解析成getter*/
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
/*得到getter的值而且從新進行依賴收集*/
get () {
/*將自身watcher觀察者實例設置給Dep.target,用以依賴收集。*/
pushTarget(this)
let value
const vm = this.vm
/*執行了getter操做,看似執行了渲染操做,實際上是執行了依賴收集。
在將Dep.target設置爲自生觀察者實例之後,執行getter操做。
譬如說如今的的data中可能有a、b、c三個數據,getter渲染須要依賴a跟c,
那麼在執行getter的時候就會觸發a跟c兩個數據的getter函數,
在getter函數中便可判斷Dep.target是否存在而後完成依賴收集,
將該觀察者對象放入閉包中的Dep的subs中去。*/
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
/*若是存在deep,則觸發每一個深層對象的依賴,追蹤其變化*/
if (this.deep) {
/*遞歸每個對象或者數組,觸發它們的getter,使得對象或數組的每個成員都被依賴收集,造成一個「深(deep)」依賴關係*/
traverse(value)
}
/*將觀察者實例從target棧中取出並設置給Dep.target*/
popTarget()
this.cleanupDeps()
return value
}
/**
* Add a dependency to this directive.
*/
/*添加一個依賴關係到Deps集合中*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
/*清理依賴收集*/
cleanupDeps () {
/*移除全部觀察者對象*/
...
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
/*
調度者接口,當依賴發生改變的時候進行回調。
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
/*同步則執行run直接渲染視圖*/
this.run()
} else {
/*異步推送到觀察者隊列中,下一個tick時調用。*/
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
/*
調度者工做接口,將被調度者回調。
*/
run () {
if (this.active) {
/* get操做在獲取value自己也會執行getter從而調用update更新視圖 */
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
/*
即使值相同,擁有Deep屬性的觀察者以及在對象/數組上的觀察者應該被觸發更新,由於它們的值可能發生改變。
*/
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
/*設置新的值*/
this.value = value
/*觸發回調*/
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
/*獲取觀察者的值*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
/*收集該watcher的全部deps依賴*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
/*將自身從全部依賴收集訂閱列表刪除*/
teardown () {
...
}
}
四、Dep
被Observer的data在觸發 getter 時,Dep 就會收集依賴的 Watcher ,其實 Dep 就像剛纔說的是一個書店,能夠接受多個訂閱者的訂閱,當有新書時即在data變更時,就會經過 Dep 給 Watcher 發通知進行更新。
src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
/*添加一個觀察者對象*/
addSub (sub: Watcher) {
this.subs.push(sub)
}
/*移除一個觀察者對象*/
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
/*依賴收集,當存在Dep.target的時候添加觀察者對象*/
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/*通知全部訂閱者*/
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
總結
其實在 Vue 中初始化渲染時,視圖上綁定的數據就會實例化一個 Watcher,依賴收集就是是經過屬性的 getter 函數完成的,文章一開始講到的 Observer 、Watcher 、Dep 都與依賴收集相關。其中 Observer 與 Dep 是一對一的關係, Dep 與 Watcher 是多對多的關係,Dep 則是 Observer 和 Watcher 之間的紐帶。依賴收集完成後,當屬性變化會執行被 Observer 對象的 dep.notify() 方法,這個方法會遍歷訂閱者(Watcher)列表向其發送消息, Watcher 會執行 run 方法去更新視圖,咱們再來看一張圖總結一下:
在 Vue 中模板編譯過程當中的指令或者數據綁定都會實例化一個 Watcher 實例,實例化過程當中會觸發 get()將自身指向 Dep.target;
data在 Observer 時執行 getter 會觸發 dep.depend() 進行依賴收集;依賴收集的結果:一、data在 Observer 時閉包的dep實例的subs添加觀察它的 Watcher 實例;2. Watcher 的deps中添加觀察對象 Observer 時的閉包dep;
當data中被 Observer 的某個對象值變化後,觸發subs中觀察它的watcher執行 update() 方法,最後其實是調用watcher的回調函數cb,進而更新視圖。
二、vue-router實現原理?
深刻Vue-Router源碼分析路由實現原理
使用Vue開發SPA應用,離不開vue-router,那麼vue和vue-router是如何協做運行的呢,下面從使用的角度,大白話幫你們一步步梳理下vue-router的整個實現流程。
到發文時使用的版本是:
- vue (v2.5.0)
- vue-router (v3.0.1)
1、vue-router 源碼結構
github 地址:https://github.com/vuejs/vue-router
components下是兩個組件<router-view> 和 <router-link>
history是路由方式的封裝,提供三種方式
util下主要是各類功能類和功能函數
create-matcher和create-router-map是生成匹配表
index是VueRouter類,也整個插件的入口
Install 提供安裝的方法
先總體展現下vue-router使用方式,請牢記一下幾步哦。
import Vue from 'vue'
import VueRouter from 'vue-router'
//註冊插件 若是是在瀏覽器環境運行的,能夠不寫該方法
Vue.use(VueRouter)
// 1. 定義(路由)組件。
// 能夠從其餘文件 import 進來
const User = { template: '<div>用戶</div>' }
const Role = { template: '<div>角色</div>' }
// 2. 定義路由
// Array,每一個路由應該映射一個組件。
const routes = [
{ path: '/user', component: User },
{ path: '/home', component: Home }
]
// 3. 建立 router 實例,並傳 `routes` 配置
const router = new VueRouter({
routes
})
// 4. 建立和掛載根實例。
// 記得要經過 router 對象以參數注入Vue,
// 從而讓整個應用都有路由功能
// 使用 router-link 組件來導航.
// 路由出口
// 路由匹配到的組件將渲染在這裏
const app = new Vue({
router,
template: `
<div id="app">
<h1>Basic</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/user">用戶</router-link></li>
<li><router-link to="/role">角色</router-link></li>
<router-link tag="li" to="/user">/用戶</router-link>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app')
分析開始
第一步
Vue是使用.use( plugins )方法將插件注入到Vue中。
use方法會檢測注入插件VueRouter內的install方法,若是有,則執行install方法。
注意:若是是在瀏覽器環境,在index.js內會自動調用.use方法。若是是基於node環境,須要手動調用。
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
Install解析 (對應目錄結構的install.js)
該方法內主要作了如下三件事:
一、對Vue實例混入beforeCreate鉤子操做(在Vue的生命週期階段會被調用)
二、經過Vue.prototype定義router、router、route 屬性(方便全部組件能夠獲取這兩個屬性)
三、Vue上註冊router-link和router-view兩個組件
export function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({
//對Vue實例混入beforeCreate鉤子操做
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
//經過Vue.prototype定義$router、$route 屬性(方便全部組件能夠獲取這兩個屬性)
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
//Vue上註冊router-link和router-view兩個組件
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
第二步 生成router實例
const router = new VueRouter({
routes
})
生成實例過程當中,主要作了如下兩件事
一、根據配置數組(傳入的routes)生成路由配置記錄表。
二、根據不一樣模式生成監控路由變化的History對象
注:History類由HTML5History、HashHistory、AbstractHistory三類繼承
history/base.js實現了基本history的操做
history/hash.js,history/html5.js和history/abstract.js繼承了base,只是根據不一樣的模式封裝了一些基本操做
第三步 生成vue實例
const app = new Vue({
router,
template: `
<div id="app">
<h1>Basic</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/user">用戶</router-link></li>
<li><router-link to="/role">角色</router-link></li>
<router-link tag="li" to="/user">/用戶</router-link>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app')
代碼執行到這,會進入Vue的生命週期,還記得第一步Vue-Router對Vue混入了beforeCreate鉤子嗎,在此會執行哦
Vue.mixin({
beforeCreate () {
//驗證vue是否有router對象了,若是有,就再也不初始化了
if (isDef(this.$options.router)) { //沒有router對象
//將_routerRoot指向根組件
this._routerRoot = this
//將router對象掛載到根組件元素_router上
this._router = this.$options.router
//初始化,創建路由監控
this._router.init(this)
//劫持數據_route,一旦_route數據發生變化後,通知router-view執行render方法
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
//若是有router對象,去尋找根組件,將_routerRoot執行根組件(解決嵌套關係時候_routerRoot指向不一致問題)
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
代碼執行到這,初始化結束,界面將顯示默認首頁
路由更新方式:
1、主動觸發
router-link綁定了click方法,觸發history.push或者history.replace,從而觸發history.transitionTo。
transitionTo用於處理路由轉換,其中包含了updateRoute用於更新_route。
在beforeCreate中有劫持_route的方法,當_route變化後,觸發router-view的變化。
2、地址變化(如:在瀏覽器地址欄直接輸入地址)
HashHistory和HTML5History會分別監控hashchange和popstate來對路由變化做對用的處理 。
HashHistory和HTML5History捕獲到變化後會對應執行push或replace方法,從而調用transitionTo
,剩下的就和上面主動觸發同樣啦。
總結
一、安裝插件
混入beforeCreate生命週期處理,初始化_routerRoot,_router,_route等數據
全局設置vue靜態訪問router和router和route,方便後期訪問
完成了router-link和 router-view 兩個組件的註冊,router-link用於觸發路由的變化,router-view做 爲功能組件,用於觸發對應路由視圖的變化
二、根據路由配置生成router實例
根據配置數組生成路由配置記錄表
生成監控路由變化的hsitory對象
三、將router實例傳入根vue實例
根據beforeCreate混入,爲根vue對象設置了劫持字段_route,用戶觸發router-view的變化
調用init()函數,完成首次路由的渲染,首次渲染的調用路徑是 調用history.transitionTo方法,根據router的match函數,生成一個新的route對象
接着經過confirmTransition對比一下新生成的route和當前的route對象是否改變,改變的話觸發updateRoute,更新hsitory.current屬性,觸發根組件的_route的變化,從而致使組件的調用render函數,更新router-view
另一種更新路由的方式是主動觸發
router-link綁定了click方法,觸發history.push或者history.replace,從而觸發history.transitionTo
同時會監控hashchange和popstate來對路由變化做對用的處理
三、爲何要選vue?與其它框架對比的優點和劣勢?
四、vue如何實現父子組件通訊,以及非父子組件通訊?
五、vuejs與angularjs以及react的區別?
六、vuex是用來作什麼的?
VueX 是一個專門爲 Vue.js 應用設計的狀態管理架構,統一管理和維護各個vue組件的可變化狀態(你能夠理解成 vue 組件裏的某些 data )。以下圖,所示:
1、什麼是Vuex
用學過react的人來講:Vuex 之於 Vue 就像 Redux 之於 React,這邊附上一篇Vuex文檔地址http://vuex.vuejs.org/en/intro.html
Vuex是集中的應用程序架構爲了Vue.js應用程序狀態管理。靈感來自Flux 和 Redux,但簡化的概念和實現是一個專門爲 Vue.js 應用設計的狀態管理架構。
狀態管理: 簡單理解就是統一管理和維護各個vue組件的可變化狀態(你能夠理解成 vue 組件裏的某些 data )
2、咱們爲何須要Vuex
若是你的應用程序很簡單,你可能不須要Vuex。不要過早地應用它。可是若是你正在構建一個medium-to-large-scale SPA,那麼你遇到的狀況,讓你思考如何更好的結構Vue組件以外的事情。這是Vuex發揮做用的地方。
當單獨使用Vue.js,咱們經常傾向於存儲狀態咱們的組件內。也就是說,每一個組件屬於咱們的應用程序狀態,所以結果狀態亂扔的處處都是。然而,有時一塊狀態須要由多個組件共享。常見的作法是讓一個組件「發送」一些使用自定義事件系統其餘組件。這種模式的問題是內部的事件流大組件樹很快就變得複雜,一般很難緣由時出現錯誤。
更好地處理共享狀態在大型應用程序中,咱們須要區分組件本地狀態和應用程序級狀態。應用程序狀態不屬於一個特定的組件,但咱們的組件還能夠觀察反應DOM更新。經過集中管理在一個地方,咱們再也不須要傳遞事件,由於一切影響多個組件應該屬於那裏。此外,這讓咱們記錄和檢查狀態變化的每個突變動容易理解,甚至實現花哨的東西穿越調試。
Vuex也執行一些意見如何狀態管理邏輯分割成不一樣的地方,但仍然容許足夠的靈活性的實際代碼結構。
3、單向數據流
做用:減小數據傳遞,數據統一存放管理
4、callApi()
在action中使用,有五個參數分別是(ApiCode,parent,success,false,fail)
success,成功後回調,false更新數據
通常用前三個參數
5、一、store 二、action 三、Vuex 四、添加到store.js
七、vue源碼結構
.談談你對vue的認識
vue概念:是一個構建用戶界面的漸進式框架,典型的MVVM框架。
注:模型(Model)只是普通的JavaScript對象,修改它則視圖(View)會自動更新。這種設計讓狀態管理變得很是簡單而直觀。
vue做用:響應式的數據綁定和組合的視圖組件
vue原理:數據雙向綁定 模板編譯和虛擬dom
Vue實現數據雙向綁定的效果,須要三大模塊:
Observer:可以對數據對象的全部屬性進行監聽,若有變更可拿到最新值並通知訂閱者
Compile:對每一個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數
Watcher:做爲鏈接Observer和Compile的橋樑,可以訂閱並收到每一個屬性變更的通知,執行指令綁定的相應回調函數,從而更新視圖。
Observer的核心是經過Obeject.defineProperty()來監聽數據的變更,這個函數內部能夠定義setter和getter,每當數據發生變化,就會觸發setter。這時候Observer就要通知訂閱者,訂閱者就是Watcher。
Watcher訂閱者做爲Observer和Compile之間通訊的橋樑,主要作的事情是:
在自身實例化時往屬性訂閱器(dep)裏面添加本身
自身必須有一個update()方法
待屬性變更dep.notice()通知時,能調用自身的update()方法,並觸發Compile中綁定的回調
Compile主要作的事情是解析模板指令,將模板中的變量替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,更新視圖。
vue生命週期:vue 實例從建立到銷燬的過程,就是Vue 的生命週期,即開始建立、初始化數據、編譯模板、掛載Dom→渲染、 更新→渲染、卸載等一系列過程。總共分爲8個階段:
beforeCreate----建立前 組件實例更被建立,組件屬性計算以前,數據對象data都爲undefined,未初始化。
created----建立後 組件實例建立完成,屬性已經綁定,數據對象data已存在,但dom未生成,$el未存在
beforeMount---掛載前 vue實例的$el和data都已初始化,掛載以前爲虛擬的dom節點,data.message未替換
mounted-----掛載後 vue實例掛載完成,data.message成功渲染。
beforeUpdate----更新前 當data變化時,會觸發beforeUpdate方法
updated----更新後 當data變化時,會觸發updated方法
beforeDestory---銷燬前 組件銷燬以前調用
destoryed---銷燬後 組件銷燬以後調用,對data的改變不會再觸發周期函數,vue實例已解除事件監聽和dom綁定,但dom結構依然存在 。
vue的優勢:低耦合、可重用、性獨立開發、可測試 。
vue和其餘框架的對比:
前期借鑑了angular和react的一些優秀思想,好比虛擬dom、指令操做等
more
2.MVVM框架是什麼?他和其餘框架(jQuery)有什麼區別,使用場景
MVVM框架:一個model+view+view-model的框架,model 是數據模型,view是視圖,view-model鏈接數據和視圖。
視圖的輸入框綁定了v-model, 用戶輸入後會改變data;Model改變也會同步視圖更新相關的依賴, 雙向綁定就是vm起了做用。
區別:vue數據驅動,經過數據來顯示視圖層而不是節點操做。
使用場景:數據操做比較多的場景更加方便快捷。
3.MVC框架和MVVM框架的區別
mvc和mvvm都是一種設計思想,主要是mvc中Controller演變成mvvm中的viewModel。
mvvm主要解決了mvc中大量的DOM 操做使頁面渲染性能下降,加載速度變慢,影響用戶體驗,以及當 Model 頻繁發生變化,開發者須要主動更新到View,這些問題。
4.vuex是什麼,它的原理
vuex:狀態管理器,實現組件間的數據共享。
原理:一個應用能夠看做是由View, Actions,State三部分組成,數據的流動也是從View => Actions => State =>View 以此達到數據的單向流動。可是項目較大的, 組件嵌套過多的時候, 多組件共享同一個State會在數據傳遞時出現不少問題.Vuex就是爲了解決這些問題而產生的.Vuex能夠被看做項目中全部組件的數據中心,咱們將全部組件中共享的State抽離出來,任何組件均可以訪問和操做咱們的數據中心。
一個實例化的Vuex.Store由state, mutations和actions三個屬性組成:
state中保存着共有數據
改變state中的數據有且只有經過mutations中的方法,且mutations中的方法必須是同步的
若是要寫異步的方法,須要些在actions中, 並經過commit到mutations中進行state中數據的更改。
注:官網https://vuex.vuejs.org/
5.列舉vue的指令及用法
v-for:遍歷循環
v-html、v-text:文本信息
v-model:實現雙向綁定
v-if、v-show: 判斷是否隱藏顯示
v-bind:class 綁定屬性
@click 綁定事件
6.vue裏面的自定義指令是什麼,其中的鉤子函數有哪些?
vue.directive,能夠寫在組件內部,也能夠寫在外部做爲全局的使用。
它的鉤子有bind,inserted,update等
7.vue組件之間如何傳值通訊
父到子:
子組件在props中建立一個屬性,用來接收父組件傳過來的值;
在父組件中註冊子組件;
在子組件標籤中添加子組件props中建立的屬性;
把須要傳給子組件的值賦給該屬性
子到父:
子組件中須要以某種方式(如點擊事件)的方法來觸發一個自定義的事件;
將須要傳的值做爲$emit的第二個參數,該值將做爲實參傳給響應事件的方法;
在父組件中註冊子組件並在子組件標籤上綁定自定義事件的監聽。
平行組件:
$emit推送,$on接收
8.vue組件之間如何跳轉
路由配置好以後,可使用下面三總方式進行組件的跳轉展現
① 直接修改地址欄的路由路徑
② 用router-link標籤的to屬性配置path便可
③經過js編程方式,在事件裏面調用this.$router.push("/home")實現
9.vue中跨域問題如何解決
① 後臺更改header:
header('Access-Control-Allow-Origin:*'); //容許全部來源訪問
header('Access-Control-Allow-Method:POST,GET'); //容許訪問的方式
② 使用JQuery提供的jsonp : 發起ajax請求,設置dataType爲jsonp
③ 使用http-proxy-middleware 代理解決
10.es6和es5對比,有何改變
es6經常使用語法:
變量聲明const和let
import導入模塊、export導出模塊
class類
promise
箭頭函數
模板字符串