出给自己的前端面试题
Posted Jafeney
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了出给自己的前端面试题相关的知识,希望对你有一定的参考价值。
前言
这个世界上最了解你的人其实是你自己,所以我觉得最好的面试其实就是自己对自己的刁难和反思。什么是自己真正擅长的领域?什么是自己真正薄弱的地方?什么是自己最恐惧的东西?这些问题往往是一个技术开发者最不愿意正视的,殊不知也正是自己多年来对这些问题的逃避,让自己的技术高度止步于此。接下来,我会不定期更一些“出给自己的前端面试题”和自己给出的解答 的相关文章,希望读者能从中获得一些进益,如果回答得不正确还望赐教 ^_^
简单讲一讲React的实现原理
React
借助JSX
语法糖在浏览器端实现了一套DOM API
,也就是我们常说的 虚拟DOM。每当数据变化时,React
会重新构建整个DOM
树,然后把当前的DOM树和上一次的DOM
树进行对比,然后将差异的部分更新到实际的DOM
结构中去。这个过程虚拟DOM
因为存在于内存中,Diff
和重构的速度非常快,大大提高了页面渲染的速度。这是我对React
实现原理的大致理解。
简单讲一下React的Diff算法
React
的虚拟DOM之所以任性,归功于React的Diff算法。我们知道传统的Diff算法是通过循环递归对节点进行依次比较,它的算法复杂度是O(n^3)
。而React则通过以下三个优化策略把复杂度降到了O(n)
:
Tree Diff
因为WebUI
中DOM节点跨层移动比较少,React
提出对差异树进行分层比较。即只会对同一层的节点进行比较,也就是说整个过程只遍历一次。
注意:虽跨层移动比较少,但如果移动了React则会先删除原先节点,然后重新创建已它为根节点的整个节点,这个过程比较消耗性能,所以性能优化时应该考虑减少此类操作。
Component Diff
拥有相同类的两个Component
会生成相似的树结构,反之则生成不同的树结构。Component
Update时我们只需要对前后的state
和props
进行比较来判断Component
是否需要更新。
注意:Component Diff是可以在组件生命周期里控制的,如果我们不希望组件更新,可以在组件的
ShouldComponentUpdate
生命周期里return false
去阻止其更新。这也是性能优化的一个常用技巧。
Element Diff
对于同一层级的一组子节点(如数组遍历出来的子节点),分配唯一的key
进行区分,然后在对比时如果存在相同的节点,只需要通过key对那些节点进行移动即可,而无需创建或是删除节点了。
谈谈React和Vue两门框架的区别
React
和Vue
都是目前非常流行的前端框架,Vue
给我最大的感觉是简单,项目配置简单、页面编写简单、事件处理也简单,比较适合新手入门。所以深入挖掘,会发现Vue
底层做的事情其实比React
要更多。先罗列一下两者呈现我们的主要差异:
监听数据变化的实现原理不同
React主要通过比较引用的方式比较组件Props
和State
的变化,而Vue
则是通过getter/setter
以及一些函数的劫持来监听数据的变化。
注意:Vue的这种方式能精确知道数据的变化,不需要特别优化能达到很好的性能,而React如果不优化,可能导致大量组件不必要的重新渲染。可以通过
PureComponent
或shouldComponentUpdate
来阻止不必要的更新。
PS:Vue和
React理念不同,Vue使用的是可变数据,React
则强调数据的不可变(如结合ImmuableJS
)
数据流不同
Vue
支持双向绑定,可以通过v-model
绑定组件和DOM(2.0开始不支持父子组件的双向绑定);React
则提倡单向数据流(onChange/setState
模式),也不支持双向绑定。
代码重用的方式不同
在Vue
中通过mixins
去实现代码重用,而React
里则提倡使用高阶组件(Hoc
)
React弃用
mixins
的原因是这种方式对组件侵入性太强(引入了隐式的依赖关系、Mixins导致命名冲突、Mixins导致复杂的滚雪球),更提倡使用组件组合的方式去重用代码,且随着项目复杂度的增加,这种方式的可拓展性也比mixins
更好。
高阶组件本质就是高阶函数,React
的组件本质也是一个纯粹的函数,所以高阶函数包裹React
组件的方式非常简单。但Vue
组件是一个被包装的函数(Vue
创建组件时实例隐式地做了很多事),如果我们之间把组件的声明进行包装,返回一个高阶函数,就会导致一些隐式的功能无法正常工作。
组件通信方式不同
Vue
中有三种方式可以实现组件通信:
- 父组件通过
props
向子组件传递数据 - 子组件通过事件机制向父组件发送消息
- 父组件通过
provide/inject
可以向子组件注入数据且跨域多个层级
React
也是三种:
- 父组件通过
props
向子组件传递数据或回调 - 子组件通过回调函数更新父组件
- 父组件通过
context
可以与子组件跨层级通信
模板渲染方式不同
React
是通过JSX
渲染模板,而Vue
通过一种扩展的html
语法进行渲染。且两者的本质不同:
React
是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值、条件判断、循环等Vue
是在和组件JS代码分离的单独模块中,通过指令(v-if
、v-for
等)来实现。
Store注入和使用方式的不同
这其实也是Vuex
和Redux
的区别。在Vue中,$store
被直接注入到了组件实例中,因此可以比较灵活地使用:
- 使用
dispatch
和commit
提交更新 - 使用
mapState
或者直接通过this.$store
读取数据
在Redux
中,我们每一个容器组件都需要显示的用 connect
把需要的props
和dispatch
连接起来
- 组件通过
dispatch
触发action
action
内部只能调用reducer
去更新store
分析原理:
Redux
使用不可变数据,而Vuex
的数据是可变的。Redux
每次都是用新的state
替代旧的,而Vuex
则是直接修改store
。
综上所诉,React更偏向于构建稳定的大型应用,非常科班化。而Vue更偏向于简单迅速地解决问题,更灵活,适合小型项目的快速构建和迭代。
介绍一下浅拷贝和深拷贝及其实现方式
什么是浅拷贝?
"="赋值操作就是最简单的浅拷贝。把字符串、数字的值直接赋值给新的变量,相当于完全复制,新变量的值改变不会影响旧变量。但是对象缺不同,因为被复制的是地址,所以新的值改变会影响原来的值。
什么是深拷贝?
深拷贝主要对对象而言,我们将对象进行层层遍历,把它内部的数字、字符串对应赋值给新对象,这个过程就叫深拷贝。
深拷贝的实现方式
最简单的方式是 转成JSON后进行赋值,然后再转回对象(这种方式只能用于单纯只有数字和字符串的对象,而Function则会丢失)。另一种靠谱的方式是采用ES6的 Object.assign
或是拓展运算符 ...
:
const aa = a: () => return 1
// bb就是aa的深拷贝
const bb = Object.asign(, aa)
// cc也是aa的深拷贝
const cc = ...aa
bb.a() // 输出 1
其他方式
利用 jQuery
的 extend()
或是 lodash
的 cloneDeep()
函数 也可以实现深拷贝。
罗列几个常用的正则表达式
面试官非常喜欢出其不意地考察你的正则表达式功底,下面罗列几个最常用的:
用途 | 正则表达式 | 备注 |
---|---|---|
匹配中文字符 | [u4e00-u9fa5] | |
匹配双字节字符(包括汉字在内) | [^x00-xff] | |
匹配空白行 | ns*r | |
匹配HTML标记 | <(S*?)[^>]*>.*?|<.*? /> | |
匹配首尾空白字符 | ^s*|s*$ | 这个被问到过 |
匹配Email地址 | w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)* | |
匹配网址URL | ^(http|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?$ | |
匹配帐号是否合法 | ^[a-zA-Z][a-zA-Z0-9_]4,15$ | 字母开头,允许5-16字节,允许字母数字下划线 |
匹配国内电话号码 | d3-d8|d4-d7 | 0511-4405222 或 021-87888822 |
匹配国内手机号码 | ^(13|15|18)\\d9$ | |
匹配腾讯QQ号 | [1-9][0-9]4, | 腾讯QQ号从10000开始 |
匹配中国邮政编码 | [1-9]d5(?!d) | |
匹配身份证 | ^\\d15(\\d2[A-Za-z0-9])? | 身份证为15位或18位,最后一位可能为字母 |
匹配组织机构代码证 | [a-zA-Z0-9]8-[a-zA-Z0-9] | |
匹配IP地址 | d+.d+.d+.d+ | 简单匹配,不精准 |
匹配非法字符 | <.*?script[^>]*?>.*?(<\\/.*?script.*?>)* |
什么是BFC
浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks
, table-cells
, 和 table-captions
),以及overflow
值不为visiable
的块级盒子,都会为他们的内容创建新的BFC
(块级格式上下文)。提炼几个要点:
float
的值不是none
position
的值不是static
或者relative
display
的值是inline-block
、table-cell
、flex
、table-caption
或者inline-flex
overflow
的值不是visible
BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。
BFC中盒子怎么对齐
在BFC中,每一个盒子的左外边缘(margin-left
)会触碰到容器的左边缘(border-left
)(对于从右到左的格式来说,则触碰到右边缘)。浮动也是如此(尽管盒子里的行盒子 Line Box 可能由于浮动而变窄),除非盒子创建了一个新的BFC(在这种情况下盒子本身可能由于浮动而变窄)
如何避免外边距折叠
常规流布局时,盒子都是垂直排列,两者之间的间距由各自的外边距所决定,但不是二者外边距之和。在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距。折叠的结果按照如下规则计算:
- 两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
- 两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
- 两个外边距一正一负时,折叠结果是两者的相加的和。
综上分析,若两个相邻元素在不同的BFC中,就能避免外边距折叠。
<div class="container">
<p>Sibling 1</p>
<p>Sibling 2</p>
<div class="newBFC">
<p>Sibling 3</p>
</div>
</div>
对应的CSS
.container
background-color: red;
overflow: hidden; /* creates a block formatting context */
p
margin: 10px 0;
background-color: lightgreen;
.newBFC
overflow: hidden; /* creates new block formatting context */
如何使浮动元素的容器也能被撑开
浮动元素是会脱离文档流的(绝对定位元素会脱离文档流)。如果一个没有高度或者height
是auto
的容器的子元素是浮动元素,则该容器的高度是不会被撑开的。我们通常会利用伪元素(:after
或者:before
)来解决这个问题:
.clearfix:after
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
BFC能包含浮动,也能解决容器高度不会被撑开的问题。比如 给容器加一个 overflow:hidden
创建BFC
如何解决多列布局最后一列下移问题
如果我们创建一个占满整个容器宽度的多列布局,在某些浏览器中最后一列有时候会掉到下一行。这可能是因为浏览器四舍五入了列宽从而所有列的总宽度会超出容器。但如果我们在多列布局中的最后一列里创建一个新的BFC
,它将总是占据其他列先占位完毕后剩下的空间。
<div class="container">
<div class="column">column 1</div>
<div class="column">column 2</div>
<div class="column">column 3</div>
</div>
.column
width: 31.33%;
background-color: green;
float: left;
margin: 0 1%;
/* Establishing a new block formatting
context in the last column */
.column:last-child
float: none;
overflow: hidden;
参考资料
以上是关于出给自己的前端面试题的主要内容,如果未能解决你的问题,请参考以下文章