在牛客网上老是看到用友的题目,于是就投递了简历... 于8.27投递简历,8.31参加了线上的笔试,9.18上午10点收到电话面试,9.19下午14:00视频面试。
9.19.14:40 更新:刚刚视频面试完,原来是个HR面,感觉还是挺nice的HR姐(da)姐(ma),聊了十几分钟,问了我作为一个广州人对南北方生活差异有什么看法...
据了解,该面试官的部门使用React框架开发,所以问的问题也围绕React展开的比较多,以下是面试官问的问题。
1. 请你说说Vue和React两个框架之间的区别,各自的优缺点
- 从原理上讲:
Vue的数据绑定依赖数据劫持Object.defineProperty()
中的getter
和setter
以及发布订阅模式(eventEmitter)
来监听值的变化,从而让virtual DOM
驱动Model层
和View层
的更新,利用v-model
这一语法糖能够轻易地实现双向的数据绑定。这种模式被称为MVVM: M <=> VM <=> V
。但其实质还是State->View->Actions
的单向数据流,只是使用了v-model
就不需要显式地编写从视图层
到Model
层更新罢了。
React则需要依赖onChange()/setState()
模式来实现数据的双向绑定,因为它在诞生之初就是被设计成单向的数据流
- 组件通信的区别:
父子之间 两者都可以通过props
绑定data
或state
来进行传值,又或者通过绑定回调函数来传值。
兄弟组件之间 都可以通过使用发布订阅模式
写一个EventBus
来监听值的变化。
跨层级:React中可以使用React.context
来进行跨层级的通信。Vue中可以使用V2.2.0新增的provide/inject来实现跨层级注入数据。
- 模板渲染的方式区别:
React在JSX中使用原生的JS语法来实现插值,条件渲染,循环渲染等等。
而Vue则需要依赖指令来进行,更加容易上手但是封装的程度更高,调试成本更大,难以定位Bug。
- 性能差异:
在React中组件的更新渲染是从数据发生变化的根组件开始往子组件逐层重新渲染,而组件的生命周期有shouldComponentUpdate()
函数供给开发者优化组件在不需要更新的时候返回false
而在Vue中是通过watcher
监听到数据的变化之后通过自己的diff
算法,在virtualDom
中直接找到以最低成本更新视图的方式。
- 所以总的来说,Vue更适合开发周期更短的相对小型的项目,React更适合构建稳定大型的应用,可定制化的能力更强。
2. 单向数据流和数据双向绑定的区别还有各自的优缺点
- 单向数据流
优点:所有状态更新更易追踪,源头易追溯,更利于维护。
缺点:只能手动地编写视图层到数据层的更新,代码量增多。
- 数据双向绑定:
优点:由框架隐式地帮我实现视图层和数据层互相驱动更新,代码更加简洁。
缺点:高度封装的“暗箱操作”增加了debug的难度,内在的数据流方向变得难以掌握。
3. 讲讲你对Redux的理解
这时我说到了在公司实习的项目中用到的redux,还说了当时为项目引入redux的理由,并且觉得太过臃肿,比如仅仅为了给当前页面保存当前查看的页码的这一功能,使用组件之间的值传递就可以替代臃肿的redux在项目里的功能。于是便引出了面试官问了以下的问题。
可以用 React.context 来代替redux的功能。
4. 谈谈react组件之间的通信方式
- 父向子传值:使用props,父组件行为触发相应的function使得state的改变,props依赖当前父组件的state,子组件的props也相应的接收到传递来的值。
- 子向父传值:子组件的props指向一个回调函数,子组件行为触发指定的props的函数,利用函数传参传到父组件的回调函数接收参数以接收传值。
- 兄弟传值:找到一个共同的子组件或者共同的父组件来借助传值;使用React.context来传值;使用Redux
5. 说说react的生命周期
mount
->update
->unmount
根据生命周期顺序调用的生命周期函数:
- 挂载
constructor()
componentWillMount()
首次渲染前调用
render()
componentDidMount()
渲染后调用
- 更新
shouldComponentUpdate()
返回布尔值,接收到新的props或state的时候被调用
componentWillUpdate()
渲染前调用
render()
componentDidUpdate()
组件完成更新后调用,初始化时不调用
- 卸载
componentWillUnmount()
组件在DOM中被移除的时候调用
怎么优化一个react组件的性能
我直接从优化web前端性能的各个方法讲了,包括:使用缓存,减少http请求,使用雪碧图,减少不必要的DOM操作,css和js的文件压缩... 感觉都没有回答到点子上,实际上针对react的组件有一套性能优化的方法
- 减少渲染的节点的量,降低组件更新渲染的复杂度,不在render函数进行不必要的计算。
- 减少不必要的嵌套,嵌套地狱将带来较严重的性能问题。
- 减少setState的次数,尽量一步到位。
- 在需要大量items滚动列表的组件使用虚拟列表react-virtualized
- 避免直接使用箭头函数作为事件处理器,因为每一次渲染都会重新创建一个新的事件处理器。
- 利用好生命周期函数
shouldComponentUpdate()
控制组件在不必要重新渲染的时候将该函数返回false
- 使用继承
React.pureComponent
代替React.Component
此时更新渲染依赖的是浅比较,也就是检查数据的引用地址是否发生变化。
flex有什么属性
讲讲http的状态码
讲讲es6有什么新特性
箭头函数和普通函数的区别
- 箭头函数相当于匿名函数,不能作为构造函数,不能使用
new
。 - 箭头函数没有
arguments
,所以用扩展运算符解决。 - 箭头函数不会根据调用它的对象而改变this的指向,会始终指向当前所在的上下文的this。
对闭包的理解
js的变量作用域有全局变量和局部变量
函数内部可以直接读取全局变量
函数外部无法读取到函数内部的局部变量,因为函数在执行完之后,函数内部的环境就被销毁了。
如果函数内部没有使用var
声明一个变量则实际上是声明为全局变量
而闭包就是能够读取外部函数内部变量的函数,并且最终外部函数return
这个内部函数,这时就生成了闭包。
关于内存泄漏
js的垃圾回收机制是根据引用计数来实现的。会自动把一个函数运行之后内部的变量给销毁掉,而产生闭包之后其外部函数中的局部变量的引用就变成了循环引用,则不会被垃圾回收机制捕获并销毁,所以这就造成了内存泄漏,除非人工把该闭包函数=null
将其引用解除并内存释放,此部分将一直留存在runtime
的内存中。
对原型链的理解
访问一个对象的属性,如果对象的内部不存在这个属性则会访问其__proto__
的属性,如果还是找不到就再继续访问它的__proto__
的属性,知道null
为止。
js字符串截取的方法和区别
String.prototype.substr()
参数1:startIndex 参数2:length
String.prototype.substring()
参数1:startIndex 参数2:endIndex
String.prototype.slice()
参数1:startIndex 参数2:endIndex
均不改变原字符串,均返回修改后的字符串 详情见MDN
数组遍历的方法和区别
for循环
Array.prototype.forEach((element)=>{...})
不改变原数组,无返回值
Array.prototype.map((element)=>{...})
不改变原数组,返回新数组
Array.prototype.filter((element)=>{condition})
不改变原数组,返回新数组
Array.prototype.reduce((previousValue,currentValue,currentIndex))
不改变原数组,返回最后一次执行回调函数的返回值
跨域解决方案
- JSONP
- CORS
- 利用NGINX反向代理转发请求
- document.domain + iframe 跨域
- location.hash + iframe 跨域
- window.name + iframe 跨域
- postMessage
- nodejs作为中间件代理跨域
- WebSocket协议跨域
说说缓存
移动端和pc端的不同
移动端页面的性能优化
Link 和 @import 的区别
- link是从html引入的,import是从css引入的
- link会在浏览器加载页面时同步加载css;页面加载完成后再加载import的css
- 优先级link > import
- import是CSS2.1加入的语法,只有IE5+才可识别,Link无兼容性问题
- 权重
!important > 行内样式 > ID > 类、伪类、属性 > 标签名 > 继承 > 通配符
async 和 promise 的区别
- 使用了async的函数始终返回一个promise对象
- await需要搭配async函数使用
- await需要跟在promise前面使用,将会使promise返回resolve的参数
- 使用async,await包裹Promise后,异步的过程就变成同步了
- 使用场景:在几个Promise之间有前后的依赖关系的情况下可以使用async,await
- 被await的Promise可以在包裹其的async函数后
.catch()
来处理错误
git的使用,合并冲突如何解决
- 通过
git diff
查看两个分支之间的差异 merge conflict
产生后通过手动比较文件中冲突的部分选择删除或保留冲突的代码再重新merge并commit- 使用图形化工具处理如Githup Desktop,Git GUI.
git rebase和merge的区别
- 两个命令殊途同归,最终都是为了合把自己新提交的代码合并到更新的分支上。
- 过程中的处理方式大不相同:
rebase
会更变当前分支的基线,先找到合并目标的分支与当前开发者分支的最近公共节点,然后把当前分支的新commit撤销,并把改动保存到.git/rebase
中,并从当前分支更新到目标分支,之后再把新的commit改动内容应用到当前的分支上。merge
则会产生一个新的merge commit
。