常见的前端面试题总结

197 阅读10分钟

2023年来啦,相信有不少同学都经历了“毕业潮”。哈哈不要灰心,新的一年,新的开始,希望本篇文章可以帮助各位同学找到自己满意的工作。

1、react、vue 这两个框架的异同以及各自的优点

vue和react的相同点

  1. 都支持服务端渲染;
  2. 都是组件化思想,组件化开发;
  3. 都利用了虚拟dom(Virtual DOM),提高渲染速度;
  4. 都有独立的路由系统,以及独立的状态管理库;
  5. 都有适配的第三方组件库;

vue和react的不同点

  1. vue的思想是响应式的,也就是基于数据是可变的,实现了数据的双向绑定,react整体是函数式的思想,是单向数据流,推崇结合immutable来实现数据不可变;
  2. vue 采用了template(template,script,style), react采用了jsx(把html和css全都写进js中,编写代码方便,简洁)这两个本质上都是模版;
  3. 在react中要想更新状态,必须调用setState方法,而在vue中只需要通过this的某种方式去更新state中的数据,这种方式更加方便;
  4. react 的性能优化需要手动去做,而vue的性能优化则是自动的,但是vue的响应式机制也有问题,就是当 state 特别多的时候,Watcher 会很多,会导致卡顿,所有更适合小型项目开发;
  5. react可以使用Create React App (CRA),而vue对应的则是Vue-cli。两个工具都能让你得到一个根据最佳实践设置的项目模板;
  6. 都有管理状态,react有redux,而vue有自己的vuex;

利用虚拟dom的区别:

在react中,当状态发⽣改变时,组件树就会⾃顶向下的全diff, 重新render⻚⾯,重新⽣成新的虚拟dom tree,新旧dom tree进⾏⽐较,进⾏patch打补丁⽅式,局部更新dom。 所以react为了避免⽗组件跟新⽽引起不必要的⼦组件更新,可以在shouldComponentUpdate做逻辑判断,减少没必要的render, 以及重新⽣成虚拟dom,做差量对⽐过程;

vue会跟踪每⼀个组件的依赖关系,局部渲染组件树⽽不需要重新渲染整个组件树。 通过Object.defineProperty()来劫持各个属性的setter/getter,在数据变动时发布消息给订阅者,触发相应监听回调。 当把⼀个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,Object.defineProperty 将它们转为 getter/setter。 ⽤户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

vue的优点

  1. 轻量级的框架+指令: 它通过双向数据绑定把 View 层和 Model 层连接了起来.实际的 DOM 封装和输出;
  2. 双向数据绑定:当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化;
  3. 组件化开发:就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护;
  4. 更好的性能: 最小力度更新,vue 每次更新会进行虚拟 dom 和屏幕已有 dom 对比,只更新有变化的部分,性能更高;
  5. 单页面路由:单页是把原本的多个页面以组件的形式集成在一个页面中,页面跳转时由 vue 路由到目标页面,分别加载不同的组件,而页面不会刷新,路由在更新。

react的优点

  1. 灵活性和响应性:它提供最大的灵活性和响应能力;
  2. 丰富的JavaScript库:来自世界各地的贡献者正在努力添加更多功能;
  3. 可扩展性:由于其灵活的结构和可扩展性,React已被证明对大型应用程序更好;
  4. 不断发展: React得到了Facebook专业开发人员的支持,他们不断寻找改进方法。

2、vue 数据响应-双向绑定的原理-v-model

vue采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,vue在初始化的时候,在initState方法中会调取initData方法初始化data数据,对data对象进行遍历,在这个方法中会调observe(监听器,观察者)对⽤户的数据进行监听,在observe中会对对象new Observe实例化创建监听,在observe中对数据进行判断,如果是数组执行observeArray深度进行监听,继续执行observe方法,如果当前传入的是对象则执行this.walk,对对象进行循环,重新定义对象的属性,这时使用的就是defineReactive,它是vue中的⼀个核心方法,用来定义响应式。在defineReactive方法中实例化了⼀个Dep(发布者),通过Object.defineProperty对数据进行拦截,把这些property 全部转为 getter/setter。get数据的时候,通过dep.depend触发Watcher(订阅者)的依赖收集,收集订阅者,如果数据是数组,执行dependArray,对数组中的每个值通过depend都添加到依赖。set时,会对数据进行比较,如果数据发⽣了变化会通过dep.notify发布通知,通知watcher,更新视图。

3、vue 无法检测数组和对象变化的原因

其实是可以检测到变化的,只是在 vue 的实现中,从性能/体验的性价比考虑,放弃了这个特性。当然,在 Vue3.0 中则对这方面做了很大程度的改进,采用 Proxy 替代了 Object.defineProperty 解决了这个问题。

4、 vue无法检测数组和对象变化的解决办法

监听数组的变化:

vue 能够监听数组变化的场景

  1. 通过赋值的形式改变正在被监听的数组;
  2. 通过 splice(index, num, val) 的形式改变正在被监听的数组;
  3. 通过数组的 push 的形式改变正在被监听的数组;

vue 无法监听数组变化的场景

  1. 通过数组索引改变数组元素的值;
  2. 改变数组的长度;

vue 无法监听数组变化的解决方案

  1. this.$set(arr, index, newVal);
  2. 通过 splice(index,num,val);
  3. 使⽤临时变量作为中转,重新赋值数组;

监听对象的变化:

vue 能够监听对象变化的场景

通过直接赋值的场景: e.g:watchObj = {name:"zyk"}

vue 无法监听对象变化的场景

对象的增加、删除、修改无法被vue监听到

vue 无法监听对象变化的解决方案

  1. 使用 this.set(object, key, value) (vue 无法监听 this.$set 修改原有属性)
  2. 使⽤ Object.assign(),直接赋值的原理;(深拷贝,推荐使用)

5、判断数据类型最常见的几种方式

typeof

可以判断数据类型,它返回表示数据类型的字符串(返回结果只能包括 number,boolean,string,function,object,undefined),可以使用typeof判断变量是否存在。

if(typeof a!="undefined"){...}

typeof 运算符的问题是无论引用的对象是什么类型它都返回object。

instanceof

原理:因为A instanceof B 可以判断A是不是B的实例,返回⼀个布尔值,由构造类型判断出数据类型。instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。

console.log(arr instanceof Array ); // true

Object.toString.call()

Object.prototype.toString.call();
console.log(toString.call(123)); //[object Number]
console.log(toString.call('123')); //[object String]
console.log(toString.call(undefined)); //[object Undefined]

6、如何实现跨域,三种⽅式

jsonp

jsonp 实现原理:主要是利⽤动态创建 script 标签请求后端接口地址,然后传递 callback 参数,后端接收callback,后端经过数据处理,返回 callback 函数调用的形式,callback 中的参数就是 json。

通过代理的方式(前端代理和后端代理)

前端代理我在 vue 中使用那个 vue.config.js 里面配置⼀个proxy,里面有个 target 属性指向跨域链接.修改完重启项目就可以了。实际上 就是启动了⼀个代理服务器.绕开同源策略,在请求的时候,通过代理服务器获取到数据再转给浏览器。

CORS

CORS 全称叫跨域资源共享,主要是后台工程师设置后端代码来达到前端跨域请求的。

注:现在主流框架都是用代理和 CORS 跨域实现的

7、computed 和 watch介绍和区别

watch

  1. watch是监听⼀个值的变化,然后执行对应的回调;
  2. watch中的函数不需要调用;
  3. watch有两个参数:

immediate:组件加载立即触发回调函数执行;

deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使⽤,例如数组中的对象内容的改变;

  1. watch中的函数名称必须要和data中的属性名⼀致,watch依赖于data中的属性,data中的属性发生变化的时候;
  2. watch中的函数就会发生变化;
  3. watch不支持缓存;

computed

  1. computed是计算属性,通过属性计算得来的属性;
  2. computed中的函数直接调用,不用;
  3. computed中的函数必须用return返回;
  4. computed是依赖data中的属性,data中属性发生改变的时候,当前函数才会执行,data中属性没有改变的时候,当前函数不会执行;
  5. computed中不能对data中的属性进行赋值操作,如果对data中的属性进行赋值,data中的属性发生变化,从而触发computed中的函数,就会形成死循环;
  6. computed属性的结果会被缓存,除非依赖的属性发生变化才会重新计算。

使用场景

watch的使用场景: 一个数据影响多个数据,需要在数据变化时执行异步操作或者开销较大的操作时使用。

例如:搜索数据、浏览器自适应、监控路由对象、监控自身属性变化。

computed的使用场景: ⼀个数据受多个数据影响,处理复杂的逻辑或多个属性影响⼀个属性的变化时使用。

例如:购物车商品结算,数量计算等。

8、虚拟 Dom 和真实 Dom 之间怎么转化

其实和深拷贝差不多,深度遍历真实DOM,遇到节点就转换为虚拟DOM,遍历标签,复制而已。

自定义指令注册使用场景

注册⼀个自定义指令有全局注册与局部注册

全局注册主要是通过Vue.directive 方法进行注册 Vue.directive 第⼀个参数是指令的名字(不需要写上v-前缀),第⼆个参数可以是对象数据,也可以是⼀个指令函数

// 注册⼀个全局⾃定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插⼊到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus() // ⻚⾯加载完成之后⾃动让输⼊框获取到焦点的⼩功能
}
})

局部注册通过在组件options 选项中设置directive 属性

directives: {
        focus: {
        // 指令的定义
        inserted: function (el) {
        el.focus() // ⻚⾯加载完成之后⾃动让输⼊框获取到焦点的⼩功能
        }
    }
}

然后你可以在模板中任何元素上使用新的 v-focus property,如下:

<input v-focus />

使用场景: 使用自定义指令可以满足我们日常⼀些场景,这里给出几个自定义指令的案例:

  • 防抖
  • 图片懒加载
  • ⼀键Copy的功能

9、keep-alive 作⽤及使⽤场景

keep-alive:主要用于保存组件状态 或避免重新渲染

应用场景:点击商品列表进⼊详情页面 点击返回按钮 回到商品列表页

第一种方法:

<keep-alive>
    <router-view></router-view>
</keep-alive>

被keep-alive标签包裹的路由都会被缓存起来 但是我们不需要缓存这么多组件

  • include:字符串或正则表达式。条件匹配的都缓存。
  • exclude:字符串或正则表达式。条件匹配都不会被缓存。

第二种方法:

在router路由中配置 在某个路由中加上⼀个meta属性

{
    path:'/index',
    component:()=>import('@/views/index'),
    meta:{
    keepAlice:true,//为true是缓存的意思
    }
}