1、active-class是哪個組件的屬性?嵌套路由怎么定義?
答:vue-router模塊的router-link組件。
2、怎么定義vue-router的動態路由?怎么獲取傳過來的動態參數?
答:在router目錄下的index.js文件中,對path屬性加上/:id。 使用router對象的params.id
3、vue-router有哪幾種導航鈎子?
答:三種,一種是全局導航鈎子:router.beforeEach(to,from,next),作用:跳轉前進行判斷攔截。第二種:組件內的鈎子;第三種:單獨路由獨享組件
4、scss是什么?安裝使用的步驟是?有哪幾大特性?
答:預處理css,把css當前函數編寫,定義變量,嵌套。 先裝css-loader、node-loader、sass-loader等加載器模塊,在webpack-base.config.js配置文件中加多一個拓展:extenstion,再加多一個模塊:module里面test、loader
4.1、scss是什么?在vue.cli中的安裝使用步驟是?有哪幾大特性?
答:css的預編譯。
使用步驟:
第一步:用npm 下三個loader(sass-loader、css-loader、node-sass)
第二步:在build目錄找到webpack.base.config.js,在那個extends屬性中加一個拓展.scss
第三步:還是在同一個文件,配置一個module屬性
第四步:然后在組件的style標簽加上lang屬性 ,例如:lang=”scss”
有哪幾大特性:
1、可以用變量,例如($變量名稱=值);
2、可以用混合器,例如()
3、可以嵌套
5、mint-ui是什么?怎么使用?說出至少三個組件使用方法?
答:基於vue的前端組件庫。npm安裝,然后import樣式和js,vue.use(mintUi)全局引入。在單個組件局部引入:import {Toast} from ‘mint-ui’。組件一:Toast(‘登錄成功’);組件二:mint-header;組件三:mint-swiper
6、v-model是什么?怎么使用? vue中標簽怎么綁定事件?
答:可以實現雙向綁定,指令(v-class、v-for、v-if、v-show、v-on)。vue的model層的data屬性。綁定事件:<input @click=doLog() />
7、axios是什么?怎么使用?描述使用它實現登錄功能的流程?
答:請求后台資源的模塊。npm install axios -S裝好,然后發送的是跨域,需在配置文件中config/index.js進行設置。后台如果是Tp5則定義一個資源路由。js中使用import進來,然后.get或.post。返回在.then函數中如果成功,失敗則是在.catch函數中
8、axios+tp5進階中,調用axios.post(‘api/user’)是進行的什么操作?axios.put(‘api/user/8′)呢?
答:跨域,添加用戶操作,更新操作。
9、什么是RESTful API?怎么使用?
答:是一個api的標准,無狀態請求。請求的路由地址是固定的,如果是tp5則先路由配置中把資源路由配置好。標准有:.post .put .delete
10、vuex是什么?怎么使用?哪種功能場景使用它?
答:vue框架中狀態管理。在main.js引入store,注入。新建了一個目錄store,….. export 。場景有:單頁應用中,組件之間的狀態。音樂播放、登錄狀態、加入購物車
11、mvvm框架是什么?它和其它框架(jquery)的區別是什么?哪些場景適合?
答:一個model+view+viewModel框架,數據模型model,viewModel連接兩個
區別:vue數據驅動,通過數據來顯示視圖層而不是節點操作。
場景:數據操作比較多的場景,更加便捷
12、自定義指令(v-check、v-focus)的方法有哪些?它有哪些鈎子函數?還有哪些鈎子函數參數?
答:全局定義指令:在vue對象的directive方法里面有兩個參數,一個是指令名稱,另外一個是函數。組件內定義指令:directives
鈎子函數:bind(綁定事件觸發)、inserted(節點插入的時候觸發)、update(組件內相關更新)
鈎子函數參數:el、binding
13、說出至少4種vue當中的指令和它的用法?
答:v-if:判斷是否隱藏;v-for:數據循環出來;v-bind:class:綁定一個屬性;v-model:實現雙向綁定
14、vue-router是什么?它有哪些組件?
答:vue用來寫路由一個插件。router-link、router-view
15、導航鈎子有哪些?它們有哪些參數?
答:導航鈎子有:a/全局鈎子和組件內獨享的鈎子。b/beforeRouteEnter、afterEnter、beforeRouterUpdate、beforeRouteLeave
參數:有to(去的那個路由)、from(離開的路由)、next(一定要用這個函數才能去到下一個路由,如果不用就攔截)最常用就這幾種
16、Vue的雙向數據綁定原理是什么?
答:vue.js 是采用數據劫持結合發布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。
具體步驟:
第一步:需要observe的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter和getter
這樣的話,給這個對象的某個值賦值,就會觸發setter,那么就能監聽到了數據變化
第二步:compile解析模板指令,將模板中的變量替換成數據,然后初始化渲染頁面視圖,並將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變動,收到通知,更新視圖
第三步:Watcher訂閱者是Observer和Compile之間通信的橋梁,主要做的事情是:
1、在自身實例化時往屬性訂閱器(dep)里面添加自己
2、自身必須有一個update()方法
3、待屬性變動dep.notice()通知時,能調用自身的update()方法,並觸發Compile中綁定的回調,則功成身退。
第四步:MVVM作為數據綁定的入口,整合Observer、Compile和Watcher三者,通過Observer來監聽自己的model數據變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋梁,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據model變更的雙向綁定效果。
ps:16題答案同樣適合”vue data是怎么實現的?”此面試題。
17、請詳細說下你對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結構依然存在
18、請說下封裝 vue 組件的過程?
答:首先,組件可以提升整個項目的開發效率。能夠把頁面抽象成多個相對獨立的模塊,解決了我們傳統項目開發:效率低、難維護、復用性等問題。
然后,使用Vue.extend方法創建一個組件,然后使用Vue.component方法注冊組件。子組件需要數據,可以在props中接受定義。而子組件修改好數據后,想把數據傳遞給父組件。可以采用emit方法。
19、你是怎么認識vuex的?
答:vuex可以理解為一種開發模式或框架。比如PHP有thinkphp,java有spring等。
通過狀態(數據源)集中管理驅動組件的變化(好比spring的IOC容器對bean進行集中管理)。
應用級的狀態集中放在store中; 改變狀態的方式是提交mutations,這是個同步的事物; 異步邏輯應該封裝在action中。
20、vue-loader是什么?使用它的用途有哪些?
答:解析.vue文件的一個加載器,跟template/js/style轉換成js模塊。
用途:js可以寫es6、style樣式可以scss或less、template可以加jade等
21、請說出vue.cli項目中src目錄每個文件夾和文件的用法?
答:assets文件夾是放靜態資源;components是放組件;router是定義路由相關的配置;view視圖;app.vue是一個應用主組件;main.js是入口文件
22、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。
23、聊聊你對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節點,里面有(標簽名、子節點、文本等等)
挑戰一下:
1、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 */)
}
1、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 來觀察數據的變化,進而更新到視圖中。
2、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就是書店的書,訂閱者在書店訂閱書籍,就可以添加訂閱者信息,一旦有新書就會通過書店給訂閱者發送消息。
3、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 () {
...
}
}
4、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() 進行依賴收集;依賴收集的結果:1、data在 Observer 時閉包的dep實例的subs添加觀察它的 Watcher 實例;2. Watcher 的deps中添加觀察對象 Observer 時的閉包dep;
當data中被 Observer 的某個對象值變化后,觸發subs中觀察它的watcher執行 update() 方法,最后實際上是調用watcher的回調函數cb,進而更新視圖。
2、vue-router實現原理?
深入Vue-Router源碼分析路由實現原理
使用Vue開發SPA應用,離不開vue-router,那么vue和vue-router是如何協作運行的呢,下面從使用的角度,大白話幫大家一步步梳理下vue-router的整個實現流程。
到發文時使用的版本是:
- vue (v2.5.0)
- vue-router (v3.0.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)
該方法內主要做了以下三件事:
1、對Vue實例混入beforeCreate鈎子操作(在Vue的生命周期階段會被調用)
2、通過Vue.prototype定義router、router、route 屬性(方便所有組件可以獲取這兩個屬性)
3、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
})
生成實例過程中,主要做了以下兩件事
1、根據配置數組(傳入的routes)生成路由配置記錄表。
2、根據不同模式生成監控路由變化的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)
}
})
代碼執行到這,初始化結束,界面將顯示默認首頁
路由更新方式:
一、主動觸發
router-link綁定了click方法,觸發history.push或者history.replace,從而觸發history.transitionTo。
transitionTo用於處理路由轉換,其中包含了updateRoute用於更新_route。
在beforeCreate中有劫持_route的方法,當_route變化后,觸發router-view的變化。
二、地址變化(如:在瀏覽器地址欄直接輸入地址)
HashHistory和HTML5History會分別監控hashchange和popstate來對路由變化作對用的處理 。
HashHistory和HTML5History捕獲到變化后會對應執行push或replace方法,從而調用transitionTo
,剩下的就和上面主動觸發一樣啦。
總結
1、安裝插件
混入beforeCreate生命周期處理,初始化_routerRoot,_router,_route等數據
全局設置vue靜態訪問router和router和route,方便后期訪問
完成了router-link和 router-view 兩個組件的注冊,router-link用於觸發路由的變化,router-view作 為功能組件,用於觸發對應路由視圖的變化
2、根據路由配置生成router實例
根據配置數組生成路由配置記錄表
生成監控路由變化的hsitory對象
3、將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來對路由變化作對用的處理
3、為什么要選vue?與其它框架對比的優勢和劣勢?
4、vue如何實現父子組件通信,以及非父子組件通信?
5、vuejs與angularjs以及react的區別?
6、vuex是用來做什么的?
VueX 是一個專門為 Vue.js 應用設計的狀態管理架構,統一管理和維護各個vue組件的可變化狀態(你可以理解成 vue 組件里的某些 data )。如下圖,所示:
一、什么是Vuex
用學過react的人來說:Vuex 之於 Vue 就像 Redux 之於 React,這邊附上一篇Vuex文檔地址http://vuex.vuejs.org/en/intro.html
Vuex是集中的應用程序架構為了Vue.js應用程序狀態管理。靈感來自Flux 和 Redux,但簡化的概念和實現是一個專門為 Vue.js 應用設計的狀態管理架構。
狀態管理: 簡單理解就是統一管理和維護各個vue組件的可變化狀態(你可以理解成 vue 組件里的某些 data )
二、我們為什么需要Vuex
如果你的應用程序很簡單,你可能不需要Vuex。不要過早地應用它。但是如果你正在構建一個medium-to-large-scale SPA,那么你遇到的情況,讓你思考如何更好的結構Vue組件之外的事情。這是Vuex發揮作用的地方。
當單獨使用Vue.js,我們常常傾向於存儲狀態我們的組件內。也就是說,每個組件屬於我們的應用程序狀態,因此結果狀態亂扔的到處都是。然而,有時一塊狀態需要由多個組件共享。常見的做法是讓一個組件“發送”一些使用自定義事件系統其他組件。這種模式的問題是內部的事件流大組件樹很快就變得復雜,通常很難原因時出現錯誤。
更好地處理共享狀態在大型應用程序中,我們需要區分組件本地狀態和應用程序級狀態。應用程序狀態不屬於一個特定的組件,但我們的組件還可以觀察反應DOM更新。通過集中管理在一個地方,我們不再需要傳遞事件,因為一切影響多個組件應該屬於那里。此外,這讓我們記錄和檢查狀態變化的每一個突變更容易理解,甚至實現花哨的東西穿越調試。
Vuex也執行一些意見如何狀態管理邏輯分割成不同的地方,但仍然允許足夠的靈活性的實際代碼結構。
三、單向數據流
作用:減少數據傳遞,數據統一存放管理
四、callApi()
在action中使用,有五個參數分別是(ApiCode,parent,success,false,fail)
success,成功后回調,false更新數據
一般用前三個參數
五、1、store 2、action 3、Vuex 4、添加到store.js
7、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
箭頭函數
模板字符串