下面分享一下九月初海尔西安卡奥斯公司前端社招一面面试题,技术一面已过,等待研发经理二面,都是些基础题,时长一个小时,面试官是一个小姐姐,未露面,面试者需要开启视频。
我尽力回想一下面试问的问题,抱歉,很多记不清了,有些问题回答内容后续找时间补下。
主要问了js、vue、react、uniapp四个方面:
JS:
- ES6新特性(面试遇到这种问题挺频繁的)
- 新增
let、const
块级作用域(和 var 的区别一般都会问到:a. var 会发生变量提升,let/const 不会,let/const声明前使用变量会报错;b. var 可以重复声明变量,let/const 不能;var/let 声明的变量可以对值修改,const 定义的是常量,值不能修改;var 在全局作用域中声明的变量会作为 window 的属性,let/const 不会) - 新增
Symbol
基础数据类型 - 数组、对象的解构赋值:
let [a, b] = [1, 2],let {a, b} = {a: 1, b: 2}
;新增添加对象属性简洁语法:let a = 1, b = 2;let obj = {a, b} // obj = {a:1,b:2}
- 扩展运算符
...
:let a = [1, 2];let b = [...a] // b = [1,2]
let obj1 = {a: 1, b: 2},let obj2 = {...obj1} // obj2 = {a: 1, b: 2}
- 函数形参可设置默认值,函数传参结合扩展运算符以及解构赋值的使用:
// 结合扩展运算符
function test(...arr){
console.log(arr)
}
test(1,2,3) // arr = [1,2,3]
// 结合解构赋值
function test([x,y]){
console.log(x, y)
}
test([1,2]) // 1 2
function foo({x, y = 5}) {
console.log(x, y);}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
function foo({x, y = 5} = {}) {
console.log(x, y);}
foo() // 方法中设置解构的默认对象后,就可以不传参。
箭头函数
的使用(与普通函数区别经常被问到:a. 箭头函数没有自己的 this,其 this 指向箭头函数定义时所在的对象;b. 箭头函数没有 prototype 原型对象属性,也就无法使用 new 当构造函数使用;c. 箭头函数没有 arguments 属性)- 新增
for .. of
对数组、Set、Map等可迭代对象的遍历 - 新增
Set、Map
数据结构( Set、Map、WeakSet、WeakMap 之间的区别) - 新增构造函数的语法糖
Class
类,及继承extends
- 新增
Promise
对象解决异步编程问题,以及async/await
- 新增
Proxy
代理对象,以及Reflect
对对象进行操作 - 数组新增方法:
filter(),map(),reduce(),some(),every(),find(),findIndex(),includes(),flat()
……
对象新增方法:
Object.is(),Object.assign(),Object.setPrototypeOf(),Object.keys(),Object.values(),Object.entries()
…… - Modules 模块导入导出语法:
export import
- Generator 迭代函数:
function* test(){
yeild '1'
yeild '2'
return
}
test.next() // {value:1, done:false}
test.next() // {value:2, done:true}
- 数组去重最快的方法?
使用 Set 去重:
var a = [1,2,3,2,3]
var s = new Set(a)
a = Array.from(s) // [1,2,3]
- 有没有用过 Promise ,介绍下 Promise ,用来作什么?
Promise 有三种状态:pendding(进行中),fulfilled(成功),rejected(失败);Promise 一旦创建就会立即执行;resolve()置为成功状态,reject()置为失败状态;Promise 状态一旦被改变后就无法再进行更改。成功触发 then 回调,失败触发 catch 回调。
Promise 链式调用解决“回调地狱”问题。 - async/await 怎么使用?
async 用来修饰函数,是 Generator 迭代函数语法糖,也是用来解决异步编程问题。async 函数返回值被封装成成功状态的Promise对象,返回值要通过 then() 回调获取。await 必须放在 async 函数中使用,await 相当于 Promise.then() 成功回调,只取 Promise 成功状态返回值,若是失败状态会导致程序报错,终止执行,所以一般将 await 代码放在异常捕获语句 try…catch 中,async/await 主要用来将异步操作变成同步操作。
async function test(){
try {
const res = await axios.get(url)
} catch (e => {
console.log(e)
})
axios.get(url, { params: {id: res.id} } )
}
- Class 类中的 super 做了什么事?
主要是在子类继承父类中使用:a.super()
用来调用父类中的构造函数(调用父构造函数super([param])
);b.super()
有改变父类 this 指向当前子类实例,使子类继承 this 的功能,所以super()
必须在子类构造函数中 this 之前使用,因为子类实际是无 this;c. super 在子类作为对象使用指向的是父类,可以调用父类中的普通方法(调用父普通方法super.方法名([param])
)。 - 如何在子类中重写父类方法?
直接在子类中定义同名方法即可。 - 前端设计模式有了解过吗?
前端设计模式有很多,大概快二十来个,只需要了解有哪些即可,主要分为三大类:①结构型模式 ②创建型模式 ③行为型模式。 行为型模式中的 观察者模式(也叫发布—订阅者模式) 必须要掌握,Vue 和 React 都采用了这种设计模式。要掌握手写实现观察者模式。了解所有设计模式可观阅这篇文章:前端开发中常用设计模式。
Vue:
- 组件之间通信方式有哪些?
a. 父子:props,$emit 或 $parent, $children 或 $attrs, $listeners
b. 父孙:provide,inject
c. 跨组件通信: Vuex,EventBus - 介绍下Vuex?模块化管理时怎么调用 state, mutations, actions ……?
Vuex 全局状态同一管理,用来组件通信,用于存放登录数据等等,然后把 Vuex 各个属性及功能介绍了一下…… 模块化时,调用对应属性前要加模块名。 - Vue-router 路由模式有几种及各自区别?
常见的有两种:history,hash。
区别:① hash: 会在路由的路径前拼接 # ,# 后面的路径发生变化时,不会导致浏览器向服务器发出请求(页面不会重载),而是会触发 hashchange 事件,需要前端处理路径匹配不上跳转404页面。② History:页面路径就是常见的 ‘/’ 拼接形式,页面路径改变,浏览器会向服务器对应路径下发送请求(页面会重新加载),需要服务器支持,把所有路由都重定向到根页面,否则如果服务器中没有相应的响应或者资源会404。浏览器前进后退不会发起url请求,但刷新会去请求服务器。 - Diff 算法了解吗?
Diff 算法是用来对新旧虚拟DOM树进行比对的算法,有两个特点:① 只会比较同级节点, 不会跨层级比较。 ② 在diff比较的过程中,循环从两边向中间比较。
源码中主要通过调用 patch、patchVnode、updateChildren 三个函数进行比对。
相同类型节点判断规则(sameVnode):两个虚拟节点的标签类型和key值均相同,若是input元素还要看type属性
详细比对过程:
① 当数据发生改变时,订阅者watcher就会调用patch给真实的DOM打补丁
② 向patch中传入新旧虚拟DOM树的根节点,patch做了以下操作:
新节点不存在,旧节点存在,调用 destroy,删除旧节点
旧节点不存在,新节点存在,说明是页面刚开始初始化,不需要比较了,直接新建节点,只调用 createElm新旧节点都存在,通过sameVnode判断是否相同节点,相同则调用patchVnode方法,不同则删除旧节点,创建新节点
③ patchVnode做了以下操作:
如果都是文本节点,则将新节点文本内容更新到DOM文本内容中
如果旧节点oldVnode有子节点而新节点VNode没有,则删除DOM中子节点
如果旧节点oldVnode没有子节点而新节点VNode有,则将VNode的子节点真实化后添加到DOM中
如果两者都有子节点,则执行updateChildren函数比较子节点
④ updateChildren主要做了以下操作:
设置新旧VNode的头尾指针
新旧头尾指针进行比较,循环从两边向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找 key一致的VNode 节点再分情况操作 - Vue 2 和 Vue 3 的区别?
① 性能上有很大提升。
② 更好地支持 TS 语言。
③ Composition API 替代了 Options API。
④ 生命周期的改变,使用 setup 函数代替 beforeCreate、created,以及其它生命周期名称上的改变,见下表。
⑤ 响应式原理上的变更:Vue 3 通过Proxy(代理对象) 替代了 Vue 2 中 Object.defineProperty 对单个属性逐个拦截,Proxy 对整个对象拦截,不需要对对象属性遍历单个拦截,所以性能更高,但兼容性相对较低。Proxy 能够拦截对象中任意属性的变化,包括:属性值的读写、添加、删除等,即通过 proxy 能够给对象添加多达十几种类型的拦截器,解决了 Vue 2 中对对象/数组的属性/元素新增、删除以及通过索引修改数组元素监听不到的问题。通过Reflect(反射):对被代理对象的属性进行操作。
⑥ 使用 computed\watch\nextTick\vuex 等 API 需要先导入才能使用:
import { computed, watch, watchEffect defineProps, defineEmits, ref } from ‘vue’
import { useStore } from ‘vuex’
⑦ Vue3<template>
模板中允许有多个根元素。
⑧ Vue3中移除了 s e t 、 set、 set、delete、$forceUpdate、.sync等api和修饰符。
⑨ 插槽写法不同:在 Vue2.x 中具名插槽和作用域插槽分别使用 slot 和 slot-scope 来实现, 在 Vue3.x 中将 slot 和 slot-scope进行了合并统一使用。
⑩ 新增 Teleport 传送门标签
<teleport to=”选择器名称”></teleport> // 将其内元素渲染到指定节点内。
- Vue 2 和 Vue 3 响应式原理有什么不同?
响应式原理上的变更:Vue 3 通过Proxy(代理对象) 替代了 Vue 2 中 Object.defineProperty 对单个属性逐个拦截,Proxy 对整个对象拦截,不需要对对象属性遍历单个拦截,所以性能更高,但兼容性相对较低。Proxy 能够拦截对象中任意属性的变化,包括:属性值的读写、添加、删除等,即通过 proxy 能够给对象添加多达十几种类型的拦截器,解决了 Vue 2 中对对象/数组的属性/元素新增、删除以及通过索引修改数组元素监听不到的问题。通过Reflect(反射):对被代理对象的属性进行操作。
React:
-
React 和 Vue 区别?
不同点:①React可以作为MVVM中第二个V,也就是View,不是MVVM框架,Vue是MVVM的。②React是单向数据绑定(视图的改变需要调用ReactDOM.render()),Vue是双向绑定的。③React是函数式思想,组件通过函数来表现,父子通信通过函数传参拿到props属性。④React使用jsx来写页面,Vue使用常规的html写页面。⑤React组件状态不能直接修改,需要使用setState,否则页面不能同步更新,Vue组件状态data中的数据可以直接修改,Vue做了双向绑定。
相同点:①React像Vue一样也有自己的全家桶,redux或mobx进行状态管理,react-router进行路由管理,axios进行接口请求,ant-design等组件库。②都使用虚拟DOM,将DOM树抽象成js对象,改变真实的DOM状态比改变一个JavaScript对象花销要大很多。③都是单向数据流,数据自顶向下流动,从父组件流向子组件,通过props属性传递。 -
React 组件间如何通信?
如下图列出的几种方式:
详细了解可参阅:React组件间通信方式 -
React 生命周期?
React 16 之前和之后的版本生命周期还不一样,但都是分三个大阶段:挂载阶段、更新阶段、卸载阶段。常用的就是:componentDidMount,componentDidUpdate,shouldComponentUpdate,componentWillUnmount
详细参阅:React生命周期 -
React 生命周期钩子在组件里怎么使用的?
① 类组件中可直接使用钩子函数;
import React ,{Component} from 'react'
class Smzq extends Component{
constructor(props){
super(props)
this.state={
msg:'我是一个msg数据'
}
}
//组件将要挂载时候触发的生命周期函数
componentWillMount(){
console.log('02组件将要挂载')
}
//组件挂载完成时候触发的生命周期函数
componentDidMount(){
//Dom操作,请求数据放在这个里面
console.log('04组件挂载完成')
}
//是否要更新数据,如果返回true才会更新数据
shouldComponentUpdate(nextProps,nextState){
console.log('01是否要更新数据')
console.log(nextProps) //父组件传给子组件的值,这里没有会显示空
console.log(nextState) //数据更新后的值
return true; //返回true,确认更新
}
//将要更新数据的时候触发的
componentWillUpdate(){
console.log('02组件将要更新')
}
//更新数据时候触发的生命周期函数
componentDidUpdate(){
console.log('04组件更新完成')
}
//你在父组件里面改变props传值的时候触发的函数
componentWillReceiveProps(){
console.log('父子组件传值,父组件里面改变了props的值触发的方法')
}
setMsg(){
this.setState({
msg:'我是改变后的msg数据'
})
}
//组件将要销毁的时候触发的生命周期函数,用在组件销毁的时候执行操作
componentWillUnmount(){
console.log('组件销毁了')
}
render(){
console.log('03数据渲染render')
return(
<div>
生命周期函数演示--{this.state.msg}--{this.props.title}
<br/>
<hr/>
<button onClick={()=>this.setMsg()}>更新msg的数据</button>
</div>
)
}
}
export default Smzq
② 函数组件没有生命周期,函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期,需要使用 hooks 中的 useEffect 模拟生命周期钩子。 useEffect 可以看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
useEffect( ()=>{ } )
只有第一个参数的时候,相当于 componentDidMount + componentDidUpdate
钩子的组合,初次渲染并且状态改变时都会触发。
useEffect用法
// 模拟 class 组件的 componentDidMount 和 componentDidUpdate
// 第一个参数执行函数,第二个参数不传
useEffect(() => {
console.log('DidMount 和 DidUpdate')
})
// 模拟 class 组件的 componentDidMount
// 第一个参数执行函数,第二个参数传空数组[]
useEffect(() => {
console.log('加载完了componentDidMount')
}, []) // 第二个参数是 [] (不依赖于任何 state)
// 模拟 class 组件的 componentDidUpdate
// 第一个参数执行函数,第二个参数传state数组
useEffect(() => {
console.log('更新了')
}, [count, name]) // 第二个参数就是依赖的 state
// 模拟 class 组件的 componentDidMount 和 componentWillUnmount
useEffect(() => {
// 如:页面加载时创建了一个定时器,在卸载时要清除
let timerId = setInterval(() => {
console.log(Date.now())
}, 1000)
// 在 useEffect 副作用中返回一个函数,则在组件卸载时,会执行改函数
// 模拟 componentWillUnmount 组件销毁的时候 停止计时器
return () => {
window.clearInterval(timerId)
}
}, [])
-
React 父组件更新时会导致子组件重新渲染,怎么在父组件更新时防止子组件重渲染?
使用React.memo()。 -
React 中对 setState 的理解?
官方文档的解释:
setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式。
将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。
按其解释,setState的作用是把组件的state更新任务排入任务队列,而不是调用就立即更新state状态,即将setState看做请求而不是立即更新组件的命令。所以在调用setState方法后就去取组件的state的值时,会取到没有更新的值。
setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。
setState 在合成事件和生命周期钩子中表现为异步。(合成事件:在 React 中直接使用的事件,如onChange、onClick等,都是由 React 封装后的事件,是合成事件,由 React 管理。)
setState 在原生事件、setTimeout/setInterval,ajax,promise.then内等 React 无法掌控的 API 情况下中调用是同步更新的。(原生事件:React 控制之外的事件,比如原生js绑定的事件,通过 js 获取 dom,然后 dom.onClick 添加事件回调)
-
React 项目里用的有类组件吗?为什么用的都是函数组件?
类组件用的少,使用函数组件多。因为类组件的性能消耗比较大,因为类组件需要创建类组件的实例,而且不能销毁。函数式组件性能消耗小,因为函数式组件不需要创建实例,渲染的时候就执行函数,得到返回的react元素后就直接把旧组件销毁。
更多可参阅:①React函数组件与类组件优劣对比 ②为什么react选择了函数式组件(剖析原理) -
React 项目用的是哪个版本?
React 16.x
Uniapp:
- Uniapp 页面生命周期有哪些?
详细请看官方文档:uniapp 页面生命周期
onLoad 监听页面加载,其参数为上个页面传递的数据,参数类型为object(用于页面传参),参考示例
onShow 监听页面显示
onReady 监听页面初次渲染完成
onHide 监听页面隐藏
onUnload 监听页面卸载
onPullDownRefresh 监听用户下拉动作
onReachBottom 页面上拉触底事件的处理函数
onShareAppMessage 用户点击右上角分享 微信小程序
onPageScroll 监听页面滚动
onTabItemTap 当前是 tab 页时,点击 tab 时触发。
- Uniapp 开发中用的大小单位是什么?和 px 怎么转换的?
rpx;
uni-app 规定屏幕基准宽度 750rpx,所以当UI设计稿宽度为750px时,设计稿中元素大小直接照搬到代码中,只需将单位改成 rpx 即可。
当设计稿大小不为750px时,页面元素宽度在uniapp中的rpx单位大小:750 * 元素在设计稿中的宽度 / 设计稿基准宽度。 - Uniapp 微信小程序开发时遇到打包后的包太大无法上传发布怎么办?
微信小程序对项目打包文件有2M的大小限制,项目代码大小超过后需要分包、代码压缩、静态资源如图片需要放到服务器端等处理。
其它:
- 你在项目中对 Axios 进行二次封装,具体做了些什么?
配置请求url域名前缀、超时时间……请求拦截器中开启加载状态显示、向请求头中加入token……响应拦截器中关闭加载状态显示、响应数据格式化处理、拦截对应错误状态码作相应处理…… - Webpack 打包做了哪些优化使打包体积更小?
① productionSourceMap,关闭生产环境的源码映射,减小打包体积。
② 安装webpack-bundle-analyzer打包分析插件,能够看到打包文件包含关系和大小。当发现vendor.js或者node_modules中其它第三方库文件较大时,可以采取CDN方式引入,配合webpack的externals排除包文件不将其打包进去。
③ 使用自带的splitChunks提取公共代码(或CommonsChunkPlugin),防止代码被重复打包,拆分过大的js文件(并行下载提升速度),合并零散的js、css文件(减少请求)。
④ terser-webpack-plugin:去除console.log\dir\info……只保留warn/error
⑤ url-loader:webpack自带功能将图片转为base64格式,可自定义小于几K的图片进行转换,太大的图片转换的话字符串就越长会导致css文件过大。太大的图片可通过图片压缩工具对图片进行压缩处理或者使用image-webpack-loader插件。
⑥ svg-loader: 安装svg相关打包处理模块,在项目中使用svg格式图片,矢量图且体积小,xml代码格式浏览器加载更快。
⑦ compression-webpack-plugin: 开启gzip压缩,需要Nginx配合作相应配置。 - 项目搭建做了哪些工作?
通过 eslint\prettier 插件进行项目代码规范的指定,npm 脚本配置,环境变量配置,vue.config.js 中做webpack 打包优化相关配置,引入第三方组件库,axios 二次封装,对接口模块化管理,vuex 进行模块化管理,指定工具方法、公共组件、静态资源存放目录等。 - 技术选型考虑了哪些因素?
项目的规模、重要程度、交付时间。
项目的需求。
团队因素:考虑团队的技术组成,还要考虑招聘因素,如小众技术不好找人。
技术因素:可行性、易用性、可维护性、可扩展性、性能、成熟度、社区活跃度、匹配度、生态完善度、npm 丰富度……