目录

#前言@[TOC](文章目录)

#一、React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?@[TOC](文章目录)

#二、Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的@[TOC](文章目录)

参数

#三、在 Vue 中,子组件为何不可以修改父组件传递的 Prop?如果修改了,Vue 是如何监控到属性的修改并给出警告的?@[TOC](文章目录)

#四、双向绑定和 vuex 是否冲突?@[TOC](文章目录)

#五、Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃Object.defineProperty?@[TOC](文章目录) 

#六、Vue 的父组件和子组件生命周期钩子执行顺序是什么?@[TOC](文章目录)

 #七、vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?@[TOC](文章目录)

 #八、vue 渲染大量数据时应该怎么优化?@[TOC](文章目录)

 #九、vue 如何优化首页的加载速度?vue 首页白屏是什么问题引起的?如何解决呢?@[TOC](文章目录)

 #十、Vue 中的 computed 是如何实现的?(腾讯、平安)@[TOC](文章目录)

#总结@[TOC](文章目录)



前言

前端面试问题,可能问题不是很全面,但基本上是常见的且自我补充的一个过程,相信从中可以完善自己。另外部分见解答案解析来自github博客等资料整理过程中如有错误欢迎指出!

一、React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

key是给每一个vnode的唯一id,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点,带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况,所以会更加准确;利用key的唯一性生成map对象来获取对应节点,比遍历方式更快,而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。

二、Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的?

核心是利用ES5的Object.defineProperty,这也是Vue.js为什么不能兼容IE8及以下浏览器的原因。

Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象.(实现的数据劫持)

Object.defineProperty(obj, prop, descriptor)

参数

obj

要定义属性的对象。

prop

要定义或修改的属性的名称或 Symbol 。

descriptor

要定义或修改的属性描述符。

observe的功能就是用来监测数据的变化。实现方式是给非VNode的对象类型数据添加一个Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个Observer对象实例Observer是一个类,它的作用是给对象属性添加getter和setter,用于 收集依赖 和 派发更新

收集依赖 

  • const dep = new Dep() // 实例化一个Dep实例
  • 在get函数中通过dep.depend()做依赖收集
  • Dep是一个Class,它定义了一些属性和方法,它有一个静态属性target,这是一个全局唯一Watcher【同一时间内只能有一个全局的Watcher被计算】Dep实际上就是对Watcher的一种管理,Dep脱离Watcher单独存在是没有意义的。Watcher和Dep就是典型的观察者设计模式。

收集过程:当我们实例化一个渲染watcher的时候,首先进入watcher的构造函数逻辑,然后执行他的this.get()方法,进入get函数把Dep.target赋值为当前渲染watcher并压栈(为了恢复用)。接着执行vm._render()方法,生成渲染VNode,并且在这个过程对vm上的数据访问,这个时候就触发数据对象的getter(在此期间执行Dep.target.addDep(this)方法,将watcher订阅到这个数据持有的dep的subs中,为后续数据变化时通知到哪些subs做准备)。然后递归遍历添加所有子项的getter。

Watcher在构造函数中初始化两个Dep实例数组。newDeps代表新添加的Dep实例数组,deps代表上一次添加的Dep实例数组。
依赖清空:在执行清空依赖(cleanupDeps)函数时,会首先遍历deps,移除对dep的订阅,然后把newDepsIds和depIds交换,newDeps和deps交换,并把newDepIds和newDeps清空。考虑场景,在条件渲染时,及时对不需要渲染数据的订阅移除,减少性能浪费。

考虑到Vue是数据驱动的,所以每次数据变化都会重写Render,那么vm._render()方法会再次执行,并再次触发数据。

收集依赖的目的是为了当这些响应式数据发生变化,触发它们的setter的时候,能知道应该通知哪些订阅者去做相应的逻辑处理派发更新

 派发更新

  • childOb = !shallow && observe(newVal) // 如果shallow为false的情况,会对新设置的值变成一个响应式对象
  • dep.notify() // 通知所有订阅者

派发过程:当我们组件中对响应的数据做了修改,就会触发setter的逻辑,最后调用dep.notify()方法,它是Dep的一个实例方法。具体做法是遍历依赖收集中建立的subs,也就是Watcher的实例数组【subs数组在依赖收集getter中被添加,期间通过一些逻辑处理判断保证同一数据不会被添加多次】,然后调用每一个watcher的update方法。

update函数中有个queueWatcher(this)方法引入了队列的概念,是vue在做派发更新时优化的一个点,它并不会每次数据改变都会触发watcher回调,而是把这些watcher先添加到一个队列中,然后在nextTick后执行watcher的run函数。

run函数:先通过this.get()得到它当前的值,然后做判断,如果满足新旧值不等、新值是对象类型、deep模式任何一个条件,则执行watcher的回调,注意回调函数执行的时候会把第一个参数和第二个参数传入新值value和旧值oldValue,这就是当我们自己添加watcher时候可以在参数中取到新旧值的来源。对应渲染watcher而言,在执行this.get()方法求值的时候,会执行getter方法。因此在我们修改组件相关数据时候,会触发组件重新渲染,接着重新执行patch的过程。

简单概括:vue通过Object.defineProperty 劫持传进来的数据, 然后在数据getter的时候订阅重新编译模板的消息,然后通过监听元素的事件,监听输入框值变化,将新的值重新赋值给被劫持的data,这样就会触发setter函数,再setter函数中就会去发布重新编译模板的消息;

三、在 Vue 中,子组件为何不可以修改父组件传递的 Prop?如果修改了,Vue 是如何监控到属性的修改并给出警告的?

  • 子组件为何不可以修改父组件传递的 Prop
    单向数据流,易于监测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置。所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
  • 如果修改了,Vue 是如何监控到属性的修改并给出警告的。
  • if (process.env.NODE_ENV !== 'production') {
          var hyphenatedKey = hyphenate(key);
          if (isReservedAttribute(hyphenatedKey) ||
              config.isReservedAttr(hyphenatedKey)) {
            warn(
              ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
              vm
            );
          }
          defineReactive$$1(props, key, value, function () {
            if (!isRoot && !isUpdatingChildComponent) {
              warn(
                "Avoid mutating a prop directly since the value will be " +
                "overwritten whenever the parent component re-renders. " +
                "Instead, use a data or computed property based on the prop's " +
                "value. Prop being mutated: \"" + key + "\"",
                vm
              );
            }
          });
        }
    

    在initProps的时候,在defineReactive时通过判断是否在开发环境,如果是开发环境,会在触发set的时候判断是否此key是否处于updatingChildren中被修改,如果不是,说明此修改来自子组件,触发warn提示。

简单来说就是:一个父组件下不只有你一个子组件。同样,使用这份 prop 数据的也不只有你一个子组件。如果每个子组件都能修改 prop 的话,将会导致修改数据的源头不止一处。所以我们需要将修改数据的源头统一为父组件,子组件像要改 prop 只能委托父组件帮它。从而保证数据修改源唯一。

四、双向绑定和 vuex 是否冲突?

严格模式"use strict" 指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。

它不是一条语句,但是是一个字面量表达式,在 JavaScript 旧版本中会被忽略。

"use strict" 的目的是指定代码在严格条件下执行。

严格模式下你不能使用未声明的变量。

在严格模式中使用Vuex,当用户输入时,v-model会试图直接修改属性值,但这个修改不是在mutation中修改的,所以会抛出一个错误。当需要在组件中使用vuex中的state时,有2种解决方案:
1、在input中绑定value(vuex中的state),然后监听input的change或者input事件,在事件回调中调用mutation修改state的值
2、使用带有setter的双向绑定计算属性。见以下例子(来自官方文档):

<input v-model="message">
computed:{ 
message:{ 
get (){ 
return this.$store.state.obj.message
 },
set (value) { this.$store.commit('updateMessage', value) 
} 
}
 }

VueX规定了单向数据流,把把VueX的State放到v-model双向绑定报错,本来就是代码问题。和冲突没关系。而且VueX的双向绑定就是利用了new Vue实现的。为了单项数据流设置了Flag作为标记。不应该是VueX和双向绑定的冲突。是coder的问题。 (来自github) 

五、Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃Object.defineProperty?

  1. Object.defineProperty无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
  2. Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象。
  3. Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

 Object.defineProperty的第一个缺陷,无法监听数组变化。 然而Vue的文档提到了Vue是可以检测到数组变化的,但是只有以下八种方法,vm.items[indexOfItem] = newValue这种是无法检测的。为了解决这个问题,经过vue内部处理后可以使用以下几种方法来监听数组

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

由于只针对了以上八种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。

Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。

而要取代它的Proxy有以下两个优点;

可以劫持整个对象,并返回一个新对象

Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。

Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。

当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写

六、Vue 的父组件和子组件生命周期钩子执行顺序是什么?

vue的生命周期:beforeCreate created beforeMount mounted beforeDestory destoryed beforeUpdate updated

  1. 加载渲染过程
    父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
  2. 子组件更新过程
    父beforeUpdate->子beforeUpdate->子updated->父updated
  3. 父组件更新过程
    父beforeUpdate->父updated
  4. 销毁过程
    父beforeDestroy->子beforeDestroy->子destroyed->父destroyed总结:从外到内,再从内到外

         总结:从外到内,再从内到外

 七、vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?

首先我们需要知道事件代理主要有什么作用?

  • 事件代理能够避免我们逐个的去给元素新增和删除事件
  • 事件代理比每一个元素都绑定一个事件性能要更好

 vue没有自动做事件代理,只有在非常非常多的节点中,使用事件代理会提高一点性能,否则绑定在每个节点中几乎没有差别

  • 在v-for中,我们直接用一个for循环就能在模板中将每个元素都绑定上事件,并且当组件销毁时,vue也会自动给我们将所有的事件处理器都移除掉。所以事件代理能做到的第一点vue已经给我们做到了
  • 在v-for中,给元素绑定的都是相同的事件,所以除非上千行的元素需要加上事件,其实和使用事件代理的性能差别不大,所以也没必要用事件代理

 八、vue 渲染大量数据时应该怎么优化?

1.添加加载动画,优化用户体验
2.利用服务器渲染SSR,在服务端渲染组件
3.避免浏览器处理大量的dom,比如懒加载,异步渲染组件,使用分页,尽量不要再用vue的双向数据绑定了 或者只用部分页面中处理的数据
4.对于固定的非响应式的数据,使用Object.freeze冻结

 九、vue 如何优化首页的加载速度?vue 首页白屏是什么问题引起的?如何解决呢?

首页白屏的原因:
vue是单页面应用, html 是靠 js 生成,因为首屏需要加载很大的js文件(app.js vendor.js),需要将所有需要的资源都下载到浏览器端并解析。

考虑解决办法:

  • 1.使用首屏SSR + 跳转SPA方式来优化
  • 2.改单页应用为多页应用,需要修改webpack的entry
  • 3.改成多页以后使用应该使用prefetch的就使用
  • 4.处理加载的时间片,合理安排加载顺序,尽量不要有大面积空隙
  • 5.CDN资源还是很重要的,最好分开,也能减少一些不必要的资源损耗
  • 6.使用Quicklink,在网速好的时候 可以帮助你预加载页面资源
  • 7.骨架屏这种的用户体验的东西一定要上,最好借助stream先将这部分输出给浏览器解析
  • 8.合理使用web worker优化一些计算
  • 9.缓存一定要使用,但是请注意合理使用
  • 10.代码拆分,code split、动态import
  • 11.优化打包后内容的体积, 去掉不必要的代码,使用gzip压缩
  • 12.最后可以借助一些工具进行性能评测,重点调优,例如使用performance评测

 十、Vue 中的 computed 是如何实现的?(腾讯、平安)

computed本身是通过代理的方式代理到组件实例上的,所以读取计算属性的时候,执行的是一个内部的getter,而不是用户定义的方法。

computed内部实现了一个惰性的watcher,在实例化的时候不会去求值,其内部通过dirty属性标记计算属性是否需要重新求值。当computed依赖的任一状态(不一定是return中的)发生变化,都会通知这个惰性watcher,让它把dirty属性设置为true。所以,当再次读取这个计算属性的时候,就会重新去求值。

惰性watcher/计算属性在创建时是不会去求值的,是在使用的时候去求值的。


总结

如果你觉得这面试题对你有帮助,我想请你帮我个小忙:来一个点赞收藏三连击;

其实做这个专栏我也有私心,就是希望借助每天写一篇面试题,督促自己学习,以免在吹水群甚至都没有谈资!
对了,如果你的朋友也在准备面试前端开发,请将这个系列扔给他,
好了,今天就到这里,学废了的同学,记得在评论区留言:打卡。给同学们以激励。

再次说明整理问题不易,如有内容的需要改进的请指出,以便及时修改!感谢!

学习过程中,祝大家早日取得合适offer!

Logo

前往低代码交流专区

更多推荐