面试5(大厂面试题)¶
统计信息:字数 43843 阅读88分钟
看懂的¶
2、浏览器渲染页面的过程¶
- 用户输入 URL 地址
- 浏览器解析 URL 解析出主机名
- 浏览器将主机名转换成服务器 ip 地址(浏览器先查找本地 DNS 缓存列表 没有的话 再向浏览器默认的 DNS 服务器发送查询请求 同时缓存)
- 浏览器将端口号从 URL 中解析出来
- 浏览器建立一条与目标 Web 服务器的 TCP 连接(扩展:三次握手+四次挥手)
- 浏览器向服务器发送一条 HTTP 请求报文(扩展:HTTP请求类型)
- 服务器向浏览器返回一条 HTTP 响应报文(扩展:HTTP响应状态码)
- 关闭连接 浏览器解析文档
8、"123456789876543212345678987654321..." 的第 n 位是什么?¶
// 官方答案:不好
function getNum(n) {
let num = 0, flag = true;
for (let i = 0; i < n; i++) {
if (num === 1) flag = true
if (num === 9) flag = false
flag ? num++ : num--
}
return num
}
// 自己答案:可以看出,这个字符串是循环的,循环部分是 1234567898765432 长度是16,那么直接求余数即可
function fn(n) {
if (n === 0) return null;
let index = n % 16;
let str = '1234567898765432';
return index === 0 ? '2' : str[index];
}
2、说一下 React 的生命周期(新增的,删除的),不同生命周期的作用¶
constructor 构造器(这里初始化state和属性)使用super实现继承
componentWillMount:即将废弃
componentDidMount: 界面首次加载后,发出请求,监听DOM事件
shouldComponentUpdate: 组件是否需要更新(props-state发生变化后)这里用于性能优化
componentWillReceiveProps 组件将要获取到Props(即将废弃,可能不稳定)
Static getDerivedStateFromProps 使用这个代替(props转换成state)
componentWillUpdate(即将废弃)getSnapshotBeforeUpdate
componentDidUpdate(组件已经更新)
componentWillUnmount 组件即将卸载——清理定时器等
5. 以下代码输出的是什么?为什么呢?¶
for (var i=0; i<5; i++) {
setTimeout( function timer() {
console.log(i);
}, 0);
}
自己:结果是5个5;原因:setTimeout内部的代码不会立即执行(虽然设置的是0ms),这部分代码会存放在一个序列中。当循环结束, i = 5;代码执行完毕,此时输出5个5;
标准:浏览器是多线程的(JS引擎线程,render线程,事件监听线程,http请求线程),但是 JS 代码在执行中是单线程的(除去ajax请求部分);所以JS代码执行过程中会发生阻塞;setTimeout内部的代码被阻塞,直到全部的代码执行完毕后,settimeout 的代码才开始执行。——实际上内部代码执行的时间不是0毫秒后。
2. CSS 如何实现弹窗水平垂直居中?(文本和块级元素,垂直居中,水平居中)¶
/* 文本 */
height: 100px;
line-height: 100px;
text-align: center;
/* 块级 */
display: flex;
justify-content: center;
align-items: center;
3. ==
和 ===
的区别¶
===
为恒等符:当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。
==
为等值符: 当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。 a、如果一个是null、一个是undefined,那么[相等]。console.log(null == undefined); // true
b、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。 c、如果任一值是 true,把它转换成 1 再比较;如果任一值是 false,把它转换成 0 再比较。 d、如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较。对象转换成基础类型,利用它的toString或者valueOf方法。 js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。 e、任何其他组合,都[不相等]。
4. 30 ==
'30' 的过程是怎样的¶
- 30为数值类型,而'30'字符串类型,因此等号两边的数据类型不相等,需要进行转换类型;
- 由于一个是数值,另一个字符串,所以需要将字符串转换成数值再进行比较,即
'30' => 30
;- 这时等号两边同样为数值型数据,即
30 == 30
,所以返回true
1. CSS 实现三栏布局(左右两边固定宽度,中间自适应)?¶
- 使用flex布局: 父元素设置
display: flex
,左右两边设置固定宽度,中间设置flex-grow: 1
;.main { display: flex; } .center { flex: 1 1 auto; } .left { width: 100px; } .right { widht: 100px; }
- 使用浮动布局:左右两边设置固定宽度,而且分别设置
float:left和right
,这个方法有一个需要注意的是在HTML中,中间栏需要和右边栏进行对调;.main { overflow: hidden; } .left { float: left; } .right { float: right: }
- 使用绝对定位布局:左右两边设置固定宽度和
position:absolute
,而且分别设置left: 0
和right: 0
,中间栏只要设置左右margin为左右栏的宽度就可以了(需要注意的是左右两边需要设置top: 0
,不然右边会被订下来).main { position: relative; } .center { margin: 0px 100px; } .left, .right { position: absolute; top: 0; } .left { left: 0; } .right { right: 0; }
3. 理解的HTTP状态码有哪些?¶
100 请求发送中
200 请求已经完成,正常
300 请求重定向
400 请求参数出错误(前端URL错误);403 权限不足;404 无资源找不到
500 服务器内部错误 502 服务器内部错误
5. display:none
和 visibility:hidden
的区别?¶
一个是界面上DOM不显示,会造成DOM的回流和重绘(reflow+repaint)性能开销比较大;后者会改变DOM元素的显示方式,不会造成DOM元素的回流,性能开销较小。优先使用后者实现界面效果。
6. CSS选择器的理解,你知道多少选择器?¶
单个选择器:标签选择器、类选择器、ID选择器、伪元素选择器等(after-before-child)(常用的是类选择器)、属性选择器(用于表单input disabled 等属性)。
组合选择器:父子选择器,兄弟层级选择器
选择器的权重:!important > ID > class > tag > 通配符 > CSS继承
14. 有一个按钮是异步生成的,怎样对它进行事件绑定?¶
由于按钮是异步生成的,所以我选择将事件绑定在按钮生成的父元素上,通过事件委托的机制,利用事件冒泡,把事件绑定在父元素上,可以通过判断
event.target
按钮是否已经生成,从而实现相应的事件。 事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document
对象; 事件捕获则跟事件冒泡相反,事件会从document
对象开始发生,直到最具体的元素;
11 还记得二叉树吗?描述二叉树的几种遍历方式?¶
- 先序遍历:若二叉树非空,访问根结点,遍历左子树,遍历右子树。
- 中序遍历:若二叉树非空,遍历左子树;访问根结点;遍历右子树。
- 后序遍历:若二叉树非空,遍历左子树;遍历右子树;访问根结点。
所有遍历是以递归的形似,直到没有子节点。
4 CSS和JS的位置会影响页面效率,为什么?¶
css在加载过程中不会影响到DOM树的生成,但是会影响到Render树的生成,进而影响到layout,所以一般来说,style的link标签需要尽量放在head里面,因为在解析DOM树的时候是自上而下的,而css样式又是通过异步加载的,这样的话,解析DOM树下的body节点和加载css样式能尽可能的并行,加快Render树的生成的速度。
js脚本应该放在底部,原因在于js线程与GUI渲染线程是互斥的关系,如果js放在首部,当下载执行js的时候,会影响渲染行程绘制页面,js的作用主要是处理交互,而交互必须得先让页面呈现才能进行,所以为了保证用户体验,尽量让页面先绘制出来。
2 说说从输入URL到看到页面发生的全过程,越详细越好¶
-
首先浏览器主进程接管,开了一个下载线程。
-
然后进行HTTP请求(DNS查询、IP寻址等等),中间会有三次握手,等待响应,开始下载响应报文。
-
将下载完的内容转交给Renderer进程管理。
-
Renderer进程开始解析css rule tree和dom tree,这两个过程是并行的,所以一般我会把link标签放在页面顶部。
-
解析绘制过程中,当浏览器遇到link标签或者script、img等标签,浏览器会去下载这些内容,遇到时候缓存的使用缓存,不适用缓存的重新下载资源。
-
css rule tree和dom tree生成完了之后,开始合成render tree,这个时候浏览器会进行layout,开始计算每一个节点的位置,然后进行绘制。
-
绘制结束后,关闭TCP连接,过程有四次挥手。
1、实现一个函数,判断输入是不是回文字符串。¶
function run(input) {
if (typeof input !== 'string') return false;
return input.split('').reverse().join('') === input;
}
2、两种以上方式实现已知或者未知宽度的垂直水平居中。¶
// 1
.wraper {
position: relative;
.box {
position: absolute;
top: 50%;
left: 50%;
width: 100px;
height: 100px;
margin: -50px 0 0 -50px;
}
}
// 2
.wraper {
position: relative;
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
// 3
.wraper {
.box {
display: flex;
justify-content:center;
align-items: center;
height: 100px;
}
}
// 4
.wraper {
display: table;
.box {
display: table-cell;
vertical-align: middle;
}
}
2 我现在有一个button,要用react在上面绑定点击事件,要怎么做?¶
class Demo {
render() {
return (
<button onClick={(e) => {
alert('我点击了按钮')
}}> 按钮 </button>
);
}
}
3 接上一个问题,你觉得你这样设置点击事件会有什么问题吗?¶
由于onClick使用的是匿名函数,所有每次重渲染的时候,会把该onClick当做一个新的prop来处理,会将内部缓存的onClick事件进行重新赋值,所以相对直接使用函数来说,可能有一点的性能下降(个人认为)。
修改:
class Demo {
onClick = (e) => { alert('我点击了按钮') }
render() {
return <button onClick={this.onClick}> 按钮 </button>
}
}
当然你在内部声明的不是箭头函数,然后你可能需要在设置onClick的时候使用bind绑定上下文,这样的效果和先前的使用匿名函数差不多,因为bind会返回新的函数,也会被react认为是一个新的prop。
3、实现效果,点击容器内的图标,图标边框变成border 1px solid red,点击空白处重置。¶
如果用React,那么组件设置一个状态,然后根据状态改变图标边框的颜色。或者直接通过REF更改颜色
class Demo {
constructor(props) {
super(props);
this.state = {
inside: false,
};
}
// 增加全局事件监听,如果点击外部,那么就是变成默认的颜色
render () {
return (
<div className={this.state.inside ? 'red-border' : ''}>
<span onClick={this.onClick}></span>
</div>
);
}
}
官方:
const box = document.getElementById('box');
function isIcon(target) {
return target.className.includes('icon');
}
box.onClick = function(e) {
e.stopPropagation();
const target = e.target;
if (isIcon(target)) {
target.style.border = '1px solid red';
}
}
const doc = document;
doc.onclick = function(e) {
const children = box.children;
for (let i; i < children.length; i++) {
if (isIcon(children[i])) {
children[i].style.border = 'none';
}
}
}
¶
似懂非懂¶
6、多语言网站建设应注意哪些事项?¶
- 不同国家语言不同:需要支持多语言(后端python设置,gettext);前端使用i18N获取
- 不同国家时区不同,那么使用moment库根据语言进行转换
- 不同国家数字格式和货币格式不同,可以使用 number.toLocaleString 转换。
7、React 非父子、兄弟组件传值¶
React 内部支持状态提升、把公共的值维护在父组件中;
使用 eventBus 订阅发布模式进行复杂层级组件的传参。
可以使用第三方库进行状态管理(状态管理:Redux、Mobx 等等。)扩展:react-redux:新建一个storage,然后在根组件中绑定,在不同的子组件中使用。
8、写过响应式网站吗?¶
媒体查询+rem控制字体大小,进行移动端屏幕适配等。
768+992 设置不同的屏幕的显示情况;屏幕像素比 dpr 判断高清屏幕等操作,然后更改根组件的基准尺寸,其他的使用REM单位
0、对于评述算法优劣术语的说明¶
-
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
-
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
-
内排序:所有排序操作都在内存中完成;
-
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
-
时间复杂度: 一个算法执行所耗费的时间。
-
空间复杂度: 运行完一个程序所需内存的大小。
16. POST
和 GET
,说说两者的区别?¶
- 大小:
GET
提交的数据最大为2k(原则上url长度无限制,可是浏览器通常限制url长度在2k左右);POST
没有限制大小。- 发送方式:
GET
请求数据放在url上,即HTTP协议头中,其格式为:url?key=value&key2=value
;POST
把数据放在HTTP的包体中(Request Body)。- 安全性:
GET
请求可被缓存,请求保存在浏览器历史记录中;POST
则不能被缓存。与POST
相比,GET
的安全性较差,因为发送的数据是URL的一部分。- 发送TCP包:对
GET
请求只产生一个TCP包,浏览器会把http header
和data
一并发送出去,服务器响应200
(返回数据);对于POST
请求产生两个TCP数据包,浏览器先发送http header
,服务器确认权限正确响应100
(continue)返回浏览器,浏览器收到100
确认继续请求,再次发送data
,服务器响应200
(返回数据)。
13. apply
和 call
的区别?¶
相同点:可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。
不同点:实际上,
apply
和call
的功能是一样的,只是传入的参数列表形式不同。apply
:最多只能有两个参数——新this
对象和一个数组argArray
。如果给该方法传递多个参数,则把参数都写进这个数组里面,当然,即使只有一个参数,也要写进数组里。如果argArray
不是一个有效的数组或arguments
对象,那么将导致一个TypeError
。如果没有提供argArray
和thisObj
任何一个参数,那么Global对象将被用作thisObj
,并且无法被传递任何参数。
call
:它可以接受多个参数,第一个参数与apply
一样,后面则是一串参数列表。这个方法主要用在js对象各方法相互调用的时候,使当前this实例指针保持一致,或者在特殊情况下需要改变this指针。如果没有提供thisObj
参数,那么 Global 对象被用作thisObj
。
12. 知道如何修改this的指向吗?¶
修改this指向的办法有三种:
apply
、call
和bind
apply
、call
:通过传入需要指向的对象,从而改变this
的指向,指向传入的第一个参数;bind
:它会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。其实还有一种:new关键字改变this指向 因为在
new
的过程中,其中有一个步骤为将构造函数内部的this
指向实例对象,所以通过new关键字
也可以改变this
的指向。在 ES6 中,可以通过箭头函数改变当前函数的指向(指向类中的方法,而不是调用环境下的方法)
window.color = 'red';
var o = { color:'blue' };
function sayColor(){
console.log(this.color);
}
var globalSaycolor = sayColor;
var objectSaycolor = sayColor.bind(o);
globalSaycolor(); // redobjectSaycolor(); // blue
14 那给我介绍一下react¶
以前我们没有jquery的时候,我们大概的流程是从后端通过ajax获取到数据然后使用jquery生成dom结果然后更新到页面当中,但是随着业务发展,我们的项目可能会越来越复杂,我们每次请求到数据,或则数据有更改的时候,我们又需要重新组装一次dom结构,然后更新页面,这样我们手动同步dom和数据的成本就越来越高,而且频繁的操作dom,也使我我们页面的性能慢慢的降低。
这个时候mvvm出现了,mvvm的双向数据绑定可以让我们在数据修改的同时同步dom的更新,dom的更新也可以直接同步我们数据的更改,这个特定可以大大降低我们手动去维护dom更新的成本,mvvm为react的特性之一,虽然react属于单项数据流,需要我们手动实现双向数据绑定。
有了mvvm还不够,因为如果每次有数据做了更改,然后我们都全量更新dom结构的话,也没办法解决我们频繁操作dom结构(降低了页面性能)的问题,为了解决这个问题,react内部实现了一套虚拟dom结构,也就是用js实现的一套dom结构,他的作用是讲真实dom在js中做一套缓存,每次有数据更改的时候,react内部先使用算法,也就是鼎鼎有名的diff算法对dom结构进行对比,找到那些我们需要新增、更新、删除的dom节点,然后一次性对真实DOM进行更新,这样就大大降低了操作dom的次数。
那么diff算法是怎么运作的呢,首先,diff针对类型不同的节点,会直接判定原来节点需要卸载并且用新的节点来装载卸载的节点的位置;针对于节点类型相同的节点,会对比这个节点的所有属性,如果节点的所有属性相同,那么判定这个节点不需要更新,如果节点属性不相同,那么会判定这个节点需要更新,react会更新并重渲染这个节点。
react设计之初是主要负责UI层的渲染,虽然每个组件有自己的state,state表示组件的状态,当状态需要变化的时候,需要使用setState更新我们的组件,但是,我们想通过一个组件重渲染它的兄弟组件,我们就需要将组件的状态提升到父组件当中,让父组件的状态来控制这两个组件的重渲染,当我们组件的层次越来越深的时候,状态需要一直往下传,无疑加大了我们代码的复杂度,我们需要一个状态管理中心,来帮我们管理我们状态state。
这个时候,redux出现了,我们可以将所有的state交给redux去管理,当我们的某一个state有变化的时候,依赖到这个state的组件就会进行一次重渲染,这样就解决了我们的我们需要一直把state往下传的问题。redux有action、reducer的概念,action为唯一修改state的来源,reducer为唯一确定state如何变化的入口,这使得redux的数据流非常规范,同时也暴露出了redux代码的复杂,本来那么简单的功能,却需要完成那么多的代码。
后来,社区就出现了另外一套解决方案,也就是mobx,它推崇代码简约易懂,只需要定义一个可观测的对象,然后哪个组价使用到这个可观测的对象,并且这个对象的数据有更改,那么这个组件就会重渲染,而且mobx内部也做好了是否重渲染组件的生命周期shouldUpdateComponent,不建议开发者进行更改,这使得我们使用mobx开发项目的时候可以简单快速的完成很多功能,连redux的作者也推荐使用mobx进行项目开发。但是,随着项目的不断变大,mobx也不断暴露出了它的缺点,就是数据流太随意,出了bug之后不好追溯数据的流向,这个缺点正好体现出了redux的优点所在,所以针对于小项目来说,社区推荐使用mobx,对大项目推荐使用redux。
15 react状态¶
问题:假如我一个组件有一个状态count为1,然后我在componentDidMount()里面执行执行了两次this.setState({count: this.state.count++}),然后又执行了两次setTimeout(() => { this.setState({count: this.state.count++}) }, 0),最后count为多少?为什么?
count为4,因为第二次执行setState的时候,取不到第一次this.state.count++的结果,react在一轮生命周期结束后才会更新内部的state,如果在一轮生命周期内多次使用了setState,react内部会有一个字段isBatchUpdate标识本次更新为批量更新,然后在最后render的时候将所有setState的结果提交到state中,一次性进行更新,并且把isBatchUpdate这个字段设置为false。
针对于两次setTimeout,js引擎会把这两个setState丢到事件队列中,等待js空闲了去执行,而我们的渲染函数render是同步执行的(react16版本默认没有开启异步渲染),所以等我们render执行完全,也就是我们的state被同步完后,在取事件队列里面的setState进行执行,setTimeout的第二个setState也是一样的,所以最后结果是4。
10 说说你接触到的tree的数据结构是怎么实现的?¶
树概念:根节点、子节点、叶子节点
树分类:二叉树(完全二叉树)、搜索二叉树、DOM树(前端常用,markdown 通过 AST 转换成 DOM 树过程)、红黑树(了解)
树遍历:二叉树三种遍历方式。DFS、BFS 深度优先遍历和广度优先遍历(根本就是递归树节点)。
特殊:字典树(用于单词的录入和查找)。二叉树和数组的转换等等(了解)。
节点:TreeNode { key: 2, children: [TreeNode] } 组成
9 描述一下this¶
this,函数执行的上下文,可以通过apply,call,bind改变this的指向。
对于匿名函数或者直接调用的函数来说,this指向全局上下文(浏览器为window,nodejs为global),
剩下的函数调用,那就是谁调用它,this就指向谁。
还有es6的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明,this就指向哪里。
12 现在要你完成一个Dialog组件,说说你设计的思路?它应该有什么功能?¶
- 该组件需要提供hook指定渲染位置,默认渲染在body下面。
- 然后改组件可以指定外层样式,如宽度等
- 组件外层还需要一层mask来遮住底层内容,点击mask可以执行传进来的onCancel函数关闭Dialog。
- 另外组件是可控的,需要外层传入visible表示是否可见。
- 然后Dialog可能需要自定义头head和底部footer,默认有头部和底部,底部有一个确认按钮和取消按钮,确认按钮会执行外部传进来的onOk事件,然后取消按钮会执行外部传进来的onCancel事件。
- 当组件的visible为true时候,设置body的overflow为hidden,隐藏body的滚动条,反之显示滚动条。
- 组件高度可能大于页面高度,组件内部需要滚动条。
- 只有组件的visible有变化且为ture时候,才重渲染组件内的所有内容。
5 现在有一个函数A和函数B,请你实现B继承A¶
// 方式1
function B(){}
function A(){}
B.prototype = new A();
// 方式2
function A(){}
function B(){
A.call(this);
}
// 方式3
function B(){}
function A(){}
B.prototype = new A();
function B(){
A.call(this);
}
6 刚刚你在Q5中说的几种继承的方式,分别说说他们的优缺点¶
- 方式1:简单易懂,但是无法实现多继承,父类新增原型方法/原型属性,子类都能访问到
- 方式2:可以实现多继承,但是只能继承父类的实例属性和方法,不能继承原型属性/方法
- 方式3:可以继承实例属性/方法,也可以继承原型属性/方法,但是示例了两个A的构造函数
2 react 的虚拟dom是怎么实现的¶
首先说说为什么要使用Virturl DOM,因为操作真实DOM的耗费的性能代价太高,所以react内部使用js实现了一套dom结构,在每次操作在和真实dom之前,使用实现好的diff算法,对虚拟dom进行比较,递归找出有变化的dom节点,然后对其进行更新操作。为了实现虚拟DOM,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是prop,每次进行diff的时候,react会先比较该节点类型,假如节点类型不一样,那么react会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较prop是否有更新,假如有prop不一样,那么react会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点。
3 react 的渲染过程中,兄弟节点之间是怎么处理的?也就是key值不一样的时候。¶
通常我们输出节点的时候都是map一个数组然后返回一个ReactNode,为了方便react内部进行优化,我们必须给每一个reactNode添加key,这个key prop在设计值处不是给开发者用的,而是给react用的,大概的作用就是给每一个reactNode添加一个身份标识,方便react进行识别,在重渲染过程中,如果key一样,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新,如果key不一样,则react先销毁该组件,然后重新创建该组件。
6 CSS¶
我现在有一个进度条,进度条中间有一串文字,当我的进度条覆盖了文字之后,文字要与进度条反色,怎么实现?
当时我给的是js的方案,在进度条宽度变化的时候,计算盖过每一个文字的50%,如果超过,设置文字相反颜色。当然css也有对应的方案,也就是 mix-blend-mode,我并没有接触过。对应html也有对应方案,也就设置两个相同位置但是颜色相反的dom结构在重叠在一起,顶层覆盖底层,最顶层的进度条取overflow为hidden,其宽度就为进度。
1 你说说你用react有什么坑点?¶
1、JSX做表达式判断时候,需要强转为boolean类型,如:
render() {
const b = 0;
return (
<div>
{!!b && <div>这是一段文本</div>}
</div>
);
}
如果不使用 !!b 进行强转数据类型,会在页面里面输出 0。
2、尽量不要在 componentWillReviceProps 里使用 setState,如果一定要使用,那么需要判断结束条件,不然会出现无限重渲染,导致页面崩溃。
3、给组件添加ref时候,尽量不要使用匿名函数,因为当组件更新的时候,匿名函数会被当做新的prop处理,让ref属性接受到新函数的时候,react内部会先清空ref,也就是会以null为回调参数先执行一次ref这个props,然后在以该组件的实例执行一次ref,所以用匿名函数做ref的时候,有的时候去ref赋值后的属性会取到null。详情见
4、遍历子节点的时候,不要用 index 作为组件的 key 进行传入。
4 你说说event loop吧¶
首先,js是单线程的,主要的任务是处理用户的交互,而用户的交互无非就是响应DOM的增删改,使用事件队列的形式,一次事件循环只处理一个事件响应,使得脚本执行相对连续,所以有了事件队列,用来储存待执行的事件,那么事件队列的事件从哪里被push进来的呢。
那就是另外一个线程叫事件触发线程做的事情了,他的作用主要是在定时触发器线程、异步HTTP请求线程满足特定条件下的回调函数push到事件队列中,等待js引擎空闲的时候去执行,当然js引擎执行过程中有优先级之分,首先js引擎在一次事件循环中,会先执行js线程的主任务,然后会去查找是否有微任务microtask(promise),如果有那就优先执行微任务,如果没有,在去查找宏任务macrotask(setTimeout、setInterval)进行执行。
5 说说事件流吧¶
事件流分为两种,捕获事件流和冒泡事件流。
- 捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。
- 冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。
事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段。
4、请简单实现双向数据绑定mvvm。¶
<input id="input"/>
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
set(value) {
input.value = value;
this.value = value;
}
});
input.onChange = function(e) {
data.text = e.target.value;
}
5、实现Storage,使得该对象为单例,并对localStorage进行封装设置值setItem(key,value)和getItem(key)¶
var instance = null;
class Storage {
static getInstance() {
if (!instance) {
instance = new Storage();
}
return instance;
}
setItem = (key, value) => localStorage.setItem(key, value)
getItem = key => localStorage.getItem(key)
}
不懂(没学过)¶
5、js 实现数值千分位¶
number.toLocaleString('en-US', {style: 'currency', currency: 'USD'})
直接写的话,就是把正负号、整数和小数分开,然后不断求1000的余数,转换成字符串
5、有没有自己实现过 Promise?¶
看了不少文章,自己也写过了,但问到细节还是答烂了。
6、有没有写过 Webpack 插件?常用的插件是哪些?¶
自己接触过 happyPack 实现多核CPU快速编译;tree-shaking 在生产环境中去掉多余节点。。。(待完成)
1、浏览器打开一个页面前端缓存了哪些东西?(答案不确定)¶
我:后端通过设置响应头 CacheControl
设置资源过期时间,用于缓存一些静态资源、DNS 缓存
面试官:后端缓存了 session
3、异步编程的四种方法¶
看到这题瞬间感觉阮老师简直就是中国前端界的教父级人物啊,面试官的理想答案应该是阮老师这篇文章:Javascript 异步编程的 4 种方法。自己特地看过,也总结在我的面试题总结里面了。then Promise async-await
15. 跨域有处理过吗?¶
我处理过的跨域有两种情况:
- 一种是在页面中嵌入了一个iframe,因此父子iframe间产生了跨域,要解决这个问题,只需要把
document.domain
设置成相同的值就可以在两个页面里进行相应的操作了;- 另外一种情况是用Vue开发涉及到的跨域问题,这个问题只需要修改config文件夹下的index.js中的dev:{}部分中修改proxyTable参数即可,相当于对跨域的url进行了代理,从而可以顺利访问。
另外说了一下自己比较熟悉的一种跨域解决方案:JSONP JSONP解决跨域问题的本质其实就是
<script>
标签可以请求不同域名下的资源,即<script>
请求不受浏览器同源策略影响。
面试官听到JSONP立刻提出了一个问题:JSONP是否可以支持 POST方法?为什么?
JSONP只支持
GET
的请求方法,上面也提到了JSONP原理其实就是利用<script>
标签发送了一个URL
给服务器,其实与ajax XMLHttpRequest
协议无关了,相当于输入了一个url
而已,所以必然只能为GET
请求方法。
12 说说你记得的所有的排序,他们的原理是什么?¶
- 冒泡排序:双层遍历,对比前后两个节点,如果满足条件,位置互换,直到遍历结束。时间复杂是N平方
- 选择排序:声明一个数组,每次去输入数组里面找数组中的最大值或者最小值,取出来后push到声明的数组中,直到输入数组为空。时间复杂是N平方
- 快速排序:去数组中间的那一个数,然后遍历所有数,小于该数的push到一个数组,大于该数的push到另外一个数组,然后递归去排序这两个数组,最后将所有结果连接起来。时间复杂是N * LogN。这个是目前最好的排序方法
- 堆排序
- 希尔排序:这个稳定性不好
10 说一下浏览器的缓存机制¶
浏览器缓存机制有两种,一种为强缓存,一种为协商缓存。
对于强缓存,浏览器在第一次请求的时候,会直接下载资源,然后缓存在本地,第二次请求的时候,直接使用缓存。
对于协商缓存,第一次请求缓存且保存缓存标识与时间,重复请求向服务器发送缓存标识和最后缓存时间,服务端进行校验,如果失效则使用缓存。
协商缓存方案:
- Exprires:服务端的响应头,第一次请求的时候,告诉客户端,该资源什么时候会过期。Exprires的缺陷是必须保证服务端时间和客户端时间严格同步。
- Cache-control:max-age,表示该资源多少时间后过期,解决了客户端和服务端时间必须同步的问题,
- If-None-Match/ETag:缓存标识,对比缓存时使用它来标识一个缓存,第一次请求的时候,服务端会返回该标识给客户端,客户端在第二次请求的时候会带上该标识与服务端进行对比并返回If-None-Match标识是否表示匹配。
- Last-modified/If-Modified-Since:第一次请求的时候服务端返回Last-modified表明请求的资源上次的修改时间,第二次请求的时候客户端带上请求头If-Modified-Since,表示资源上次的修改时间,服务端拿到这两个字段进行对比。
2 你说一下webpack的一些plugin,怎么使用webpack对项目进行优化。¶
正好最近在做webpack构建优化和性能优化的事儿,当时吹了大概15~20分钟吧,插件请见webpack插件归纳总结。
构建优化:
- 减少编译体积 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime
- 并行编译 happypack、thread-loader、uglifyjsWebpackPlugin开启并行
- 缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin开启缓存、babel-loader开启缓存
- 预编译 dllWebpackPlugin && DllReferencePlugin、auto-dll-webapck-plugin
性能优化:
- 减少编译体积 Tree-shaking、Scope Hositing
- hash缓存 webpack-md5-plugin
- 拆包 splitChunksPlugin、import()、require.ensure
Q3 es6 class 的new实例和es5的new实例有什么区别
面试官说不一样。。。后来我看了一下babel的编译结果,发现只是类的方法声明的过程不一样而已,最后new的结果是一样的。。。具体答案现在我也不知道。。。
4 算法¶
1、我现在有一个数组[1,2,3,4],请实现算法,得到这个数组的全排列的数组,如[2,1,3,4],[2,1,4,3]。。。。你这个算法的时间复杂度是多少。这个我没写出来,大概给了个思路,将每一个数组拆除俩个小数组进行求它的全排列,然后得到的结果互相之间又进行全排列,然后把最后的结果连接起来。。。感兴趣的同学见数组全排列
思路:这是一个无重复的数组。那么使用回溯算法即可遍历全部的数组元素。排列问题使用回溯算法。
2、我现在有一个背包,容量为m,然后有n个货物,重量分别为w1,w2,w3...wn,每个货物的价值是v1,v2,v3...vn,w和v没有任何关系,请求背包能装下的最大价值。这个我也没写出来,也给了个思路,首先使用Q4的方法得到货物重量数组的全组合(包括拆分成小数组的全组合),然后计算每一个组合的价值,并进行排序,然后遍历数组,找到价值较高切刚好能装进背包m的组合。本题 动态规划面试题
,感兴趣的同学请自行百度或者谷歌。
思路:背包问题:使用贪心算法或者DP。计算当前价值最高的商品,然后优先放入背包。部分可能是动态规划。
每一个货物的单位价值是 V / W,那么优先放价值最大的部分。最优解 = 价值最高的商品 + 价值剩余的商品。如果有空余,那么比较第二种和第一种算法的优劣,然后选择合适的方法。
不需要(可能超纲)¶
1、爬虫引擎是怎样抓取页面的¶
自己:pyhton 获取 HTML 界面,然后爬取 title H1标签等内容。对于前端,使用语义化标签和合理的关键词,可以优化SEO。从后端的角度,可以使用 python beautifulSoup 库爬取界面。我作为前端,可以爬取简单静态页面。
4、解释同步异步、阻塞非阻塞、并行并发之间的区别¶
这里补充一下并行和并发:并行是指运算中的两件或更多件事情在同一时刻发生。实事求是地说,这种情况只会在系统 CPU 拥有两个独立核心时发生,这样在任何时刻才会有不同的电脉冲信号发出。并发意味着至少两件事务在同一时间段发生。但注意,这里的事务是(高级的)任务,而不是(低级的)操作。所以,请分清并发和并行。
3、你的博客用的是 Koa,Express 用过吗?¶
我的博客是github上托管的。界面使用过 python-mcdocs 和 ruby 的 jekyll 工具进行构建。node-JS 简单的操作了解(Egg,express,Koa),简单的SQL语句可以手写。
4、你的博客用的什么服务器?¶
我的博客是github上托管的。
2 刚刚说的java调用js离线生成数据报告?java调用js的promise异步返回结果怎么实现的?¶
使用java的js引擎Nashorn,Nashorn不支持事件队列,是要引进polyfill,然后java调用js方法获得java的promise对象,然后在调用该对象的then方法,回调函数为java中的某各类的某个方法,然后while一个表示是否已执行回调的变量,如果未执行,则让java主线程sleep,如果已经执行,则跳出循环,表示是否已执行回调的变量在传入promise的回调函数中设置更改。详情代码见地址
3 说说svg和canvas各自的优缺点?¶
共同点:都是有效的图形工具,对于数据较小的情况下,都很又高的性能,它们都使用 JavaScript 和 HTML;它们都遵守万维网联合会 (W3C) 标准。
svg优点(矢量图):
- 矢量图,不依赖于像素,无限放大后不会失真。
- 以dom的形式表示,事件绑定由浏览器直接分发到节点上。
svg缺点:
- dom形式,涉及到动画时候需要更新dom,性能较低。
canvas优点(栅格图):
- 定制型更强,可以绘制绘制自己想要的东西。
- 非dom结构形式,用JavaScript进行绘制,涉及到动画性能较高。
canvas缺点:
- 事件分发由canvas处理,绘制的内容的事件需要自己做处理。
- 依赖于像素,无法高效保真,画布较大时候性能较低。因为canvas依赖于像素,在绘制过程中是一个一个像素去绘制的,当画布足够大,像素点也就会足够多,那么想能就会足够低。
6 假设我现在有5000个圆,完全绘制出来,点击某一个圆,该圆高亮,另外4999个圆设为半透明,分别说说用svg和canvas怎么实现?¶
首先,从数据出发,我们的每个圆是一个数据,这个数据有圆的x、y、radius、isHighlight如果是svg,直接渲染节点即可,然后往节点上边绑定点击事件,点击改变所有数据的高亮属性(必须同步执行完成),然后让浏览器进行绘制。如果是canvas,我们需要自己绑定事件到canvans标签上,然后点击的时候判断点击的位置是否在圆内,如果在某个圆内,则更新所有数据的高亮属性,之后在进行一次性绘制。
7 刚刚说的canvas的点击事件,怎么样实现?假如不是圆,这些图形是正方形、长方形、规则图形、不规则图形呢¶
针对于每一个形状,将其抽象成shape类,每一个类有自己的方法isPointInSide来判断节点是否在图形内,对于不规则图形,当做矩形处理,点击的时候执行该方法判断点击位置是否在图形内。
8 那假如我的图形可能有变形、放大、偏移、旋转的需求呢?你的这个isPointInSide怎么处理?¶
这个我答不出来,据面试官提示,好像有相应的API处理变形、旋转、放大等等之后的位置映射关系。
9 那个这个canvas的点击事件,点击的时候怎么样快速的从这5000个圆中找到你点击的那个圆(不完全遍历5000个节点)?¶
可以通过预查找的形式,当鼠标划过的时候预先查找到鼠标附近的一些节点,当点击的时候在从这些预先筛选好的节点里查找点击下来的节点,当然这个方法的前提是不能影响js主线程的执行,必须是异步的形式。
5 我现在有一个canvas,上面随机布着一些黑块,请实现方法,计算canvas上有多少个黑块¶
使用getImageData获取像素数组,然后遍历数组,把在遍历节点的过程中,查看节点上下左右的像素颜色是否相同,如果相同,然后设置标识,最后groupBy一下所有像素。(这是我当时的方案)
11 ETag是这个字符串是怎么生成的?¶
没答出来,我当时猜是根据文件内容或者最后修改时间进行的加密算法。其实官方没有明确指定生成ETag值的方法。 通常,使用内容的散列,最后修改时间戳的哈希值,或简单地使用版本号。
职业发展¶
- 未来前端的规划?
- 真的学习前端是什么时候开始?
- 方向是选择全栈还是只做前端?主要是前端,也接触一部分python。
- 有没有在nodeJS上做过什么?;也接触一部分nodeJS(http-server部署本地的服务器进行调试;使用 node-email 写一个插件发送邮件;使用nodeJS读取本地的文件,ZipJS压缩文件;参与过一个nodeJS-server搭建中间件的项目,路由,视图函数,简单的模板函数ejs)
- 说一下你觉得你做过的最复杂的项目?中间遇到的困难,以及你是怎么解决的?
3 假如让你来做技术架构,你会怎么做?考虑到团队每一个前端的技术栈可能不一致,这个时候我可能选择微前端架构,让每个人负责的模块可以单独开发,单独部署,单独回滚,不依赖于其他项目模块,在尽可能的情况下节约团队成员之间的学习成本,当然这肯定也有缺点,那就是每个模块都需要一个前端项目,单独部署,单独回滚无疑也加大了运维成本。