前端面试vue总结

138 阅读4分钟

Vue原理

响应式原理

Object.definProperty,如何监听对象(深度监听),监听数组,几个缺点

  • 深度监听需要递归到底,一次性计算量大

  • 无法监听新增属性/删除属性(vue.set vue.delete)

  • 无法原生监听数组,需要特殊处理重写数组原型

    // 触发更新视图function updateView() {
        console.log('视图更新')}
    // 重新定义数组原型const oldArrayProperty = Array.prototype
    // 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
    const arrProto = Object.create(oldArrayProperty);
    ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
        arrProto[methodName] = function () {
            updateView()
     // 触发视图更新
            oldArrayProperty[methodName].call(this, ...arguments)
            // Array.prototype.push.call(this, ...arguments)
        }})
    // 重新定义属性,监听起来
    function defineReactive(target, key, value) {
        // 深度监听
        observer(value)
        // 核心 API
        Object.defineProperty(target, key, {
            get() {
                return value
            },
            set(newValue) {
                if (newValue !== value) {
                    // 深度监听
                    observer(newValue)
                    // 设置新值
                    // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                    value = newValue
                    // 触发更新视图
                    updateView()
                }
            }
        })}
    // 监听对象属性function observer(target) {
        if (typeof target !== 'object' || target === null) {
            // 不是对象或数组
            return target
        }
        // 污染全局的 Array 原型
        // Array.prototype.push = function () {
        //     updateView()
        //     ...
        // }
        if (Array.isArray(target)) {
            target.__proto__ = arrProto
        }
        // 重新定义各个属性(for in 也可以遍历数组)
        for (let key in target) {
            defineReactive(target, key, target[key])
        }}
    // 准备数据const data = {
        name: 'zhangsan',
        age: 20,
        info: {
            address: '北京'
     // 需要深度监听
        },
        nums: [10, 20, 30]}
    // 监听数据observer(data)
    // 测试/
    / data.name = 'lisi'
    // data.age = 21//
     // console.log('age', data.age)
    // data.x = '100'
     // 新增属性,监听不到 —— 所以有 Vue.set
    // delete data.name
     // 删除属性,监听不到 —— 所有已 Vue.delete
    // data.info.address = '上海'
     // 深度监听data.nums.push(4)
     // 监听数组
    

vdom和diff算法

用js模拟DOM结构,计算出最小的变更,操作DOM

- 只比较同一层级,不跨级比较

- tag不相同,则直接删掉重建,不在深度比较

- tag和key两者都相同,则认为是相同节点不在深度比较

patchVnode,addVnodes,removeVnodes,updateChildren(key的重要性)

模板编译

  • js的with语法,vue template complier将模板编译为render函数,执行rende函数生产vnode

  • 模板编译为render函数,执行render函数返回vnode

  • 基于vnode再执行patch和diff

  • 使用webpack vue-loader,会在开发环境下编译模板

    const compiler = require('vue-template-compiler')
    // 插值
    // const template = `<p>{{message}}</p>`
    // with(this){return createElement('p',[createTextVNode(toString(message))])}
    // h -> vnode
    // createElement -> vnode
    // // 表达式
    // const template = `<p>{{flag ? message : 'no message found'}}</p>`
    // // with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}
    // // 属性和动态属性
    // const template = `//
         <div id="div1" class="container">//
             <img :src="imgUrl"/>
    //     </div>
    // `
    // with(this){return _c('div',
    //      {staticClass:"container",attrs:{"id":"div1"}},
    //      [
    //          _c('img',{attrs:{"src":imgUrl}})])}
    // // 条件
    // const template = `
    //     <div>
    //         <p v-if="flag === 'a'">A</p>
    //         <p v-else>B</p>
    //     </div>
    // `
    // with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}
    // 循环
    // const template = `
    //     <ul>
    //         <li v-for="item in list" :key="item.id">{{item.title}}</li>
    //     </ul>
    // `
    // with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}
    // 事件// const template = `//     <button @click="clickHandler">submit</button>
    // `// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}
    // v-modelconst template = `<input type="text" v-model="name">`
    // 主要看 input 事件
    // with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}
    // render 函数
    // 返回 vnode// patch// 编译const res = compiler.compile(template)console.log(res.render)
    // ---------------分割线--------------
    // // 从 vue 源码中找到缩写函数的含义
    // function installRenderHelpers (target) {
    //     target._o = markOnce;
    //     target._n = toNumber;
    //     target._s = toString;
    //     target._l = renderList;
    //     target._t = renderSlot;
    //     target._q = looseEqual;
    //     target._i = looseIndexOf;
    //     target._m = renderStatic;
    //     target._f = resolveFilter;
    //     target._k = checkKeyCodes;
    //     target._b = bindObjectProps;
    //     target._v = createTextVNode;
    //     target._e = createEmptyVNode;
    //     target._u = resolveScopedSlots;
    //     target._g = bindObjectListeners;
    //     target._d = bindDynamicKeys;
    //     target._p = prependModifier;
    // }
    

组件渲染过程

-响应式:监听data属性getter setter(包括数组)

-模板编译:模板到render函数,再到vnode

-vdom:patch(elem,vnode)和patch(vnode, newVnode)

初次渲染过程

  • 解析模板为render函数(或在开发环境已完成,vue-loader)
  • 触发响应式,监听data属性getter setter
  • 执行render函数,生成vnode,patch(elem, vnode)

更新过程

  • 修改data,触发setter(此前在getter中已经被监听到)
  • 重新执行render函数,生成newVnode
  • patch(vnode,newVnode)

异步渲染

前端路由

  • hash - window.onhashchange
  • H5 history - history.pushState 和 window.onpopstate

Vue

1.v-show和v-for的区别

  • v-show通过css display控制显示和隐藏
  • v-if组件真正的渲染和销毁,而不是显示和隐藏
  • 频繁切换显示状态使用v-show,否则用v-if

2.为何v-for中要用key

  • 必须使用key,而且不用试index和random
  • diff算法中通过key和tag来判断,是否是sameNode
  • 减少渲染次数,提升渲染性能

3.描述vue组件生命周期(有父子组件的情况)

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

  • 加载渲染过程

    父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  • 子组件更新过程

    父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  • 父组件更新过程

    父 beforeUpdate -> 父 updated

  • 销毁过程

    父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

4.vue组件如何通讯

  • 父子组件props和this.$emit
  • 之定义事件evet.noevent.no event.off event.$emit
  • vuex

5.描述组件渲染和更新的过程

6.双向数据绑定v-model的实现原理

  • input元素的value=this.name
  • 绑定input事件this.name = $event.target.value
  • data更新触发re-render

7.computed和watch

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

运用场景:

  • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

  • 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

8.为何组件data必须是一个函数

9.vue常见性能优化方案

(1)代码层面的优化

  • 合理使用v-if 和 v-show
  • 合理使用computed 和 watch
  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
  • keep-alive、异步组件
  • 自定义事件、DOM事件的及时销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化无限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 减少 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建结果输出分析
  • Vue 项目的编译优化

(3)基础的 Web 技术的优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的使用
  • 使用 Chrome Performance 查找性能瓶颈

10.proxy实现响应式