整理一下自己上周的前端面试,简单写了一下答案,有些基础回答的还是不太好,比如前端缓存…,离offer的路还很长(╥╯^╰╥)
优势:
1. 通过虚拟dom进行操作,渲染速度快,提高了web性能
2. 兼容性好,因为虚拟dom帮我们解决了跨浏览器兼容性问题,为我们提供了标准化的api
3、组件化好,react里每一个模块都是一个组件,便于维护,降低开发成本
4. 单向数据流,便于管理
5. 灵活,可以跟其他的库进行配合
缺点:
1. react是MVC中的view部分,需要与其他的模块进行配合开发
2. 当父组件需要重新渲染的时候,即使子组件的props或者state没有变化,也同样会重新渲染
setState是异步的,一般不会立即更新,会放到一个任务队列里,在dirtyComponent中等当前事件循环结束
之后再进行批量操作。
但是在原生事件、setTimeOut、setInterval、Promise等异步操作里,是同步更新的。原因是因为这些异步
操作不属于事务的更新流程里,等它们执行的时候isBatchingUpdates为false,不会被放入dirtyComponent
中进行异步更新,所以是同步的。
调用setState后,会依次触发shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate这4个方法
((如果shouldComponentUpdate没有返回 false的话))
如果我们在render、componentWillUpdate或componentDidUpdate中调用了setState方法,那么可能会造成
循环调用,最终导致浏览器内存占满后崩溃
参考:阮一峰大大的博客
原理:
用户通过view发出action,然后store触发reducer,并传递2个参数(当前state和action),返回新的state,
state更新之后,view会重新渲染。
实现过程:
通过Provider包裹根组件,向connect组件传递store
connect封装ui组件成容器组件,将state和action传入组件内部;监听store变化。
中间件:redux-thunk
中间件就是要对redux的store.dispatch方法做一些改造,以实现其他的功能。
store.dispatch()是view发出action的唯一方法
store.subscribe(listeners)state发生变化的时候会自动调用监听函数,把render或者setState作为listener
s的话,就会立即刷新页面
中间件就是一个函数,对store.dispatch()进行的改造,在执行reducer之前添加了一些别的功能
正常情况下,store.dispatch()的参数是一个对象,可以利用中间件redux-thunk改造,使其能接受一个函数
react diff算法有3种策略:
1. tree diff:将树按照层级分解,只比较同级元素;对于跨层级的节点,因为这种操作比较少,所以忽略不计
2. component diff:相同class名称的组件有相同的树形结构,不同名称的组件的结构不相同。
对于相同名称的继续按照层级进行比较,不同名称的直接删掉,重新创建新的组件。
3. element diff:通过唯一标识key属性比较,如果key相同的话,react就认为是同一个节点,置灰进行更新
操作,如果不同的话就会进行创建。所以尽量避免用index作为列表元素的key。原因是因为如果原来的集合是
【a, b】,新集合是【a, c, b】,都以index作为key标记,在进行diff比较的时候,对于第0个元素,因为新旧
集合都有这个节点,所以react认为是同一个节点,而且内容也没有变化,所以就不做任何操作;而对于第1
个元素,新旧集合中同样存在,react也是认为是同一个节点,通过进一步比较发现内容不一致,所以就更新
内容为c;对于第2个元素,因为旧集合中不存在,react就认为是一个全新的节点,需要重新创建。因为只是
单纯插入了一个c,a和b是不应该有操作的,但是如果用index作为key的话就会造成一定的性能损耗。
如果换成唯一的id作为key就不会存在这种情况。
对于同一层级的节点,react有3种节点操作:插入、删除、移动
通过遍历新集合,如果旧集合中存在相同的key,就认为不需要删除和插入,只需移动即可。如果节点在
新集合中的位置 < lastIndex(访问过的旧集合中最右的位置,初始为0,一直更新,取的是当前节点在
新集合中的位置索引与lastIndex的最大值),
就会进行移动。
如果旧集合不存在相同的key,说明要进行插入操作
遍历完新集合之后,还要遍历下旧集合,如果存在一个节点新集合中没有,旧集合中有,说明就要进行删除
操作。
1、生成promise对象
2、此时promise的状态是pending,进行一系列同步操作,
3、将操作结果通过resolve方法传递出去,reject方法传递错误
4、then方法定义resolve状态和reject状态的函数,饼返回一个新的promise实例,因此可以采用链式写法
let、const都是es6中定义变量的方式
let定义一个变量,可以修改,没有变量提升,声明之前使用会报错
const定义一个常量,不能修改
var定义一个变量,函数作用域,存在变量提升
state变化之后,当shouldCompontUpdate方法没有返回false时,会重新触发render方法,所以就重新渲染了
父子组件是通过传递props来通信,
兄弟组件之间通信的话有2种:通过回调函数的形式传递状态到共同的父组件,然后由父组件传递props到子组件;通过redux管理store
子父组件之间通信:通过给子组件添加ref属性,父组件获取ref;或者子组件通过回调函数的形式传递状态到父组件
webpack是一个模块化的打包工具,通过给定的入口文件分析项目依赖,下载所需要的项目,把所有依赖打包
成一个bundle.js的文件,然后通过代码分割成单元片段实现按需加载。
主要分为4个部分:entry、output,module 处理多种文件格式的loader,plugin
常用的loader:css-loader,style-loader,less-loader,scss-loader,babel-loader等
plugin: HtmlWebpackPlugin,clean-webpack-plugin,DefinePlugin,uglifyJsPlugin,extractTextPlugin
共同点:
都是保存在浏览器端。
不同点:
1、cookie可以在服务端和浏览器之间传递,大小不能超过是4k,同源的http请求中携带(即使不需要),一般是在服务
端生成,用途是标识用户身份。
2、sessionStorage和localStorage不会主动发送给服务端,仅在本地保存,也有大小限制,不过比cookie大得
多,可以达到5M或者更大
3、有效期不同。cookie是在设置的过期时间之前一直有效;localStorage永久保存,即使浏览器关闭也不会消
失;sessionStorage在浏览器窗口关闭之后就会消失
1. Accept: 可接收的内容类型(content-type: application/json,text/plain)
2. accept-language: 可接受的响应内容语言
3. accept-encoding:可接受的响应内容编码格式
4. cookie: 缓存
5. date: 发送该消息的日期和时间
6. user-agent: 浏览器身份标识
7. "method":请求方法
8. Origin:发起一个针对跨域资源共享的请求
9. "referer":发出请求的页面的URL。
10. Host:主机
11. Connection:浏览器与服务器之间连接的类型(keep-alive)
// 响应头组成
1. Date:表示消息发送的时间,
2. server:服务器名字。
3. Connection:浏览器与服务器之间连接的类型
4. content-type:表示后面的文档属于什么MIME类型,例如text/html
5. Cache-Control:控制HTTP缓存
6. Expires:失效日期
7. accept-encoding:可接受的响应内容编码格式
1. antd的Select,Cascader,Datepicker的下拉框随着页面的滚动而滚动
这个问题是官方默认相对于页面body定位,只要改为相对于父级定位就可以了,通过getPopupContainer
2. 表格数据量较大时,设置fix时,会导致表格对不齐,通过自己写样式解决,table-layout:fixed;列宽由
设定的宽度决定,与内容无关
3. 表单input,checkbox不要使用defaultValue,要用value
节流:保证某个事件XXXms执行一次(throttle)
防抖:把触发比较频繁的事件(比如输入)合并成一次,等停止输入的时候再触发。(debounce)
区别:不管事件触发多频发,在一定时间内都会执行一次,只是防抖是最后一次操作停止之后才会执行,比如
滚动加载页面的时候,我们需要每隔一段时间都要去请求一次,而不是在用户停止滚动的时候再去请求,这个
时候就适合节流。
节流方法:
function throttle(fn, delay) {
let canRun = true; // 表示可以开始下一次请求的标记 (闭包)
return function() {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true;
}, delay)
}
}
防抖方法:
function debounce(fn, delay) {
let timer = null;
return function() {
clearTimeout(timer); // 每一次执行之前都先清除掉上一次的定时器
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
1. mathjs.format(val * 1, {
notation: "fixed",
precision: num,
})
2. 重写Number.toFixed()方法
1. 能介绍下公司目前使用的技术栈吗?
2. 公司目前做的项目是什么?
3. 公司的技术团队是怎么配合的呢?前后端分离的吗?
1. 有length属性
2. 不能调用数组原型上的方法,例如push,slice,shift,map等等
缓存分:本地缓存和浏览器缓存
浏览器缓存是通过http协议控制的,所以又可以叫http缓存
var obj = {
arr: ['aa', 'bb', 'cc'],
fun: function() {
for (var i = 0; i < this.arr.length; i++) {
setTimeout(function(){ // 当执行到这里的时候this指向的已经不是obj了,指向的是window,所以this.arr是undefine,this.arr[i]会报错
console.log(this);
console.log('arr' + i); // 输出arr3
console.log(this.arr[i]); // 报错
console.log('--------------------');
}, 0);
console.log(i); // 先执行这一条,输出0, 1 , 2
}
}
}
obj.fun();
+this在setTimeOut中指向的是window,可以用闭包,bind,箭头函数的方法来让其指向正确的值
修改:
方法1:
var obj = {
arr: ['aa', 'bb', 'cc'],
fun: function() {
const thatArr = this.arr; // 用一个临时变量把this保存起来,这样当执行到setTimeOut的时候,this指向的还是obj
for (let i = 0; i < thatArr.length; i++) {
setTimeout(function(){
console.log('arr' + i); // 输出arr0,arr1,arr2
console.log(thatArr[i]); //输出aa, bb, cc
console.log('--------------------');
}, 0);
console.log(i); // 先执行这一条,输出0, 1 , 2
}
}
}
方法2:利用bind方法
var obj = {
arr: ['aa', 'bb', 'cc'],
fun: function() {
for (let i = 0; i < this.arr.length; i++) {
setTimeout(function(){
console.log('arr' + i); // 输出arr0,arr1,arr2
console.log(this.arr[i]); //输出aa, bb, cc
console.log('--------------------');
}.bind(this), 0);
console.log(i); // 先执行这一条,输出0, 1 , 2
}
}
}
方法3:箭头函数中的this指向定义的对象
var obj = {
arr: ['aa', 'bb', 'cc'],
fun: function() {
for (let i = 0; i < this.arr.length; i++) {
setTimeout(() => {
console.log('arr' + i); // 输出arr0,arr1,arr2
console.log(this.arr[i]); //输出aa, bb, cc
console.log('--------------------');
}, 0);
console.log(i); // 先执行这一条,输出0, 1 , 2
}
}
}