2020大廠前端面試之vue專題(二)

11.Vue中模板編譯原理

  • 將 template 轉化成 render 函數
    function baseCompile(
        template: string,
        options: CompilerOptions
    ) {
        const ast = parse(template.trim(), options) // 1.將模板轉化成ast語法樹
        if (options.optimize !== false) { // 2.優化樹
            optimize(ast, options)
        }
        const code = generate(ast, options) // 3.生成樹
        return {
            ast,
            render: code.render,
            staticRenderFns: code.staticRenderFns
        }
    })
    const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
    const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
    const startTagOpen = new RegExp(`^<${qnameCapture}`); // 標籤開頭的正則 捕獲的內容是
    標籤名
    const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配標籤結尾的 </div>
    const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|
        ([^\s"'=<>`]+)))?/; // 匹配屬性的
	const startTagClose = /^\s*(\/?)>/; // 匹配標籤結束的 >
    let root;
    let currentParent;
    let stack = []
    function createASTElement(tagName, attrs) {
        return {
            tag: tagName,
            type: 1,
            children: [],
            attrs,
            parent: null
        }
    }
    function start(tagName, attrs) {
        let element = createASTElement(tagName, attrs);
        if (!root) {
            root = element;
        }
        currentParent = element;
        stack.push(element);
    }
    function chars(text) {
        currentParent.children.push({
            type: 3,
            text
        })
    }
    function end(tagName) {
        const element = stack[stack.length - 1];
        stack.length--;
        currentParent = stack[stack.length - 1];
        if (currentParent) {
            element.parent = currentParent;
            currentParent.children.push(element)
        }
    }
    function parseHTML(html) {
        while (html) {
            let textEnd = html.indexOf('<');
            if (textEnd == 0) {
                const startTagMatch = parseStartTag();
                if (startTagMatch) {
                    start(startTagMatch.tagName, startTagMatch.attrs);
                    continue;
                }
                const endTagMatch = html.match(endTag);
                if (endTagMatch) {
                    advance(endTagMatch[0].length);
                    end(endTagMatch[1])
                }
            }
            let text;
            if (textEnd >= 0) {
                text = html.substring(0, textEnd)
            }
            if (text) {
                advance(text.length);
                chars(text);
            }
        }
        function advance(n) {
            html = html.substring(n);
        }
        function parseStartTag() {
            const start = html.match(startTagOpen);
            if (start) {
                const match = {
                    tagName: start[1],
                    attrs: []
                }
                advance(start[0].length);
                let attr, end
                while (!(end = html.match(startTagClose)) &&
                    (attr = html.match(attribute))) {
                    advance(attr[0].length);
                    match.attrs.push({ name: attr[1], value: attr[3] })
                }
                if (end) {
                    advance(end[0].length);
                    return match
                }
            }
        }
    }
    // 生成語法樹
    parseHTML(`<div id="container"><p>hello<span>zf</span></p></div>`);
    function gen(node) {
        if (node.type == 1) {
            return generate(node);
        } else {
            return `_v(${JSON.stringify(node.text)})`
        }
    }
    function genChildren(el) {
        const children = el.children;
        if (el.children) {
            return `[${children.map(c => gen(c)).join(',')}]`
        } else {
            return false;
        }
    }
    function genProps(attrs) {
        let str = '';
        for (let i = 0; i < attrs.length; i++) {
            let attr = attrs[i];
            str += `${attr.name}:${attr.value},`;
        }
        return `{attrs:{${str.slice(0, -1)}}}`
    }
    function generate(el) {
        let children = genChildren(el);
        let code = `_c('${el.tag}'${
            el.attrs.length ? `,${genProps(el.attrs)}` : ''
            }${
            children ? `,${children}` : ''
            })`;
        return code;
    }
    // 根據語法樹生成新的代碼
    let code = generate(root);
    let render = `with(this){return ${code}}`;
    // 包裝成函數
    let renderFn = new Function(render);
    console.log(renderFn.toString());

12.Vue中v-if和v-show的區別

理解:

  • v-if 如果條件不成立不會渲染當前指令所在節點的 dom 元素
  • v-show 只是切換當前 dom 的顯示或者隱藏

原理:

const VueTemplateCompiler = require('vue-template-compiler');
let r1 = VueTemplateCompiler.compile(`<div v-if="true"><span v-for="i in
3">hello</span></div>`);
/**
with(this) {
return (true) ? _c('div', _l((3), function (i) {
return _c('span', [_v("hello")])
}), 0) : _e()
}
*/
    const VueTemplateCompiler = require('vue-template-compiler');
    let r2 = VueTemplateCompiler.compile(`<div v-show="true"></div>`);
    /**
    with(this) {
    return _c('div', {
    directives: [{
    name: "show",
    rawName: "v-show",
    value: (true),
    expression: "true"
    }]
    })
    }
    */
    // v-show 操作的是樣式 定義在platforms/web/runtime/directives/show.js
    bind(el: any, { value }: VNodeDirective, vnode: VNodeWithData) {
        vnode = locateNode(vnode)
        const transition = vnode.data && vnode.data.transition
        const originalDisplay = el.__vOriginalDisplay =
            el.style.display === 'none' ? '' : el.style.display
        if (value && transition) {
            vnode.data.show = true
            enter(vnode, () => {
                el.style.display = originalDisplay
            })
        } else {
            el.style.display = value ? originalDisplay : 'none'
        }
    }

13.爲什麼V-for和v-if不能連用

理解:

const VueTemplateCompiler = require('vue-template-compiler');
let r1 = VueTemplateCompiler.compile(`<div v-if="false" v-for="i in
3">hello</div>`);
/**
with(this) {
return _l((3), function (i) {
return (false) ? _c('div', [_v("hello")]) : _e()
})
}
*/
console.log(r1.render);
  • v-for 會比 v-if 的優先級高一些,如果連用的話會把 v-if 給每個元素都添加一下,會造成性能問題

14.用vnode來描述一個DOM結構

  • 虛擬節點就是用一個對象來描述真實的 dom 元素
function $createElement(tag,data,...children){
let key = data.key;
delete data.key;
children = children.map(child=>{
if(typeof child === 'object'){
return child
}else{
return vnode(undefined,undefined,undefined,undefined,child)
}
})
return vnode(tag,props,key,children);
}
export function vnode(tag,data,key,children,text){
return {
tag, // 表示的是當前的標籤名
data, // 表示的是當前標籤上的屬性
key, // 唯一表示用戶可能傳遞
children,
text
}
}

15.diff算法的時間複雜度

兩個樹的完全的 diff 算法是一個時間複雜度爲 O(n3) , Vue 進行了優化·O(n3) 複雜度的問題轉換成
O(n) 複雜度的問題(只比較同級不考慮跨級問題) 在前端當中, 你很少會跨越層級地移動Dom元素。 所
以 Virtual Dom只會對同一個層級的元素進行對比。

16.簡述Vue中diff算法原理

理解:

  • 1.先同級比較,在比較子節點
  • 2.先判斷一方有兒子一方沒兒子的情況
  • 3.比較都有兒子的情況
  • 4.遞歸比較子節點
    在這裏插入圖片描述

原理:

    const oldCh = oldVnode.children // 老的兒子
    const ch = vnode.children // 新的兒子
    if (isUndef(vnode.text)) {
        if (isDef(oldCh) && isDef(ch)) {
            // 比較孩子
            if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue,
                removeOnly)
        } else if (isDef(ch)) { // 新的兒子有 老的沒有
            if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
        } else if (isDef(oldCh)) { // 如果老的有新的沒有 就刪除
            removeVnodes(oldCh, 0, oldCh.length - 1)
        } else if (isDef(oldVnode.text)) { // 老的有文本 新的沒文本
            nodeOps.setTextContent(elm, '') // 將老的清空
        }
    } else if (oldVnode.text !== vnode.text) { // 文本不相同替換
        nodeOps.setTextContent(elm, vnode.text)
    }
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue,
        removeOnly) {
        let oldStartIdx = 0
        let newStartIdx = 0
        let oldEndIdx = oldCh.length - 1
        let oldStartVnode = oldCh[0]
        let oldEndVnode = oldCh[oldEndIdx]
        let newEndIdx = newCh.length - 1
        let newStartVnode = newCh[0]
        let newEndVnode = newCh[newEndIdx]
        let oldKeyToIdx, idxInOld, vnodeToMove, refElm
        // removeOnly is a special flag used only by <transition-group>
        // to ensure removed elements stay in correct relative positions
        // during leaving transitions
        const canMove = !removeOnly
        if (process.env.NODE_ENV !== 'production') {
            checkDuplicateKeys(newCh)
        }
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
            if (isUndef(oldStartVnode)) {
                oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
            } else if (isUndef(oldEndVnode)) {
                oldEndVnode = oldCh[--oldEndIdx]
            } else if (sameVnode(oldStartVnode, newStartVnode)) {
                patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh,
                    newStartIdx)
                oldStartVnode = oldCh[++oldStartIdx]
                newStartVnode = newCh[++newStartIdx]
            } else if (sameVnode(oldEndVnode, newEndVnode)) {
                patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh,
                    newEndIdx)
                oldEndVnode = oldCh[--oldEndIdx]
                newEndVnode = newCh[--newEndIdx]
            } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
                patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh,
                    newEndIdx)
                canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm,
                    nodeOps.nextSibling(oldEndVnode.elm))
                oldStartVnode = oldCh[++oldStartIdx]
                newEndVnode = newCh[--newEndIdx]
            } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
                patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh,
                    newStartIdx)
                canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm,
                    oldStartVnode.elm)
                oldEndVnode = oldCh[--oldEndIdx]
                newStartVnode = newCh[++newStartIdx]
            } else {
                if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh,
                    oldStartIdx, oldEndIdx)
                idxInOld = isDef(newStartVnode.key)
                    ? oldKeyToIdx[newStartVnode.key]
                    : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
                if (isUndef(idxInOld)) { // New element
                    createElm(newStartVnode, insertedVnodeQueue, parentElm,
                        oldStartVnode.elm, false, newCh, newStartIdx)
                } else {
                    vnodeToMove = oldCh[idxInOld]
                    if (sameVnode(vnodeToMove, newStartVnode)) {
                        patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh,
                            newStartIdx)
                        oldCh[idxInOld] = undefined
                        canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm,
                            oldStartVnode.elm)
                    } else {
                        // same key but different element. treat as new element
                        createElm(newStartVnode, insertedVnodeQueue, parentElm,
                            oldStartVnode.elm, false, newCh, newStartIdx)
                    }
                }
                newStartVnode = newCh[++newStartIdx]
            }
        }
        if (oldStartIdx > oldEndIdx) {
            refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
            addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx,
                insertedVnodeQueue)
        } else if (newStartIdx > newEndIdx) {
            removeVnodes(oldCh, oldStartIdx, oldEndIdx)
        }
    }

17.v-for中爲什麼要用key (圖解)

在這裏插入圖片描述

18.描述組件渲染和更新過程

理解:

  • 渲染組件時,會通過 Vue.extend 方法構建子組件的構造函數,並進行實例化。最終手動調用
    $mount() 進行掛載。更新組件時會進行 patchVnode 流程.核心就是diff算法
    在這裏插入圖片描述

19.組件中的 data爲什麼是一個函數?

function VueComponent(){}
VueComponent.prototype.$options = {
data:{name:'zf'}
}
let vc1 = new VueComponent();
vc1.$options.data = 'jw';
let vc2 = new VueComponent();
console.log(vc2.$options.data);

理解:

同一個組件被複用多次,會創建多個實例。這些實例用的是同一個構造函數,如果 data 是一個對象的
話。那麼所有組件都共享了同一個對象。爲了保證組件的數據獨立性要求每個組件必須通過 data 函數
返回一個對象作爲組件的狀態。

原理:

Sub.options = mergeOptions(
        Super.options,
        extendOptions
    )
    function mergeOptions() {
        function mergeField(key) {
            const strat = strats[key] || defaultStrat
            options[key] = strat(parent[key], child[key], vm, key)
        }
    }
    strats.data = function (
        parentVal: any,
        childVal: any,
        vm?: Component
    ): ?Function {
        if (!vm) { // 合併是會判斷子類的data必須是一個函數
            if (childVal && typeof childVal !== 'function') {
                process.env.NODE_ENV !== 'production' && warn(
                    'The "data" option should be a function ' +
                    'that returns a per-instance value in component ' +
                    'definitions.',
                    vm
                )
                return parentVal
            }
            return mergeDataOrFn(parentVal, childVal)
        }
        return mergeDataOrFn(parentVal, childVal, vm)
    }
  • 一個組件被使用多次,用的都是同一個構造函數。爲了保證組件的不同的實例data不衝突,要求
    data必須是一個函數,這樣組件間不會相互影響

20.Vue中事件綁定的原理

Vue 的事件綁定分爲兩種一種是原生的事件綁定,還有一種是組件的事件綁定,

理解:

  • 1.原生 dom 事件的綁定,採用的是 addEventListener 實現
  • 2.組件綁定事件採用的是 $on 方法

原理:

  • 事件的編譯:
let compiler = require('vue-template-compiler');
let r1 = compiler.compile('<div @click="fn()"></div>');
let r2 = compiler.compile('<my-component @click.native="fn" @click="fn1"></mycomponent>');
console.log(r1); // {on:{click}}
console.log(r2); // {nativeOnOn:{click},on:{click}}

在這裏插入圖片描述

1.原生 dom 的綁定

  • Vue 在創建真是 dom 時會調用 createElm ,默認會調用 invokeCreateHooks
  • 會遍歷當前平臺下相對的屬性處理代碼,其中就有 updateDOMListeners 方法,內部會傳入 add 方法
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
target = vnode.elm
normalizeEvents(on)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}
function add (
name: string,
handler: Function,
capture: boolean,
passive: boolean
) {
target.addEventListener( // 給當前的dom添加事件
name,
handler,
supportsPassive
? { capture, passive }
: capture
)
}

2.組件中綁定事件

vue 中綁定事件是直接綁定給真實 dom 元素的

export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler,
vm)
target = undefined
}
function add (event, fn) {
target.$on(event, fn)
}

組件綁定事件是通過 vue 中自定義的 $on 方法來實現的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章