一、CSS
1、重排与重绘
1、描述
重排与重绘是指页面的重新渲染,具体一点的话,当页面的布局发生了改变比如元素的宽高,坐标发生了变化会引发重排。当部分属性变化,例如字体颜色、背景颜色等不会影响位置的元素变化时,会触发重绘。重排会引起重绘。
2、优化
重排比重绘的开销大。
- 优化重绘的方法 一次性更改,避免多次更改。比如使用cssText和class,而不是一次次的更改属性
- 优化重排的方法
- 仍然一样使用class改变可能会影响位置的属性。
- DOM离线修改,可以将dom的display设置为none,这样之后无论怎么更改都不会触发重排。更改完毕后再将display属性还原。这样就指挥触发两次重排。
- 脱离文档流,设置为absolute或者fixed,这样修改时也不会触发重排。
- 缩小影响的范围。比如要改变子元素的样式,可以给子元素添加class,而不是给父元素添加class改变。
- 可以使用类似于虚拟dom的方法,完整修改完一块Dom之后做替换。
2、BFC和IFC
BFC
1、描述
BFC是一块块级作用区域,有独立的作用域,和其他区域的样式不互相影响。
2、形成条件
- 根元素
- overflow属性不为visible
- display属性为flex、table-cell等
- float属性不为none
- position为absolute、fixed
3、作用
- margin重叠
- 解决内部浮动元素引起的高度塌陷问题
- 解决元素侵占浮动元素位置的问题,例如文字环绕
IFC
IFC是行级作用域,有一些自己的排列方式。 常用的地方有:水平居中或者垂直居中,使文字居中或者底部、顶部对齐
4、隐藏元素
- visible: hidden
- opacity: 0
- display: none
- position: absolute, 然后设置位置移动到浏览器外面
5、block、inline和inline-block
- block是块级元素,inline是行级元素,块级元素上下侧会换行,行级元素不会,一行内可以排列多个行级元素
- inline不可以设置宽高,margin不能设置上下但是可以设置左右,padding上下左右都可以设置
- inline-block就是设置元素既不用换行又有块级元素的一些特性,可以设置宽高和margin
6、transition和animation的区别
- transition:需要事件驱动,一次事件只执行一次。transition只有起始和终态两个状态,中间是平滑过渡。
- animation:可以设置一定的间隔多次执行。animation可以设置其他形态,比较灵活。
7、垂直水平居中
- display: flex;justify-content: center;align-items: center; 子元素定宽高或者不定都可以
- display: flex; 子元素: margin: auto;
- 子元素设置绝对定位,left、top设置成50%,tranform的translateX、translateY都设置成-50%
- 父元素:display: table-cell;vetical-align: middle;text-align:center; 子元素:display: inline-block
- grid布局,display: grid; 子元素:margin: auto;或者align-self:center;justify-self:center;
8、响应式布局的方法
- 媒体查询:@medis screen and (max-width: 1000px){} ,判断屏幕宽度给不同的样式
- vw/vh
- rem
- 百分比/栅格布局
9、作用域有哪几种
1、BFC
BFC参考目录1.
2、IFC
内联格式化上下文,是一个行级作用域,作用一般是可以设置水平或者垂直居中。水品居中就设置text-align:center;垂直的话可以给内部最高的元素设置vetical-align: middle或者top、sub来实现内部元素居中或者顶部或者底部对齐
3、GFC
网格布局格式化上下文,通过设置display: grid来实现,可以通过设置行列,来实现对齐的效果
4、FFC
自适应格式化上下文,设置display为flex或者inline-flex,是一个弹性容器,内部也时弹性元素,可以使用流式布局
10、分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景
他们都可以隐藏元素,但是有几点不同
- opacity: 0;透明度设为完全透明,元素不可见,但是会占据位置,也可以点击
- visibility: hidden; 设置为不可见,会占据位置,不可点击
- display: none; 元素脱离文档流,不会占据位置,不可点击 性能:display:none会改变其他元素的排列位置,会引起重排;其他两个只会引起当前元素的重绘
11、选择器的权重和优先级
按顺序排列
- 行表内联样式,就是style
- ID选择器
- 类选择器、属性选择器
- 元素、伪元素选择器
12、伪元素和伪类有哪些
- 伪类是元素处于某种特定的状态,像是给元素添加了一个特殊的css类,可以通过这个css类来添加样式
- 伪元素是类似于添加了一个新的dom节点,但是不是真的添加,在dom文档之外
13、背景图片缩放
background-size: contain和background-size: cover
二者的区别:
- contain会自动缩放至宽度或高度铺满,元素可能会有留白
- cover也会自动缩放至宽度或高度铺满,图片会完全占据元素并可能超出
14、为什么要react-basis
15、css优化
- 异步
- webpack怎么配置异步
16、UMD
二、React相关
1、setState是同步还是异步
本质上是同步的代码,但是在react相关的部分,比如onClick、onChange等react合成事件,和react的生命周期中时表现为异步。在其他的部分比如原生事件和settimeout中表现出正常的同步,可以在更新后立马拿到值。
原因是由于react为了避免多次更新重复渲染页面的性能消耗,采用事务的形势进行批量更新,即将多次更新合并后执行刷新。在更新的代码执行完之后,事务还没有结束,所以不能立即拿到值。而在原生事件或setTimeout中,已经脱离了react的控制,不在事务的包裹中了,所以能够在更新后立即拿到值。
2、react中循环为什么要加key
加Key是为了避免不必要的更新。在react中如果没有进行特殊处理的话,在父组件遍历生成列表子组件时,如果没有加key,如果在列表中间插入了一个节点,那么这个节点之后的节点也都会重新渲染。如果加了key,react会使用key作为唯一标识,前后keyxiang
3、什么是HOC?在什么地方用过
HOC就是高阶组件,主要目的是复用逻辑,同时不改变原组件的使用方法。 只要有复用的地方都可以使用,例如权限控制,给组件和页面统一加一个判断方法判断是否有权限展示页面;例如利用高阶组件统计页面的渲染时间,做性能优化;还有相同的订阅发布逻辑或者处理数据的逻辑,都可以封装成高阶组件。
4、fiber
fiber是react用于解决页面更新卡顿的方案。因为浏览器是单线程的,js执行和浏览器渲染是互斥并且不能够打断的,当页面需要更新的元素较多,react先计算virtual dom,再渲染页面,计算的时间过长时就会造成页面卡顿。filber通过任务分片和分级来调度任务,大致的流程是先计算一部分的虚拟节点,渲染在页面上,然后再重新计算重新渲染,反复执行,这样页面看起来就流畅一些。
5、说一下hooks,有什么优势
- 优势
- 更容易复用组件逻辑,淡化生命周期,减少组件层级的嵌套(之前是高阶组件会多一层级)
- 状态管理更加清晰。每一个或多个状态值变化时有自己的处理方法()
- 不用在考虑this指向的问题,在类组件中必须理解javascript的this问题(可以举例class组件绑定函数要用bind)
- 劣势 useEffect不是很好用,有时候会发生比预想的更多的调用次数
- 性能的比较
6、react和vue对比
- 语法不同。vue是html和js分开写,更符合原生的写法。react是all in js,采用jsx语法,模板和js写在一个js文件中。vue的好处是更加语义化和方便一些,react的优点是更加灵活。
- vue集成了很多功能,比如vuex和路由等,而react基本上都是由社区维护,使用时需要自己引用第三方的包,reac的很多东西需要自己搭建。
- vue视图更新是当数据变化时,在setter中拦截,通知全局的视图更新方法来更新对应的节点。而react的更新都是在组件内部完成的。
- vue更新组件时会自动判断哪里需要更新哪里不需要。而react是当父节点变化时,子孙节点全部都会变化,但是可以通过shouldComponentUpdate函数来判断。当应用比较庞大时,使用react可以更好的进行优化。
7、服务端渲染怎么做
先写好html页面,其中数据可以先留好位置,然后使用模板引擎填充数据。调用接口时返回整个html字符串,content-type标注为text/html。
8、受控组件和非受控组件
受控组件和非受控组件的区别是组件值的存储方式和获取方式不同。受控组件将值存储在组件的state中,并且一般有自己的处理程序,让state和组件的值会同步更新,非受控组件值存储在dom内部,只能通过访问dom节点来获取值。一般需要拿到值做判断、处理或者特殊逻辑,比如判断input的值是否合理,判断账号密码是否输入来决定按钮是否可点击更适合用受控组件。如果逻辑比较简单,只用获得值那么用非受控组件更加简洁。
9、react16废弃了哪些生命周期?为什么废弃
废弃了三个will周期函数,componentWillMount,componentWillUpdate, componentWillReveiveProps,原因是16的版本启用了fiber架构,fiber的调和过程可能会多次调用will函数,这样这些生命周期就失去了原有的意义。
同时react16提供了两个新的生命周期函数。第一个是static getDerivedStateFromProps,这个函数在初始挂载,后续更新或者父组件重新渲染时都会重新调用,在一定程度上可以替代之前的三个will函数。另一个是getSnapshotBeforeUpdate,在每次渲染前会先执行这个函数,可以在这个生命周期里拿到更新前的一些信息,比如一些dom元素的高度或者滚轮的位置等,并且这个函数的返回值可以在componentDidUpdate函数的第三个参数拿到。
10、说说redux,redux是如何检测store值的变化
redux是一个全局的状态管理容器,使任意层级的组件都可以方便的访问到一些公共的属性。redux和flux的设计模式相似,都是单向数据流。redux有几个特点,第一个是单一数据源,整个的state都存放在一个状态树中,根节点就是store。第二个是redux中的state是只读的,不能修改,只能通过action和reducer返回新的state。第三个就是纯函数式的修改,在reducer中有确定的返回的结果。
本质还是通过发布订阅的模式,在store的值变化了就会通知组件内部更新
11、react vitualDom比原生操作dom更快吗
哪一种更快是要看场景的,如果只是简单的替换一个元素,原生操作可能就是一行代码的功夫,但是virtualDom却要进行diff生成新的虚拟dom树,再进行dom操作。但是当业务比较复杂时,原生需要进行多次的dom操作,这样可能就会引发多次的重绘和回流。但是使用虚拟dom的话,操作的是虚拟dom,进行diff之后生成新的dom树,再进行dom操作生成实际的dom树,这样能够避免频繁的更新。 虚拟dom更重要的一点在于能够更加方便快捷的操作dom,提升开发效率,还有跨平台。
12、什么是forwardRef
React.forwardRef会生成一个新的函数组件,用于传递ref到下层的组件树中。一般在下面两种情况中用到:
- 在高阶组件中转发ref,高阶组件想操作所包裹的组件内的元素时
- 转发ref到dom组件上
13、React生命周期?
挂载:
- constructor
- static getDrivedStateFromProps
- render
- componentDidUpdate
更新:
- static getDrivedStateFromProps
- shouldComponentUpdate
- render
- static getSnapshotBeforeUpdate
- componentDidUpdate
14、rem怎么计算的
rem是根据根元素字体大小来定的,浏览器默认的字体大小是16px,所以1rem=16px。可以通过设置根元素字体大小来改变,比如设置根元素fontSize是10px,那么1rem=10px。平常做自适应的时候可以根据当前屏幕尺寸通过脚本改变根元素字体的大小,比方默认750px宽度对应的是16px,那么当当前的屏幕宽度为500时,可以将根元素设置为500*16/750
15、为什么不能在循环、判断中使用hooks
因为state状态值是存放在数组中,在数组的下标位置和useState的声明位置一一对应。如果某一次更新时判断没有通过,那么就会出现声明的state值没有对应起来的情况,对应的setState也会失效。
16、useEffect是怎么实现依赖更新执行副作用的
当hook组件挂载或者更新时,都会执行一次hook函数,在执行hook函数的时候就会执行对应的useEffect函数,在UseEffect内部会根据这个useEffect是否执行过和判断前后两次的依赖项是否变化来决定是否执行useEffect的副作用函数。
追问
- 依赖项的比较是浅比较还是深比较 答:浅比较
- 怎么实现的 答:用Object.assign(a, b)
- 如果依赖项是引用对象怎么办 答:可以用use-deep-compare-effect这个库,或者用useRef来存上一次渲染的值,来比较前后两次的
17、单向数据流的好处?
单向数据流就是数据朝一个方向流动,当一个节点数据改变时,只会影响这个节点下面的数据,不会影响上面的。
在react中就是只可以父组件改变传递给子组件的值,子组件不能直接修改父组件的值。这样可以避免重复循环渲染,也使得数据更容易追溯。
缺点就是代码量上升了,如果想改父组件的值只能通过调用父组件的方法,较为繁琐
18、redux性能优化
- 优化store的数据解构。例如通过store的数组渲染一个列表时,一般可能会直接遍历store的数组。这样当数组中的一项变化时,会重新遍历渲染。这时候可以将store的数据修改一下,父组件的props只引入store不会变化的id,通过id去遍历,子组件接收到id通过id去找对应的值渲染子组件。
- 使用immutable.js优化
20、react的diff算法,细节
可以简单答一下算法细节:
- tree diff。默认只对同层级的节点作比较,如果某个节点不同,则这个节点以下的所有节点都不会再比较,转为创建新的节点
- component diff。比较是否是同类型组件,如果是同类型,则按照tree diff的策略进行。如果同类型组件,但是有变化,执行shouldComponentUpdate判断是否需要更新。如果组件类型不同,则同样认为这个组件所有子节点不同,要重新创建
- element diff。分为三个,插入、移动、删除,如果不在原来的集合中,则执行插入操作,如果元素从集合中删除了,则执行删除操作,如果key相同的元素,只是位置变了,则执行移动操作。
21、react绑定事件和原生事件的区别是什么?也可以问react是如何绑定事件的
react并不是直接将事件绑定在dom上,当事件发生时,事件冒泡至document处,document将事件内容交给一个中间层SintheicEvent,由这个中间层dispatchEvent.
作用:避免dom上绑定了过多的方法占用内存,也能避免方法没清除造成内存泄漏。由中间层统一处理也能避免不同浏览器的事件差异。
22、hooks的原理
react在初始化时会将hook以链表的形式挂载到实例对象上,在下一次执行函数式组件时再从链表中取值,也正是因为链表这个形式,hook不能够写在判断语句中。
useState的更新操作,是会将更新的函数保存起来,在下一次执行函数的时候才会执行这些保存起来的函数。
useEffect同样也会将effect的函数和依赖项以链表的形式挂载在FiberNode实例对象上,在下一次执行函数的时候就会比较deps依赖项。
useEffect的销毁是怎么执行的:会将上一次useEffect返回的函数保存在Effect.destory,然后在下一次执行时执行对应的清除副作用的操作,然后再执行这一次的useEffect
23、react的优化
24、component和pureComponent的区别
pureComponent会比较组件前后两次的props是否相等,相等就不会更新
- 函数式组件有相同的功能是什么 react.memo
25、useReducer的作用?和useState有什么区别
useReducer和useState都是用来保存和操作state的hook,useReducer适合操作更为复杂的,引用类型的state,也适合state值依赖上一个state值的情况。useReducer返回的值同样会自带浅比较,如果值相同的话不会渲染子组件。同样修改值得操作可以给子组件传dispatch,dispatch是稳定得不会触发更新
26、class高阶组件的实现方式?
属性代理(包裹一层组件)和反向继承(继承原组件)
- 追问:有什么是class高阶组件能够实现,但是hook实现不了的
- 还有什么能实现组件复用 render props
27、redux和context的区别
context会在一个其中一个属性值变化时强制更新引用了context的组件,即便这个组件没有使用到context中的这个属性 基于这个原因context更适合传递一些静态的属性
28、react-native
三、原生JS
1、async、await和generator的关系?用generator实现async、await
function* asyncGenerator() {
const a = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(500)
}, 2000)
})
const b = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(6)
}, 2000)
})
return a + b
}

function run(param) {
return new Promise((resolve, reject) => {
const g = param()
function innerRun(v) {
const {
value,
done
} = g.next(v)
if (done) {
resolve(value)
} else {
if(value instanceof Promise)
value.then(nextValue => {
innerRun(nextValue)
})
else
innerRun(value)
}
}
innerRun()
})
}
run(asyncGenerator).then(v => {
console.log(v)
})
2、手写promise
3、手写一个bind、apply、call实现
bind、apply、call的区别
- bind返回一个函数,apply和call会立即执行
- apply的第二个参数是数组,call和bind从第二个参数开始可以传递一个个单独的参数
function test() {
this.ppp = 'wyatt'
}
test.ppp = '2222'
function test2(param1, param2, param3) {
console.log(param1, param2, param3)
console.log(this.ppp)
}
test2.kkk = 'lolo'
Function.prototype.myBind = function (replaceThis, ...args) {
return (newArgs) => {
const k = Symbol()
replaceThis[k] = this
const result = replaceThis[k](...args, ...newArgs)
delete replaceThis[k]
return result
}
}
Function.prototype.myApply = function(context, ...args){
const k = Symbol()
context[k] = this
const result = context[k](...args)
delete context[k]
return result
}
test2.apply(test)
4、手写防抖、节流
//除了定时器,还有时间戳版本
// 手写防抖_非立即执行
function debounce(fn, time) {
return () => {
if (fn._timer) {
clearTimeout(fn._timer)
}
fn._timer = setTimeout(() => {
fn(arguments)
}, time)
}
}
// 防抖_立即执行
function debounce_immediate(fn, time) {
return () => {
clearTimeout(fn._debounceTimer)
if (!fn._debounceFlag) {
fn._debounceFlag = true
fn(arguments)
}
fn._debounceTimer = setTimeout(() => {
fn._debounceFlag = false
}, time)
}
}
// 节流
function throttle(fn, time) {
return () => {
if (!fn._throttleTimer) {
fn._throttleTimer = setTimeout(() => {
fn(arguments)
fn._throttleTimer = null
}, time)
}
}
}
5、手写深拷贝
普通递归版本
// 不完善,只考虑到嵌套对象递归解决,还有日期、函数、正则等等类型没有处理
function deepClone(obj){
const cloneObj = {}
if(typeof obj !== 'object') return obj
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const value = obj[key];
cloneObj[key] = deepClone(value)
}
}
return cloneObj
}
尾调用递归版本
const t = {
a: 1,
b: 2,
c: {
d: 4,
f: {
w: '乌拉',
f: 'kaqima'
},
g: {
h: 56
}
}
}
// 不完善,只考虑到嵌套对象递归解决,还有日期、函数、正则等等类型没有处理
function deepClone(clonedObj, keysObj = []) {
if (keysObj.length === 0) return clonedObj
let newKeysObj = []
keysObj.forEach(item => {
let t = clonedObj
item.keys.forEach((key, index) => {
if (index === item.keys.length - 1) return
t = t[key]
})
let lastKey = null
if (item.keys.length > 0) {
lastKey = item.keys[item.keys.length - 1]
t[lastKey] = {}
}
for (const key in item.data) {
if (Object.hasOwnProperty.call(item.data, key)) {
const element = item.data[key];
if (typeof element === 'object') {
const newKeys = [...item.keys]
newKeys.push(key)
newKeysObj.push({ keys: newKeys, data: element })
} else {
if (lastKey) {
t[lastKey][key] = element
} else {
t[key] = element
}
}
}
}
})
return deepClone(clonedObj, newKeysObj)
}
console.log(deepClone({}, [{
keys: [],
data: t
}]))
迭代(循环)版本:
// 使用迭代实现深拷贝
function deepClone(obj) {
const loopList = []
const result = {}
loopList.push({
data: obj,
parent: result
})
while (loopList.length > 0) {
const copyObj = loopList.pop()
let { parent, data, key } = copyObj
if(key){
parent[key] = {}
parent = parent[key]
}
for (const skey in data) {
if (Object.hasOwnProperty.call(data, skey)) {
const value = data[skey]
if (typeof value === 'object') {
loopList.push({
key: skey,
data: value,
parent
})
} else {
parent[skey] = value
}
}
}
}
return result
}
const t = {
a: 1,
b: 2,
c: {
d: 4,
f: 5,
g: {
h: 56
}
}
}
console.log(deepClone(t))
追问:递归有什么问题
如何优化递归:
6、js事件循环
收到js执行机制的影响,所有的js执行任务都在一个主线程中执行,是同步的。但是Js又是非阻塞的语言,非阻塞是通过回调函数来实现的,事件循环可以说就是如何执行回调函数。
事件循环在浏览器和node环境中的执行顺序不太一样。在浏览器中,任务分为主线程,微任务,宏任务。浏览器会先执行主线程的同步代码,如果在这些代码中遇到了异步代码,就会将其挂起,等到主线程的任务执行完毕之后再来执行异步的回调函数。而异步根据类型会挂在不同的任务队列,有一些任务如setTimeOut会放在宏任务队列中,还有一些如pormise.resolve会放在微任务队列中。浏览器在执行完主线程的同步任务后,会先查找微任务队列中是否有任务,如果有就先执行。然后再去查看宏任务的队列并按顺序执行。大致就是 同步 -> 宏任务 -> 微任务,这样就称为一次事件循环。而如果宏任务或微任务的同步代码中还有异步代码的话,浏览器仍然会将其挂起在宏任务或者微任务的队列,在宏任务或者微任务的同步代码执行完毕后再按规律执行,这就称为事件循环。
在node环境最底层的引擎和浏览器的不一样,所以执行任务顺序有所不同,不再是简单的微任务和宏任务之分。node的异步代码大致按timer阶段,Io callback,poll, check等执行,执行机制差不多,按顺序执行完一次阶段循环后,仍然将其中的异步代码再次挂起,再次执行循环。
7、有哪些是宏任务,有哪些是微任务
宏任务:setTimeOut/setInterval,node中有setImmediate、io等。微任务有promise.then,node中有单独的process.nextTick等。
8、http缓存说下?常用的状态码 / etag和last-modified、expire
http缓存分为强缓存和协商缓存,强缓存就是不发起请求,默认使用缓存,协商缓存是先发起请求,判断资源没有改变了再使用缓存。
强缓存有expire和cache-control,expire是过期时间,允许在过期时间内使用缓存。cache-control是http1.1的标准,通过设置max-age判断是否过期。cache-control大于expire,也就是如果同时存在的话,过期时间会由cache-control来决定。虽然不发起请求,但是network里会有一条记录,返回200.
协商缓存有last-modified和etag,last-modified是服务端返回的资源上次修改的时间,再下一次请求的时候,请求头会通过if-modified-since将这个时间带上,如果在这个时间之后资源没有发生改变,则会返回304,标识客户端将使用缓存。etag是资源文件内容的hash标识符,也就是只有当文件内容发生了改变etag才会变化,在第一次请求后服务端会返回etag响应头,下一次请求时会通过if-none-match将这个值携带上,如果两次Hash值相同则返回304,表示使用缓存。
追问:为什么服务器优先使用etag做缓存判断
因为lasl-modified有几个问题
- 是有可能资源只是修改时间发生了变化,但是内容没变
- 是last-modified的粒度是秒级的,如果是在1秒内内容发生了改变,last-modified判断不到
- 有些服务器不能准确的获取到文件的最后修改时间
追问:expires和cache-control为什么优先使用cache-control
因为expires返回的是服务器的过期时间,如果服务端和客户端的时间相差过大容易有问题。
追问:知道program吗?
program是旧产物,有的网站为了向下兼容还保留了这个字段,当program设置no-cache时表示没有缓存,program>no-cache-expire
追问:cache-control还有哪些属性
max-age:过期时间 no-cache:无论缓存是否过期,都要进行校验 no-store:表示有机密信息,不可缓存 cache-extension:自定义拓展值
追问:http请求头还知道哪些?
- cookies
- content-type
- accept-encoding 可接受的数据格式,例如gzip等
- range:只请求实体的一部分
状态码 | 含义 |
---|---|
304 | 资源未修改,会读取缓存的资源 |
200 | 成功 |
404 | 资源未找到 |
500 | 服务器错误 |
301 | 永久重定向,地址会替换为新的地址 |
302 | 临时重定向,地址会替换为旧的地址 |
9、http1.0 1.1和2.0的区别
http1.0:浏览器与服务器只保持短暂的连接,每次请求都会新建一个tcp连接。
http1.1:
- 浏览器和服务器保持长连接,多个请求可以只用一个连接。但是通讯是按次序进行的,如果上一个请求等待了太久,会阻塞后面请求的返回结果
- 新增了一些请求类型:put\delete\option等。新增了一些控制缓存的请求头响应头,如if-unmodified-since,if-match等
http2.0:
- 完全实现多路复用,请求不用等待
- 采用二进制格式,而不是文本格式,解析起来更高效
- 首部压缩。在连接的存续期会存在首部表,用于存每次发送的数据,之后的每次请求指挥发送不一样的数据,由浏览器和服务端同步更新,这样可以减少冗余数据,降低开销
- 允许服务器推送资源,可以随请求将资源一起推送给浏览器,不用建立新的请求。
10、数组中的reduce用过吗?实现一个reduce
Array.prototype.myReduce = function(callBack, initVal){
let sum = initVal
this.forEach((item, index) => {
const res = callBack(sum, item)
sum = res
})
return sum
}
const arr = [1, 2, 3]
const r = arr.myReduce((previousVal, currentVal, currentIndex, array) => {
return currentVal + previousVal
}, 0)
console.log(r)
11、把一个多维数组拉平
- Array.flat()
- 自己写递归
const array = [
1,
[1, 2, [
34
]],
5
]
function laArray() {
let resArray = []
function dealArray(arr) {
if (arr instanceof Array) {
arr.forEach(element => {
dealArray(element)
});
} else {
resArray.push(arr)
}
}
dealArray(array)
return resArray
}
12、对称加密和非对称加密
对称加密和非对称加密的区别就在于密钥是否相同。 对称加密加密和解密使用的是同一套规则,密钥在传递的时候容易被捕捉到,所以有安全性问题。常用的对称加密是使用cryptojs库,这个库支持很多种加密方式。 非对称加密指的是加密和解密使用不同的规则,即公钥和私钥,在传递的时候只传递公钥,私钥不会暴露。常用的是rsa加密。
14、实现一个对比版本号的函数
// 使用.分割成数组,逐个比对
function compareVersion(v1, v2) {
const v1Arr = v1.split('.')
const v2Arr = v2.split('.')
for (let i = 0; i < v1Arr.length; i++) {
// 先判断v2Arr是否超出了下标
if (i > (v2Arr.length - 1)) {
return 1
}
// 判断是否是数字
if (isNaN(v1Arr[i]) || isNaN(v2Arr[i])) {
throw new Error('版本号有误')
}
// 开始比对
const num1 = Number(v1Arr[i])
const num2 = Number(v2Arr[i])
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
} else {
if (i === (v1Arr.length - 1)) {
if (i < (v2Arr.length - 1)) {
return -1
} else {
return 0
}
} else {
continue
}
}
}
}
15、get和post区别
- 本质是tcp/ip连接,并无区别,Post请求也可以在url上加参数,get也可以通过body传参
- 在浏览器和一些web服务中会表现出来的区别有:1、get在url上传递参数的长度是有限制的。2、get更不安全,因为参数直接在路径上
- 有一个最根本的区别是,get只会产生一个tcp数据包,post会产生两个。post会先发送一次请求看能否请求成功
- 语义上的区别,get用来获取数据,post用来提交,更加规范
16、cookie、session、localStorage和sessionStorage
cookie和localStorage、sessionStorage都是客户端存储的技术,区别:
- cookie如果不设置过期事件,那么每次关闭浏览器就会失效。localStorage除非手动清除,永远不会清除,sessionStorage关闭网页或者浏览器都会清除
- cookie每次在请求中携带发送给服务端,如果数据过大会影响性能,cookie也可以通过服务端返回的响应的set-cookie设置。localStorage和sessionStorage则是纯客户端服务器存储技术,不会影响请求。
- session是在服务端存储状态,基于cookie实现的,第一次请求过后,服务器会将sessionId返回到浏览器的cookie中,下一次请求的时候请求会发送给服务器。服务器通过这个sessionId判断是否有对应的session信息。
17、null和undefined的区别?
- 相同点:他们都是js的7种基本类型;使用Boolean转换都会转为false
- 区别:使用==时为true,使用===时为false;undefined是定义了变量但是没有赋值,或者是未明确定义返回值,这些js都会为其分配undefined,null是不表示任何值的值,是明确给的值;
18、&& 和 || 运算符能用来做什么?
表示逻辑与,可以用来简化判断的代码,当使用链式的&&时,只有前面的条件都为真才会执行后面的代码 ||标识逻辑或,一般简化赋值,比如值为空则赋值个默认值。
19、字符串转化数字最快的方法?
+运算符,根据MDN如果原本就是数字类型,则不会执行任何操作。
20、闭包
闭包是可以访问其他私有作用域的函数,这个私有作用域一般是定义的闭包函数所在的函数作用域。
闭包的作用一般是在可以访问的私有作用域内定义变量,防止污染全局环境。另外就是缓存变量,防止闭包函数调用过后变量消失了
21、路由有哪几种,有什么区别
路由有hash路由和history路由
- hash路由地址中带#,刷新时没有问题
- history路由不带#号,刷新容易404,需要做重定向处理 优劣:
- history路由跳转新的url可以和旧的url一样,会存在浏览历史中,hash不会
- 通过history.state可以设置任意类型的数据,hash只能添加短字符
- history可以设置额外的title属性,供后续使用
- history跳转时容易404,需要特殊设置
22、es6新增的语法
箭头函数、let/const、class、promise、symbol等
23、symbol的作用
- 防止属性名重复,比如在自己写bind,apply函数就可以用到
- 模拟私有属性,因为symbol做key不会被遍历到
24、前端性能监控
需要做性能监控的大致有首页白屏时间,首页渲染时间,事件响应时间 可以通过做埋点和分析performance
25、说说白屏优化的方式有哪些
- 服务端渲染
- 预渲染:使用prerender-spa-plugin预先生成一个html文件,访问首页时会先访问这个文件
- 骨架屏:先写一个结构页面,使在访问的时候先访问结构页面
- 根本问题:要解决依赖加载慢和渲染慢的问题,一般都是加载js和css耗时很久,针对这个可以检查依赖,去除不必要的依赖,例如有的UI库只引用了一个组件则不用整个引入组件库。第二个开启gzip压缩,使用compression-webpack-plugin,在改一下配置。第三个是如果有的模块比较大,可以使用splitChunks分离模块,分开打包。
26、节流的作用,哪些场景有用到
节流是指在一定的时间内只执行一次方法。常用的场景有按钮快速点击,监听是否滑到底部加载更多。
27、js的错误类型
- 语法错误, uncaught syntaxError
- 引用错误,uncaught refrenceError,比如用了一个没有定义的变量
- 类型错误,uncught typeError,比如new了一个字符串或者数字
- 范围错误,uncaught rangeError,数组下标越界或者调用栈溢出
- eval错误,eval没有正确的执行
- URI错误,一般是函数的参数不正确
28、瀑布流的问题
如何实现:
用flexbox实现,一个大的flexbox嵌套两个或者多个小的flexbox,大的flexbox横向排列,小的纵向排列。依次往小的flexbox里添加图片元素,每次放元素前比较一下两个小的flexbox的高度,往高度较小的flexbox上添加图片。
追问: 如果提升体验
可以缓存当前页后面一至两页的内容
追问:如果下拉很多,图片元素很多比较卡的时候怎么优化呢
使用虚拟列表:将当前页之前所有的图片替换成一个等高的div。
29、css-in-js的好与坏
- 好处
- 自带css局部作用域
- 避免无用的样式堆积
- 自带css动态加载,组件加载之后css才会加到html中
- 动态的生成样式
- 坏处
- 更多的js代码带来的更大的打包体积
- 运行时耗时更久
30、什么是函数式编程?
函数式编程是指将计算过程分解成一个个可复用的函数,每个函数都是一个映射关系,是纯函数,即特定的输入会有特定的输出结果。
使用过函数式编程的哪些函数?
- map,将值映射到另一个值
- of,生成新的容器,不用new
- maybe,判空的
- either,判断,一般用来提供默认值
31、==和===的区别
- ===会先比较类型,如果类型不相等,则认为不相等,接下来再比较值
- ==再遇到类型不相等时会先转换类型。大致规则如下:
- 判断是不是在比较null和undefined,如果是则默认相等,如果不是则继续进行下一步
- 判断是不是在比较string和number,如果是则先将string转换成number再比较
- 判断比较项中是否有boolean值,如果有转换成number再回到第一步比较
- 判断比较项中是否有object,如果有则转换成原始值再执行
追问:object转换原始值的规则
对象、日期调用顺序略有不一样,但差不读就是先调用valueOf,不是原始值再调用tostring(判断是不是原始值的标志是判断type是不是object)
32、手写new方法
function myNew(fn){
var obj = {}
obj = Object.create(fn.prototype)
const args = Array.prototype.slice.call(arguments, 1)
const result = fn.call(obj, ...args)
return (typeof result === 'object' || result instanceof 'Function') ? result : obj
}
33、继承的写法
1.原型链继承
function SuperType() {
this.property = true;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
缺点:多个实例操作引用类型会互相影响,这是因为用的同一个原型链
2.构造函数继承
function SuperType() {
this.property = true;
}
function SubType() {
SuperType.call(this)
}
这样每次创建SubType的实例时,会执行一遍SuperType里的方法,缺点就是只能复制SuperType代码里执行的赋值属性,而且每次是一份新的值无法共享,原型链的值没有办法继承
3.组合继承
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
// 继承属性
// 第二次调用SuperType()
SuperType.call(this, name);
this.age = age;
}
// 继承方法
// 构建原型链
// 第一次调用SuperType()
SubType.prototype = new SuperType();
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
3.原型式继承 4.寄生式继承 5.寄生组合式继承 6.混入式继承 7.es6的继承
34、实现异步的方式
- async/await
- promise
- 回调函数(类似于ajax的sucess回调)
- 追问:async/await会被编译成什么 在es6版本中会被编译成promise+generator的形式,在更低的不支持promise的版本中会被编译成一个个switch
35、引用类型和基本类型都有哪些,他们有什么区别
基本类型:string、number、boolean、null、undefined
引用类型:数组、对象、函数
描述:
基本类型的值存放在栈内存中,引用类型的值存放在堆内存中,其指针存放在栈内存中。
区别:
- 基本类型的值是不可变的,而引用类型对应的值是可变的
- 基本类型比较时是比较他们的值,引用类型比较的是引用地址
36、函数传参传递的是引用还是值
是值传递。
基本类型传递的是值,引用类型传递的是引用地址的值。
37、怎么计算白屏时间和首屏加载时间
- 白屏时间
- 白屏时间可以通过在html标签中插入script标签来获取,在head标签加在资源开始前和head结束加两个时间点计算时间差
- 调用window.performance的API,responseStart - navigationStart
- 首屏时间
performance.getEntriesByName("first-contentful-paint")[0].startTime - navigationStart
38、精度问题
四、webpack
1、webpack的作用
- 模块打包。可以将不同模块的文件打包在一起,并使他们正确的引用,我们在写代码的时候就可以按模块书写,解构更加清晰明了。
- 编译兼容。可以使用各种Loader编译诸如.jsx,.ts,.vue各种类型的文件,同时还可以兼容高版本的代码,转换成各种浏览器可以识别的版本。
- 能力扩展。
2、有哪些常见的Loader? 你用过哪些loader
- file-loader,将文件输出到一个文件夹用,然后通过相对路径引用,用于图片和字体文件
- url-loader, 与file-loader相似,区别在于文件较小时可以使用这个Loader转化成base64编码,范围值可以自己设置
- ts-loader,awesome-typescript-loader,转换ts的loader,aweloader效率更高一些
- babel-loader,转换Js代码版本,兼容浏览器
- source-map-loader,加载sourceMap文件,用于浏览器调试
- sass-loader,less-loader,转化sass、less文件
- css-loader、eslint-loader等
3、用过哪些plugin
- html-webpack-plugin,简化html模板的创建流程
- mini-css-extract-pliugin,将css单独抽离成模块,支持按需加载
- clean-webpack-plugin,清空目录
4、Loader和plugin的区别
webpack默认只能识别js文件,loader是用来转换不同类型的文件。 plugin是通过webpack广播出来的api改变结果。
5、webpack构建流程
- 加载配置文件和命令行中的参数
- 开始编译,利用参数初始化complier对象,执行对象的run方法
- 找到入口,确定entry配置的所有入口文件
- 编译模块,从每个入口文件开始,调用Loader,按依赖关系递归翻译文件
- 输出文件,根据入口和模块文件的关系,输出成一个个chunk,再把每个chunk转换成输出的文件
- 根据配置确定输出的文件名和路径,写入到文件系统中
6、使用过哪些提高效率的插件
- webpack-dashboard,可以更友好的展示打包信息
- webpack-merge,提取公共配置,减少重复配置代码
- smp,有助于分析打包过程中loader和plugin的耗时,用于优化构建性能
- size-plugin,监控资源体积变化,及早发现问题
7、sourceMap是什么?生产环境怎么用
webpack将代码压缩打包后很难查看调试,sourceMap就是将压缩后的代码映射回源代码的过程。 线上环境一般有三种方式:
- hidden-source-map,借助第三方工具sentry使用
- nosource-source-map,只会显示代码的行数和错误栈,然后自己去源码找,安全性要高一些
- 设置白名单,通过Nginx将map文件设置只对公司内网开放
8、webpack打包原理
webpack为每个模块创建了一个可以导入和导出的环境,从入口文件开始,按依赖的加载顺序依次打包。
9、文件监听原理
轮询判断文件最后的编辑时间是否变化,当检测到变化后等到aggretTimeout时间后通知监听程序
10、webpack热更新原理
webpack和浏览器之间建立了一个websocket,当服务器更新时会向浏览器推送文件列表和hash,浏览器拿到hash之后会进行对比,如果有更新,就会从服务端拉取资源。当拉取到更新后进行热替换,通过hotModulePlugin完成
11、如何对bundle体积进行监控和分析
vscode有一个插件叫Import cost可以对引入资源的体积检测。还可以通过webpack-bundle-analyzer生成Bundle的组成图,显示所占的体积。 bundlesize工具包可以进行自动化资源体积监控。
12、文件指纹是什么?如何用
文件指纹就是打包生成的文件的后缀名。分为:
- hash:和整个项目有关,只要项目有更改,生成的hash就会改变
- chunkhash:生成的chunk的hash,不同的entry会生成不同的hash
- contentHash: 和文件内容有关,文件内容发生变化会生成不同的hash 怎么用举几个例子:
- 设置chunk的output名字使用chunkhash
- 设置css文件hash时,使用contentHash
- 设置图片输出名字的时候,使用hash
13、在实际工程中,配置文件上百行乃是常事,如何保证各个loader按照预想方式工作?
可以使用enforce强制执行loader的作用顺序, pre标识在所有loader执行前执行,post表示之后
14、如何优化webpack的构建速度?
- 使用高版本的webpack和node
- 多进城/多实例构建,thread-loader
- 压缩代码,多进程并行压缩,开启一些插件的paralell参数。通过minicss-extract-plugin提取css到单独的文件,通过css-loader压缩css
- 图片压缩:使用基于Node库的imagemin,使用image-webpack-loader设置参数压缩
- 充分利用缓存提升二次构建速度:babel-loader开启缓存,使用cache-loader
15、代码分割是什么?有什么作用
代码分割就是在直接上线源代码和唯一一个main.bundle.js之间做了一个平衡。直接上线源代码,会加载很多次资源,一个main.bundle.js又太大了。分开成若干个bundle.js,需要时拉取就能做平衡
16、是否编写过loader?描述写loader的思路
loader的api在官网可以查看到。本质上loader是一个返回值为函数的模块。在处理过程中可以使用任意的nodeapi或第三方api,要尽可能使用异步调用。loader是无状态的不能在loader中保存状态
17、是否编写过plugin? 描述写plugin的思路
plugin是通过监听webpack在构建过程中广播出来的函数,添加自定义功能。compiler暴露了和webpack整个生命周期相关的函数。compilation暴露了模块和依赖相关的粒度更小的事件钩子。找出合适的生命周期完成想做的事就可以了。
18、babel的原理,解析过程?
babel的转换大致分为三个部分:
- 通过babaylon解析:将代码解析成ast(抽象语法树)
通过词法解析和语法解析两步骤,词法解析会将源代码解析成tokens,语法解析再将tokens组合成树
怎么进行词法解析的:会把源代码逐个单词的解析成对象,对象包含类型、值、行数这些属性,比如const a = 1,const会解析成{type: 'KEYWORD_CONST', value : 'const}, a会解析成{type: 'VARIABLE', value: 'a'} 2. 转换:访问ast的节点进行变化操作生成新的ast 3. 生成: 以新的ast为基础生成代码
19、怎么提取公共文件
optimization.splitChunks
20、webpack是怎么引入文件的
- 使用__webpack__require,会先判断是否在缓存中,如果没有就取到对应模块的内容,并放在缓存中
21、tree-shaking
-追问:webpack
22、UMD
五、网络相关
1、nginx如何配置负载均衡
通过配置upstream,可以设置权重
2、后端设置的cors是如何让浏览器支持跨域的
服务端会返回几个响应头
- access-control-allow-origin
- access-control-allow-crendential 复杂请求还会有:
- access-control-allow-methods
- access-control-allow-headers
- access-control-allow-max-age
3、option请求的作用
option请求是为了获取服务器是否支持跨域,获取支持的方法。
复杂请求才会发起,简单请求不会。
1.请求方式只能是:GET、POST、HEAD
2.HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
3.Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
4、什么是简单请求?什么是复杂请求?
- 请求方法为
GET、HEAD、POST
时发的请求 - 人为设置了规范集合之内的首部字段,如
Accept/Accept-Language/Content-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width
Content-Type
的值仅限于下列三者之一,即application/x-www-form-urlencoded、multipart/form-data、text/plain
- 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;
- 请求中没有使用 ReadableStream 对象。
复杂请求
- 使用了下面任一 HTTP 方法,PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH
- 人为设置了以下集合之外首部字段,即简单请求外的字段
- Content-Type 的值不属于下列之一,即
application/x-www-form-urlencoded、multipart/form-data、text/plain
5、URL输入网址后都发生了什么
- 解析域名。浏览器缓存 -> 本地host文件 -> 本地DNS解析器缓存 -> DNS服务器
- 建立tcp连接。http连接会经历三次握手,https会经历四次握手。
6、HTTP协议属于TCP/IP的哪一层
答:应用层 总共有五层:应用层、传输层、网络层、数据链路层、物理层
7、TCP三次握手
- 客户端向服务端发送SYN报文,并置发送序号为X,客户端进入SYN-SENT状态
- 服务端接受到报文后,发送SYN+ACK报文,ACK报文序号为X+1,并置发送序号为Y,服务端进入SYN-RECEIVED状态
- 客户端接受到报文后,再向服务端发送ACK报文,ACK为Y+1,客户端进入ESTABLISHED,服务端接收到报文后也进入established状态
追问:明明两次握手能够确定连接,为什么要用三次呢
这是因为如果只用两次连接,当网络条件不好时,客户端发送的请求可能过了很久才能发送到服务端,这时候客户端超过了最大的连接时间可能就断开了连接,但是服务端在接收到了请求后却会新建一个TCP连接端口进入等待状态,等待着客户端发送请求。这种无用的连接会早晨服务器的资源浪费,服务器能开启的TCP连接数是有限的。三次连接会在客户端再次发送确认信息后才开启连接,能够避免这种浪费。
8、TCP四次挥手
四次挥手是描述客户端和服务端断开连接的过程
- 客户端给服务端发送FIN报文,告诉服务端自己不再发送数据了,并进入 CLOSE_WAIT状态
- 服务端接收到报文后,给客户端发送ACK报文,表明自己已经接受到了报文,后续不会再接收新的请求
- 服务端将未发送完成的数据发送完,关闭服务端到客户端的数据传送,然后发送FIN报文
- 客户端接收到报文后,会给服务端发送最后一个ACK报文,然后进入一定时间的等待状态,如果这段时间内没有服务端发送过来的数据,那么客户端就会进入closed状态。服务端接收到ACK报文,也会进入CLOSED状态
追问:为什么握手是三次,而挥手是四次呢?
因为在服务端第一次收到客户端的关闭连接的请求时,只是表示客户端不再发送请求,但是还可以接收数据。这时服务端也有可能还有待处理未发送的数据,服务端只能先发送ACK报文表示自己受到了请求,然后再发送完数据后再发送FIN报文。所以四次挥手主要是因为服务端给客户端的SYN和ACK报文是分开发送的。
9、浏览器缓存策略
从上往下
- service-worker
- memory-cache,内存缓存,小文件,频繁使用的浏览器会用这个
- disk-cache,硬盘缓存,大文件,不频繁使用的浏览器会用这个 另外就是强缓存和协商缓存,一般指的是disk-cache。 有关这块可以说:
- 强缓存:expires和cache-control的max-age(单位:秒)来控制
- 协商缓存:if-modified-since和etag控制 last-modified和if-modified-since/if-unmodified-since配合使用,etag和if-none-match/if-match配合使用
10、弱网请求优化
- 前后端设置accept-encoding和content-encoding为gzip
- 图片格式使用webp。使用方法:1、提前准备好wbep格式的图片请求。2、使用webpack的Plugin打包转换,可以用的plugin有imaginemin-webp-webpack-plugin
- 对数据做缓存。
- 设计上的优化。比如弱网不请求图片,只显示个占用位置。根据网络请求不同清晰度的图片等
11、https加密过程
graph TD
浏览器给服务器发送客户端版本,支持的加密算法版本,一个随机数
-->
服务器发送确认的加密算法套件,CA证书,服务端随机数给浏览器
-->
浏览器校验CA证书是否合法,然后根据服务端传过来的参数通过ECDHA加密算法计算出来一个密钥,用前面两个随机数加密钥对称加密
-->
浏览器还会使用确定好的对称加密密钥加密一个摘要信息,服务器会接收到ECDHA加密信息后,也计算出自己的私钥,对摘要信息进行解密验证
-->
之后使用该密钥对称加密
12、XSS
跨站脚本攻击,在页面嵌入脚本读取用户的cookie、session_id等信息。 常见的有
- 存储型XSS 将恶意脚本或者链接存储到目标数据库中,下一次页面渲染时就会执行脚本或显示恶意链接
- 反射型XSS 将恶意代码拼接在URL中,诱导用户点击,服务端在接收到参数后执行恶意代码或访问恶意网站
- DOM型XSS 直接在前端DOM中嵌入脚本或者构造一个恶意链接,然后前端在渲染时会执行对应的脚本
- 如何防范
- 重要的cookie设置httpOnly,防止脚本读取
- 特殊的地方如评论、账号输入做特殊的转义,如转义<、/,校验格式
- 使用纯前端渲染,可以避免存储型和反射型攻击。在前端代码中避免直接用innerHtml、doucument.write这一类的API,使用React/Vue框架,一般不会这么直接操作DOM,可以避免这种问题。要注意一些可以直接将字符串解析成脚本的地方,比如eval、href属性等,尽量避免能让用户在这类方法中嵌入字符串
13、CSRF
跨站点请求伪造,一般是用户在登录了正常网站之后,又访问了恶意网站,恶意网站利用用户在正常网站的认证信息,发起请求,执行恶意操作。 如何防御:
- 同源检测,检查请求的referce请求头,判断请求是不是来自收信任的网站
14、http1.0 1.1和2.0的区别
http1.0:浏览器与服务器只保持短暂的连接,每次请求都会新建一个tcp连接。
http1.1:
- 浏览器和服务器保持长连接,多个请求可以只用一个连接。但是通讯是按次序进行的,如果上一个请求等待了太久,会阻塞后面请求的返回结果
- 新增了一些请求类型:put\delete\option等。新增了一些控制缓存的请求头响应头,如if-unmodified-since,if-match等.
http2.0: - 完全实现多路复用,请求不用等待
- 采用二进制格式,而不是文本格式,解析起来更高效
- 首部压缩。在连接的存续期会存在首部表,用于存每次发送的数据,之后的每次请求指挥发送不一样的数据,由浏览器和服务端同步更新,这样可以减少冗余数据,降低开销
- 允许服务器推送资源,可以随请求将资源一起推送给浏览器,不用建立新的请求。
六、HTML
- 使用token做安全性验证,不用cookie。这里可能会延申出分布式token的验证问题:token如果只存储在session中的话,当服务端是多台服务器时token不能共享。这里可以使用redis或者数据库存储token,或者使用像JWT-Token之类无状态验证,每次会在服务端重新计算一遍token
1、如何遍历一个DOM树
function traversal(node) {
//对node的处理
if (node && node.nodeType === 1) {
console.log(node.tagName);
}
var i = 0,
childNodes = node.childNodes,
item;
for (; i < childNodes.length; i++) {
item = childNodes[i];
if (item.nodeType === 1) {
//递归先序遍历子节点
traversal(item);
}
}
}
2、HTML为什么要语义化
- 网页加载慢导致CSS文件还未加载时(没有CSS),页面仍然清晰、可读、好看。
- 提升用户体验,例如title、alt可用于解释名词或解释图片信息。
- 有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息。
- 方便其他设备(如屏幕阅读器、盲人阅读器、移动设备)更好的解析页面。
- 使代码更具可读性,便于团队开发和维护。
3、html的meta标签有哪些属性
- name属性,content属性
- name为keywords,content的内容就是告诉搜索引擎网页的关键字
- name为description,content告诉搜索引擎网站有哪些主要内容
- name为author,content标明作者
- name为copyright,content标明版权
- name为viewport,content则用来注明content="width=device-width, initial-scale=1, maximum-scale=1",分别指屏幕的CSS宽度,初始缩放比例,最大缩放比例(为1不允许缩放)
- http-equiv属性,content属性
- http-equiv为content-Type, content设置的是字符集,例如'text/html;charset=UTF-8'
- http-equiv为cache-control,content设置的是缓存,例如no-cache,no-store,max-age
- http-equiv为X-UA-Compatible,用于告知浏览器以何种版本来渲染页面,例如:content="IE=edge,chrome=1"指定IE和chrome使用最新版本
4、script标签异步加载
- async属性,脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。用来解决脚本加载阻塞的问题
- defer属性
-
这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发
DOMContentLoaded (en-US)
事件前执行。 -
有
defer
属性的脚本会阻止DOMContentLoaded
事件,直到脚本被加载并且解析完成。 -
追问:怎么在webpack加这些属性
七、node
1、中间件(洋葱模型)原理
内部将所有注册的中间件放在一个数组中,在执行时会将数组的第一个元素对应的函数放入一个Promise.resolve中,同时给这个函数传递一个next参数,每个next都会将数组下标+1,这样在执行next时就可以执行下一个中间件。在执行回调时就会依次执行中间件函数
八、ts
1、实现readonly类型(通过泛型将类型的属性都设置为readonly)
type newType<T> = {
readonly [key in keyof T]: T[key]
}
2、interface和type的区别
- interface可以继承而type不可以,但是type可以使用&来实现
- type可以声明基本类型别名,元组类型,联合类型,interface不行
- type语句可以使用typeof获取实例的类型进行赋值
- 相同名字的interface可以进行声明合并
3、手工实现partial
type newType<T> = {
[key in keyof T]?: T[key]
}
4、ts和js的区别
5、any和unkonwn的区别
任何类型都可以赋给unknown,但是unknown不可以赋值给任何类型
任何类型都可以赋给any,any也可以赋值给任何类型
unknown一般用来不确定类型但又需要做类型检查的时候,当一个参数设置为unknown时,可以传递任何参数,但是当需要使用这个参数时,需要先断言他的类型。
九、微前端
1、微前端的原理
微前端的实现有几种方法:
- iframe,每个子应用放在一个iframe中,好处是天然的样式隔离和脚本隔离,劣势是样式问题和兼容性问题
- nginx转发,不同的路由展示不同的子应用页面,主应用页面公用,切换时页面会刷新
- webComponent,每个子应用都要用web component技术编写,这是一套全新的开发模式,比较耗时,通过shadow dom实现样式脚本隔离,可以通过插槽和监听路由来切换不同的子应用
- 组合式路由分发,每个子应用独立开发和部署,统一由基座应用进行路由管理,子应用卸载,卸载加载 现在常用的是第四种,说一下现在常用的微前端的原理:
- 路由切换 路由切换是通过hashChange或者popState监听到路由变化的时机,然后远程拉取对应微应用的信息,然后进行解析,解析的过程会先将html和css拼接在基座应用给微应用预留的空白地方,然后使用eval来执行对应的js代码
- 应用隔离
css隔离:
子应用之间的样式隔离可以在卸载子应用的时候同步卸载style和link标签的内容就可以了,所以主要是解决主应用和子应用之间的样式隔离,这里可以使用css module或者命名空间的问题,比如可以使用postcss给每个样式添加前缀。
js隔离:
这里采用沙箱机制,可以将每个应用的变量挂载在一个proxy的window对象上,每次修改或获得属性值的时候拦截处理一下。 - 消息通信 消息通信是使用基座应用做为中介,采用订阅发布的模式,每个子应用注册事件在主应用上,在触发了之后再统一分发。
十、真题
1 腾讯子公司
1、js事件流
分为冒泡和捕获,捕获是从顶层至目标元素,冒泡是从目标元素至顶层,注意这里是先有事件捕获哦,再有事件冒泡。
可以通过设置addEventListener的第三个参数来决定添加的是冒泡还是捕获,false或者不设置是冒泡,设置为true是捕获,为了浏览器兼容的原因,一般是设置冒泡事件。
- 追问:同时设置了父元素和目标元素的冒泡和捕获的监听事件,执行顺序是怎样的? 父元素捕获 -> (子元素先执行冒泡还是捕获看添加事件的顺序) -> 父元素冒泡
- 追问:给一个元素添加相同的事件是都会执行还是怎么样,执行顺序咋么样? 按添加的顺序依次执行
2、遍历对象有哪些方法?
for...in,Object.keys(),Object.values(),Object.entries()
- 追问:怎样设置属性不可遍历 Object.defineProperty()的enumerable属性设置为fasle
- 追问:之后又想遍历又怎样设置为可遍历 Object.defineProperty()预先设置configurable为true,那么在之后想遍历的时候可以再讲enumerable设置为true
2 小红书
1、HTML为什么要语义化
- 网页加载慢导致CSS文件还未加载时(没有CSS),页面仍然清晰、可读、好看。
- 提升用户体验,例如title、alt可用于解释名词或解释图片信息。
- 有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息。
- 方便其他设备(如屏幕阅读器、盲人阅读器、移动设备)更好的解析页面。
- 使代码更具可读性,便于团队开发和维护。
2、html的meta标签有哪些属性
- name属性,content属性
- name为keywords,content的内容就是告诉搜索引擎网页的关键字
- name为description,content告诉搜索引擎网站有哪些主要内容
- name为author,content标明作者
- name为copyright,content标明版权
- name为viewport,content则用来注明content="width=device-width, initial-scale=1, maximum-scale=1",分别指屏幕的CSS宽度,初始缩放比例,最大缩放比例(为1不允许缩放)
- http-equiv属性,content属性
- http-equiv为content-Type, content设置的是字符集,例如'text/html;charset=UTF-8'
- http-equiv为cache-control,content设置的是缓存,例如no-cache,no-store,max-age
- http-equiv为X-UA-Compatible,用于告知浏览器以何种版本来渲染页面,例如:content="IE=edge,chrome=1"指定IE和chrome使用最新版本
3、any和unkonwn的区别
任何类型都可以赋给unknown,但是unknown不可以赋值给任何类型
任何类型都可以赋给any,any也可以赋值给任何类型
unknown一般用来不确定类型但又需要做类型检查的时候,当一个参数设置为unknown时,可以传递任何参数,但是当需要使用这个参数时,需要先断言他的类型。
3 金山
二面
1、UMD规范
2、HTTP1和HTTP2的区别
- HTTP2的优化 缺点:
- 队头阻塞的问题,因为tcp有超时重传的机制,丢失的包必须等待重新确认
- 多路复用没有限制请求数,容易超时,给服务器也可能带来更大的压力
- 建立连接耗时
- HTTP2多路复用的实现 通过分帧和流,将每个请求分解成帧,每一帧都有序号,客户端在接收到数据后通过序号再将数据组装在一起
- 优化 可以通过自定义函数来限制请求数,对头阻塞的问题不能解决,只能使用http3
- 说一下HTTP3 使用了新的QUIC协议,是一种基于UDP的协议
- 在UDP上包了一层确保传输的可靠性
- 集成了TLS加密功能,使用的是最新1.3版本的加密,耗时更少
- 解决了队头阻塞的问题,在同一个物理连接上有多个独立的逻辑数据流
- 实现了快速握手的功能
3、状态管理的方法(除了redux、cotext还有什么)
还有mobx
4、tree-shaking
就是将没有使用的导出文件删除,实现打包的优化
在webpack怎么实现:
- 使用 ESM 规范编写模块代码
- 配置
optimization.usedExports
为true
,启动标记功能 - 启动代码优化功能,可以通过如下方式实现:
- 配置
mode = production
- 配置
optimization.minimize = true
- 提供
optimization.minimizer
数组 原理:
- 配置
5、script标签的async和defer有什么区别
defer会将文件放在文档的最后执行,并且会按照声明的顺序加载
async脚本遇到的话会先去加载,加载的而同时执行其他的HTML渲染或者脚本的任务,在脚本下载好了在执行,因此不一定按照声明的顺序执行
所以defer适用于有依赖关系的脚本,async适用于不依赖其他的独立的脚本
6、webpack的script标签怎么设置为异步
通过script-ext-html-webpack-plugin开启
7、CSS优化,怎么设为异步呢
- 通过 JS 动态插入 link 标签来异步载入 CSS 代码
- 利用 link 上的
media
属性,将它设置为和用户当前浏览器环境不匹配的值,这样一来,浏览器就会认为这个 CSS 文件优先级非常低,就会在不阻塞的情况下进行加载,但是为了让 CSS 规则生效,最后还是要将 media 值改对才行,<link rel="stylesheet" href="cssfile.css"media="jscourse" onload="this.media='all'">
- 新方子就是利用规范中新增加的
rel="preload"
,就像这样:
<link rel="preload" href="cssfile.css" as="style" onload="this.rel='stylesheet'">
通过 preload
属性值就是告诉浏览器这个资源文件随后会用到,请提前加载好。但是这只是加载,所以你看当它加载完毕后,还是需要将 rel 值改回去,这才能让 CSS 生效。
8、UMD
所谓UMD (Universal Module Definition)
,就是一种javascript
通用模块定义规范,让你的模块能在javascript
所有运行环境中发挥作用。