zoukankan      html  css  js  c++  java
  • Vue实战面试知识点汇总

    1. 双向绑定详解

      参考地址:https://juejin.im/post/5f11672fe51d45348e27b3ff

    2.computed 的实现原理

      1)为什么需要computed?

        template 使用大量复杂的逻辑表达式处理数据,使得代码维护性差,且相同数据重复计算对性能开销大

      2)计算属性的实现原理? 

        1.初始化 data 和 computed,分别代理其 set 和 get 方法,对 data 中的所有属性生成唯一的 dep 实例

        2.对 computed 中的 属性生成唯一的 watcher,并保存在 vm._computedWatchers 中

        3.访问计算属性时,设置 Dep.target 指向 计算属性的 watcher,调用该属性具体方法

        4.方法中访问 data 的属性,即会调用 data 属性的 get 方法,将 data 属性的 dep 加入到 计算属性的 watcher , 同时该 dep 中的 subs 添加这个 watcher

        5.设置 data 的这个属性时,调用该属性代理的 set 方法,触发 dep 的 notify 方法

        6.因为时 computed 属性,只是将 watcher 中的 dirty 设置为 true

        7.最后,访问计算属性的 get 方法时,得知该属性的 watcher.dirty 为 true,则调用 watcher.evaluate() 方法获取新的值

        综合以上:也可以解释了为什么有些时候当computed没有被访问(或者没有被模板依赖),当修改了this.data值后,通过vue-tools发现其computed中的值没有变化的原因,因为没有触发到其get方法。

    3.为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

      1)Object.defineProperty 本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性,导致通过数组下标添加属性无法实时响应

      2) Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象

      3)Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性

      补救:push();pop();shift();unshift();splice();sort();reverse(); Vue重写了数组这个7个方法,称为变异数组,添加了响应式,使得这7个方法添加的属性可以实时响应


    4.Vue中的key有什么作用?
      1)
    key 是给每一个 vnode 的唯一 id,依靠 key,我们的 diff 操作可以更准确、更快速 (对于简单列表页渲染来说 diff 节点也更快,但会产生一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位) .diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行比对,从而找到相应旧节点.

      2)更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确,如果不加 key,会导致之前节点的状态被保留下来,会产生一系列的 bug

      3)更快速 : key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1),源码如下:

    function createKeyToOldIdx(children, beginIdx, endIdx) {
      let i, key; 
      const map = {}; 
    for (i = beginIdx; i <= endIdx; ++i) {    
      key = children[i].key;    
      if (isDef(key)) map[key] = i;  
     } 
     return map;
    }

     5.谈一谈nextTick 的原理

    1. vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行

    2. microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕

    3. 考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案

      Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更;Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替

    6.vue 是如何对数组方法进行变异的 ?

     1 const arrayProto = Array.prototype;
     2 export const arrayMethods = Object.create(arrayProto);
     3 const methodsToPatch = [
     4     "push",
     5     "pop",
     6     "shift",
     7     "unshift",
     8     "splice",
     9     "sort",
    10     "reverse"
    11 ];
    12 /** * Intercept mutating methods and emit events */
    13 methodsToPatch.forEach(function (method) {
    14     // cache original method  
    15     const original = arrayProto[method];
    16     def(arrayMethods, method, function mutator(...args) {
    17         const result = original.apply(this, args);
    18         const ob = this.__ob__; let inserted;
    19         switch (method) {
    20             case "push":
    21             case "unshift":
    22                 inserted = args;
    23                 break;
    24             case "splice":
    25                 inserted = args.slice(2);
    26                 break;
    27         }
    28         if (inserted)
    29             ob.observeArray(inserted);
    30         // notify change   
    31         ob.dep.notify();
    32         return result;
    33     });
    34 });
    35 /** * Observe a list of Array items. */
    36 Observer.prototype.observeArray = function observeArray(items) {
    37     for (var i = 0, l = items.length; i < l; i++) {
    38         observe(items[i]);
    39     }
    40 };
    简单来说,Vue 通过原型拦截的方式重写了数组的 7 个方法,首先获取到这个数组的ob,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update

    7.Vue 组件 data 为什么必须是函数 ?

    new Vue()实例中,data 可以直接是一个对象,为什么在 vue 组件中,data 必须是一个函数呢?

      因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。

    所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。

    8.聊聊 keep-alive 的实现原理和缓存策略?
     1 export default {
     2     name: "keep-alive",
     3     abstract: true,
     4     // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中  
     5     props: {
     6         include: patternTypes,
     7         // 被缓存组件   
     8         exclude: patternTypes,
     9         // 不被缓存组件   
    10         max: [String, Number] // 指定缓存大小  
    11     },
    12     created() {
    13         this.cache = Object.create(null); // 缓存  
    14         this.keys = []; // 缓存的VNode的键 
    15     },
    16     destroyed() {
    17         for (const key in this.cache) {
    18             // 删除所有缓存      
    19             pruneCacheEntry(this.cache, key, this.keys);
    20         }
    21     },
    22     mounted() {
    23         // 监听缓存/不缓存组件   
    24         this.$watch("include", val => {
    25             pruneCache(this, name => matches(val, name));
    26         });
    27         this.$watch("exclude", val => {
    28             pruneCache(this, name => !matches(val, name));
    29         });
    30     },
    31     render() {
    32         // 获取第一个子元素的 vnode 
    33         const slot = this.$slots.default;
    34         const vnode: VNode = getFirstComponentChild(slot);
    35         const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions;
    36         if (componentOptions) {
    37             // name不在inlcude中或者在exlude中 直接返回vnode     
    38             // check pattern     
    39             const name: ?string = getComponentName(componentOptions);
    40             const { include, exclude } = this; if (
    41                 // not included     
    42                 (include && (!name || !matches(include, name))) ||
    43                 // excluded       
    44                 (exclude && name && matches(exclude, name))
    45             ) { return vnode; }
    46             const { cache, keys } = this;
    47             // 获取键,优先获取组件的name字段,否则是组件的tag     
    48             const key: ?string = vnode.key == null ?
    49                 // same constructor may get registered as different local components       
    50                 // so cid alone is not enough (#3269)          
    51                 componentOptions.Ctor.cid +
    52                 (componentOptions.tag ? `::${componentOptions.tag}` : "") : vnode.key;
    53             // 命中缓存,直接从缓存拿vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个     
    54             if (cache[key]) {
    55                 vnode.componentInstance = cache[key].componentInstance;
    56                 // make current key freshest        
    57                 remove(keys, key); keys.push(key);
    58             }
    59             // 不命中缓存,把 vnode 设置进缓存      
    60             else {
    61                 cache[key] = vnode; keys.push(key);
    62                 // prune oldest entry      
    63                 // 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个   
    64                 if (this.max && keys.length > parseInt(this.max)) {
    65                     pruneCacheEntry(cache, keys[0], keys, this._vnode);
    66                 }
    67             }
    68             // keepAlive标记位      
    69             vnode.data.keepAlive = true;
    70         } return vnode || (slot && slot[0]);
    71     }
    72 };

    原理

    1. 获取 keep-alive 包裹着的第一个子组件对象及其组件名

    2. 根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例

    3. 根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键)

    4. 在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key)

    5. 最后组件实例的 keepAlive 属性设置为 true,这个在渲染和执行被包裹组件的钩子函数会用到,这里不细说

    LRU 缓存淘汰算法

    LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高

    keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]

    9.vm.$set()实现原理是什么?

    受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。

    由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

    对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

    那么 Vue 内部是如何解决对象新增属性不能响应的问题的呢?

     1 export function set(target: Array<any> | Object, key: any, val: any): any {
     2     // target 为数组  
     3     if (Array.isArray(target) && isValidArrayIndex(key)) {
     4         // 修改数组的长度, 避免索引>数组长度导致splice()执行有误   
     5         target.length = Math.max(target.length, key);
     6         // 利用数组的splice变异方法触发响应式    
     7         target.splice(key, 1, val); return val;
     8     }
     9     // target为对象, key在target或者target.prototype上 且必须不能在 Object.prototype 上,直接赋值
    10     if (key in target && !(key in Object.prototype)) {
    11         target[key] = val; return val;
    12     }
    13     // 以上都不成立, 即开始给target创建一个全新的属性  
    14     // 获取Observer实例  
    15     const ob = (target: any).__ob__;
    16     // target 本身就不是响应式数据, 直接赋值  
    17     if (!ob) { target[key] = val; return val; }
    18     // 进行响应式处理 
    19     defineReactive(ob.value, key, val);
    20     ob.dep.notify();
    21     return val;
    22 }
    1. 如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式

    2. 如果目标是对象,判断属性存在,即为响应式,直接赋值

    3. 如果 target 本身就不是响应式,直接赋值

    4. 如果属性不是响应式,则调用 defineReactive 方法进行响应式处理

     10.Object.defineProperty和Proxy的区别?

    • Object.defineProperty
      • 不能监听到数组length属性的变化;
      • 不能监听对象的添加;
      • 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
    • Proxy
      • 可以监听数组length属性的变化;
      • 可以监听对象的添加;
      • 可代理整个对象,不需要对对象进行遍历,极大提高性能;
      • 多达13种的拦截远超Object.defineProperty只有get和set两种拦截

     11.你认为Vue的核心是什么?

    Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。

      以上是官方原话,从中可以得知Vue的核心是模板语法和数据渲染。

    12.Vue为什么要求组件模板只能有一个根元素?

    当前的virtualDOM差异和diff算法在很大程度上依赖于每个子组件总是只有一个根元素。

    13.ajax、fetch、axios这三都有什么区别?

    ajax是最初出现的发送临时请求的技术,属于原生js标准,核心是使用XMLHttpRequest对象,使用并存并有先后顺序的话,容易产生地狱。

    fetch号称可以代替ajax的技术,是基于es6中的Promise对象设计的,参数和jQuery中的ajax类似,它并不是对ajax的进一步封装,它属于原生js尺寸。没有使用XMLHttpRequest对象。

    axios不是原生js,使用时需要进行进行安装,客户端和服务器端都可以使用,可以在请求和相应阶段进行拦截,基于promise对象。

     14.如果想扩展某个现有的Vue组件时,怎么做呢?

    • 用mixins混入
    • 用extends,比mixins先触发
    • 用高阶组件HOC封装

      注意:extends使得组件能像面向对象变成一样便于拓展

        extends会比mixins先执行。执行顺序:extends > mixins > 组件

        extends只能暴露一个extends对象,暴露多个extends不会执行。

        mixins能暴露多个

    15.vue组件和插件的区别

    组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue。

    插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身。

    简单来说,插件就是指对Vue的功能的增强或补充。

    比如说,让你在每个单页面的组件里,都可以调用某个方法,或者共享使用某个变量,或者在某个方法之前执行一段代码等

    就可以写一个插件,在Vue原型上扩展方法,要实现这个需求绝对没法写成组件。

    1 let whatever = {
    2   install: function(Vue, options) {
    3     Vue.prototype.$whatever = function(){
    4     // do something
    5     };
    6   }
    7 }

    16.为什么Vue使用异步更新组件?

    批量更新 收集当前的改动一次性更新 节省diff开销;关联this.$nextTick使用微任务方式得到更新后的dom

    17.用vue怎么实现一个换肤的功能?

      1.base.scss: 一些通用样式文件(使用scss定义颜色变量,通过@include 引入mixin中的变量)

        #content{
          @include bg_color()
        }    

      2.varibale.scss: 颜色,字体,背景的配置文件

    $background-color-them1:red
    $background-color-them2:blue

      3.mixin.scss: 定义mixin方法的文件(通过判断HTML上属性,识别加载模板)

    @import "./variable";/*引入配置*/
    @mixin font_size($size){/*通过该函数设置字体大小,后期方便统一管理;*/
      @include font-dpr($size);
    }
    @mixin bg_color($color){/*通过该函数设置主题颜色,后期方便统一管理;*/
      background-color:$color;
      [data-theme="theme1"] & {
        background-color:$background-color-theme1;
      }
      [data-theme="theme2"] & {
        background-color:$background-color-theme2;
      }
      [data-theme="theme3"] & {
        background-color:$background-color-theme3;
      }
    }

      主要原理:

        通过设置html的attribute属性在封装的函数中进行判断,进行相应的设置不同的颜色

        css中 [ ] 可以识别到在html标签上设置的属性,所以在html上对应属性发生变化时,就会执行相应的样式,

        这一步有点类似于平时给div添加一个.active属性,css自动执行相应样式

    18.对于 vue3.0 特性你有什么了解的吗?

      1.使用porxy替换object.defineProperty

      • 只能监测属性,不能监测对象
      • 检测属性的添加和删除;
      • 检测数组索引和长度的变更;
      • 支持 Map、Set、WeakMap 和 WeakSet。

      2.模板
      模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能
    同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。

      3.对typescript结合使用更容易

      4.其他改变:

        • 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
        • 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
        • 基于 treeshaking 优化,提供了更多的内置功能

    19.key除了在v-for中使用,还有什么作用?

    还可以强制替换元素/组件而不是重复使用它。在以下场景可以使用

    • 完整地触发组件的生命周期钩子
    • 触发过渡
    <transition>
      <span :key="text">{{ text }}</span>
    </transition>

    当 text 发生改变时,<span>会随时被更新,因此会触发过渡

    • 不要使用对象或数组之类的非基本类型值作为key,请用字符串或数值类型的值;

    • 不要使用数组的index作为key值,因为在删除数组某一项,index也会随之变化,导致key变化,渲染会出错

  • 相关阅读:
    左耳听风
    极客时间-算法
    极客时间-左耳听风阅读笔记
    涨知识
    学做饭
    开发流程
    线上问题复盘
    反思学习复习练习
    系统安全(转)
    单元测试
  • 原文地址:https://www.cnblogs.com/advanceCabbage/p/13402355.html
Copyright © 2011-2022 走看看