1. 程式人生 > 實用技巧 >Vue面試專題(未完)

Vue面試專題(未完)

1.談一下你對MVVM原理的理解 傳統的 MVC 指的是,使用者操作會請求服務端路由,路由會呼叫對應的控制器來處理,控制器會獲取數 據。將結果返回給前端,頁面重新渲染。 MVVM :傳統的前端會將資料手動渲染到頁面上, MVVM 模式不需要使用者收到操作 dom 元素,將資料綁 定到 viewModel 層上,會自動將資料渲染到頁面中,檢視變化會通知 viewModel層 更新資料。 ViewModel 就是我們 MVVM 模式中的橋樑. 2.請說一下響應式資料的原理? 理解: 1.核心點: Object.defineProperty 2.預設 Vue 在初始化資料時,會給 data 中的屬性使用 Object.defineProperty 重新定義所有屬 性,當頁面取到對應屬性時。會進行依賴收集(收集當前元件的watcher) 如果屬性發生變化會通 知相關依賴進行更新操作。 原理:

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
            dep.depend() // ** 收集依賴 ** / 
            if (childOb) {
                childOb.dep.depend() 
if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) {
return } if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } val = newVal childOb = !shallow && observe(newVal) dep.notify() /**通知相關依賴進行更新**/ } })

3.Vue中是如何檢測陣列變化? 理解: 1、使用函式劫持的方式,重寫了陣列的方法 2、Vue 將 data 中的陣列,進行了原型鏈重寫。指向了自己定義的陣列原型方法,這樣當呼叫陣列 api 時,可以通知依賴更新.如果陣列中包含著引用型別。會對陣列中的引用型別再次進行監控。

  1. export const arrayMethods = Object.create(arrayProto)
    const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
    methodsToPatch.forEach(function (method) { // 重寫原型方法 
        const original = arrayProto[method] // 呼叫原陣列的方法 
        def(arrayMethods, method, function mutator(...args) {
            const result = original.apply(this, args)
            const ob = this.__ob__
            let inserted
            switch (method) {
                case 'push':
                case 'unshift':
                    inserted = args
                    break
                case 'splice':
                    inserted = args.slice(2)
                    break
            }
            if (inserted) ob.observeArray(inserted) // notify change 
            ob.dep.notify() // 當呼叫陣列方法後,手動通知檢視更新 
            return result
        })
    })
    this.observeArray(value) // 進行深度監控
4.為何Vue採用非同步渲染? 理解: 因為如果不採用非同步更新,那麼每次更新資料都會對當前元件進行重新渲染.所以為了效能考慮。 Vue 會在本輪資料更新後,再去非同步更新檢視! 原理:
update() {
    /* istanbul ignore else */
    if (this.lazy) {
        this.dirty = true
    } else if (this.sync) {
        this.run()
    } else {
        queueWatcher(this); // 當資料發生變化時會將watcher放到一個佇列中批量更新 
    }
}
export function queueWatcher(watcher: Watcher) {
    const id = watcher.id // 會對相同的watcher進行過濾 
    if (has[id] == null) {
        has[id] = true
        if (!flushing) {
            queue.push(watcher)
        } else {
            let i = queue.length - 1
            while (i > index && queue[i].id > watcher.id) {
                i--
            }
            queue.splice(i + 1, 0, watcher)
        } // queue the flush 
        if (!waiting) {
            waiting = true
            if (process.env.NODE_ENV !== 'production' && !config.async) {
                flushSchedulerQueue()
                return
            }
            nextTick(flushSchedulerQueue) // 呼叫nextTick方法 批量的進行更新 
        }
    }
}
5.nextTick實現原理? 理解:(巨集任務和微任務) 非同步方法 nextTick 方法主要是使用了巨集任務和微任務,定義了一個非同步方法.多次呼叫 nextTick 會將方法存入 佇列中,通過這個非同步方法清空當前佇列。 所以這個 nextTick 方法就是非同步方法 原理:

let timerFunc // 會定義一個非同步方法
if (typeof Promise !== 'undefined' && isNative(Promise)) { // promise 
    const p = Promise.resolve()
    timerFunc = () => {
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
    }
    isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && ( // MutationObserver 
        isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]')) {
    let counter = 1
    const observer = new MutationObserver(flushCallbacks)
    const textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
        characterData: true
    })
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)
    }
    isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined') { // setImmediate 
    timerFunc = () => {
        setImmediate(flushCallbacks)
    }
} else {
    timerFunc = () => { // setTimeout 
        setTimeout(flushCallbacks, 0)
    }
} // nextTick實現 
export function nextTick(cb ? : Function, ctx ? : Object) {
    let _resolve
    callbacks.push(() => {
        if (cb) {
            try {
                cb.call(ctx)
            } catch (e) {
                handleError(e, ctx, 'nextTick')
            }
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    if (!pending) {
        pending = true
        timerFunc()
    }
}

6.Vue中Computed的特點 理解: 預設 computed 也是一個 watcher 是具備快取的,只要當依賴的屬性發生變化時才會更新檢視

function initComputed(vm: Component, computed: Object) {
    const watchers = vm._computedWatchers = Object.create(null) const isSSR = isServerRendering() for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        if (!isSSR) { // create internal watcher for the computed property. 
            watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
        }
        // component-defined computed properties are already defined on the 
        // component prototype. We only need to define computed properties defined
        // at instantiation here. 
        if (!(key in vm)) {
            defineComputed(vm, key, userDef)
        } else if (process.env.NODE_ENV !== 'production') {
            if (key in vm.$data) {
                warn(`The computed property "${key}" is already defined in data.`, vm)
            } else if (vm.$options.props && key in vm.$options.props) {
                warn(`The computed property "${key}" is already defined as a prop.`, vm)
            }
        }
    }
}

function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
            if (watcher.dirty) { // 如果依賴的值沒發生變化,就不會重新求值 
                watcher.evaluate()
            }
            if (Dep.target) {
                watcher.depend()
            }
            return watcher.value
        }
    }
}
7.Watch中的deep:true 是如何實現的 理解: 當用戶指定了 watch 中的deep屬性為 true 時,如果當前監控的值是陣列型別。會對物件中的每 一項進行求值,此時會將當前 watcher 存入到對應屬性的依賴中,這樣陣列中物件發生變化時也 會通知資料更新 原理:
function initComputed(vm: Component, computed: Object) {
    const watchers = vm._computedWatchers = Object.create(null) const isSSR = isServerRendering() for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        if (!isSSR) { // create internal watcher for the computed property. 
            watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
        }
        // component-defined computed properties are already defined on the 
        // component prototype. We only need to define computed properties defined
        // at instantiation here. 
        if (!(key in vm)) {
            defineComputed(vm, key, userDef)
        } else if (process.env.NODE_ENV !== 'production') {
            if (key in vm.$data) {
                warn(`The computed property "${key}" is already defined in data.`, vm)
            } else if (vm.$options.props && key in vm.$options.props) {
                warn(`The computed property "${key}" is already defined as a prop.`, vm)
            }
        }
    }
}

function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
            if (watcher.dirty) { // 如果依賴的值沒發生變化,就不會重新求值 
                watcher.evaluate()
            }
            if (Dep.target) {
                watcher.depend()
            }
            return watcher.value
        }
    }
}

8.Vue元件的生命週期 理解: 要掌握每個生命週期什麼時候被呼叫 beforeCreate 在例項初始化之後,資料觀測(data observer) 之前被呼叫。 created 例項已經建立完成之後被呼叫。在這一步,例項已完成以下的配置:資料觀測(data observer),屬性和方法的運算, watch/event 事件回撥。這裡沒有$el beforeMount 在掛載開始之前被呼叫:相關的 render 函式首次被呼叫。 mounted el 被新建立的 vm.$el 替換,並掛載到例項上去之後呼叫該鉤子。 beforeUpdate 資料更新時呼叫,發生在虛擬 DOM 重新渲染和打補丁之前。 updated 由於資料更改導致的虛擬 DOM 重新渲染和打補丁,在這之後會呼叫該鉤子。 beforeDestroy 例項銷燬之前呼叫。在這一步,例項仍然完全可用。 destroyed Vue 例項銷燬後呼叫。呼叫後, Vue 例項指示的所有東西都會解繫結,所有的事件 監聽器會被移除,所有的子例項也會被銷燬。 該鉤子在伺服器端渲染期間不被呼叫。 要掌握每個生命週期內部可以做什麼事 created 例項已經建立完成,因為它是最早觸發的原因可以進行一些資料,資源的請求。 mounted 例項已經掛載完成,可以進行一些DOM操作 beforeUpdate 可以在這個鉤子中進一步地更改狀態,這不會觸發附加的重渲染過程。 updated 可以執行依賴於 DOM 的操作。然而在大多數情況下,你應該避免在此期間更改狀態, 因為這可能會導致更新無限迴圈。 該鉤子在伺服器端渲染期間不被呼叫。 destroyed 可以執行一些優化操作,清空定時器,解除繫結事件

原理: