interview准备3- 持续更新中

742 阅读8分钟

一  性能优化相关

1.1 有做过前端优化相关的工作吗?

做性能优化的目的

减少首屏加载时间,提高用户体验,可以从以下几个方面进行优化;

从哪里入手

1,让加载更快

1,只请求当前需要的资源
异步加载, 懒加载, polyfily等
2,缩减资源体积,进行资源合并;
打包压缩 webpack 4
gzip压缩 1.2M => 300K
图片格式的优化,压缩,根据屏幕分辨率展示不同分辨率的图片,可以采用webp格式,尽量控制cookie的大小,request header每次都会携带cookie;
3,可以做一些时序的优化
js代码方面,采用promise.all,渲染方面使用ssr,提高seo,资源下载采用prefetch, pretender,preload;
4,合理利用缓存,多使用内存或其他方法
cdn  cdn预热  cdn刷新
5,减少CPU计算量,减少网络加载耗时;

2,让渲染更快

1,css放在head,js放在body最下面;
2,尽早开始执行js,用DOMContentLoaded触发;
3,采用懒加载(图片懒加载,上滑加载更多);
4,对DOM查询进行缓存
5,将频繁执行的DOM操作,合并到一起插入DOM树中;
6,截流throttle和防抖debounce;

3,具体实现如下举例

缓存
1,静态资源添加hash后缀,根据文件内容计算hash;
2,文件内容不变,则hash不变,则url不变;
3,url和文件不变,则会自动触发http缓存机制,返回304;

module.exports = {
  mode: 'production',
  entry: path.join(__dirname, 'src', 'index'),
  output: {
    filename: 'bundle.[contenthash].js',
    path: path.join(__dirname, 'dist')
  },
}

缓存DOM查询

//不缓存DOM查询结果
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
  // 每次循环,都会计算length,频繁进行DOM操作
}

//缓存DOM查询结果
const pList = document.getElementsByTagName('p');
const length = pList.length;
for (let i = 0; i < length; i++) {
  // 缓存length,只进行一次DOM查询
}

多个DOM操作,一起插入到DOM结构

const listNode = document.getElementsById('list');
//创建一个文档片段,此时还没插入到DOM树中
const flag = document.createDocumentFlagment();
//执行插入
for (let x = 0; x < 10; x++) {
  const li = document.createElement("li");
  li.innerHTML = "List item " + x;
  flag.appendChild(li);
}
// 都完成之后,在插入到DOM中
listNode.appendChild(flag);

尽早开始执行JS

window.addEventListener('load', function() {
  // 页面的全部资源加载完才会执行,包括图片,视频等
})

window.addEventListener('DOMContentLoaded', function() {
  // DOM渲染完即可,此时图片,视频还可能没有加载完
})

1.1.1 首页加载慢的优化

  • 首页加载图片过多
  • 首页的请求量过多
  • 首页请求的静态资源(html, css, js, 图片)过大

首页加载图片过多的问题,可以通过以下几种方式解决

  1. 通过懒加载的方式处理非首屏的图片
  2. 对于小图标可以采用iconfont的方式解决(设置font-family的css属性)
  3. 对于小图片可以采用雪碧图的方式解决(把所有小图片拼接到一张大图上,并用background-position的css属性来修改图片坐标)

首页的请求过多怎么解决

先通过工具来确定是哪些类型的资源请求过多

  1. 先通过浏览器的NetWork可以确定首页加载的资源和请求量
    request:请求数量
    resource:前端资源总大小
    DOMContentLoaded:浏览器已经完全加载了HTML,其他静态资源(JS, CSS, 图片等)并没有下载完毕(能看,不能用)
    Load:浏览器已经加载了所有的静态资源(能用了)
  2. 通过converge来查看代码的使用状况
    只针对JS和CSS
    可以看出哪些代码虽然加载了但是没有执行
    没有执行的代码可以考虑一下是否可以懒加载

可以通过减少资源的请求量

  • 通过nginx服务器(可用来做CDN,用来处理静态资源)来做资源文件合并
  • 通过打包工具(webpack)来做资源文件的物理打包

合并静态资源的其他方式

  • 如果在项目中引了第三方库(antd element) 函数库(lodash)等,可以设定按需加载,一般都是用Babel插件来实现
  • 可以通过前端路由懒加载的方式(通过React Lazy进行动态路由的懒加载)可以减少首页的JS和CSS的大小;

React Lazy动态路由懒加载

使用方式

// 1, 引入reactlazy,并且使用import动态导入组件
import  { lazy } from 'react';
lazy(() => import('./Home'));

//2,引入suspense组件,并使用Suspense将根组件进行包裹,并使用fallback props传入loading组件
import { Suspense } from 'react';

// 注意:使用lazy加载的组件,必须是Suspense子组件,或者孙组件
<Suspense fallback={<div>Loading...</div>}>
  <OtherComponent />
</Suspense>

动态导入(dynamic import): 当代码运行import的时候,再导入组件

import ("./math").then(math => {
  cosnsole.log(math.add(16, 26));
});

// 类似于fetch,都是返回一个promise
fetch("./math").then(math => {
  cosnsole.log(math.add(16, 26));
});

首先React lazy是使用了dynamic import的标准,webpack只要遇到了dynamic import,就会把里面引入的代码单独打一个包。

由于dynamic import返回的是一个Promise,所以可以使用Promise的状态来渲染的流程控制

如果当前promise是pending状态,那么就渲染Loading组件,如果Promise是resolve状态,那么久渲染动态导入的组件;

首页请求过多总结如下:

  1. 通过nginx服务器来做资源文件的合并,或者通过webpack等打包工具进行物理打包
  2. 在代码层面,对于需要引入一些大型的第三方库的时候,可以通过特定的Babel插件进行按需加载
  3. 还可以使用React lazy或其他动态导入方案来进行前端路由层面的动态加载,从而可以减少首页的JS和CSS的大小;

首页请求的资源(js css 图片...)过大怎么解决

  1. 要分资源文件,css, js,图片等要分开来处理
  2. css和js可以通过webpack来进行混淆和压缩
    混淆:将js代码进行字符串加密(最大层度减少代码,比如将长变量名变成单个字母等等)
    压缩:去除注释行以及console等调试代码
  3. 图片也可以进行压缩
    可以通过自动化工具来压缩图片
    对图片进行转码 -> base64格式
    使用WebP格式
  4. 通过开启gzip进行全部资源压缩
    gzip:是一种压缩文件格式,可以对任何文件进行压缩
    可以通过nginx服务器的配置项进行开启

优化图片的做法

  1. 减少图片的请求-可懒加载图片
  2. 减小图片的大小

等比率无损压缩?

通过相似颜色量化的技术减少颜色数量,并且可以将24位的png图片格式转化成8位的彩色图片,同时也可以将不必要的元数据进行剥离;即通过减少颜色的数量以及不必要的数据来实现文件压缩;

如何对图片进行转码->base64格式

可以使用webpack 的url-loader进行图片策略配置,将小图片转换成base64格式,因为base64格式的图片的作用是减少资源的数量,但是base64格式的图片会增大原有图片的体积;

webp格式

优势:根据Google的测试,同等条件等比率无损压缩后的WebP比PNG文件少了26%的体积,并且图片越多,压缩后的体积优势越明显。

图片优化的总结

图片的优化,也是从两个方面来考虑:太多和太大

  • 可以通过懒加载减少图片的请求,或者通过雪碧图来合并图片,以及将小图转化为base64的格式,来解决多的问题
  • 图片大的问题,可以通过自动化压缩工具来压缩图片,或者使用 WebP格式的图片。

webpack打包优化

少-使用webpack进行物理打包

小-使用webpack进行混淆压缩,所以与webpack优化相关的配置都是在optimization这个配置项里管理

从webpack4 开始,会根据选择的mode来执行不同的优化,不过所有的优化还是可以手动配置和重写
development:不混淆,不压缩,不优化
production:混淆+压缩,自动内置优化
结论:只需要将mode改成production即可

使用webpack对代码进行混淆和压缩,并且可以使用React lazy进行拆包,结合路由进行按需加载。会不会造成文件变多;打包后的文件不能同时加载的,所以就不会造成同一时间资源请求过多的请求。注意打包策略:

通常把包分为两类:第三方包(node_modules里面的),自己实现的代码(src目录里面的:公共的。非公共的)

所以我们可以把第三方包打包成一个包,公共的代码打一个包,非公共的代码打一个包。
第三方包:改动频率小
公共代码包:改动频率中
非公共代码包:改动频率高

可以将打包策略结合网络缓存来做优化

对于不需要经常变动的资源(第三方包)。可以使用Cache-Control: max-age=31536000(缓存一年)并配合协商缓存ETag使用(一旦文件名变动才会下载新文件)
对于需要频繁变动的资源(代码包),可以使用Cache-Control: no-cache并配合ETag使用,表示该资源已被缓存,但是每次都会发送请求咨询资源是否更新。

webpack打包优化总结

  1. 可以通过设置mode=production來默認实现webpack对代码的混淆和压缩,从而最大程度的减少代码体积
  2. 使用webpack+dynamic import并结合路由的入口文件做拆包处理
  3. 并且可以设定一些打包策略,并配合网络缓存做最终的加载性能优化

1.1.2 实现CDN加速

什么叫CDN(内容分发网络):放静态资源的服务器(js, css,圖片,字體)

为什么cdn可以实现加速

CDN服务器就是在你家门口放一台服务器,把所有的静态资源都同步到你家门口的这台服务器上,以后只要你访问这个网站,都直接从这台服务器上下载静态资源。

  1. cdn服务器主要是用来放静态资源的服务,可以用来加速静态资源的下载
  2. cdn之所以能够加速,是因为会在很多地方都部署cdn服务器,如果用户需要下载静态资源,会自动选择最近的节点下载
  3. 同时由于cdn服务器的地址一般都根据服务器的地址不同,所以可以破除浏览器对同一个域名发送请求的限制;

1.1.3 为什么渲染很多数据会造成浏览器卡顿

  1. 无论是浏览器中的DOM和BOM,还是nodejs,都是基于javascript引擎之上开发出来的

  2. DOM和BOM的处理最终都是要被转换成javascript引擎能够处理的数据

  3. 这个转换过程很耗时

  4. 所以在浏览器中最消耗性能的就是操作DOM

怎么优化渲染很多数据的情况

尽可能的减少DOM的操作

插入十万条数据

// 插入十万条数据
  const total = 100000;
  let ul = document.querySelector('ul');

  // 懒加载的思路--分段渲染
  // 1,一次渲染一屏的量
  const once = 20;
  //2,全部渲染完需要多久,循环的时候调用
  const loopCoiunt = total / once;
  //3,已经渲染了多少次
  let counthashRender = 0;

  function add() {
    // 创建虚拟节点,使用createDocumentFragment不会触发渲染
    const fragment = document.createDocumentFragment();
    // 循环20次
    for (let i = 0; i < once; i++) {
      const li = document.createElement('li');
      li.innerHTML = Math.floor(Math.random() * total);
      fragment.appendChild(li);
    }
    // 最后把虚拟节点append到ul上
    ul.appendChild(fragment);
    // 4, 已渲染的次数+1
    counthashRender += 1;
    loop();
  }

  // 最重要的部分
  function loop() {
    // 5,如果还没有渲染完,那么就使用requestAnimationFrame来继续渲染
    if (counthashRender < loopCoiunt) {
      // requestAnimationFrame叫做逐帧渲染
      // 类似于setTimeout(add, 16);
      // 帧:一秒钟播放多少张图片,一秒钟播放的图片越多,动画就越流畅
      // 1000/60 = 16;
      window.requestAnimationFrame(add);
    }
  }
  loop();

渲染步骤:

  1. 可以使用createDocumentFragment创建虚拟节点,从而引起没有必要的渲染
  2. 当所有的li都创建完毕后,一次性把虚拟节点里的li标签全部渲染出来
  3. 可以采取分段渲染的方式,比如一次只渲染一屏的数据
  4. 最后使用window.requestAnimationFrame来逐帧渲染;

1.1.4 前端中性能优化的总结

在前端中性能优化的点主要分为两个阶段:

  1. 初始阶段,主要就是加载方面优化的问题,所有问题的指导原则就两点:
    尽可能的减少前端资源的数量
    尽可能的减少前端资源的大小
  2. 运行阶段,主要就是渲染方面优化的问题,只要在浏览器中,所有的问题的指导原则就是:尽可能的减少操作DOM;

1.2 实现懒加载

        编写代码实现图片的懒加载,懒加载是前端性能优化的重要方案,通过图片或者数据的延迟加载,我们可以加快页面渲染的速度,让第一次打开页面的速度加快,只有当滑动到某个区域,我们才加载真实的图片,这样也可以节省加载的流量;实现方案说明如下:
1,把所有需要延迟加载的图片用一个盒子包起来,设置宽高和默认占位图;
2,开始让所有的img的src为空,把真实图片地址放到img的自定义属性上,让img隐藏;
3,等到所有其他资源都加载完成后,我们再开始加载图片;
4,对于很多图片,需要当页面滚动的时候,当前区域完全显示出来后再加载真实图片;

单张图片懒加载实现如下

<style>
    .imgBox{
      margin: 1000px auto;
      width: 800px;
      height: 160px;
      overflow: hidden;
      background: #bbb;
    }
    .imgBox img{
      width: 100%;
      display: none;
    }
  </style>
</head>
<body>
  <div class="imgBox">
    <img src="" alt="" data-img = "http://www.zhufengpeixun.cn/main/img/banner10.png">
  </div>
  <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  <script type="text/javascript">
    let $imgBox  = $('.imgBox'),
        $img = $imgBox.children('img'),
        $window = $(window);
    $window.on('load scroll', function() {
      if ($img.attr('isLoad') === 'true') {
        return;
      }
      let $A = $imgBox.outerHeight() + $imgBox.offset().top,
          $B = $window.outerHeight() + $window.scrollTop();
      if($A <= $B) {
        $img.attr('src', $img.attr('data-img'));
        $img.on('load', function() {
          $img.stop().fadeIn();
        });
        $img.attr('isLoad', true);
      }
    })
  </script>
</body>

多张图片懒加载:

 <style>
    * {
      margin: 0;
      padding: 0;
    }
    .container {
      width: 960px;
      margin: 500px auto 0;
      text-align: center;
    }
    .container .img-box {
      width: 500px;
      height: 667px;
      margin: 0 auto;
      overflow: hidden;
      background-color: #aaaaaa;
      margin-bottom: 20px;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="img-box">
      <img src="" data-src="https://images.pexels.com/photos/2755165/pexels-photo-2755165.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500">
    </div>
  </div>
  <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  <script>
    // 假数据
window.onload = function () {
  const container = document.querySelector('.container')
  const imgBox = document.querySelector('.img-box')
  new Array(10).fill(null).forEach(el => {
    const node = imgBox.cloneNode(true)
    container.appendChild(node)
  })
}

window.onscroll = function () {
  const imgBoxes = [...document.querySelectorAll('.img-box')]
  imgBoxes.forEach((el) => {
    lazyLoad(el, el.firstElementChild)
  })
}

// 懒加载函数
/**
 * 
 * @param {*图片容器} imgWrapperNode 
 * @param {*图片} imgNode 
 */
function lazyLoad (imgWrapperNode, imgNode) {
  if (imgNode.getAttribute('src')) {
    return true
  }
  let A = imgWrapperNode.offsetTop + imgWrapperNode.clientHeight
  let B = window.innerHeight + window.scrollY
  if (B > A) {
    const dataSrc = imgNode.getAttribute('data-src')
    imgNode.setAttribute('src', dataSrc)
  }
}
  </script>
</body>

1.3  说一下从输入URL到页面渲染的过程

这里主要考察以下三点:
1,加载资源的形式
2,加载资源的过程
3,渲染页面的过程

资源的形式:

html代码
媒体文件,如图片,视频等
JavaScript css

加载过程:

DNS解析:域名 - > IP 地址
浏览器根据IP地址向服务器发起http请求
服务器处理http请求,并返回给浏览器

渲染过程:

根据html代码生成DOM Tree
根据css代码生成CSSDOM
将DOM Tree和CSSOM整合形成Render Tree;
根据Render Tree渲染页面
遇到script标签则暂停渲染,优先加载并执行JS代码,完成再继续
直至把Render Tree渲染完成

当我们在web浏览器的地址栏中输入:www.baidu.com,具体发生了什么

1,对www.baidu.com这个网址进行DNS域名解析,得到相应的IP地址;
2,根据这个IP,找到对应的服务器,发起TCP的三次握手;
3,建立TCP后连接后发起http请求;
4,服务器响应http请求,浏览器得到html代码;
5,浏览器解析html代码,并请求html代码中的资源(如js, css, 图片等)(先得到html代码,才能去找这些资源);
6,浏览器对页面进行渲染呈现给用户;
7,服务器关闭tcp连接;

1.4 面试题分析

1,如果一段js执行时间非常长,怎么去分析

可以采用装饰器的模式,代码实现如下

export function measure(target, name, descriptor) {
	const oldvalue = descriptor.value;
	descriptor.value = async function() {
		console.time(name);
		const ret = await oldvalue.apply(this, arguments);
		console.timeEnd(name);
		return ret;
	}
	return descriptor;
}

export default class Home extends Vue {
  public longTimeFn() {
    return new Promise((resolve) => setTimeout(resolve, 3000));
  }
  
  @measure
  public asyncc created() {
    await this.longTime();
  }
}

2,阿里云oss支持通过链接后面拼接参数来做图片的格式转换,尝试写一下,把任意图片格式转换为webp,需要注意什么?

function checkWep() {
	try {
		return (
			document.createElement('canvas')
			.toDataURL('image/webp')
			.indexOf('data:image/webp') === 0
		);
	} catch (e) {
		return false;
	}
}

const supportWebp = checkWep();
export function getEebpImageUrl(url) {
	if(!url) {
		throw Error('url 不能为空');
	}
	if(url.startsWith('data:')) {
		return url;
	}
	if(!supportWebp) {
		return url;
	}
	return url + '?x-oss-processXXXXXX';
}

3,如果有大量的图片需要展示,除了懒加载的方式,有没有什么其他方法限制一下同时加载的图片数量?

function limitLoad(urls, handler, limit) {
	
}

const urls = [
	{
		info: '1.png',
		time: '1000'
	},
	{
		info: '2.png',
		time: '500'
	},
	{
		info: '3.png',
		time: '300'
	},
	{
		info: '4.png',
		time: '400'
	}
];

// 设置我们要执行的任务
function loadImg(url) {
	return new Promise((resolve, reject) => {
		console.log("----" + url.info + " start!");
		setTimeout(() => {
			console.logg(url.info + " OK!!!");
			resolve();
		}, url.time)
	});
};

limitLoad(urls, loadImg, 3)

主要实现promise的并发控制

function limitLoad(urls, handler, limit) {
	const sequence = [].concat(urls);
	let promises = [];
	promises = sequence.splice(0, limit).map((url, index) => {
		return handler(url).then(() => {
			return index;
		})
	});
	let p = Promise.race(promises);
	for (let i = 0; i < sequence.length; i++) {
		p = p.then((res) => {
			promises[res] = handler(sequence[i]).then(() => {
				return res;
			});
			return Promise.race(promises);
		});
	}
}

二  内存管理相关

2.1  平时有关注过前端的内存处理吗?

1,内存的生命周期

内存分配:声明变量,函数,对象的时候,js会自动分配内存
内存的使用:函数调用的时候;
内存回收

2,js中的垃圾回收机制

引用计数垃圾回收:a对象对b对象有访问权限,那么a引用b对象,只要有引用存在,就不能被回收,这种方式有一个问题,就是循环引用。
标记清除法:无法到达的对象,过程如下:
1,在运行的时候给存储在内存的所有变量加上标记;
2,从根部触发,能触及的对象,把标记清楚;
3,哪些有标记的就被视为即将要删除的变量;

3,js中,有哪些常见的内存泄漏
1,全局变量

window.a = 'xxxx'
window.a = null;

      2,未被清楚的定时器和回调

const timer = setTimeout(() => ....}, 1000);
clearTimeout(timer);

setInterval(());
clearInterval();

      3,闭包

      4,dom的引用

const elements = {
    image: document.getElemetyId('image');
}

document.body.removeChild(document.getElemetyId('image'));
elements.image = null;

4,如何避免内存泄漏
减少不必要的全局变量
使用完数据后,及时清除引用;

2.2  代码实现题

1,实现sizeOf函数,传入一个参数object,计算这个Object占用了多少bytes?

const testData = {
	a: 111,
	b: 'cccc',
	2222: false
}

function calculator(object) {
	const objectType = typeof object;

	switch (objectType) {
		case 'string': {
			return object.length * 2;
		}
		case 'boolean': {
			return 4;
		}
		case 'number': {
			return 8;
		}
		case 'object': {
			if(Array.isArray(object)) {
				return object.map(calculator).reduce((res, curr) => res + curr, 0);
			} else {
				return sizeOfObject(object);
			}
		}
		default: {
			return 0;
		}
	}
}

const seen = new WeakSet();
function sizeOfObject(object) {
	if(object == null) return 0;
	let bytes = 0;
	const properties = Object.keys(object);
	for (let i = 0; i < properties.length; i++) {
		const key = properties[i];
		bytes += calculator(key);
		if(typeof object[key] === 'object' && object[key] !== null) {
			if(seen.has(object[key])) {
				continue;
			}
			seen.add(object[key]);
		}	
		bytes += calculator(object[key]);
	}
	return bytes;
}

console.log(calculator(testData));

三  发布订阅模式

平时用过发布订阅模式吗?

手写一个简易的Event Bus

class EventEmitter {
	constructor(maxListens) {
		this.events = {};
		this.maxListens = maxListens || Infinity;
	}

	emit(event, ...args) {
		const cbs = this.events[event];
		if(!cbs) {
			console.log('没有这个事件');
			return this;
		}
		cbs.forEach(cb => cb.apply(this, args));
		return this;
	}

	on(event, cb) {
		if(!this.events[event]) {
			this.events[event] = [];
		}
		if (this.maxListens !== Infinity && this.events[event].length >= this.maxListens) {
			console.log(`当前事件${event}超过最大监听数`);
			return this;
		}
		this.events[event].push(cb);
		return this;
	}

	once(event, cb) {
		const func = (...args) => {
			this.off(event, func);
			cb.apply(this, args);
		}
		this.on(event, func);
		return this;
	}

	off(event, cb) {
		if(!cb) {
			this.events[event] = null;
		} else {
			this.events[event] = this.events[event].filter(item => item != cb);
		}
		return this;
	}
	
}

const add = (a, b) => console.log(a + b);
const log = (...args) => console.log(...args);
const event = new EventEmitter();

event.on('add', add);
event.on('log', log);
event.emit('add', 1, 2);
event.emit('log', 'hi~');
event.off('add');
event.emit('add', 1, 2);
event.once('once', add);
event.emit('add', 1, 2);
event.emit('add', 1, 2);
event.emit('add', 1, 2);

四   HTTP请求相关

聊一聊前端http请求相关

4.1 跨域相关

1,介绍一下通源策略

1,ajax请求时,浏览器要求当前网页和server必须同源(安全);
2,同源:协议,域名,端口,三者必须一致;

2,加载图片 css js,可无视同源策略

<img src = 跨域的图片地址 />  //可用于统计打点,可使用第三方统计服务
<link src = 跨域的css地址 /> // 可使用CDN,CDN一般都是外域
<script src = 跨域的js地址></script> //可使用CDN,CDN一般都是外域 ,可实现JSONP

3,平时怎么解决跨域问题的

所有的跨域,都必须经过server端允许和配合,未经过server端允许就实现跨域,说明浏览器有漏洞,危险信号;

1,jsonp
jsonp的原理,就是利用script标签可以绕过跨域限制,服务器可以任意动态拼接数据返回,所以script就可以获得跨域的数据,只要服务端愿意返回;

客户端

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  <script>
    $.ajax({
      url: 'http://127.0.0.1:8001/list',
      method: 'get',
      dataType: 'jsonp',
      success: res => {
        console.log(res);
      }
    });
  </script>

服务端

let express = require('express'),
    app = express();
app.listen(8001, _ => {
	console.log('OK~');
});
app.get('/list', (req, res) => {
	let {
		callback = Function.prototype
	} = req.query;
	let data = {
		code: 0,
		message: 'hello stoney'
	};
	res.send(`${callback}(${JSON.stringify(data)})`);
});

2, CORS跨域资源共享

客户端

import axios = from 'axios';
import qs from 'qs';
axios.default.baseURL = baseURL = "http://127.0.0.1:3000";
axios.default.timeout = 10000;
axios.default.withCredentials = true;

/*
 * 设置请求传递的数据格式(看服务器要求什么格式)
 * x-www-form-urlencoded
*/
axios.default.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.default.transformRequest = data => qs.stringify(data);

/*
 * 设置请求拦截
 * TOKEN校验(JWT): 接收服务器返回的token,存储到vuex/本地存储中,每一次香港服务器发请求,都要带上token
*/
axios.interceptors.request.use(config => {
  let token = localStorage.getItem('token');
  token && (configg.headers.Authorization = token);
  return config;
}, error => {
  return Promise.reject(error)
});

/*
 * 相应拦截器
*/
axios.interceptors.response.use(response => {
  return response.data;
}, error => {});

export default axios;

服务端

app.use(req, res, next) => {
  res.header("Access-Control-Allow-Origin", "");
  res.header("Access-Control-Allow-Credentials", true);
  res.header("Access-Control-Allow-Headers" , "PUT, POST, GET, DELETE, OPTIONS, HEAD");
  res.header("Access-Control-Allow-Methods", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With");
  req.method === 'OPTIONS' ? res.send('CURRENT SERVERS SUPPORT CROSS DOMAIN REQUESTS!') : next();
});

3, node 正向代理, /api -> 同域的node服务 -> /api -> 前端
4,nginx反向代理,proxy_pass
5,基于iframe的跨域解决方案

window.name
document.domain
location.hash
post message

4.2 代码实现题

2,有做过全局的请求处理吗?比如统一处理登录态?统一处理全局错误?

axios
adaptor
interceptor request response

3,代码题,你能给xhr添加hook,实现在各个阶段打日志吗?

class XhrHook {
	
}

new XhrHook({
	open: function() {
		console.log('open');
		// return false;
	},
	onload: function () {
		console.log('onload');
	},
	onreadystatechange: function() {
		console.log('onreadystatechange');
	},
	onerror: function () {
		console.log('hook error')
	}
});

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.baidu.com', true),
xhr.send();
xhr.onreadystatechange = function (res) {
	console.log('statechange');
};
xhr.onerror = function() {
	console.log('error');
}

实现如下:

class XhrHook {
	constructor(beforeHooks = beforeHooks = {}, afterHooks = {}) {
		this.XHR = window.XMLHttpRequest;
		this.beforeHooks = beforeHooks;
		this.afterHooks = afterHooks;
		this.init();
	}
	
	init() {
		let _this = this;
		window.XMLHttpRequest = function() {
			this._xhr = new _this.XHR();
			_this.overwrite(this);
		}
	}

	overwrite(proxyXHR) {
		for (let key in proxyXHR) {
			if (typeof proxyXHR._xhr[key] === 'function') {
				this.overwriteMethod(key, proxyXHR);
				continue;
			}
			this.overwriteAttributes(key, proxyXHR);
		}
	}

	overwriteMethod(key, proxyXHR) {
		let beforeHooks = this.beforeHooks;
		let afterHooks = this.afterHooks;
		proxyXHR[key] = (...args) => {
			if(beforeHooks[key]) {
				const res = beforeHooks[key].call(proxyXHR, args);
				if(res === false) {
					return;
				}
			}
			const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);

			afterHooks[key] && afterHooks[key].call(proxyXHR._xhr, args);
			return res;
		}
	}

	overwriteAttributes(key, proxyXHR) {
		Object.defineProperties(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
	}

	setProperyDescriptor (key, proxyXHR) {
		let obj = Object.create(null);
		let _this = this;
		obj.set = function(val) {
			if(!key.startsWith('on')) {
				proxyXHR['_' + key] = val;
				return;
			}
			if(_this.beforeHooks[key]) {
				this._xhr[key] = function(...args) {
					_this.beforeHooks[key].call(proxyXHR);
					val.apply(proxyXHR, args);
				}
				return;
			}
			this._xhr[key] = val;
		}
		obj.get = function() {
			return proxyXHR['_' + key] || this._xhr[key];
		}
		return obj;
	}
}

4.3 实现ajax

function ajax(url) {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onreadystatechange = function() {
      if(xhr.readyState === 4) {
        if(xhr.status === 200) {
          resolve(
            JSON.parse(xhr.responseText)
          )
        } else if(xhr.status === 404) {
          reject(new Error('404 not found'));
        }
      }
    }
    xhr.send(null);
  });
  return p;
}

const url = '/data/test.json';
ajax(url).then(res => console.log(res))
.catch(err => console.log(err));

4.4 说一说http2.0

1,二进制分帧层

HTTP2性能提升的核心就在于二进制分帧,HTTP2是二进制协议,采用二进制格式传输数据而不是1.0的文本格式;

2,多路复用

        在http1.1中,我们传输的request和response都是基于文本传输,这样所有的数据必须按照顺序串行传输,不能并行传输,因为接收端并不知道这些 字符的顺序,http2.0引入二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样浏览器接收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后 数据错乱的情况。同样也是因为有了序列,服务器就可以并行的传输数据,这就是流做的事情;

        当链接数过多的时候,假设Apache设置了最大并发数为300,因为浏览器的限制,浏览器发起的最大请求数为6,也就是服务器能承受的最高并发数为50, 当第51个人访问时, 就需要等待前面某个请求处理完成;由于http2.0对同一域名的所有请求都是基于流,也就是说同一域名下不管访问多少文件,也只是建立一路连接。同样Apache的最大连接 数为300,因为有了这个新特性,最大的并发数就可以达到300,比原来提升了6倍;

3,头部压缩

4,服务器端推送

服务器端推送使得服务器可以预测客户端需要的资源,主动推送到客户端;

4.5 https相关

https认证加密过程是怎样的,它是怎么保证内容不会被篡改

1,https是基于tcp协议的,客户端会先和服务端发起连接建立;
2,接着服务端会把它的证书返回给客户端,证书里面包括公钥S.pub, 颁发机构,有效期等
3,拿到的证书可以通过浏览器内置的根证书C.pub验证其合法性;
4,客户端生成随机的对称密钥Z,通过服务端的公钥S.pub加密发给服务端
5,客户端和服务端通过对称密钥Z来进行http通信;

根证书怎么保证签发的证书是安全有效的

1,服务器会预先生成非对称加密密钥,私钥S.pri自己保留,而公钥S.pub则发给CA机构进行签名认证;
2,CA也会预先生成一非对称加密密钥,其私钥C.pri用来对服务器的公钥S.pub进行签名生成CA证书;
3,CA机构会把签名生成的CA证书返回给服务器,也就是刚才服务端给客户端那个证书;
4,因为CA(证书颁发机构)比较权威,所以很多浏览器会内置包含它公钥(C.pub)的证书,称之为根证书。然后可以使用根证书来验证其颁发证书的合法性了;

4.6 fetch和axios的区别

fetch是一个底层的api, 浏览器原生支持的,axios是一个封装好的框架;

fetch的优点:

  • 语法简洁,更加语意化;
  • 基于标准的promise实现,支持async/await;
  • 更加底层,提供API丰富(request,response);
  • 脱离了xhr,是es规范里的新的实现方式;

fetch缺点:

  • 不支持文件上传进度监测;
  • 使用不完美,需要封装,fetch只针对网络请求报错,对400,500都当做成功的请求,服务器返回400,500错误码时并不会reject,只有当网络错误这些导致请求不能完成时,fetch才会reject;
  • 不支持请求中止;
  • 默认不带cookie,需要添加配置项: fetch(url, {credentials: 'include'});

axios是一个基于Promise用于浏览器和nodejs的http客户端,本质上也是对原生xhr的封装,只不过它是promise的实现版本,符合最新的ES规范,具有以下特征;

  • 支持浏览器和nodejs发请求,前后端发请求
  • 支持promise语法;
  • 支持自动解析json;
  • 支持中断请求;
  • 支持拦截请求;
  • 支持请求进度监测;
  • 支持客户端防止csrf

总结:axios既提供了并发的封装,也没有fetch的各种问题,体积也小;

五  浏览器存储

1,描述cookie localStorage sessionStorage的区别

cookie:
本身用于浏览器和server通讯,被借用到本地存储来,可以用document.cookie = '..'来修改;
缺点:
1,存储大小有限制,最大4KB;
2,http请求时需要发送到服务端,增加了请求数据量
3,只能用document.cookie = '..'来修改,api太简陋;

localStorage和sessionStorage:
1,HTML5专门为存储而设计,最大可达到5M;
2,API简单易用,setItem, getItem;
3,数据不会随着http请求发送给服务端;

localStorage和sessionStorage区别:

1,localStorage数据会永久存储,除非代码或手动删除;
2,sessionStorage数据只存在当前会话中。浏览器tab页关闭则清空;
3,一般用localStorage会更多一些;

2, cookie和session区别:

  • 存储位置不同:cookie的数据信息存放在客户端浏览器上,session数据信息存储在服务器上。 
  • 存储容量不同:单个cookie保存的数据 <= 4kb,一个站点最多保存20个cookie,而对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西, 并且设置session删除机制; 
  • 隐私方式不同:cookie中只能保存ASCII码,并需要通过编码方式存储为Unicode字符或者二进制数据,session中能够存储任何类型的数据,包括不限于string,interger,list, map等; 
  • 隐私策略不同:cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的,而session存储在服务器上,对客户端是透明的,不存在敏感信息泄漏的风险; 
  • 有效期不同:开发者可以通过设置cookie的属性,达到cookie长期有效的效果,session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只要关闭该session就会失效,因而session不能达到长期有效的效果; 
  • 服务器压力不同:cookie保管在客户端,不占用服务器资源,对于并发用户十分多的网站,cookie是很好的选择,session是保管在服务器端,每个用户都会产生一个session,假如并发访问的用户十分多,耗费大量的内存; 
  • 跨域支持上不同:cookie支持跨域访问,session不支持跨域名访问;

六 同步异步

6.1 同步和异步的区别

单线程和同步

1,js是单线程语言,只能同时做一件事
2,浏览器和nodeJs已经支持JS启动进程,如web worker
3,js和DOM 渲染共用同一个线程,因为js可以修改DOM结构;
4,遇到等待(网络请求和定时任务)不能卡住
5,需要异步来实现
6,回调函数callback函数形式

//异步
console.log(100);
setTimeout(function() {
  console.log(200);
}, 1000);
console.log(300)

//同步
console.log(100);
alert(200);
console.log(300);

JS是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行;

应用场景

网络请求,如ajax,图片加载
定时任务,如setTimeout等

//ajax
console.log('start');
$.get('./data1.json', function(data1) {
  console.log(data1);
})
console.logg('end');

//图片加载 
console.logg('start');
let img = document.createElement('img');
img.onload = function() {
  console.log('loaded');
}
img.src = '/xxx.png';
console.log('end');

实现形式

回调函数

//获取第一份数据
$.get(url1, (data1) => {
  console.log(data1);

  //获取第二份数据
  $.get(url2, (data2) => {
    console.log(data2);

    //获取第三份数据
    $.get(url3, (data3) => {
      console.log(data3);

      // 还可能获取更多数据
    })
  })
})

Promise形式

function getData(url) {
  return new Promise((resolve, reject) => {
    $.ajax({
      url,
      success(data) {
        resolve(data);
      },
      error(err) {
        reject(err);
      }
    })
  })
}

const url1 = '/data1.json';
const url2 = '/data2.json';
const url3 = '/data3.json';
getData(url1).then(data1 => {
  console.log(data1);
  return getData(url2);
}).then(data2 => {
  console.log(data2);
  return getData(url3)
}).then(data3 => {
  console.log(data3);
}).catch(err => console.log(err));

6.2 手写promise加载图片

function loadImg(src) {
  const p = new Promisw((resolve, reject) => {
    const img = document.createElement('img');
    img.onload = () => {
      resolve(img);
    }
    img.onerror = () => {
      const err = new Error(`图片加载失败 ${err}`);
      reject(err);
    }
    img.src = src;
  })
  return p;
}