20190821电话面试
1. react的setState后发生了什么
解析:setState处于equeueSetState()中,首先将particalState放入_pendingStateQueue中,调用enquequeUpdate()判断是否为isBratchingUpdates批量更新策略,如果是批量更新策略,则调用dirtyComponent()将组件放入dirtyComponent,如果不是批量更新策略,则直接调用bratchUpdate即react中的默认批量更新策略,此时会进入ReactDefaultBatchStrategy.batchedUpdates,(某个初始isBratchingUpdates为false),调用事务流(含wrapper,inital,close,调用过程为inital,perform,close),transaction.perform(enQueueUpdate执行函数),此时将修改isBatchingUpdates为true,再次回调时将进入dirtyComponent,事务流结束时将修改isBatchingUpates: false,同时执行flushBatchedUpdates将遍历dirtyComponent内的组件,根据调用的先后顺序执行updateComponent,进而更新props、state。updateComponent中有一代码Object.assign(nextState, typeof partical === 'function' ? partical.call(inst, nextState, props, context): partical)此代码可解决多次执行this.setState修改某个数值时值不如预期的情况。不如预期的原因:this.setState()已经在bratchedUpdates更新事务流中,故此时isBatchingUpdates为true将会进入dirtyComponent,但如果为timeout(callback,timeInt)将会不在batchedUpdates的事务流中,此时isBatchingUpdates为false将会进入transaction的事务流然后执行dirtyComponent,最后执行事务流流中的close函数,达到更新界面的效果(isBatchingUpdates: false, flushBratchUpdate); 结论: this.setState将会进入一个待更新的队列,并不保证同步更新,仅通过回调函数可拿到setState修改后的值,setState通常会集齐一些组件状态后更新组件,保证性能,代码内涵即react的批量更新策略。- 参考地址:setState批量更新策略
- 渲染界面
- 图片详解



- 番外:通过babel将jsx生成VDom元素,即
React.createElement()含type(标签元素),attributes(属性),children(标签子节点);会在render函数被调用的时候执行,render调用返回一个element。格式如下
{
type: 'div',
props: {
className: 'cn',
children: [
{
type: function Header,
props: {
children: 'Hello, This is React'
}
},
{
type: 'div',
props: {
children: 'start to learn right now!'
}
},
'Right Reserve'
]
}
}
- 在首次渲染的时候,先去按照规则初始化element,接着ReactComponentComponentWrapper通过递归,最终调用ReactDOMComponent的mountComponent方法来帮助生成真实DOM节点。
- 结论:从jsx到渲染成真实realdom;JSX代码经过babel编译之后变成React.createElement的表达式,这个表达式在render函数被调用的时候执行生成一个element。
updateComponent涉及三个生命周期函数(更新渲染):shouldComponentUpdate componentWillUpdate componentDidUpdate1.计算nextState => shouldComponentUpdate 2.render得到nextElement元素 => componentWillUpdate 3.preElement于nextElement进行diff算法更新界面 => componentdidUpdate
2. 抛开react的diff算法,怎么实现dom对比
- diff算法:
- 基于假设: 1. 相同的组件组件具有相同的dom结构,不同的组件具有不同的dom结构
- 对于同一层次的一组子节点,它们可以通过唯一的id区分 diff算法考虑三种情况:
- 节点类型不同
- 节点类型相同 => dom元素直接比较需要改变的属性 组件: 根据新节点的props去更新原来根节点的组件实例,触发一个更新的过程,最后在对所有的child节点进行diff递归比较更新。
- 子节点比较 => 无key按顺序比较,有key根据唯一的id进行比较
- tree diff component diff element diff
- 结论:保持dom结构的稳定性,map时加上key
- dfs深度优先遍历算法,给每个节点赋予唯一的key值,比较新旧两颗dom树中节点的type,props有何变化,根据唯一的key值在pathchs中记录相应的节点变化,如果有子节点,递归深度优先遍历算法,同样记录diapcth节点差异,最终通过diff算法得到了新旧两颗dom树中的差异,在通过已生成的element对象,即vdom通过深度优先遍历算法应用这种差异,最后将vdom渲染成真实的dom结构(mountComponent);
3. 实现页面多ajax请求完成后渲染页面 并发请求
- 将异步请求封装成
promise,ajax回调中resolve()得到异步请求后的数据,Promise.all([])发异步请求时通过async和await同步的方式写异步代码,同时保证了多个异步请求执行结束后渲染界面。 - 或者将异步请求封装为
promise直接发出两个promise请求,await等待两个promise的值都存在开始下一步。 或者generator函数等待两个promise值,在第一个next()函数拿到promise值在then函数内调用next()拿到下一个异步请求的值,同样实现并发操作拿到请求值。
4. set去重原理?(待全面分析)
- 内部通过Map实现的,相同的key时会直接覆盖掉故不会出现相同的Key
5. const
- 常量不可修改,初始时必须赋值,形成块作用域,不会成为全局变量的属性。
6. 移动端适配
- 解决方案: 媒体查询,media srcreen(device-width)、viewport、rem、flex弹性布局
- 媒体查询根据查询的屏幕宽度编写不同的css样式,调整页面宽度时不用刷新页面即可实现响应式布局,各种设备维护一套代码,缺点加载更多的css资源,图片资源。
- viewport视口 通过设置device-width 和initial-size实现css物理像素更据不同屏幕匹配真正的物理像素,设备像素比(dpr) = 物理像素/设备独立像素,设置初始化的大小为1/dpr即实现css中的px和移动端的px相同
- rem即根据根元素的fontSize实现响应布局,调整所有的字体的大小
- 3种适配方式
1. media查询,同上
2. rem适配,相对于根元素的大小,在理想视口的情况下(width = device-width)即css像素随着不同的布局视口作出等比改变,但是css像素和物理像素不变。优点:利用了理想视口 缺点:需要计算,出现小数点,字符显示不完整
<script>
let ele = document.createElement('style');
let size = document.documentElement.clientWidth * 16 / 375; //布局视口
ele.innerHTML = 'html{font-size:'+ size +' px !important}';
document.head.appendChild(ele);
</script>
<script>
//rem适配
let styleElem = document.createElement('style');
let size = (document.documentElement.clientWidth * 16 * dpr) / 375;
styleElem.innerHTML = 'html{font-size:' + size + 'px!important}';
document.appendChild(styleElem);
let metaElem = document.querySelector('meta[name=viewport]');
let dpr = window.devicePixelRatio || 1; //像素比
metaElem.conent =
'initial-scale=' + 1 / dpr + ',maximun=' + 1 / dpr + ',minimun=' + 1 / dpr + 'user-scalable=no';
</script>
3. viewport 视口: 布局视口(document.documentElement.clientWidth) 可视视口(document.innerWidth) 理想视口(screen.width)
通过1/dpr实现等比缩放,改变了布局视口的大小,实现1px css像素对应1px 物理像素,同时保证rem的css满足布局,font-size需要*dpr(缩小布局视口增大,css像素应实现等比改变)
<script>
//rem适配
let styleElem = document.createElement('style');
let size = (document.documentElement.clientWidth * 16 * dpr) / 375;
styleElem.innerHTML = 'html{font-size:' + size + 'px!important}';
document.appendChild(styleElem);
let metaElem = document.querySelector('meta[name=viewport]');
let dpr = window.devicePixelRatio || 1; //像素比
metaElem.conent =
'initial-scale=' + 1 / dpr + ',maximun=' + 1 / dpr + ',minimun=' + 1 / dpr + 'user-scalable=no';
</script>
/* 媒体查询 */
@media only screen and (device-pixel-ratio: 2) {
#test {
font-size: 12 * 2 + 'px';
transform: scaleY(0.5);
}
}
4. 不同设备下布局视口一致,css大小不变,但css像素对应物理设备像素改变
<script>
let metaElem = document.createElement('meta');
let scale = documnet.createElement.clientWidth / 750;
metaElem.name = 'viewport';
metaElme.content = `initial-scale=${scale}, maximun=${scale}, minimun=${scale}, user-scalable=no`;
document.head.appendChild(metaElem);
</script>
- 理解概念: 物理像素 css像素 设备独立像素(width=device-width)时 css像素和物理像素关联起来 像素比 布局视口 可视视口 理想视口 分辨率
7. css动画
- animation: animation-delay, animation-duration, animation-timing,animation-keyframes from to 或者百分比实现过渡效果,animation-direction,animation-iterator-count;
- transition: 过渡效果,property,transition-delay,transition-duration,transition-timing-function(速度 => linear、easein(加速)、easeout(减速)、cubic-bezier(自定义速度模式))transition对于display属性添加过渡效果无效应使用visibility
<style>
/* transition解决display问题 */
div>ul{
visibility: hidden;
/* display: none; */
/* height: 0px; */
opacity: 0;
overflow: hidden;
transition: visibility 0s, opacity 0.5s linear;
}
div:hover>ul{
visibility: visible;
/* display: block; */
opacity: 1;
/* height: auto; */
}
</style>
</head>
<body>
<div>
<ul>
<li>fd</li>
<li>fd</li>
<li>fd</li>
<li>fd</li>
</ul>
</div>
</body>
8. 页面白屏如何处理,想到的场景以及处理办法
- 白屏原因:初始化 webview -> 请求页面 -> 下载数据 -> 解析HTML -> 请求 js/css 资源 -> dom 渲染 -> 解析 JS 执行 -> JS 请求数据 -> 解析渲染 -> 下载渲染图片
- 解决办法:
-
降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。
-
加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。
-
缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage。
-
渲染:JS/CSS优化,加载顺序,服务端渲染,pipeline。
-
前端后端渲染的区别 答案类似ajax的优缺点 前端渲染:优点:1. 操作js实现界面数据改变,局部刷新界面,无需每次完整加载页面
-
懒加载,只需加载可视区的数据
-
利用js实现一些酷炫效果,一些操作可以在前端做,减轻服务器的压力,整个网络的数据量小
-
实现了前后端分离,通过后端提供的接口获取数据,渲染界面 缺点:1.无法后退获取上一次的状态 2.需加载过多的js css资源导致首页性能差3.暴露更多的数据交互逻辑,存在安全问题 后端渲染:1. 首页性能好,直接显示一个完整的页面,发回一整个html页面 2.勿需加载css js资源
9. 说一些常用array的api
- map(),splice(start,[deletecount],[insertValue]),slice(),find(),findIndex(),filter(),push(),pop(),shift(),unshift(),indexOf(),concat(),join(),reduce(),includes(),keys(),forEach(),reverse(),sort(),lastIndexOf(),fill(value, start, end) => 出现的最后一个索引,from()
- substr(index, [len])可为负index; subString(start,end) 不为负index
10.数组并集
- a集合包含b集合中的元素,具有互异性,即不重复性。
- 通过includes方法,a.concat(b.filter(value) => !a.includes(value));
- 通过Aarry.from()方法实现类数组的转化,new Array.from(new Set(a.concat(b));
- 通过filter, a.concat(b.filter(value)=> a.indexOf(value) === -1)
11. 数组右移x位
- 经典的三次翻转,先翻转前0-x,再翻转x-n-1,最后在整体翻转0-n-1实现数组循环右移x位
//经典的三次反转方法:实现数组循环右移k位
void Reverse(int* array, int p, int q)
{
for (; p<q; p++, q--)
{
int temp = array[q];
array[q] = array[p];
array[p] = temp;
}
}
void RightShift(int* array,int n, int k)
{
k %= n;
Reverse(array, 0, n - k - 1);
Reverse(array, n - k, n - 1);
Reverse(array, 0, n - 1);
}
12.给一个数组,给一个数x,找数组内想加等于x的数的索引
- leetcode题直接上最优解
var sumNum = (arr,sum) => {
for(var i = 0; i < arr.length; i++){
let target = sum - arr[i];
if(arr.indexOf(target) !== -1){
if(i !== arr.indexOf(target)){
return [i,arr.indexOf(target)]
}
}
}
return [];
//map解法
var map = new Map();
for(var i = 0; i < arr.length; i++){
let target = sum - arr[i];
if(map.get(target) && map.get(target) != i){
return [i, map.get(target) ]
}else{
map.set(arr[i], i);
}
}
}
20190819电话面试
1.display属性有哪些?
- block,flex,none,inline,inline-block,table,grid,inline-grid
- grid: grid-template-rows grid-template-columns grid-gap grid-template-areas grid-area grid-columns-start grid-columns-end justify-items:stretch align-item:stretch justify-self(自己的对齐方式)
2. position属性有哪些?每一个属性的作用,使用?
- fixed,relative,position,static
3. css中引入样式有哪几种方法,不考虑预处理和正常的三种方法,有没有其他的方法?
- 行内式,内嵌式,(),链接式,导入式@import文件路径
- 优先级 !important > 行内式 > id > 类+伪类+属性 > 标签+伪元素 > 通配符 class 组通过就近原则 具体优先级通过计算得知 内联1000 id 100 class 10 标签 1 ;animation执行0s也能超过id选择器
- 伪类:用单冒号实现特定的效果如:active :focus :hover 伪元素用双冒号::添加某个元素而实现相应的效果::after ::before
4. css单位,详细说一下每一种的使用?
- px: css像素 当width=device-width时,css像素和物理像素的关系等于dpr = 物理像素/设备独立像素
- em: 相对长度单位,相对当前行内字体大小
- rem: 相对根元素的字体大小
- vh: 视高:可视区域宽度或者高度,innerWidth/innerHeight
- vw: 视宽
- vmin vmax : vh或vw中的最小值或者最大值
5.React中的context原理? 一级Context API
- 跨层级的组件通信,类似共享一全局变量,采用生产者消费者模式,声明context对象,申请访问context对象的属性;案例有react-redux react-router
- 使用了Context破坏了组件间的依赖性,不利用组件复用和可维护性
- 则组件间的通信方式有:props context createRef() => this.createIns.current.property
- context通过静态属性childContextTypes声明一个context对象属性,通过getChildContext返回context对象,有状态组件使用context需要通过静态contextTypes属性声明要获取的context对象属性,通过this.context获取属性值,无状态组件通过静态contextProps声明context属性,通过context获得属性值;代码如下
import React from 'react'
import PropTypes from 'prop-types'
class context extends React.component{
//声明context对象属性
static childContextProps = {
backgroundcolor: PropTypes.string,
color: PropTypes.func
}
//返回context对象
getChildContext(){
return{
backgroundcolor: 'red',
color: 'blue'
}
}
render(){
}
}
//有状态组件使用context
class StateComponent extends React.Component{
static contextTypes = {
backgroundcolor: 'red'
}
render(){
let {backgoundcolor, color} = this.context; //仅能访问到backgroundcolor属性
}
}
//无状态组件使用context
function NoState(props, context){
return context.color
}
NoState.contextProps = {
color: PropTypes.func
}
- Context的API
let ContextComponent = React.createContext({
backgroundcolor: 'red',
color: 'blue'
});
//生产者
<ContextComponent.Provider value={backgroundcolor: 'red', color: 'blue'}>
</ContextComponent.Provider>
//消费者 通过Consumer 内部为一函数获得context对象值
<ContextComponent.Consumer>
(context) => {
console.log(context.color);
}
</ContextComponent.Consumer>
6.浏览器机制(DOM事件流,一开始答成了event loop后来面试官就让我讲一下宏微任务)
- 浏览器机制主要是浏览器渲染机制,浏览器的功能主要是向服务器发起请求,展示请求的资源,可以是html,pdf,图片资源;整个浏览器可以分为以下几个部分,用户界面、浏览器引擎、内核(渲染引擎)、js引擎、网络、用户界面后端、数据存储。

- 内核即呈现引擎流程图: 将请求的资源变为dom树 => 渲染dom树 => 布局dom树 => 绘出dom树;整个渲染过程js引擎执行完才会执行渲染过程,这是由于浏览器的处理用户交互,如果是多线程,会导致更多复杂的同步问题。同时为了更快的显示到用户界面同时解析成dom树并渲染非先将dom树生成完成在渲染。
- 浏览器性能优化考虑点:js执行时间过长,操作dom都回导致页面卡顿
- 减少js加载对dom渲染的影响,将js代码加载逻辑放在html文件尾部,减少渲染引擎呈现工作的影响
- 避免重排,减少重绘(避免白屏,或者交互过程中的卡顿)
- 减少dom的层级,减少渲染引擎工作过程中的计算量
- 使用setTimeout() setInterval()来执行动画视觉变化,导致丢失帧,导致卡顿。
- 阻塞加载:
- css不阻塞dom解析,阻塞dom渲染,cssom和dom解析完成后才开始渲染,link都是并行下载的,link后是script的话会阻塞js的执行,不会阻塞js的加载。因为js可能对html元素,css样式作出修改,故会阻塞js的执行。因此此时将script放在link标签前面。
- js阻塞dom解析,script标签下载和执行完才开始dom解析,可通过async defer改善加载和执行情况。defer并行下载,DOMContentLoaded即dom接下完成前执行,执行顺序和defer顺序一致,async异步也是并行下载不阻塞dom解析,但解析完成后则执行,执行过程阻塞dom解析,defer和async只对外联有效。
- link放头部,script放body尾部优化性能。

- 浏览器是多线程的 浏览器事件触发线程 定时触发器线程 异步http请求线程 ui线程 js线程

- js引擎:js是脚本语言,运行则执行输出结果,浏览器会作出优化先编译后执行达到性能提升。js能操作dom节点,故Js加载完后才会启动浏览器渲染过程。Event Loop js是单线程的,所有同步任务在主线程上执行,形成一个执行 栈,当有异步事件的时候,该异步事件会挂起,同时告诉浏览器有这么一件事,主线程继续执行,当浏览器感知异步事件可以执行时,会将异步事件回调函数放入事件队列,当主线程中的执行栈执行完毕,就会读取事件队列依次执行,是一个循环的过程,有事件就执行,否则就不断循环检查,这个过程中涉及到宏/微任务, 这里涉及到宏/微任务队列,每次执行栈完后会执行所有的微任务队列中的任务,在执行下一个宏任务。即宏任务排队执行,微任务插队执行。不断重复以上过程

- 宏任务和微任务:都为异步任务,但有不同的执行顺序 -先执行微任务队列在执行一个宏任务

- 宏任务:整体代码script,setTimeout(), setInterval(),setImmediate(),ajax,事件回调(onclick())
- 微任务:原生Promise(有些实现的promise将then方法放入宏任务中,大部分是微任务,catch,finally),process.nextTick(node)
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
打印顺序:1 7 6 8 2 4 3 5 9 11 10 12
注意事件循环一个事件执行完成才执行下一个任务
- js引擎结论:则是分析执行栈通过宏微任务进行执行。先宏后微任务
- node.js 事件循环 不同于浏览器事件循环

- node.js由chorme v8引擎进行解析,其自己实现了I/O操作的libuv,事件循环是根据不同的阶段来执行,每个阶段维持相应的队列,带队列为空在进行下一个阶段。
- timer:执行定时器相关的回调 pending callback:执行一些系统回调,网络错误等 idle callback:node内部使用 poll轮询: 执行I/O的回调 check: 执行setImmediate回调 close: 执行socket.close回调
- poll: I/O操作当该阶段事件执行完后,检查有没有setImmediate()如有去check阶段执行,若没有则一直阻塞,同时检测有无timer执行,若有timer存在则开始执行下一次事件循环。process.nextTick()是微任务,微任务在每个阶段执行完后再执行,故process.nextTick()可能造成starving 饿死,setImmediate()则不会。
- node 环境中事件循环案例
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
浏览器:timer1 promise1 timer2 promise2
Node:timer1 timer2 promise1 promise2
const fs = require('fs')
const starttime = Date.now()
let endtime
fs.readFile('text.txt', () => {
endtime = Date.now()
console.log('finish reading time: ', endtime - starttime)
})
let index = 0
function handler () {
if (index++ >= 1000) return
console.log(`nextTick ${index}`)
process.nextTick(handler)
// console.log(`setImmediate ${index}`)
// setImmediate(handler)
}
handler()

7.高级浏览器内核
- webkit chorme safari
- trident IE
- Gecko mozllia firfox
8.理解dom事件流的三个阶段
- 事件流就是从页面中接收事件的顺序
三个阶段 - 事件捕获阶段:父向子传递事件
- 目标事件阶段:更据注册的捕获/冒泡的先后顺序执行
- 事件冒泡阶段:子向父传递事件 event.stopPragation()阻止冒泡,addEventListener true事件捕获,false事件冒泡,attachEvent(event,callback)。
- dom事件流:则是先事件捕获-> 目标事件->执行事件冒泡
- 事件委托:基于事件冒泡的思想将子元素事件委托给父元素事件
- 阻止冒泡:js: event.stopPrapagtion()
- 阻止默认行为:默认行为即event.cancelable:true,如a submit会自动跳转自动提交,js: event.preventDefault() IE: return false jquery:return false阻止了事件默认行为也阻止了事件冒泡,IE中的event在全局变量window上,而firfox仅为临时变量,在时间触发时产生
- event.target: 事件触发者 event.currentTarget:事件监听者 eventCurrentTarget === this即this始终和事件监听者相等。
<ul>
<li></li>
<li></li>
<li></li>
</ul>
<!--事件委托-->
var ulist = document.getElementsByTagName('ul');
ullist.onclick = function(e){
if(e.target.nodeName.toLowerCase() === 'li'){
console.log('li click!');
}
}
- 事件委托机制具有局限性,如focus blur无冒泡机制,无法实现事件委托
- mousemove mouseout 有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的

9. ajax前后端通信,除此以外另一种通信方式了解过么?
- 同源策略:协议 端口 域名/ip不同都会导致跨域,无法进行资源交互,不能访问cookie,localStorage,indexDB,不能操作dom节点,无法发送ajax请求,是一种浏览器安全策略,隔离恶意文件
- ajax 同源下的通信方式 封装ajax
var util = {};
//获取 Ajax 请求之后的 json
util.json = function (options) {
var opt = {
url: '',
type: 'get',
data: {},
success: function () {
},
error: function () {
},
};
Object.assign(opt, options);
//IE兼容性处理:浏览器特征检查。检查该浏览器是否存在XMLHttpRequest这个api,没有的话,就用IE的api
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');
var data = opt.data,
var type = opt.type.toUpperCase();
var dataArr = [];
if (opt.url) {
var url = opt.url;
}
for (var key in data) {
dataArr.push(key + '=' + data[key]);
}
if (type === 'GET') {
url = url + '?' + dataArr.join('&');
xhr.open(type, url.replace(/\?$/g, ''), true);
xhr.send();
}
if (type === 'POST') {
xhr.open(type, url, true);
// 如果想要使用post提交数据,需要明确设置Request Header
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(dataArr.join('&'));
}
xhr.onreadystatechange = function () {
if (xhr.status === 200 || xhr.status === 304) {
//304表示:用缓存即可。206表示获取媒体资源的前面一部分
var res;
if (opt.success && opt.success instanceof Function) {
res = xhr.responseText;
if (typeof res === 'string') {
//将字符串转成json
res = JSON.parse(res);
opt.success.call(xhr, res);
}
}
} else {
if (opt.error && opt.error instanceof Function) {
opt.error.call(xhr, res);
}
}
};
}
注意兼容性,XMLHttpRequest兼容性,window.XMLHttpReques,否则 new ActiveXObject("Microsoft.XMLHTTP");解决异步请求对象兼容
post需设置请求头 xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
get请求url用?连接参数
优点:不刷新页面则更新数据;异步于服务器进行通信(不打断用户操作,更迅速的相应能力);前端和后端负载均衡(将服务端的任务在客户端处理,减轻服务器的带宽负担,节约空间和带宽租用成本);数据和界面分离,前后端分离,提高开发效率。 缺点:破坏了back和History按钮,无法回到前一个页面状态,暴露了更多数据引发安全问题如(SQL注入攻击和基于Credential安全漏洞);违背URL和资源定位的褚总,同一URL可得到不同的内容 使用场景:基本的数据操作和数据过滤 不适用场景:大量的文本替换 导航和搜索
- WebSorket 基于客户端和服务端的双向长连接通信(可跨域) 特点:一般HTtp请求只能有客户端主动发起,WebSocket技术,服务端可主动向客户端推送信息,客户端也可以主动向服务器发送请求,属于服务器推送技术的一种。常用方法:send(),onerror(),onmessage(),onclose(),close()
- CORS 跨域资源共享 需在服务端和客户端做向应配置,允许跨域的ajax请求
- 后端:
- Access-Control-Allow-Origin: *|源,当允许跨域携带身份凭证时不能为*
- Access-Control-Allow-Credentials:允许跨域携带身份凭证true,前端同样需要设置xhr.withCredential=true 否则出错
- Acess-Control-Allow-Max-Age: 非简单请求(即POST,GET,HEAD)出外的预检请求的有效期,有效期内统一请求不需要在进行发送预检请求(Options)
- Acess-Control-Allow-Methods: 允许预检请求的HTtp请求方法
- Acess-Control-Allow-Headers: 允许预检请求的请求头
- 预检请求则是预先发出OPtions请求看能否进行跨域访问,避免不必要的数据交互。
- 前端:
- Origin:预检请求或者实际请求的源
- Access-Control-Request-Method:实际请求方法
- Access-Control-Request-Headers:实际请求头 HTTP头部字段


- 跨域解决方案:
- jsonp 利用Script标签可以跨域,传递一个callback函数,后端将值放入这个函数并返回,将执行这个callback函数进而拿到数据。只能处理get请求
- WebSocket 双向长连接,解决跨域
- cors 跨域的ajax请求
- hash 通过监听onhashchange进行数据交互
// B中的伪代码
window.onhashchange = function () { //通过onhashchange方法监听,url中的 hash 是否发生变化
var data = window.location.hash;
};
- postMessage()
// 在窗口B中监听 message 事件
Awindow.addEventListener('message', function (event) { //这里强调的是A窗口里的window对象
console.log(event.origin); //获取 :url。这里指:http://A.com
console.log(event.source); //获取:A window对象
console.log(event.data); //获取传过来的数据
}, false);
- jsonp模拟
//将对象转化为&连接后的字符串
function dataToUrl(data){
let res = [];
for(let key in data){
res.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
}
return res.join('&');
}
function jsonp(url, data, success, errorCb, time){
//callback参数
let cb = 'cb' + Math.floor(Math.random() * 10);
//设置callback = cb
data.callback = cb;
let flag = url.indexOf('?') > -1 ? '&' : '?';
let script = document.createElement('script');
script.setAttribute('src') = url + flag + dataToUrl(data);
window[cb] = function(data){
clearTimeout(script.timer);
window[cb] = null;
document.head.removeChild(script);
success && success(data);
}
if(time){
script.timer = setTimeOut(function(){
clearTimeout(script.timer);
window[cb] = null;
document.head.removeChild(script);
errorCb && errorCb('超时访问');
},time);
}
document.head.appendChild(script);
}
//后端实现
onst http = require('http');
const { parse } = require('url');
// 假设这是在数据库中找到的数据~
const data = {
name: 'russ',
age: 20,
gender: 'male'
};
const server = http.createServer((req, res) => {
const url = parse(req.url, true); // 解析 url
// 只有路由为 `/user` 的 GET 请求会被响应
if (req.method === 'GET' && url.pathname === '/user') {
const { callback } = url.query; // 获取 callback 回调名称
if (callback) // 如果传递了 callback 参数,说明是 JSONP 请求
return res.end(`${callback}(${JSON.stringify(data)})`);
else // 没传递 callback,直接当做不跨域的 GET 请求返回数据
return res.end(JSON.stringify(data));
}
return res.end('Not Found'); // 不匹配的路由返回错误
});
server.listen(3000, () => {
console.log('Server listening at port 3000...');
});
- get 和 post的区别
- get参数放在url内,post参数放在请求报文body内
- get url由于浏览器的限制,有长度限制,2k,post数据没有长度限制
- get 数据url内可见,相对于post安全性较差,但http都是明文传输的,所以安全性都挺差的,https能保证安全性。
- get 请求能够被浏览器缓存,post请求无法被浏览器自动缓存
- get 参数保存在浏览器历史内,post参数不会保存在浏览器历史
- get 只能ascii字符,post没有限制,可以是二进制数据
- get 和 post本质都是tcp链接,由于http规定和浏览器/服务器限制,导致get和Post有一定的差异性。
- get只发送一次请求 返回200, post发送header 返回100 continue 后再次发送body返回200,即发送了2次请求,具体实现方式和浏览器相关。
- get 数据格式为application/x-www-form-urlencoded post可以为 aplication/x-www-form-urlencoded multipart/form-data
- post 和 put的区别
- post没有幂等性, put有幂等性,即一次或者多次请求,产生的效果是一样,结果是一致,比如put上传文件会对该文件进行更新覆盖,结果无差异性。post提交相同数据时则会改变结果,创建资源。如每次请求金额增加100,post则可以实现此效果。
- post主要是创建资源 put主要是更新资源
- 封装一个ajax
/**
* 1.考虑兼容性 IE
* 2.考虑请求方式
* 3.掌握原生ajax的实现过程
*/
function ajax(optionsArg) {
let options, xhr;
options = {
type: 'get',
url: '',
data: {},
contentType: '',
success: function() {},
error: function() {}
};
Object.assign(options, optionsArg);
//兼容IE
xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject(Microsoft.XMLHttp);
let { url, type, data, success, error } = options;
let dataArr = objectToUrl(data).join('&');
if (type.toLowerCase() === 'get') {
url = url.indexOf('?') === -1 ? url + '?' + dataArr : url + dataArr;
xhr.open(type, url);
xhr.send();
}
if (type.toLowerCase() === 'post') {
xhr.open(type, url); //ture为异步 false为同步
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(dataArr);
}
xhr.onreadystatechange = function() {
if (xhr.readystate === 4 && ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) {
//请求相应成功
let res = JSON.parse(xhr.responseText); //转化为对象
if (successs && typeof success == 'function') {
success.call(undefined, res);
}
} else {
if (error && typeof error === 'function') {
error.call(undefined, xhr);
}
}
};
}
//将对象转化成等号相连的数据
function objectToUrl(data) {
let res = [];
for (let key in data) {
res.push(key + '=' + data[key]);
}
return res;
}
- 传递数据的方式主要有 application/x-www-form-urlencoded;multipart/form-data;raw;binary
10. Keep-Alive
- 持久连接,通过content-length判断数据是否接受完成,http1.1默认开启connection: keep-alive transfer-encoding: chunked
11. 浏览器存储以及各自的区别?如何用cookie实现session机制?
- cookie localStorage sessionStorage session:服务端存储

- cookie-parser: res.cookie() 属性 max-age:(默认-1) expires(默认都是会话cookie,浏览器窗口关闭cookie失效) httpOnly(仅通过http传输获取cookie) signed(签名) domain(域名) path(访问目录,默认为/当前根目录) encoded
- cookie签名通过在cookieParser(sercret)给req设置.sercret对res.cookie(key,value,{signed: true})进行加密,后再req通过sercret解密cookie
- cookie可以跨二级名访问(例外),cookie无法跨域访问
12. es6的新特性说一下

Array.prototype.find((value,key,array)=>{},this)返回满足条件的第一个值否则为undefined
Array.prototype.findIndex((value,key,array)=> {},this)返回满足第一个条件值的下标
13.箭头函数
- 匿名函数 语法简洁
- this指向不同于常规的几种指向,是this中的例外,其this指向基于词法作用域指向外层函数的this
- this指向优先级最高
- 不能使用new进行构造,没有prototype属性
- 没有arguments参数
- 不能作为generator函数,不能使用yield语法
14. var、let\const的区别
- var 声明提升 没有块级作用域
- let 拥有块级作用域 不会声明提升 不能重复定义变量名
- const 必须赋初值 拥有跨级作用域 无法修改
15.async\await原理
/**
* async await原理 特点:
* 1. 同步的方式写异步代码,async非阻塞异步方式执行, 遇到await函数内部等待, async不阻塞
* 2. 将自动动执行器和generator封装在一个函数
* 3. async 返回一个promise对象 await等待一个promise对象或者立即值等数据,且成对存在
*/
//自动执行器
function run(gen) {
return new Promise(function(resolve, reject) {
//生成一个iterator迭代器
let it = gen();
//next自动执行
function next(fn) {
let next;
try {
next = fn(); //得到yield的结果
} catch (err) {
reject(err);
}
if (next.done) {
resolve(next.value); // 执行完毕将返回值作为resolve决议值
}
Promise.resolve(next.value).then(
function(val) {
next(function() {
it.next(val);
}); // 决议后继续执行
},
function(err) {
next(function() {
it.throw(err);
}); //抛出错误执行的时候进行捕获
}
);
}
next(function() {
return it.next(undefined);
}); //首次执行无参数传递
});
}
/**
* thunk函数:
* 1. 将回调的执行结果给回调函数,并返回回调函数的函数
* 2. 将回调函数的所有权给了thunk函数
* 3. 对函数的一次封装
*/
//例子
function thunk(fileName){
return function(callback){
return fs.readFile(fileName, callback); //执行结果给回调函数,返回这个回调函数的函数,回调所有权赋予thunk函数
}
}
//yield等待的则是thunk函数和promise对象 方便自动自动执行 thunk函数的自动执行器
function run(gen){
let it = gen();
function next(val){
let next = it.next(val);
if(next.done){
return next.value;
}
next.value(next); //回调函数获取值
}
next(undefined); //传递数据
}
//promise的自动执行 这些都是简化版 具体自动执行依据 async 函数 自动执行器+generator
function run(gen) {
let it = gen();
function next(val) {
let res = it.next(val);
(function handle(res) {
if (res.done) {
return res.value;
}
Promise.resolve(res.value).then(
next,
function(err) {
handle(it.throw(err)); //通过闭包的方式处理错误情况
}
);
})(res);
}
next(undefined);
}
16.几种继承的区别,如何优化?
- 原型链继承:原型对象为构造函数的实例 sub.prototype = new Super();缺点,引用类型的原型属性被所有实例对象所共享 => 生成的原型对象绑定了相应的属性,子类无法向父类传参
- 构造函数继承:Super.call(this,arguments) 方法和属性在构造函数中定义,每次创建实例需创建一次方法,解决了引用类型被所有实例对象所共享,可以传递参数,未利用原型继承。
- 组合继承:原型式继承和构造函数相结合,会两次调用父类函数,会使子类原型对象上产生多余的父类属性,
- 原型式继承:Sub.prototype = Object.create(Super.prototype, {constructor: {value:"Sub"}) => Sub.prototype.proto = Super.prototype避免多余属性,没有修改constructor属性 ;等价于Object.setPrototypeOf(a, b); (a.proto = b)
- 寄生组合式:即Object.create和构造函数相结合,可实现父类属性和原型方法得继承,且不产生多余的属性,此时需要修改子类的prototype.constructor属性。
- 静态方法继承:Ojbect.setPrototypeOf(Child, Parent),访问静态方法也从原型上去寻找。Object.setPrototypeOf原理和Object.create有相同之处,不过未改变constructor的值,因为为直接修改Sub.prototype.proto = Super.prototype;
- 代码演示5中继承方式
//父类
function Person(name, age, places) {
this.name = name;
this.age = age;
this.places = places;
}
//原型方法 this指向的是实例对象 this = Parent.prototype 隐式绑定
Person.prototype.getName = function() {
return this.name;
};
//静态方法
Person.say = function() {
return 'I am the super class!';
};
//子类
function Student(school, id) {
this.school = school;
this.id = id;
}
Student.prototype.getSchool = function() {
return this.school;
};
//1. 原型链式继承 所有实例共享原型上的引用属性, 无法传递参数
Student.prototype = new Person('wq', '45', ['cd', 'zy']);
let stu1 = new Student('sic', '12');
let stu2 = new Student('sic', '32');
stu1.places.push('none');
//所有实例共享引用类型的原型属性
console.log(stu1.places, stu2.places);
console.log(stu1.name, stu2.name);
//2. 构造函数式继承 可以传递参数,每次创建实例都需执行方法,未实现原型继承
function Jober(name, age) {
Person.call(this, name, age);
}
let job1 = new Jober('jober', '22');
console.log(job1.name, job1.age);
//3. 组合式继承 子类原型上有多余的父类的属性,父类函数执行了2次
function Farmer(name, age) {
Person.call(this, name, age);
}
//实现原型方法继承
Farmer.prototype = new Person();
let farmer1 = new Farmer('farmer', '89');
console.log(farmer1.name, farmer1.age, farmer1.getName());
//4. 寄生组合式继承 Object.create + 构造式继承/ sub.prototype.__proto__ = Super.prototype / Object.setPrototypeOf(sub.prototype, Super) / Object.setPrototypeOf(Sub, Super);
//Object.create原生实现
function objCreate(obj) {
function F() {}
F.prototype = obj;
return new F(); //返回一个对象o, o.__proto__ = obj;
}
function Boss(name, age) {
Person.call(this, name, age);
}
// Boss.prototype = Object.create(Person.prototype, {'constructor' : {'value': 'Sub'}});
// Boss.prototype.__proto__ = Person.prototype;
Object.setPrototypeOf(Boss.prototype, Person.prototype);
Object.setPrototypeOf(Boss, Person); //静态方法实现原型继承
let boss1 = new Boss('boss', '56');
console.log(boss1.name, boss1.age, boss1.getName());
console.log(Boss.say());
16-1 es5和 es6继承的区别? this.生成顺序不同
- es5通过原型链式继承或者构造函数式继承实现的, 先生成子类的实例,再将父类得方法绑定在子类的this上
- es6则是通过constructor里的super() 先生成父类的实例,将子类的方法添加到this上,返回这个this,故使用this必须在constructor后使用。
16-2 function 和 class的区别?
- function 和声明提前, class不能声明提前,class故存在暂时性死区
- function能够重复定义,覆盖之前定义的函数,class重复定义则会出错
- class的所有方法(实例方法,静态方法)不能枚举(enumerable:false),故不能使用Object.keys, 或for in 遍历到相应属性,可通过Object.getOwnPropertyNames()获取对象自身上的所有属性不包括Symbol属性。
- class所有方法没有prototype属性,不能new
- class内部使用严格模式,无法重写类名,必须通过new才能调用。
- TDZ的理解:暂时性死区,未声明到声明初始化等待的时间则为TDZ,在块作用域使用未声明以及之后声明的变量会出现暂时性死区。
var Foo = function() {
this.foo = 21;
};
{
const foo = new Foo(); // ReferenceError: Foo is not defined
class Foo {
constructor() {
this.foo = 37;
}
}
}
function a(x = y, y) {
console.log(x);
}
function b(x, y = x){
console.log(y);
}
a(undefined, 1) //TDZ es6的默认赋值是根据let实现的
b(1, undefined) //正确
17. http缓存有哪些?优先级是?(协商缓存)
- 都是从缓存获取资源,强缓存不发出请求根据请求资源header判断是否命中强缓存;协商缓存发出请求包含(if-modified-since/if-none-match)判断是否命中协商缓存,返回200/304
- 强缓存:在有效时间内,不会再请求服务器而直接从浏览器本地缓存获取资源,对应的响应资源字段:expires(过期日期) cache-control:max-age (多少s后过期) no-cache no-store public private --- cache-control属性
- 协商缓存:无论是否变化或者是否过期都需从新发出请求,更据Etag值或者last-modified值判断返回304还是200,此请求不会返回资源数据,具体请求头为if-modified-since/if-none-match,向服务器请求last-modified/Etag值是否修改,资源没有修改从浏览器缓存获得资源,否则更新资源。
- 同时存在缓存头优先级情况
- 强缓存 : cach-control优先级 > expires优先级
- 对比缓存:Etag > last-modified
- 强缓存 (200 from-cache) > 对比缓存(last-modified)
- 加密资源使用强缓存,非加密资源使用对比缓存
- 1、设置cache-control: public, max-age=0;记住,这里的public是关键。因为默认值是private,表示其他代理都不要缓存,只有服务器缓存,而max-age又为0,所以每次都会发起200的请求。设置public的意思就是允许其他各级代理缓存资源,因此如果资源没改变会返回304。 2、直接设置max-age=1000。即是一秒之后内容过期,目的是触发浏览器缓存。也能达到想要304的效果。 3、强缓存包含协商缓存,协商缓存必须在有强缓存时才有效
18. html meta标签
- name: keywords, description, viewport,robot,athor,copyright,
- http-equiv:相当于http请求头,content-type,cache-control,expires,set-cookie
- content: name和http-equiv配合使用content描述相应内容
- 元数据具体对于网页信息的描述,便于搜索引擎抓取数据
19. node EventEmitter事件驱动机制
- 所有实现事件驱动的对象都是EventEmitter的实例,本质是观察者模式,即发布订阅模式,注册事件/触发相应事件
//EventEmitter实例
const fs = require('fs');
const path = require('path');
const events = require('events');
//继承events.EventEmitter实例
class MyEeventEmitter extends events.EventEmitter {}
let eventEmitterIns = new MyEeventEmitter();
//注册事件
eventEmitterIns.addListener('read', function(data) {
console.log(data);
});
eventEmitterIns.on('read', function(data) {
console.log('这是观察者模式');
});
//触发事件 error first的方式
fs.readFile(path.resolve(__dirname,'date.html'), {encoding: 'utf-8'}, function(err, data){
eventEmitterIns.emit('read', data); //触发事件
});
- EventEmitter模拟实现
/**
* 相关api:
* on(type, callback) addListener(type, callback) emit(type, data) removeListener(type, callback) removeAllListeners() setMaxListenter(number)
* once(type,callback) 注册的回调值执行一次,执行完后则删除
* listenerCount() //注册监听数量
*/
//自己实现一个EventEmitter()
class EventEmitter {
constructor() {
//对象属性
this.events = {};
this.maxListeners = 10;
}
//原型方法
//注册回调 同一事件可以有多个回调
on(type, callback) {
if (this.events[type]) {
if (this.events[type] > this.maxListeners) {
throw new TypeError('the listeners is full');
}
this.events[type].push(callback);
} else {
this.events[type] = [callback];
}
}
//触发事件
emit(type, data) {
this.events[type] && this.events[type].forEach(cb => cb.call(this, data));
// for(let cb of this.events[type]){
// cb.call(this, data);
// }
}
//只调用一次的回调函数 调用后则删除注册的事件
once(type, callback) {
let wrapper = data => {
callback.call(this, data);
this.removeListener(type, wrapper); //执行后则删除该注册事件
};
this.on(type, wrapper);
}
//删除事件
removeListener(type, callback) {
this.events[type] && (this.events[type] = this.events[type].filter(cb => callback !== cb));
}
//删除所有注册事件
removeAllListeners(type) {
if (type) {
delete this.events[type];
} else {
this.events = {};
}
}
setMaxListeners(number) {
this.maxListeners = number;
}
getMaxListeners() {
return this.maxListeners;
}
listeners(type) {
return this.events[type];
}
}
20. bind在call/apply时无法修改
function thisTest(){
console.log(this.dog);
}
let obj1 = {
dog: 'wei'
}
let obj2 = {
dog: 'hello'
}
thisTest.bind(obj1).call(obj2);
//bind绑定的this无法修改
21. delete运算符
- 返回ture/false 删除可以configurable:true的属性 var,let,const都不能删除
- 删除自身的属性,没有的属性删除仍会返回false
前端每日一题
1. web安全与防御措施?
- xss 跨站脚本攻击 被动攻击 需要引诱
- 执行供击者代码,如html,js,可以通过注入代码伪造表单,获取用户的私密信息,通过url方式获取cookie信息
- 不要从url获取数据,对url用户输入显示到界面的数据进行转义处理,将其转为普通字符,不为特殊字符,执行一些不必要的行为
- 字符集xss脚本设置meta charset = 'utf-8'字符集,避免其他字符集攻击
- 客户端不要信任任何客户端的数据,进行转义和过滤处理 0 1
- Http请求头 x-xss-contection对一些基本的xss进行过滤
- csrf 跨站伪装请求 被动攻击
- 用户登录网站后,不幸点击进入一个危险网站,获取登录状态信息,如cookie,向原网站发起请求,获得相应的登录权限,进行用户不知情的危险操作
- 多用Post请求,用户足以发现一些伪造表单,使用验证码,对操作进行验证,确保本人发出的操作,使用token(唯一随机) 保存到用户session 或 cookie,给表单添加token参数,token不同则验证不通过。
- SQL注入
- 通过执行数据库命令拿到数据,利用对数据库权限执行一些操作
- sql少使用数据sql拼接,使用参数化传值(?); 给与用户程序功能内的最小权限,sql注入时对数据库的影响最小;对数据输入进行过滤,转义,避免对sql语句造成影响;不要暴露过多关于数据库字段信息
- 命令行注入
- 在调用shell命令的地方添加其他shell命令,即os提供的命令,对文件系统服务器数据造成致命攻击。
- 对用户输入进行转义,过滤,过滤shell操作;避免拼接的shell命令
- ddos攻击 分布式拒绝服务
- 不断发起http请求,导致资源过载,服务器不可服务
-
- 对url进行过滤 2. 对用户进行封禁 3.判断是机器(爬虫)还是人为造成的 4.向相关机构反映
- 流量劫持
- dns劫持 访问的域名返回的是其他域名的内容
- http劫持 篡改http报文首部或者主题信息,导致页面显示信息错误。 防御利用https
2. https?
- http + ssl + tcl的协议,集加密 + 认证 + 数据的完整性的一种安全的通信协议。
- 加密:保证通信的安全,结合对称与非对称加密的一种混合加密机制,http报文使用对称加密,对称加密的秘钥使用非对称加密,保证秘钥的正确性。
- 认证:需要向相关机构购买可信任的证书,确保服务器是合法的,可信任的,避免伪造的服务器或者客户端。
- 数据完整性:保证http报文不会被篡改,使用加密技术。
- 端口为443 http端口为80
3. 实现Promise
//实现整个Promise的思路 共分为4部走 实现Promise 实现原型方法 实现回调 实现静态方法
//Promise构造阶段
function Promise(fn) {
if (!(this instanceof Promise)) throw new TypeError('promise must be used new promise'); //必须new才能使用
if (typeof fn != 'function') throw new TypeError('the arguments must be the function'); //参数必须是函数
this.state = 0; // 0: pending 1: fullfilled 2:rejected 3:fullfilled 但是决议值时promise故需展开获得决议值
this.value = undefined; //决议值
this.handled = false; //是否处理回调
this.deffereds = []; //注册的回调函数
doResolve(this, fn);
}
function deResolve(self, fn) {
//fn立即执行
let done = false;
try {
fn(
function(value) {
//外部resolve reject执行该函数 由此可看出只决议一次
if (done) return;
done = true;
resolve(self, value);
},
function(value) {
if (done) return;
done = true;
reject(self, value);
}
);
} catch (err) {
if (done) return; //只决议一次
done = true;
reject(self, err);
}
}
function reject(self, value) {
self.state = 2;
self.value = value;
finale(self);
}
function resolve(self, value) {
if (value === self) throw new TypeError('the promise value is not itself'); //决议值不能是自己
//thenable检查 函数或对象有then方法 则认为是promise
if (value && (typeof value == 'object' || typeof value === 'function')) {
if (value instanceof promise) {
//决议值时promise 则需展开
self.state = 3;
self.value = value;
finale(self);
return;
} else {
then = value.then;
if (typeof then === 'function') {
doResolve(self, then.bind(value)); //thenable promise则展开
}
}
}
self.state = 1;
self.value = value;
finale(self);
}
//异步回调
Promise.immediateFn =
(typeof immediate === 'function' &&
function(fn) {
setImmediate(fn);
}) ||
function(fn) {
setTimeout(fn, 0);
};
//拿到状态执行回调
function finale(self) {
if (self.state === 2 && self.deferres.length === 0) {
//拒绝状态且无注册回调则不处理
Promise.immediateFn(function() {
if (!self.handled) {
console.log('the unhandle promise rejection');
}
});
}
for (let i = 0; i < self.deferreds.length; i++) {
//执行回调
handle(self, self.deferreds[i]);
}
self.deferreds = []; //处理完毕回调
}
//Promise 的原型方法
//返回一个新的promise 对象
function noop() {}
function Handle(self, onFullfilled, onRejected) {
//注册的回调构造函数
this.onFullfilled = typeof onFullfilled == 'function' ? onFullfilled : null;
this.onRejected = typeof onRejected == 'function' ? onRejected : null;
this.promise = self;
}
Promise.prototype.then = function(onFullfilled, onRejected) {
let pro = new Promise(noop); //未决议
handle(this, new Handle(pro, onFullfilled, onRejected)); //注册回调
return pro;
};
Promise.prototype['catch'] = function(onRejected) {
return this.then(null, onRejected);
};
//不管决议值成功失败都将执行 且返回的promise的决议值为原Promise的决议值
Promise.prototype['finally'] = function(callback) {
//最后执行
return this.then(
function(value) {
return Promise.resolve(callback()).then(function() {
return value;
});
},
function(value) {
return Promise.resolve(callback()).then(function() {
return value;
});
}
);
};
//处理回调
function handle(self, deffered) {
while (self.state === 3) {
self = self.value; //展开其promise值 获得其决议值
}
if (self.state === 0) {
self.deffereds.push(deffered);
return;
}
self.handled = true;
//异步执行回调
Promise.immediateFn(function() {
try {
let callback = self.state === 1 ? deffered.onFullfilled : deffered.onRejected;
if (callback === null) {
//默认无注册回调 将新promise的决议值变为原promise的决议值
self.state === 1 ? resolve(deffered.promise, self.value) : reject(defferd.promise, self.value);
}
let res = callback(self.value); //执行回调函数
self.state === 1 ? resolve(deffered.promise, res) : reject(deffered.promise, res);
} catch (err) {
reject(deffered.promise, err);
}
});
}
//静态方法
function isArrayLike(arr) {
if (arr && typeof arr === 'object' && arr.length > 0 && isFinite(arr.length)) {
return true;
}
return false;
}
//返回promise 参数为类数组 竞赛决议 数组为空则永不决议
Promise.race = function(arr) {
return new Promise(function(resolve, reject) {
if (!isArrayLike(arr)) {
throw new TypeError('the arguments must the array like');
}
let newArr = Array.from(arr);
newArr.forEach(function(val) {
Promise.resolve(val).then(resolve, reject);
});
});
};
Promise.all = function(arr) {
return new Promise(function(resolve, reject) {
if (!isArrayLike(arr)) {
throw new TypeError('the arguments must the array like');
}
let newArr = Array.from(arr); //可将类数组 或迭代对象 变为数组
if (newArr.length === 0) resolve([]); //数组为空立即决议
let count = newArr.length,
result = [];
function res(index, value) {
//判断是否为thenable
if (value && (typeof value === 'object' || typeof value === 'function')) {
let thenFunc = value.then;
if (typeof thenFunc == 'function') {
Promise.resolve(value).then(function(val) {
res(index, val);
}, reject);
}
}
result[index] = value;
count--;
if (!count) {
resolve(result);
}
}
for (let i = 0; i < newArr.length; i++) {
result(i, newArr[i]); //一一对应获取相应promise的结果
}
});
};
Promise.reject = function(val) {
return new Promise(function(resolve, rejecte) {
reject(val);
});
};
//使其成为真正的promise
Promise.resolve = function(val) {
if (val instanceof Promise) {
return val;
}
if (val && (typeof val === 'function' || typeof val === 'object')) {
let thenFunc = val.then;
if (typeof theFunc === 'function') {
return new Promise(thenFunc.bind(val));
}
}
return new Promise(function(resolve, reject) {
resolve(val);
});
};
4. MVC -> MVP -> MVVM
- MVC 优点:1. Ui和业务逻辑都分离了,ui操作交给controller进行数据处理,调用model相应的接口,处理数据逻辑。通过观察者(pub/sub)订阅模式model数据反应到view上。2.能过做到多视图同时更新。多个view订阅同一个model; 缺点: 1. controller测试困难,需要有ui的支持,同时model数据的变化也需要有进行订阅,反应到界面上。2. 不易组件化 view 依赖于model
- MVP(passive view) presenter(主持人)优点:1.测试容易,prenster和view间通过接口进行数据交换,dom操作。model 和 presenter间通过观察者模式进行数据交换。2. 利于组件化 缺点:1.需要实现许多手动操作,实现界面同步,管理比较麻烦
- MVVM(ViewModel) viewModel 即视图模型,对数据的抽象,管理了许多状态,实现了双向数据绑定。即view通过viewModel同步到Model,model的数据也同步到view,viewModel通过binder实现view和相应model的绑定,而用户不需去手动去处理同步问题。优点:1. 提高代码的维护性,提供双向绑定,避免用户过多的同步问题。2. 便于测试, 只要model正确,界面也会正确的展示。 缺点:1. 不适用于大型界面,需要管理较多的数据和状态 2.界面需要进行相应的绑定,不易调试debug。(参考小程序的双向绑定)
4. 完全二叉树的叶子节点数
- n0 = n2 + 1 (推导度数) T = n - 1 = n2 * 2 + n1; n = n0 + n1 + n2
- 完全二叉树 n1 = 0 或 1 故 n0 = (n + 1) / 2 或 n0 = n / 2;
5. 移动浏览器触发的事件过程是?
- touchstart touchmove touchend touchcancel
6. 数据结构中各种排序比较?
- 不基于比较实现排序的是? 基数排序,桶排序 其余都是基于比较
- 稳定的排序算法 冒泡 归并 直接插入
- 快排的时间复杂度 最好O(logn) 最坏 O(n)
- 快排:最好:每次哨兵都能均分数组 则类似二分法 故递归调用栈为O(logn) 最坏:每次哨兵为身剩下数组的最大值或者最小值,即将数组分为1个和剩下数组,则每个数都要递归调用,空间复杂度为O(n)
- 二叉树递归遍历时间复杂度和空间复杂度都为O(n)

7. Symbol 及基本类型
- 不可枚举,getOwnPropertyNames无法得到symbol的属性值,仅通过getOwnPropertySymbols获取Symbol值,独一无二的值,参数为字符串,即使相同的字符串也返回false,不能new,Symbol.for(str) 可得到两个为true的symbol值

8. axios的原理?
9. css预处理的理解?
10. 关于javascript深浅拷贝?
//浅拷贝:只拷贝一层对象,常用方法有:手动实现拷贝一层Object.assign(target, ...source) slice() concat() ...展开运算符
function shollowClone(obj) {
if (!isCanCloneType(obj)) {
return obj;
}
let res = new obj.constructor(); //生成相应的拷贝对象
for (let key in obj) {
//访问原型链上的属性
if (obj.hasOwnProperty(key)) {
res[key] = obj[key];
}
}
return res;
}
// let a = 3;
// let b = { data: { node: 'html' }, func: function() {} };
// let c = [7, 7, [77]];
// //暂未考虑特殊情况 基本类型的对象 function RegExp map set
// console.log(shollowClone(a));
// console.log(shollowClone(b));
// let d = shollowClone(c);
// console.log(d);
// d[2][0] = 66; //只拷贝一层
// console.log(c);
//深拷贝:基本数据类型不用拷贝,只能拷贝对象(数组,函数, set, map都算对象), 且基本类型的对象拷贝 和 function,RegExp需要特殊考虑
//递归进行深拷贝
/**
* 1. 考虑循环引用的问题,或者不同属性引用同一属性的问题,已经拷贝了的对象直接返回而不用在生成对象
* 2. 考虑特殊情况: 基本类型的对象,Boolean, Number, String, RegExp, Date, set, map, function/箭头函数
* 3. 不用递归做,将递归改变循环栈的形式 => 基本都是dfs深度优先遍历
*/
let isCanCloneType = obj => (typeof obj == 'object' || typeof obj == 'function') && obj !== null; //代码精简
let calcType = obj => Object.prototype.toString.call(obj); // 返回对象的类型
let mapTypes = '[object Map]';
let setTypes = '[object Set]';
let numberTypes = '[object Number]';
let stringTyps = '[object String]';
let booleanTypes = '[object Boolean]';
let regexpTypes = '[object RegExp]';
let funcTypes = '[object Function]';
let symbolTypes = '[object Symbol]';
let dateTypes = '[object Date]';
//可以处理的对象
let canTraverse = {
'[object Object]': true,
'[object Array]': true,
'[object Map]': true,
'[object Set]': true,
'[object Arguments]': true
};
//深拷贝特殊类型的对象
let cloneSepecialObj = (obj, objTypes) => {
let cons = obj.constructor;
switch (objTypes) {
case numberTypes:
return new Object(Number.prototypeof.valueOf.call(obj));
case stringTyps:
return new Object(String.prototypeof.valueOf.call(obj));
case booleanTypes:
return new Object(Boolean.prototypeof.valueOf.call(obj));
case symbolTypes:
return new Object(Symbol.prototypeof.valueOf.call(obj));
case dateTypes:
return new cons(obj);
case regexpTypes:
handleRegExp(obj);
case funcTypes:
handleFunc(obj);
default:
return new cons(obj);
}
};
function handleRegExp(obj) {
const { source, flags } = obj; // flag: g i m(多行匹配) source:正则表达字符串
return new obj.constructor(source, flags);
}
function handleFunc(obj) {
if (!obj.prototype) {
//如果是箭头函数直接返回,因为箭头函数每次都是新的值,而不会产生引用
return obj;
}
let param, body, func;
let paramReg = /(?<=\().*(?=\))\s*{/m; // ?<= 后行断言 ?= 先行断言
let bodyReg = /(?<=\{)(.|\n)*(?=})/m; // ?<= 后行断言 ?= 先行断言
func = obj.toString();
param = paramReg.exec(func); //匹配成功第一项返回成功的值 匹配失败返回null
body = bodyReg.exec(func);
if (!body) return null;
if (param) {
return new Function(...param[0], body[0]);
} else {
return new Function(body[0]);
}
}
function deepClone(obj, map = new WeakMap()) {
if (!isCanCloneType(obj)) {
//判断是否可以拷贝
return obj;
}
if (map.get(obj)) {
return map.get(obj); //返回相对应的拷贝后对象
}
let res, objTypes;
objTypes = calcType(obj);
if (!canTraverse[objTypes]) {
return cloneSepecialObj(obj, objTypes);
}
res = new obj.constructor(); //生成相应的对象
map.set(obj, res); //保存对象 和其拷贝对象
if (objTypes === mapTypes) {
//如果是map则遍历进行拷贝
obj.forEach((val, key) => {
res.set(deepClone(key, map), deepClone(val, map));
});
}
if (objTypes === setTypes) {
obj.forEach(val => {
res.add(deepClone(val, map));
});
}
//拷贝对象
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
res[key] = deepClone(obj[key], map);
}
}
return res;
}
let a = 3;
console.log(deepClone(a));
let b = { data: { node: 'html' } };
//暂未考虑特殊情况 基本类型的对象 function RegExp map set
let bb = deepClone(b);
bb.data.node = 'div';
console.log(b);
console.log(bb);
let c = [7, 7, [77]];
let d = deepClone(c);
d[2][0] = 66; //只拷贝一层
console.log(c);
console.log(d);
//map 强引用键值的对象 程序结束才垃圾回收 weakMap弱引用键值的对象,会则垃圾回收机制的时候进行回收
let map = new Map();
map.set(b, 'b');
map.set(c, 'c');
map.set('key', b);
let newMap = deepClone(map);
b.data.node = 'map';
console.log(map);
console.log(newMap);
//循环引用
var cir = { val: 3 };
cir.data = cir;
var newCir = deepClone(cir);
cir.data = { data: 4 };
console.log(cir);
console.log(newCir);
11. javascript高阶函数?
- 一个函数可以接受另一个函数作为参数或者返回值为一个函数,这种函数称为高阶函数。
12. 什么是函数的柯里化?
/**
* 柯里化函数:部分求值,传递给函数部分参数,并返回一个函数来接收剩余的参数,分步求值,提高函数的复用性
*/
//1.通过获取所有参数最后执行函数实现
function curring(fn){
let args = [].slice.call(arguments, 1);
let len = fn.length - args.length;
return function temp(){
len = len - arguments.length;
args.push(...arguments);
if(len > 0){
return temp;
}else{
return fn.apply(this, args);
}
}
}
function add(a, b){
return a + b;
}
//2.通过对原有函数进行包装,最后传递参数进行执行
function subCurring(fn, ...args){ //对原有函数进行包装
return function(...newArgs){
return fn.apply(this,args.concat(newArgs));
}
}
function curring(fn, length){
let len = length || fn.length;
let args = [];
return function (){
len = len - arguments.length;
args.push(...arguments);
if(len > 0){
return curring(subCurring.apply(this, [fn].concat(args)), len);
}else{
return fn.apply(this, args);
}
}
}
let foo = curring(add);
console.log(foo(1)(2));
13. React中高阶组件是什么?
- 高阶组件是参数为组件,返回值为组件的函数。实现了复用组件的逻辑,对传入组件进行包装,更改组件属性,抽象state,劫持渲染子组件。常用的两种高阶组件方式有:属性代理,反向继承。
- 属性代理的作用:更改props:增删改查;获取refs即组件的实例,无状态组件不可获取;抽象state将包装组件的state封装的高阶组件内,通过props进行父子组件传值;将包装组件和其他组件进行包装
- 反向继承:返回的组件继承传参的组件,劫持渲染:条件渲染,重新渲染树; 操作state
- 属性代理
//更改props 增加 删除 修改 读取 要传递给包装组件的props
//获取refs实例
//抽象state
function Hoc(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef(); //获取实例
this.changeFunc = this.changeFunc.bind(this);
this.blur = this.blur.bind(this);
this.state = {
value: "抽象state, 复用组件逻辑",
onChange: this.changeFunc,
onBlur: this.blur
};
}
blur() {
alert(this.input.current.props.value);
}
changeFunc(event) {
event.stopPropagation(); //阻止冒泡
this.setState({
//抽象state
value: event.target.value
});
}
render() {
let style = {
backgroundColor: "red",
fontSize: 20
};
return (
<WrappedComponent
ref={this.input}
{...this.props}
{...this.state}
style={style}
/>
);
}
};
}
class WrappedComponent extends React.Component {
render() {
return <input type="input" {...this.props} />;
}
}
function App() {
let HocCom = Hoc(WrappedComponent);
return <HocCom value="hello world!" />;
}
- 反向继承
import React from "react";
import { Component } from "react";
//反向继承
//劫持渲染
class WrappedComponent extends Component {
constructor(props) {
super(props);
this.state = {
time: 2019
};
}
render() {
return (
<h3 login={false}>
hello world!
<p>paragraf</p>
{this.state.time}
</h3>
);
}
}
function Hoc(WrappedComponent) {
return class Test extends WrappedComponent {
render() {
//根据 props条件渲染
if (this.props.login) {
return super.render();
} else {
return <p>没有登录</p>;
}
}
};
}
//重新渲染树 更改props更改子树 重新渲染 重未更改包装组件的props props是只读的不可更改
function HocII(WrappedComponentI) {
return class Test extends WrappedComponentI {
render() {
let elementTree = super.render();
let newProps = {
style: { backgroundColor: "red" },
value: "hello 反向继承"
};
return (
<div>
{this.state.time}
{React.cloneElement( elementTree, newProps, // <input value="hahahh" />
elementTree.props.children )}
</div>
);
}
};
}
export default HocII(WrappedComponent);
14. javascript垃圾回收?
- 定义: Js具有自动垃圾回收机制,即自动回收不使用的变量,全局变量,生命周期为整个页面,不会垃圾回收;局部变量除闭包外一般执行完毕后也会被垃圾回收,函数内部的全局变量不被回收
- 垃圾回收方案:
- 标记清除:变量执行的时候标记为进入执行 执行完后标记为离开环境,回收离开环境的变量
- 引用计数:对引用的变量使用次数进行计数,回收使用次数为0的变量
- 内存泄漏 即无法回收未使用的变量
- 函数内产生全局变量后且未使用的变量
function fn() {
name = "你我贷"
}
console.log(name) //name无法回收
- 定时器没有适当销毁,保留整个定时器环境
- 对dom的引用导致已卸载的dom仍然无法回收,有时父元素也无法回收
- 闭包也会产生内存泄漏
15. 数组扁平化?
//[1, [2, 3, [4, 5]]] ------> [1, 2, 3, 4, 5] 递归法扁平化数组,求数组深度
function flatten(arr, res){
let count = 1;
for(let value of arr){
if(Array.isArray(value)){
count = flatten(value, res) + 1;
}else{
res.push(value);
}
}
return count;
}
let res = [];
let count = flatten([1, [2, 3, [4, 5]],10], res);
console.log(res, count);
//法二 优先选择
function flattenI(arr){
let res = [];
for(let value of arr){
if(Array.isArray(value)){
res = res.concat(flattenI(value));
}else{
res.push(value);
}
}
return res;
}
console.log(flattenI([1, [2, 3, [4, 5]],10]));
//法三 reduce
function flattenII(arr){
return arr.reduce(function(pre, current){
return pre.concat(Array.isArray(current) ? flattenII(current) : current);
},[]);
}
console.log(flattenII([1, [2, 3, [4, 5]],10]));
//法四 toString()/join()
function flattenIII(arr){
return arr.toString().split(',').map(value => Number(value));
}
console.log(flattenIII([1, [2, 3, [4, 5]],10]));
function flattenIIII(arr){
return arr.join(',').split(',').map(value => parseInt(value));
}
console.log(flattenIIII([1, [2, 3, [4, 5]],10]));
//...展开运算符,展开二维数组
function flattenIIIII(arr){
while(arr.find(value => Array.isArray(value))){
arr = [].concat(...arr);
}
return arr;
}
console.log(flattenIIIII([1, [2, 3, [4, 5]],10]));
//展开方法
function faltWays(arr){
return arr.flat(Infinity); // 扁平深度为无穷 仅浏览器中存在该方法
}
//正则表达式匹配
function faltReg(arr){
return JSON.stringify(arr).replace(/(\[|\])/g, '').split(',').map(val => Number(val));
}
//JSON.stringify和正则表达式匹配最后转化为整形
function faltRegI(arr){
let str = JSON.stringify(arr).replace(/(\[|\])/g, '');
str = '[' + str + ']';
return JSON.parse(str).map(val => parseInt(val));
}
16. 实现new方法
/**
* new: 生成一个对象,对象进行原型链委托,this指向这个对象,如果返回值是对象(不是null)或者函数,则返回自身,否则返回生成的对象
*/
function newFunc(fn, ...args) {
if (typeof fn != 'function') {
throw new TypeError(`the first param is not a function`);
}
let obj = Object.create(fn.prototype); //完成2步
let res = fn.apply(obj, args);
let isObject = typeof res == 'object' && res !== null;
let isFunction = typeof res == 'function';
return isObject || isFunction ? res : obj;
}
- 递归做深拷贝
//递归做深拷贝
function deepClone(obj, map = new WeakMap()){
if(!isCanCloneType(obj)){
return obj;
}
let objTypes, root, stack = [];
objTypes = calcType(obj);
if(!canTraverse[objTypes]){
return cloneSpecialObj(obj, objTypes);
}
root = new obj.constructor();
stack[0] = { //初始化首栈 key data 代表当前父元素需要拷贝的下一个对象
parent: root,
key: undefined,
data: obj
}
while(stack.length){
let node = stack.pop();
let {parent, key, data} = node;
let res;
if(key !== undefined){
res = parent[key] = new data.constructor(); //如果存在下一层级,则进行关联,指向同一地址,进行拷贝,没有则拷贝在父元素上
}else{
res = parent;
}
if(map.get(data)){ //循环引用
parent[key] = map.get(data);
continue;
}
map.set(data, res);
for(let key in data){
if(obj.hasOwnProperty(key)){
if(isCanCloneType(data[key])){
stack.push({
parent: res,
key: key,
data: data[key]
})
} else{
res[key] = data[key];
}
}
}
}
return root;
}
17. js严格模式和非严格模式区别
- 代码检查上会更严格,非严格模式能通过的,严格模式无法通过,有时会报错
- 严格模式更规范,严谨,便于维护和排查错误。
- 严格模式体现:
- 不能使用未定义的变量,会报错,正常情况成为全局变量
- delete删除configurable:false会报错,正常情况返回false
- 严格模式下this指向undefined 正常情况this指向window
- 严格模式下arguments不会追踪函数参数变量,正常情况下会进行追踪
- 严格模式函数参数不能重名,会报错
- 严格模式不能使用with,(with欺骗词法作用域,运行的时候才执行,js引擎无法进性优化,影响程序性能,故不推荐使用with,with中变量和对象属性同名时指向对象属性)
({
x: 10,
foo: function () {
function bar() {
console.log(x); //undefined
console.log(y); // 30
console.log(this.x); //20
}
with (this) {
var x = 20; // 同名属性进行修改 声明提前
var y = 30; //局部变量值改变 声明提前
bar.call(this);
}
}
}).foo();
- 严格模式eval会产生单独的作用域,即局部作用域不会成为全局变量
18. this指向,按优先级进行例举
- this 词法作用域指向外层函数的this
- new 指向生成的对象
- 显示绑定 call/apply/bind 传参指向this
- 隐式绑定:obj.a //指向Obj
- 默认绑定:函数直接执行,非严格模式指向全局变量window/global /严格模式指向undefined
- dom事件流中 onclick 或者 addEventListener(type, cb, false) this指定调用的dom元素 attachEvent():IE中指向window
19. 实现instanceOf
function ins(obj, obj2) {
if (typeof obj2 !== 'function') {
throw new TypeError('the second param must be function');
}
let obj1 = obj;
while (obj1) {
if (obj1.__proto__ === obj2.prototype) {
return true;
}
obj1 = obj1.__proto__;
}
return false;
}