Vue相关面试题

93 阅读4分钟

1 MVC 和 MVVM

MVC:
以前是通过接口将数据存在后台,然后后台再给一些反馈。
缺点: 前后端无法独立开发;前端过于依赖后端的数据;
MVVM:
vue中的data让前端有了自己的数据管理器,可以相对独立开发,实现前后端分离

2 v-model

表现形式:

    <div id="app">
        <input v-model="message"/>
        {{message}}
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    message: 'hello world'
                }
            }
        })
    </script>

原理: 双向数据绑定

 <input placeholder="请输入名字" id="username"/>
    <p id="uname"></p>
       const obj = {}
       const inputEl = document.querySelector('#username')
       const textEl = document.querySelector('#uname')

       inputEl.addEventListener('input', function(e) {
           obj.username = e.target.value
       })

       Object.defineProperty(obj, 'username', {
           get: function() {
              console.log('取值')
           },
           set: function(val) {
              console.log('设置值')
              textEl.innerText = val
           }
       })

3 data为什么是个函数?

data函数是个闭包的设计,这样就可以保证每一个组件都有自己的私有作用域,确保各组件的之间数据不会相互干扰;
而如果是纯对象的方式,则组件之间的数据就容易相互干扰。

4 v-if和v-show

v-if: 不满足条件,则不会渲染dom,单次判断;
v-show: 隐藏dom,适合多次切换

5 $nextTick

在vue中,数据更新后,dom并不会立即更新。
如果想对新的dom进行js操作,则需要使用$nextTick。
这样就可以确保dom在更新后再执行延迟回调。

6 单页与多页的区别

单页应用:只有一个主页面的应用

优点: 
1.体验好,快。
2.因为分成了很多组件,所以改动内容,不用加载整个页面
3.前后端分离
缺点:
1.不利于seo
2.初次加载慢
3.页面的复杂度较高

多页应用:页面跳转整个刷新,和单页相反

7 v-if 和 v-for

不推荐同时使用。
因为 v-for的优先级高于v-if,所以会导致if语句运行在每个循环之间,影响性能。

8 vue-router和location.href区别

1.vue-router是静态跳转,页面不会重新记载;location.href会触发浏览器,页面重新加载
2.vue-router使用diff算法,实现按需加载,减少dom操作
3.vue-rouer是同一个页面跳转;location.href是不同页面跳转
4.vue-router是异步加载this.$nextTick(()=>{获取url}),loaction.href是同步加载

9 vue响应式原理

响应式:数据联动(双向绑定);能捕获到数据的修改

发布订阅模式 + 数据劫持

vue通过Object.defineProperty实现双向数据绑定,get用来依赖收集,set当数据进行更改时做一个通知;之所以能够进行通知,是因为提前对数据进行了订阅。所以只有发布订阅模式结合数据劫持,才能实现响应式。

 <div id="app">
        订阅视图-1: <span class="box-1"></span><br/>
        订阅视图-2: <span class="box-2"></span>
    </div>
    <script>
        let obj = {}
        dataHi({
            data: obj,
            tag: 'view-1',
            datakey: 'one',
            selector: '.box-1'
        })
        dataHi({
            data: obj,
            tag: 'view-2',
            datakey: 'two',
            selector: '.box-2'
        })
        // 初始化赋值:
        obj.one = '这是视图1'
        obj.two = '这是视图2'
        // 2 更新时,劫持数据,更新这负责重新渲染
        setTimeout(() => {
            obj.one = '修改后的值'
        }, 3000)
    </script>


// 订阅器模型
/**
 * clientList:容器
 * listen:添加订阅的方法。
 * trigger:发布的方法
*/
let Dep = {
    clientList: {}, 
    listen: function(key, fn) {// 比如:key是一个人的id,fn是他订阅的多个东西
        // if(!this.clientList[key]) {
        //     this.clientList[key] = []
        // }
        // this.clientList[key].push(fn)
        // 上面的可以简化为下面的短路表达式:
        (this.clientList[key] || (this.clientList[key] = [])).push(fn)
    },
    tigger: function() {
        let key = Array.prototype.shift.call(arguments), // shift用于把数组的第一个元素删除,并返回第一个元素的值,会改变原数组的长度
            fns = this.clientList[key];
        if (!fns || fns.length === 0) {
            return false
        }
        for (let i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    }
}

/**
 * 数据劫持
*/
    let dataHi = function({data, tag, datakey, selector}) {
    let value = '',
        el = document.querySelector(selector);
    Object.defineProperty(data, datakey, {
        get() {
            console.log('取值')
            return value
        },
        set(val) {
            console.log('改值')
            value = val
            // 通知模板
            Dep.tigger(tag, val)
        }
    })
    // 订阅
    Dep.listen(tag, function(text) {
       el.innerText = text
    })
}

10 数据驱动原理(如何劫持、监听所有数据)

通过一个观测类,让每一个属性都可以被观测

获取对象中所有的key值,然后逐个遍历,利用Object.defineProperty的get和set方法实现所有数据的监听

以下是期望达成的效果:

      const obj = new Observer({
            name: '张三',
            age: 18,
            demo: {
                a: 'aaa',
                b: 'bbb'
            }
        })
        console.log(obj.value)
        console.log(obj.value.name)
        obj.value.age = 25

        console.log(obj.value.demo.a)
        obj.value.demo.b = '1234'

实现

       class Observer {
            constructor(value) {
                this.value = value
                this.fn(value)
            }
            fn(obj) {
                const keys = Object.keys(obj)
                for (let i = 0; i < keys.length; i++) {
                    defineReactive(obj, keys[i])
                }
            }
        }
        function defineReactive(obj, key, val) {
            if (arguments.length === 2) {
                val = obj[key]
            }
            if (typeof val === 'object') {
                new Observer(val) // 递归
            }
            Object.defineProperty(obj, key, {
                get() {
                    console.log('取值',key)
                    return val
                },
                set(newVal) {
                    console.log('赋值',key, newVal)
                    val = newVal
                }
            })
        }

11 虚拟dom

是什么?

虚拟dom是在vue2.x版本加入的;本质是js对象,所以可以跨平台。

vue的渲染过程

vue中的render函数会把template转化为真实的dom,然后会根据真实的dom生成虚拟dom,最后再变成真实dom。

image.png

在vue中做了什么?

将真实dom转化为虚拟dom;更新的时候做对比

虚拟dom是如何提升vue的渲染效率的

局部更新(节点数据);将直接操作dom的地方拿到两个js对象中去做比较

diff中的patch方法

// 初始化 patch(container, vnode)
// 更新 update(vnode, newVnode)
function creatElement(vnode) {
    /**
     * 三要素,目标元素(tag),属性(attrs),子节点(children)
    */
   const tag = vnode.tag
   const attrs = vnode.attrs || {}
   const children = vnode.children || []

    if (!tag) {
        return null
    }
    // 1 创建对应的dom
    const elem = document.createElement(tag)
    let attrName;
    // 2 给dom添加属性
    for(attrName in attrs) {
        if (attrs.hasOwnProperty(attrName)) {
            elem.setAttribute(attrName, attrs[attrName])
        }
    }
    // 3 将子元素添加到目标之上
    children.forEach(childVnode => {
        elem.appendChild(createElement(childVnode))
    });
    
    return elem
}

function update(vnode, newVnode) {
    let children = vnode.children || [] // 现有节点
    let newChildren = newVnode.children || [] // 新节点
    children.forEach((childVnode, index) => {
        let newChildVnode = newChildren[index]
        // 判断第一层没有变化
        if (childVnode.tag === newChildVnode.tag) {
            update(childVnode, newChildVnode)
        } else {
            replaceNode(childVnode, newChildVnode)
        }
    })
}

diff算法

概念

对比新旧虚拟dom,并根据对比后的结果更新真实dom

虚拟dom是什么

一个用来描述真实dom的对象。

它有六个属性,sel表示当前节点标签名,data内是节点的属性,children表示当前节点的其他子标签节点,elm表示当前虚拟节点对应的真实节点,key即为当前节点的key,text表示当前节点下的文本,结构类似这样:

let vnode = {
    sel: 'ul', 
    data: {},
    children: [ 
        {
            sel: 'li', data: { class: 'item' }, text: 'son1'
        },
        {
            sel: 'li', data: { class: 'item' }, text: 'son2'
        },    
    ],
    elm: undefined,
    key: undefined,
    text: undefined
}