前端面经答案解析

471 阅读38分钟

20190821电话面试

1. react的setState后发生了什么

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

2. 抛开react的diff算法,怎么实现dom对比

  • diff算法:
  • 基于假设: 1. 相同的组件组件具有相同的dom结构,不同的组件具有不同的dom结构
  1. 对于同一层次的一组子节点,它们可以通过唯一的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 请求数据 -> 解析渲染 -> 下载渲染图片
  • 解决办法:
  1. 降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。

  2. 加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。

  3. 缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage。

  4. 渲染:JS/CSS优化,加载顺序,服务端渲染,pipeline。

  5. 前端后端渲染的区别 答案类似ajax的优缺点 前端渲染:优点:1. 操作js实现界面数据改变,局部刷新界面,无需每次完整加载页面

  6. 懒加载,只需加载可视区的数据

  7. 利用js实现一些酷炫效果,一些操作可以在前端做,减轻服务器的压力,整个网络的数据量小

  8. 实现了前后端分离,通过后端提供的接口获取数据,渲染界面 缺点: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都回导致页面卡顿
  1. 减少js加载对dom渲染的影响,将js代码加载逻辑放在html文件尾部,减少渲染引擎呈现工作的影响
  2. 避免重排,减少重绘(避免白屏,或者交互过程中的卡顿)
  3. 减少dom的层级,减少渲染引擎工作过程中的计算量
  4. 使用setTimeout() setInterval()来执行动画视觉变化,导致丢失帧,导致卡顿。
  • 阻塞加载:
  1. css不阻塞dom解析,阻塞dom渲染,cssom和dom解析完成后才开始渲染,link都是并行下载的,link后是script的话会阻塞js的执行,不会阻塞js的加载。因为js可能对html元素,css样式作出修改,故会阻塞js的执行。因此此时将script放在link标签前面。
  2. js阻塞dom解析,script标签下载和执行完才开始dom解析,可通过async defer改善加载和执行情况。defer并行下载,DOMContentLoaded即dom接下完成前执行,执行顺序和defer顺序一致,async异步也是并行下载不阻塞dom解析,但解析完成后则执行,执行过程阻塞dom解析,defer和async只对外联有效。
  3. link放头部,script放body尾部优化性能。
  • 浏览器是多线程的 浏览器事件触发线程 定时触发器线程 异步http请求线程 ui线程 js线程
  • js引擎:js是脚本语言,运行则执行输出结果,浏览器会作出优化先编译后执行达到性能提升。js能操作dom节点,故Js加载完后才会启动浏览器渲染过程。Event Loop js是单线程的,所有同步任务在主线程上执行,形成一个执行 栈,当有异步事件的时候,该异步事件会挂起,同时告诉浏览器有这么一件事,主线程继续执行,当浏览器感知异步事件可以执行时,会将异步事件回调函数放入事件队列,当主线程中的执行栈执行完毕,就会读取事件队列依次执行,是一个循环的过程,有事件就执行,否则就不断循环检查,这个过程中涉及到宏/微任务, 这里涉及到宏/微任务队列,每次执行栈完后会执行所有的微任务队列中的任务,在执行下一个宏任务。即宏任务排队执行,微任务插队执行。不断重复以上过程

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

  1. 宏任务:整体代码script,setTimeout(), setInterval(),setImmediate(),ajax,事件回调(onclick())
  2. 微任务:原生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请求
  • 后端:
  1. Access-Control-Allow-Origin: *|源,当允许跨域携带身份凭证时不能为*
  2. Access-Control-Allow-Credentials:允许跨域携带身份凭证true,前端同样需要设置xhr.withCredential=true 否则出错
  3. Acess-Control-Allow-Max-Age: 非简单请求(即POST,GET,HEAD)出外的预检请求的有效期,有效期内统一请求不需要在进行发送预检请求(Options)
  4. Acess-Control-Allow-Methods: 允许预检请求的HTtp请求方法
  5. Acess-Control-Allow-Headers: 允许预检请求的请求头
  6. 预检请求则是预先发出OPtions请求看能否进行跨域访问,避免不必要的数据交互。
  • 前端:
  1. Origin:预检请求或者实际请求的源
  2. Access-Control-Request-Method:实际请求方法
  3. Access-Control-Request-Headers:实际请求头 HTTP头部字段

简单请求: Content-Type:类型 application/x-www-form-data text/plain multipart/form-data

  • 跨域解决方案:
  1. jsonp 利用Script标签可以跨域,传递一个callback函数,后端将值放入这个函数并返回,将执行这个callback函数进而拿到数据。只能处理get请求
  2. WebSocket 双向长连接,解决跨域
  3. cors 跨域的ajax请求
  4. hash 通过监听onhashchange进行数据交互
 // B中的伪代码
    window.onhashchange = function () {  //通过onhashchange方法监听,url中的 hash 是否发生变化
        var data = window.location.hash;
    };
  1. 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的区别
  1. get参数放在url内,post参数放在请求报文body内
  2. get url由于浏览器的限制,有长度限制,2k,post数据没有长度限制
  3. get 数据url内可见,相对于post安全性较差,但http都是明文传输的,所以安全性都挺差的,https能保证安全性。
  4. get 请求能够被浏览器缓存,post请求无法被浏览器自动缓存
  5. get 参数保存在浏览器历史内,post参数不会保存在浏览器历史
  6. get 只能ascii字符,post没有限制,可以是二进制数据
  7. get 和 post本质都是tcp链接,由于http规定和浏览器/服务器限制,导致get和Post有一定的差异性。
  8. get只发送一次请求 返回200, post发送header 返回100 continue 后再次发送body返回200,即发送了2次请求,具体实现方式和浏览器相关。
  9. get 数据格式为application/x-www-form-urlencoded post可以为 aplication/x-www-form-urlencoded multipart/form-data
  • post 和 put的区别
  1. post没有幂等性, put有幂等性,即一次或者多次请求,产生的效果是一样,结果是一致,比如put上传文件会对该文件进行更新覆盖,结果无差异性。post提交相同数据时则会改变结果,创建资源。如每次请求金额增加100,post则可以实现此效果。
  2. 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 回话cookie cookie失效进行url重写,将sessionId放入url中 cookie产生过程:1.生成cookie对象(给用户创建一个唯一的cookie id 默认为会话即cookie,设置MaxAge则为持久性cookie 设置为0则是让浏览器删除该cookie 将cookie放入http相应报头,发送http相应报文) 2.设置存储cookie 3.发送cookie 4.获取cookie

  • 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.fill(value,start,end) 返回新数组
Array.prototype.find((value,key,array)=>{},this)返回满足条件的第一个值否则为undefined
Array.prototype.findIndex((value,key,array)=> {},this)返回满足第一个条件值的下标

13.箭头函数

  1. 匿名函数 语法简洁
  2. this指向不同于常规的几种指向,是this中的例外,其this指向基于词法作用域指向外层函数的this
  3. this指向优先级最高
  4. 不能使用new进行构造,没有prototype属性
  5. 没有arguments参数
  6. 不能作为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 跨站脚本攻击 被动攻击 需要引诱
  1. 执行供击者代码,如html,js,可以通过注入代码伪造表单,获取用户的私密信息,通过url方式获取cookie信息
  2. 不要从url获取数据,对url用户输入显示到界面的数据进行转义处理,将其转为普通字符,不为特殊字符,执行一些不必要的行为
  3. 字符集xss脚本设置meta charset = 'utf-8'字符集,避免其他字符集攻击
  4. 客户端不要信任任何客户端的数据,进行转义和过滤处理 0 1
  5. Http请求头 x-xss-contection对一些基本的xss进行过滤
  • csrf 跨站伪装请求 被动攻击
  1. 用户登录网站后,不幸点击进入一个危险网站,获取登录状态信息,如cookie,向原网站发起请求,获得相应的登录权限,进行用户不知情的危险操作
  2. 多用Post请求,用户足以发现一些伪造表单,使用验证码,对操作进行验证,确保本人发出的操作,使用token(唯一随机) 保存到用户session 或 cookie,给表单添加token参数,token不同则验证不通过。
  • SQL注入
  1. 通过执行数据库命令拿到数据,利用对数据库权限执行一些操作
  2. sql少使用数据sql拼接,使用参数化传值(?); 给与用户程序功能内的最小权限,sql注入时对数据库的影响最小;对数据输入进行过滤,转义,避免对sql语句造成影响;不要暴露过多关于数据库字段信息
  • 命令行注入
  1. 在调用shell命令的地方添加其他shell命令,即os提供的命令,对文件系统服务器数据造成致命攻击。
  2. 对用户输入进行转义,过滤,过滤shell操作;避免拼接的shell命令
  • ddos攻击 分布式拒绝服务
  1. 不断发起http请求,导致资源过载,服务器不可服务
    1. 对url进行过滤 2. 对用户进行封禁 3.判断是机器(爬虫)还是人为造成的 4.向相关机构反映
  • 流量劫持
  1. dns劫持 访问的域名返回的是其他域名的内容
  2. 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

  1. MVC 优点:1. Ui和业务逻辑都分离了,ui操作交给controller进行数据处理,调用model相应的接口,处理数据逻辑。通过观察者(pub/sub)订阅模式model数据反应到view上。2.能过做到多视图同时更新。多个view订阅同一个model; 缺点: 1. controller测试困难,需要有ui的支持,同时model数据的变化也需要有进行订阅,反应到界面上。2. 不易组件化 view 依赖于model
  2. MVP(passive view) presenter(主持人)优点:1.测试容易,prenster和view间通过接口进行数据交换,dom操作。model 和 presenter间通过观察者模式进行数据交换。2. 利于组件化 缺点:1.需要实现许多手动操作,实现界面同步,管理比较麻烦
  3. 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具有自动垃圾回收机制,即自动回收不使用的变量,全局变量,生命周期为整个页面,不会垃圾回收;局部变量除闭包外一般执行完毕后也会被垃圾回收,函数内部的全局变量不被回收
  • 垃圾回收方案:
  1. 标记清除:变量执行的时候标记为进入执行 执行完后标记为离开环境,回收离开环境的变量
  2. 引用计数:对引用的变量使用次数进行计数,回收使用次数为0的变量
  • 内存泄漏 即无法回收未使用的变量
  1. 函数内产生全局变量后且未使用的变量
 function fn() {
   		name = "你我贷"
             }
   	     console.log(name) //name无法回收
  1. 定时器没有适当销毁,保留整个定时器环境
  2. 对dom的引用导致已卸载的dom仍然无法回收,有时父元素也无法回收
  3. 闭包也会产生内存泄漏

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严格模式和非严格模式区别

  1. 代码检查上会更严格,非严格模式能通过的,严格模式无法通过,有时会报错
  2. 严格模式更规范,严谨,便于维护和排查错误。
  3. 严格模式体现:
  • 不能使用未定义的变量,会报错,正常情况成为全局变量
  • 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指向,按优先级进行例举

  1. this 词法作用域指向外层函数的this
  2. new 指向生成的对象
  3. 显示绑定 call/apply/bind 传参指向this
  4. 隐式绑定:obj.a //指向Obj
  5. 默认绑定:函数直接执行,非严格模式指向全局变量window/global /严格模式指向undefined
  6. 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;
}