面试集锦(二)
1.React相关知识
1.1 react生命周期
react生命周期分为三个阶段:装载过程(Mount)、更新过程(update)、卸载过程(unmount)。
参考:https://blog.csdn.net/mafan121/article/details/77965379
1.2 react父子组件传参
父组件向子组件传参:props。
在父组件中向子组件添加属性,子组件通过this.props获取。
子组件向父组件传参:回调函数。
在父组件向子组件添加事件,子组件调用父组件事件并将参数带回。
1.3 react高阶组件HOC
HOC
是一个函数,其接收一个组件作为参数,返回一个增强的组件。
高阶组件主要被用于:代码模块化、代码复用、修改props、劫持渲染(操作state)。
常用的实现方式有:
- 属性代理(Props Proxy):高阶组件操控传递给 WrappedComponent 的 props。
- 反向继承(Inheritance Inversion)高阶组件继承(extends)WrappedComponent。
示例:
import React, { Component } from 'react'
const withStorage = WrappedComponent => {
return class extends Component{
componentWillMount() {
let data = localStorage.getItem('data')
this.setState({ data })
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
export default withStorage
这里采用属性代理的方式定义了一个高阶组件,其每个传入组件加载前都将去拿取
localStorage
的data
数据,避免了在每个组件中写componentWillMount
,提高了代码复用率。
1.4 react的children嵌套
react
中任何组件都有一个children属性
。他表示当前组件下的子元素,有点类似于innerHTML属性
。
this.props.children
的值有3种类型:undefined
(无子节点)、object
(一个子节点)、Array
(多个子节点)。
常用的方法:
React.Children.count(object children)
:返回子节点个数React.Children.map(object children, function fn [, object context])
:遍历children子元素,执行fn,并返回结果集。React.Children.forEach(object children, function fn [, object context])
:仅仅遍历子元素执行fn,无结果返回。React.Children.only(object children)
:返回children中仅有的子级,否则跑出异常。
1.5 父子组件如何事件互调
父组件调用子组件事件:refs。
为子组件绑定ref属性,在需要用到的时候通过
this.props.refs
属性获取到子组件,然后子组件就可以调用自己的事件了。
子组件调用父组件事件:props。
在父组件为子组件绑定事件,在子组件中通过this.props.事件名调用。
1.6 react diff算法原理
react diff算法分为3大策略,简化了比较的复杂度。
-
Tree diff
:通过updateDepth
对Virtual DOM
树进行层级控制,两棵树只进行同层节点比较,这样只需一次遍历即可完成整颗树的比较 -
component diff
:依旧根据层级比较,同类型的组件可通过shouldComponentUpdate
判断是否更新,不同类型组件直接替换。 -
element diff
:对同层的节点,可根据key值进行删除、插入、移动。通过这三大策略,所有的比较可操作均在同一层级完成,大大节省了比较重绘时间。
1.7 react router
window.location.hash
:获取url中的hash值,即#之后的部分。
hash值的改变并不会引起浏览器发送请求,所以可以借鉴这一原理,来实现页面的前端切换
但是含有#
的url不太符合我们的要求,我们期望url中的路由都是以/
拼接的。这就相当于url整体变更了,那么页面也将重新渲染。在Html5中history API提出了window.history.pushState
、window.history.replaceState
方法,其可以让我们的url达到#
类似的效果,url改变也不刷新页面,仅仅从历史记录中查找更新组件。
react router
利用封装了window.history
的第三方库history
,可以用来兼容不同的浏览器和环境。主要包括以下3类history:
createHashHistory
:老版浏览器,主要使用hash来实现。createBrowserHistory
:支持H5的浏览器,主要使用h5中的history来实现。createMemoryHistory
:node环境下,使用memeory存储。
执行url前进
createBrowserHistory
: pushState()、replaceState()createHashHistory
: location.hash= window.location.replace()createMemoryHistory
: 在内存中进行历史记录的存储
检查url回退
createBrowserHistory
: popstatecreateHashHistory
: hashchangecreateMemoryHistory
:不涉及UI,直接在历史记录中查找回退
createBrowserHistory/createHashHistory
将state存储在sessionStorage中,方便state传递获取。
1.8 setState原理
setState(newState,fn)
或者
setState((prevState,prevProps)=>{
return newState
},fn)
setState的第一个参数,也可以接受一个函数,该函数,接收上一次的状态和props作为参数
- setState只在合成时间和钩子函数中是“异步”的,在原生事件和setTimeout中是同步的。
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
setState工作流程:
- 将传入的newState合并到当前state,并放入状态队列中
- 检查是否需要批量更新,如果需要,则将当前组件实例放入
dirtyComponent
。 - 当所有需要变更的组件收集完成后,遍历
dirtyComponent
,对每个组件进行状态更新。 - 在
updateComponent
时会比较前后状态是否一致,不一致则重新渲染,否则不重绘。
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
输出0 0 2 3
- 因为setState在同一进程中会合并状态,且本身是异步操作,所以1,2次赋值会被合并,主线程打印1、2次时,赋值未完成。
- 当执行setTimeout时,1、2次赋值已执行完成,此时val=1。因为setTimeout进入了新的异步队列,和1、2次赋值并不在同一进程所以不会与1、2次赋值合并。
- 因为在setTimeout中setState是同步的,所以依次打印2,3
1.9 react强制渲染
默认情况下,当组件的state或props改变时,组件将重新渲染。如果你的render()方法依赖于一些其他的数据,你可以告诉React组件需要通过调用forceUpdate()
重新渲染。
forceUpdate
会跳过shouldComponentUpdate
直接执行render()
。
2.redux相关知识
2.1 redux原理
redux:component --> dispatch(action) --> reducer --> subscribe --> getState --> component
参考:https://blog.csdn.net/mafan121/article/details/72673815
react-redux:component --> actionCreator(data) --> reducer --> component
connect(state => state, action)(Component);
参考:https://blog.csdn.net/mafan121/article/details/72830491
dva: dva=react-router
+redux
+redux-saga
2.2 redux 优缺点
-
一个组件的所有数据都必须从父组件传递过来,不能像flux,从store中直接获取。
-
当一个组件相关的数据更新时,即使父组件不需要用到这个组件,父组件依旧需要重新render,这将导致渲染效率降低,需要通过
shouldComponentUpdate
来优化。 -
redux将数据流规范了,所有数据均在store中管理,避免了父子组件不易传参的问题。
-
redux将流程规范了,减少了手动编码量。
2.3 redux-thunk和redux-saga
副作用*:调用函数时,除了返回值,还对主函数产生了附加的影响。
除了返回值外,还做了其他的影响。
凡是跟外部环境存在的交互都属于副作用。
redux-saga
redux-saga是基于sagas模式,通过Generator函数来实现异步协调操作的。它在项目启动的时候会监听action,当特定的action被dispatch时会唤醒saga。
saga是由多个effect组成的,一个effect就是一个js链式对象,其包含了一些可以被saga middleware执行的指令。
call、put、take都是saga提供的effect创建器,他们都会生成一个effect对象,然后将其交由saga middleware处理。
使用redux-saga:
- 注册saga
// 创建redux-saga中间件
const sagaMiddleware = createSagaMiddleware()
// 生成store
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
// 执行redux-saga中间件
sagaMiddleware.run(rootSaga)
- 创建saga
export function* rootSaga() {
while(true) {
yield take('FETCH_USER');
const { data } = yield call(axios.post, 'http://rest.learncode.academy/api/wetern/users');
yield put({ type: 'UPDATE_USER', payload: data })
}
}
saga必定是一个*函数,每个effect指令都应yield处理。
优点:
- 声明式 Effects:所有的操作以JavaScript对象的方式被 yield,并被 middleware 执行。使得在 saga 内部测试变得更加容易,可以通过简单地遍历 Generator 并在 yield 后的成功值上面做一个 deepEqual 测试。
- 高级的异步控制流以及并发管理:可以使用简单的同步方式描述异步流,并通过 fork 实现并发任务。
- 架构上的优势:将所有的异步流程控制都移入到了 sagas,UI 组件不用执行业务逻辑,只需 dispatch action 就行,增强组件复用性。
redux-thunk
redux-thunk也是对action层做了副作用处理,它允许我们接受一个函数作为action,但是这个函数action必须接收dispatch作为参数,我们可以在函数action中处理异步操作。
当action为函数时会执行函数内部逻辑,从而达到异步效果
使用redux-thunk
1.注册中间件
const middleware = applyMiddleware(thunk);
2.定义action函数
export default ()=>(dispatch)=>{
//异步逻辑处理
//发送同步action更新状态
dispatch({type:'init',data:data});
};
缺点:
- action 虽然扩展了,但因此变得复杂,后期可维护性降低;
- thunks 内部测试逻辑比较困难,需要mock所有的触发函数;
- 协调并发任务比较困难,当自己的 action 调用了别人的 action,别人的 action 发生改动,则需要自己主动修改;
- 业务逻辑会散布在不同的地方:启动的模块,组件以及thunks内部。
3.css布局
3.1 水平居中
内联元素:text-align:center;
块级元素:margin:0 auto;
或者绝对定位。
flex居中:display: flex; justify-content: center;
参考:https://blog.csdn.net/mafan121/article/details/53925555
3.2 垂直居中
单行元素:line-height:行高;
多行元素:绝对定位
flex居中:display: flex; align-items: center;
参考:https://blog.csdn.net/mafan121/article/details/53925555
3.3 左右布局
-
flex居中:
flex:1;
-
calc计算:
calc(100%-200px)
; -
浮动布局:
float:left
; -
定位布局:
position:absolute;
参考:https://blog.csdn.net/mafan121/article/details/100057888
3.4 属性冲突
animation与display不兼容等。
参考:https://blog.csdn.net/mafan121/article/details/87795065
当然还有很多兼容性的属性,这里并没有列举,例如透明度:opacity等
3.5 盒子模型
盒子=外边距+边框+内边距+组件大小
W3C盒模型:padding、border所占的空间不算入width、height,相当于:box-sizing: content-box;
width=content
IE盒模型:padding、border所占的空间算入width、height,相当于: box-sizing: border-box;
width=padding+border+content
box-sizing可以改变盒子模型。
参考:https://blog.csdn.net/mafan121/article/details/51548222
3.6 样式优先级
!important > 行内样式 > ID选择器 > 类选择器 > 元素 > 通配符 > 继承 > 浏览器默认属性
参考:https://blog.csdn.net/mafan121/article/details/48158001
3.7 CSS中link 和@import的区别
link
是html标签
,只能存放于html源码
中,既可以加载css文件
,也能设置rss、rel等连接属性。在页面加载的时候同时被加载。可以通过dom操作插入。
@import
是CSS2.1中的语法,只有导入样式的作用,在页面加载完成后才开始加载。无法通过dom操作导入。
4.js相关知识
4.1 js基础类型
String、number、boolean、null、undefined、symbol
4.2 js原型和原型链
- 每个构造函数都有一个
prototype
属性,其指向原型对象。 - 每个对象都有一个
constructor
属性,其指向构造函数。 - 每个对象还有一个
__proto__
属性,其指向构造函数的原型(原型对象),即__proto__ === constructor.prototype
实例对象相似于原型对象,但是原型对象是为了实现继承而存在的,并不相等。
参考:https://blog.csdn.net/mafan121/article/details/48970993
4.3 事件委托
事件委托又称事件代理,是利用事件冒泡原理,将子元素的事件绑定到父元素上,让父元素担当事件的监听者。然后通过特殊的子元素标识来区分目前触发的是哪个子元素事件。例如常见的点击浏览器任意位置关闭弹窗,就是将点击事件委托给了document对象。
优点是:
- 可以节省大量的内存,减少子元素相同事件的注册。
- 当新增子元素时无需再次对其进行事件绑定。
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
</head>
<body>
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
<script>
var ulEle = document.getElementById("myLinks");
ulEle.addEventListener("click", function (event) {
var target = event.target;
switch (target.id) {
case "doSomething":
document.title = "事件委托";
break;
case "goSomewhere":
location.href = "http://www.baidu.com";
break;
case "sayHi":
alert("hi");
break;
}
</script>
</body>
</html>
4.4 防抖和节流
防抖:事件的执行必须间隔一定的时间,如果在指定时间内再次出发,则重新计时。
节流:在一段时间内,事件只能触发一次,时间段内的触发不生效。
参考:https://blog.csdn.net/mafan121/article/details/82115933
4.5 闭包的使用
闭包是函数内部的函数,作用是防止变量污染,模仿块级作用域,使函数内部的变量可以被外部访问。
参考:https://blog.csdn.net/mafan121/article/details/48243895
4.6 cookie、localStorage、sessionStorage的区别和优缺点
三者都是将数据缓存在浏览器端,但cookie数据存储量大约在4k左右,而storage存储量为5M左右。
cookie将会随http传递给后端,而storage仅仅是保存在本地,除非用户手动设置,否则不会传递给服务器。
storage:https://blog.csdn.net/mafan121/article/details/60133107
cookie:https://blog.csdn.net/mafan121/article/details/100062220
4.7 CommonJS、AMD、CMD的区别
CommonJS
:是服务器端js模块化的规范,一个单独的文件就是一个模块,加载模块使用require
方法,该方法读取一个文件并执行,最后返回文件内部的exports
对象。NodeJs
采用该规范。
AMD
:异步模块定义,客户端js模块化的规范,只有一个接口:define(id?,dependencies?,factory);
需要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中。通过define
定义,通过require
方法加载。RequireJS
采用这种规范。
CMD
:通用模块定义,客户端js模块化的规范,通过define
定义,通过require
方法加载。SeaJs
采用这种规范。
AMD和CMD的区别
- 对于依赖的模块,
AMD
提前执行,而CMD
则采用懒加载的方式延迟执行。 - 对于
require
,AMD
分全局require
和局部require
,CMD
中没有全局require
,而是按需加载。 CMD
推崇依赖就近,AMD
推崇依赖前置。
4.8 apply和 call的区别
apply和 call都是以一个对象代替当前对象,调用当前对象的方法,实际上是变更了当前方法的this指向。
区别在于:出第一个参数外,call接收任何类型数据作为参数,且参数可以是多个,而apply只能接收数组作为参数,且只能有一个数组。
参考: https://blog.csdn.net/mafan121/article/details/52922149
5.跨域问题
浏览器同源策略导致了跨域问题,同时也减少了浏览器遭受的XSS、CSFR等攻击。
常见的解决方案:
- 通过jsonp跨域
- postMessage跨域
- nginx跨域
- 设置document.domain相同
- 服务端设置Access-Control-Allow-Origin
参考:https://segmentfault.com/a/1190000011145364
6.webpack打包优化
1.代码抽离,提取公共代码,分离css代码。
2.Externals
第三方库外部引入。
3.减少loader筛选范围,精确resolve匹配路径,降低打包速度。
4.缓存loader的执行结果(cacheDirectory)
loader:它是一个转换器,主要用于编译文件进行文件转换的。
plugin:它是一个扩展容器,是针对编译完成后,监听完善整个打包过程的。
6.1webpack插件书写
plugin本质上就是一个类,其包含一个apply(compiler)
方法。我们可以在compiler对象的构造上挂载一些监听函数,当钩子被触发时,执行这些监听函数。
compiler:包含了webpack环境中所有的配置信息(包含options、loaders、plugins等)。在webpack启动时被实例化,全局唯一。
相当一一个webpack实例
compilation:包含当前模块的资源、编译生成的资源、文件的变化等信息。当在开发模式下运行时,每次文件的改变都将生成一个新的compilation对象,对应一组新的编译资源。
webpack通过Tapable来组织插件执行顺序。其利用观察者模式,广播所有事件,插件只需关注它监听的事件,并执行其相关操作即可。
// 监听编译完毕事件
class DonePlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
// 每当编译[done写完了]完成后,都会call done这个事件
compiler.hooks.done.tap('DonePlugin', () => {
console.log(this.options.message || arguments);
});
}
}
module.exports = DonePlugin;
常见的监听事件包含done(编译完成)、emit(chunk输出到结果文件)、compilation(文件改变)、run(开始编译),具体请参照webpack事件。
7 git或svn 常用命令
git 常用的命令:
git fetch <远程主机名> <分支名> //拉取远程分支到本地
git pull <远程主机名> <远程分支名> //拉取远程分支代码,并将远程分支代码和本地分支代码合并。
git checkout <分支名> //切换分支
git merge <分支名> //合并分支到当前分支
git reset --hard 目标版本号 // 回滚到指定版本
git revert -n 提交号 // 撤销某次提交
git add . //获取所有修改的文件
git commit -m "描述" //提交修改
git push origin <分支名> //推送代码到远端服务器
git commit --no-verify -m "提交描述"
(跳过eslint或tslint校验)
svn常用命令:
svn checkout <path> //切换到服务器指定目录
svn add <file> //获取需要提交的文件
svn commit -m "描述" //提交文件
svn update -r m <path> //更新版本
svn lock -m “描述” [--force] <path> //加/解锁
svn delete <path> -m “描述” //删除文件
svn log <path> //显示该文件的修改日志
svn svn merge -r m:n <path> //合并2个版本中的指定文件
svn diff <path> //比较当前文件和基础版本文件的差异