# 一位前端小姐姐的五万字面试宝典

·  阅读 150031

## 一、算法

### 1.全排列

``````微信公众号：世界上有意思的事

function permutate(str) {
var array = str.split('');
function loop(array, pre = []) {
if (array.length == 1) {
return [pre.concat(array).join('')];
}
let res = [];
for (let index = 0; index < array.length; index++) {
var first = array.pop();
res = res.concat(loop(array, [...pre, first]));
array.unshift(first);
}
return res;
}
return Array.from(new Set(loop(array)))
}

### 2.二分搜索

``````微信公众号：世界上有意思的事

function BinarySearch1 (arr, target) {
return search(arr, target, 0, arr.length - 1)
function search (arr, target, from, to) {
if (from > to) {
return -1
}
const mid = Math.floor((from + to)/2)
if (arr[mid] > target) {
return search(arr, target, from, mid - 1)
} else if (arr[mid] < target) {
return search(arr, target, mid + 1, to)
} else {
return mid
}
}
}

function BinarySearch2 (arr, target) {
let from = 0
let to = arr.length - 1
let mid = Math.floor((from + to)/2)
while (from <= to) {
mid = Math.floor((from + to)/2)
if (arr[mid] > target) {
to = mid - 1
} else if (arr[mid] < target) {
from = mid + 1
} else {
return mid
}
}

return -1
}

### 3.排序

#### (1).冒泡排序

``````微信公众号：世界上有意思的事

/*

*/
function BubbleSort (arr) {
const length = arr.length

for (let i = 0; i < length; i++) {
for (let j = 1; j < length-i; j++) {
if (arr[j] < arr[j - 1]) {
const temp = arr[j]
arr[j] = arr[j - 1]
arr[j - 1] = temp
}
}
}

return arr
}

#### (2).快速排序

``````微信公众号：世界上有意思的事

/*

*/
function QuickSort(arr, low, high) {
let left = low
let right = high
let basic = arr[low]
while (left < right) {
while (left < right && arr[right] > basic) {
right--
}
while (left < right && arr[left] <= basic) {
left++
}

if (left < right) {
const temp = arr[left]
arr[left] = arr[right]
arr[right] = temp
} else {
const temp = arr[low]
arr[low] = arr[left]
arr[left] = temp

QuickSort(arr, low, left - 1)
QuickSort(arr, right + 1, high)
}
}

return arr
}

#### (3).选择排序

``````微信公众号：世界上有意思的事

/*
寻找第i小的数的位置，放到i位置上
*/
function SelectionSort (arr) {
const length = arr.length
for (let i = 0; i < length; i++ ) {
let minIndex= i
for (let j = i + 1; j < length; j++) {
minIndex = arr[minIndex] <= arr[j] ? minIndex : j
}
if (minIndex !== i) {
const temp = arr[i]
arr[i] = arr[minIndex]
arr[minIndex] = temp

}
}
return arr
}

#### (4).插入排序

``````微信公众号：世界上有意思的事

function InsertionSort (arr) {
const length = arr.length
for (let i = 1; i < length; i++) {
const temp = arr[i]
let j
for (j = i - 1; j >= 0 && temp < arr[j]; j--) {
arr[j+1] = arr[j]
}
arr[j+1] = temp
}
return arr
}

#### (5).希尔排序

``````微信公众号：世界上有意思的事

function ShellSort (arr) {
const length = arr.length
let gap = Math.floor(length)
while (gap) {
for (let i = gap; i < length; i++) {
const temp = arr[i]
let j
for (j = i - gap; j >= 0 && temp < arr[j]; j = j - gap) {
arr[j + gap] = arr[j]
}
arr[j + gap] = temp
}
gap = Math.floor(gap / 2)
}
return arr
}

#### (6).归并排序

``````微信公众号：世界上有意思的事

function MergeSort (arr, low, high) {
const length = arr.length
if (low === high) {
return arr[low]
}
const mid = Math.floor((low + high)/2)
MergeSort(arr, low, mid)
MergeSort(arr, mid + 1, high)
merge(arr, low, high)
return arr

}

function merge (arr, low, high) {
const mid = Math.floor((low + high)/2)
let left = low
let right = mid + 1
const result = []
while (left <= mid && right <= high) {
if (arr[left] <= arr[right]) {
result.push(arr[left++])
} else {
result.push(arr[right++])
}
}
while (left <= mid) {
result.push(arr[left++])
}
while (right <= high) {
result.push(arr[right++])
}

arr.splice(low, high-low+1, ...result)
}

const test = [2, 34, 452,3,5, 785, 32, 345, 567, 322,5]

console.log(MergeSort(test, 0, test.length - 1))

#### (7).堆排序

``````微信公众号：世界上有意思的事

function HeapSort (arr) {
const length = arr.length

// 调整初始堆，调整完其实也确定了最大值
// 但此时最大值是在 arr[0] 中
for (let i = Math.floor(length/2) - 1; i >= 0; i--) {
}

// 把 arr[0](最大值)换到后面
for (let i = length - 1; i >=0; i--) {
const temp = arr[0]
arr[0] = arr[i]
arr[i] = temp
}

return arr
}

// size 是还需要调整的堆的大小
// 随着一个个最大值的确定，size 会越来越小
function adjustHeap (arr, position, size) {
const left = position * 2 + 1
const right = left + 1
let maxIndex = position
if (left < size && arr[left] > arr[maxIndex]) {
maxIndex = left
}
if (right < size && arr[right] > arr[maxIndex]) {
maxIndex = right
}
if (maxIndex !== position) {
const temp = arr[position]
arr[position] = arr[maxIndex]
arr[maxIndex] = temp
}
return arr
}

## 二、JS基础

### 1.继承

• 1、原型链继承，将父类的实例作为子类的原型，他的特点是实例是子类的实例也是父类的实例，父类新增的原型方法/属性，子类都能够访问，并且原型链继承简单易于实现，缺点是来自原型对象的所有属性被所有实例共享，无法实现多继承，无法向父类构造函数传参。

• 2、构造继承，使用父类的构造函数来增强子类实例，即复制父类的实例属性给子类，构造继承可以向父类传递参数，可以实现多继承，通过call多个父类对象。但是构造继承只能继承父类的实例属性和方法，不能继承原型属性和方法，无法实现函数服用，每个子类都有父类实例函数的副本，影响性能

• 3、实例继承，为父类实例添加新特性，作为子类实例返回，实例继承的特点是不限制调用方法，不管是new 子类（）还是子类（）返回的对象具有相同的效果，缺点是实例是父类的实例，不是子类的实例，不支持多继承

• 4、拷贝继承：特点：支持多继承，缺点：效率较低，内存占用高（因为要拷贝父类的属性）无法获取父类不可枚举的方法（不可枚举方法，不能使用for in 访问到）

• 5、组合继承：通过调用父类构造，继承父类的属性并保留传参的优点，然后通过将父类实例作为子类原型，实现函数复用

• 6、寄生组合继承：通过寄生方式，砍掉父类的实例属性，这样，在调用两次父类的构造的时候，就不会初始化两次实例方法/属性，避免的组合继承的缺点

### 2.this指向

#### (1).this 指向有哪几种

• 1.默认绑定：全局环境中，this默认绑定到window。

• 2.隐式绑定：一般地，被直接对象所包含的函数调用时，也称为方法调用，this隐式绑定到该直接对象。

• 3.隐式丢失：隐式丢失是指被隐式绑定的函数丢失绑定对象，从而默认绑定到window。显式绑定：通过call()、apply()、bind()方法把对象绑定到this上，叫做显式绑定。

• 4.new绑定：如果函数或者方法调用之前带有关键字new，它就构成构造函数调用。对于this绑定来说，称为new绑定。

• 构造函数通常不使用return关键字，它们通常初始化新对象，当构造函数的函数体执行完毕时，它会显式返回。在这种情况下，构造函数调用表达式的计算结果就是这个新对象的值。
• 如果构造函数使用return语句但没有指定返回值，或者返回一个原始值，那么这时将忽略返回值，同时使用这个新对象作为调用结果。
• 如果构造函数显式地使用return语句返回一个对象，那么调用表达式的值就是这个对象。

#### (2).改变函数内部 this 指针的指向函数（bind，apply，call的区别）

• 1.apply：调用一个对象的一个方法，用另一个对象替换当前对象。例如：B.apply(A, arguments);即A对象应用B对象的方法。

• 2.call：调用一个对象的一个方法，用另一个对象替换当前对象。例如：B.call(A, args1,args2);即A对象调用B对象的方法。

• 3.bind除了返回是函数以外，它的参数和call一样。

#### (3).箭头函数

• 1.箭头函数没有this，所以需要通过查找作用域链来确定this的值，这就意味着如果箭头函数被非箭头函数包含，this绑定的就是最近一层非箭头函数的this，
• 2.箭头函数没有自己的arguments对象，但是可以访问外围函数的arguments对象
• 3.不能通过new关键字调用，同样也没有new.target值和原型

### 3.数据类型

#### (1).基本数据类型

Undefined、Null、Boolean、Number 、String、Symbol

#### (2).symbol

• 1.语法：

``````// 不能用 new
let s = Symbol()

// 可以接受一个字符串作为参数，表示对 Symbol 实例的描述，主要是为了在控制台显示，或者转为字符串时，比较容易区分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
复制代码``````
• 2.作用：定义一个独一无二的值

• 1.用作对象的属性名

• 1.不会出现在`for...in``for...of`循环中，也不会被`Object.keys()``Object.getOwnPropertyNames()``JSON.stringify()`返回。
• 2.`Object.getOwnPropertySymbols()`方法，可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组，成员是当前对象的所有用作属性名的 Symbol 值。
• 3.`Reflect.ownKeys()`方法可以返回所有类型的键名，包括常规键名和 Symbol 键名。
• 2.用于定义一组常量

``````log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};
复制代码``````
• 3.类型转换：

• 1.转成字符串

``````String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
复制代码``````
• 2.转成布尔值

``````Boolean(sym)
!sym
复制代码``````
• 3.不能转成数字

• 4.不能与其他类型的值进行运算

``````let sym = Symbol('My symbol');

"your symbol is " + sym
// TypeError: can't convert symbol to string
// TypeError: can't convert symbol to string
复制代码``````
• 4.属性：Symbol.prototype.description

• 5.Symbol.for()，Symbol.keyFor()

• 1.在全局环境中登记 Symbol 值。之后不会再重复生成

#### (3).如何判断类型

typeof()，instanceof，Object.prototype.toString.call()

• 1.`typeof`操作符

• 1."undefined"——如果这个值未定义;
• 2."boolean"——如果这个值是布尔值;
• 3."string"——如果这个值是字符串;
• 4."number"——如果这个值是数值;
• 5."object"——如果这个值是对象或 null;
• 6."function"——如果这个值是函数。
• 7."symbol"——es6新增的symbol类型
• 2.`instanceof`：用来判断对象是不是某个构造函数的实例。会沿着原型链找的

• 3.`Object.prototype.toString.call()`

``````var toString = Object.prototype.toString;

toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
toString.call([]); // [Object Array]
toString.call(new Number) // [object Number]
toString.call(true) // [object Boolean]
toString.call(function(){}) // [object Function]
toString.call({}) // [object Object]
toString.call(new Promise(() => {})) // [object Promise]

toString.call(new Map) // [object Map]
toString.call(new RegExp) // [object RegExp]
toString.call(Symbol()) // [object Symbol]
toString.call(function *a(){}) // [object GeneratorFunction]
toString.call(new DOMException()) // [object DOMException]
toString.call(new Error) // [object Error]

toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]

// 还有 WeakMap、 WeakSet、Proxy 等
复制代码``````

#### (4).判断是否是数组

• 1.`Array.isArray(arr)`
• 2.`Object.prototype.toString.call(arr) === '[Object Array]'`
• 3.`arr instanceof Array`
• 4.`array.constructor === Array`

#### (5).字符串转数字

`parseInt(string, radix)`

### 4.CallBack Hell

#### (1).Promise 为什么以及如何用于解决控制反转信任问题

Promise 的实现可以看这里

Promise 这种模式通过可信任的语义把回调作为参数传递，使得这种行为更可靠更合理。 通过把回调的控制反转反转回来，我们把控制权放在了一个可信任的系统(Promise)中， 这种系统的设计目的就是为了使异步编码更清晰。Promise 并没有摈弃回调，只是把回调的安排转交给了一个位于我们和其他工具之间的可信任 的中介机制。

• 调用回调过早;

• 这个问题主要就是担心代码是否会引入类似 Zalgo 这样的副作用(参见第 2 章)。在这类问 题中，一个任务有时同步完成，有时异步完成，这可能会导致竞态条件。

根据定义，Promise 就不必担心这种问题，因为即使是立即完成的 Promise(类似于 new Promise(function(resolve){ resolve(42); }))也无法被同步观察到。

也就是说，对一个 Promise 调用 then(..) 的时候，即使这个 Promise 已经决议，提供给 then(..) 的回调也总会被异步调用(对此的更多讨论，请参见 1.5 节)。

• 调用回调过晚(或不被调用);

• 和前面一点类似，Promise 创建对象调用 resolve(..) 或 reject(..) 时，这个 Promise 的 then(..) 注册的观察回调就会被自动调度。可以确信，这些被调度的回调在下一个异步事 件点上一定会被触发(参见 1.5 节)。
• 回调未调用

• 首先，没有任何东西(甚至 JavaScript 错误)能阻止 Promise 向你通知它的决议(如果它 决议了的话)。如果你对一个 Promise 注册了一个完成回调和一个拒绝回调，那么 Promise 在决议时总是会调用其中的一个。
• 但是，如果 Promise 本身永远不被决议呢?即使这样，Promise 也提供了解决方案，其使用 了一种称为竞态的高级抽象机制:
• 调用回调次数过多;

• Promise 的定义方式使得它只能被决议一次。如果出于某种 原因，Promise 创建代码试图调用 resolve(..) 或 reject(..) 多次，或者试图两者都调用， 那么这个 Promise 将只会接受第一次决议，并默默地忽略任何后续调用。
• 由于 Promise 只能被决议一次，所以任何通过 then(..) 注册的(每个)回调就只会被调 用一次。
• 未能传递所需的环境和参数;

• Promise 至多只能有一个决议值(完成或拒绝)。

如果你没有用任何值显式决议，那么这个值就是 undefined，这是 JavaScript 常见的处理方 式。但不管这个值是什么，无论当前或未来，它都会被传给所有注册的(且适当的完成或 拒绝)回调。

• 吞掉可能出现的错误和异常。

• 如果拒绝一个 Promise 并给出一个理由(也就是一个出错消息)，这个值就会被传给拒绝回调

#### (2).promise、generator、async/await

• promise

• 优点：解决了回调地狱的问题
• 缺点：无法取消 Promise ，错误需要通过回调函数来捕获
• generator

• 生成器内部的代码是以自然的同步 / 顺序方式表达任务的一系列步骤
• async/await

• 优点：代码清晰，不用像 Promise 写一大堆 then 链，处理了回调地狱的问题

• 缺点：await 将异步代码改造成同步代码，如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

### 5.加载

#### (1).异步加载js的方法

• defer：只支持IE如果您的脚本不会改变文档的内容，可将 defer 属性加入到`<script>`标签中，以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本，它将推迟对脚本的解释，直到文档已经显示给用户为止。

• async：HTML5 属性，仅适用于外部脚本；并且如果在IE中，同时存在defer和async，那么defer的优先级比较高；脚本将在页面完成时执行。

#### (2).图片的懒加载和预加载

• 预加载：提前加载图片，当用户需要查看时可直接从本地缓存中渲染。
• 懒加载：懒加载的主要目的是作为服务器前端的优化，减少请求数或延迟请求数。

### 6.事件

#### (1).事件流

HTML中与javascript交互是通过事件驱动来实现的，例如鼠标点击事件onclick、页面的滚动事件onscroll等等，可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的，就需要了解一下“事件流”的概念。

• 事件捕获阶段
• 处于目标阶段
• 事件冒泡阶段

IE只支持事件冒泡。

#### (2).什么是事件监听

`addEventListener()`方法，用于向指定元素添加事件句柄，它可以更简单的控制事件，语法为

`element.addEventListener(event, function, useCapture)`;

• 第一个参数是事件的类型(如 "click" 或 "mousedown").

• 第二个参数是事件触发后调用的函数。

• 第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。

``````target.addEventListener(type, listener, options: EventListenerOptions);
target.addEventListener(type, listener, useCapture: boolean, wantsUntrusted: boolean  );  // Gecko/Mozilla only

``````interface EventListenerOptions {
capture?: boolean // 表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发
once?: boolean // 表示 listener 在添加之后最多只调用一次。如果是 true， listener 会在其被调用之后自动移除
passive?: boolean // 设置为true时，表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数，客户端将会忽略它并抛出一个控制台警告
}

#### (3). mouseover 和 mouseenter 的区别

• mouseover：当鼠标移入元素或其子元素都会触发事件，所以有一个重复触发，冒泡的过程。对应的移除事件是mouseout
• mouseenter：当鼠标移除元素本身（不包含元素的子元素）会触发事件，也就是不会冒泡，对应的移除事件是mouseleave

### 7.跨域

#### (1).CORS

CORS(Cross-Origin Resource Sharing，跨源资源共享) 背后的基本思想，就是使用自定义的 HTTP 头部 让浏览器与服务器进行沟通。

`Origin: http://www.nczonline.net` 如果服务器认为这个请求可以接受，就在 Access-Control-Allow-Origin 头部中回发相同的源

`Access-Control-Allow-Origin: http://www.nczonline.net`

#### (2).IE

2. 只能设置请求头部信息中的 Content-Type 字段。
3. 不能访问响应头部信息。
4. 只支持GET和POST请求。

#### (4).JSONP

``````微信公众号：世界上有意思的事

function handleResponse(response){
alert("You’re at IP address " + response.ip + ", which is in " +
response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse"; document.body.insertBefore(script, document.body.firstChild);

• JSON只支持get，因为script标签只能使用get请求；
• JSONP需要后端配合返回指定格式的数据。

#### (6).利用 iframe

• window.postMessage
• Cross Frame(aba)
• window.name

lovelock.coding.me/javascript/…

#### (8). child 与 parent 通信

• 给接收数据的一方添加事件绑定：`addEventListener('message', receiveMessage);`

• 发送数据的一方拿到接收数据一方的window：`targetWindow.postMessage("Welcome to unixera.com", "http://iframe1.unixera.com");`

#### (11).window.name

`window`对象的`name`属性是一个很特殊的属性，在设定了`window.name`之后，执行`location.href`跳转，`window.name`属性仍然不会发生变化，可以通过这种方式实现变量的传递。

### 8.Ajax

#### (1).实现一个Ajax

``````微信公众号：世界上有意思的事

var xhr = new XMLHttpRequest()
if (xhr.status >= 200 && xhr.status < 300 || xhr.status ==== 304) {
console.log(xhr.responseText)
} else {
console.log('Error:' + xhr.status)
}
}
}
// 第三个参数表示异步发送请求
xhr.open('get', '/api/getSth',  true)
// 参数为作为请求主体发送的数据
xhr.send(null)

#### (2).Ajax状态

1. 未初始化。尚未调用 open()方法。
2. 启动。已经调用 open()方法，但尚未调用 send()方法。
3. 发送。已经调用 send()方法，但尚未接收到响应。
4. 接收。已经接收到部分响应数据。
5. 完成。已经接收到全部响应数据，而且已经可以在客户端使用了。

#### (3).将原生的 ajax 封装成 promise

``````微信公众号：世界上有意思的事

const ajax = (url, method, async, data) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
// 已经接收到全部响应数据，而且已经可以在客户端使用了
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else if (xhr.status > 400) {
reject('发生错误')
}
}
}
xhr.open(url, method, async)
xhr.send(data || null)
})
}

### 10.eval是什么

eval 方法就像是一个完整的 ECMAScript 解析器，它只接受一个参数，即要执行的 ECMAScript (或JavaScript) 字符串

• 1.性能差：引擎无法在编译时对作用域查找进行优化
• 1.JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的 词法进行静态分析，并预先确定所有变量和函数的定义位置，才能在执行过程中快速找到 标识符。
• 2.无法在词法分析阶段明确知道 eval(..) 会接收到什么代码，这些代码会 如何对作用域进行修改，也无法知道传递给 with 用来创建新词法作用域的对象的内容到底 是什么。最悲观的情况是如果出现了 eval(..) 或 with，所有的优化可能都是无意义的，因此最简 单的做法就是完全不做任何优化。
• 2.欺骗作用域：但在严格模式的程序中，eval(..) 在运行时有其自己的词法作用域，意味着其 中的声明无法修改所在的作用域。

### 11.监听对象属性的改变

#### (一).ES5 中

``````微信公众号：世界上有意思的事

Object.defineProperty(user,'name',{
set：function(key,value){
// 这也是 Vue 的原理
}
})

#### (二). ES6 中

``````微信公众号：世界上有意思的事

var  user = new Proxy({}, {

}
})

### 12.实现一个私有变量

• 1.配置属性

``````obj={
name: 'xujiahui',
getName:function(){
return this.name
}
}
object.defineProperty(obj,"name",{
//不可枚举不可配置

});
复制代码``````
• 2.代码

``````微信公众号：世界上有意思的事

function product(){
var name='xujiahui';
this.getName=function(){
return name;
}
}
var obj=new product();

### 13.操作符

#### (1).`==`和`===`、以及`Object.is`的区别

• 1.`==`

• 1.会进行强制类型转换（!=也是）

• 2.在转换不同的数据类型时，相等和不相等操作符遵循下列基本规则:

• 3.如果有一个操作数是布尔值，则在比较相等性之前先将其转换为数值——false 转换为 0，而true 转换为 1

• 4.如果一个操作数是字符串，另一个操作数是数值，在比较相等性之前先将字符串转换为数值;

• 5.如果一个操作数是对象，另一个操作数不是，则调用对象的 valueOf()方法，用得到的基本类型值按照前面的规则进行比较; 这两个操作符在进行比较时则要遵循下列规则。

• 6.null 和 undefined 是相等的。

• 7.要比较相等性之前，不能将 null 和 undefined 转换成其他任何值。

• 8.如果有一个操作数是 NaN，则相等操作符返回 false，而不相等操作符返回 true。重要提示⚠️：即使两个操作数都是 NaN，相等操作符也返回 false;因为按照规则，NaN 不等于 NaN。

• 9.如果两个操作数都是对象，则比较它们是不是同一个对象。如果两个操作数都指向同一个对象，则相等操作符返回 true;否则，返回 false。

• 2.`===`：全等于，不转换

• 3.`Object.is`

• 1.也不会进行强制类型转换。
• 2.与`===`有以下几点不同：
• 1.`+0===-0``Object.is(+0, -0)`为 false
• 2.`NaN !== NaN``Object.is(NaN, NaN)`为 true

#### (2).new 操作符做了哪些事情

• 1.创建一个新对象;
• 2.将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
• 3.执行构造函数中的代码(为这个新对象添加属性);
• 4.返回新对象。
• 5.将构造函数的prototype关联到实例的__proto__

### 14.数组

#### (1).数组常用方法

push()，pop()，shift()，unshift()，splice()，sort()，reverse()，map()等

#### (2).数组去重

• 1.双重循环

每次插入一个元素的时候都和前面的每个元素比较一下

``````var array = [1, 1, '1', '1'];

function unique(array) {
// res用来存储结果
var res = [];
for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
for (var j = 0, resLen = res.length; j < resLen; j++ ) {
if (array[i] === res[j]) {
break;
}
}
// 如果array[i]是唯一的，那么执行完循环，j等于resLen
if (j === resLen) {
res.push(array[i])
}
}
return res;
}

console.log(unique(array)); // [1, "1"]
复制代码``````
• 2.`indexOf`

原理和双重循环是一样的

``````var array = [1, 1, '1'];

function unique(array) {
var res = [];
for (var i = 0, len = array.length; i < len; i++) {
var current = array[i];
if (res.indexOf(current) === -1) {
res.push(current)
}
}
return res;
}

console.log(unique(array));
复制代码``````
• 3.排序后去重

对于排好序的数组，可以将每个元素与前一个比较

``````var array = [1, 1, '1'];

function unique(array) {
var res = [];
var sortedArray = array.concat().sort();
var seen;
for (var i = 0, len = sortedArray.length; i < len; i++) {
// 如果是第一个元素或者相邻的元素不相同
if (!i || seen !== sortedArray[i]) {
res.push(sortedArray[i])
}
seen = sortedArray[i];
}
return res;
}

console.log(unique(array));
复制代码``````
• 4.Object 键值对

把每一个元素存成 object 的 key。例如 `['a']`，存成`{'a': true}`

``````var array = [1, 2, 1, 1, '1'];

function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
return obj.hasOwnProperty(item) ? false : (obj[item] = true)
})
}

console.log(unique(array)); // [1, 2]
复制代码``````

我们可以发现，是有问题的，因为 1 和 '1' 是不同的，但是这种方法会判断为同一个值，这是因为对象的键值只能是字符串，所以我们可以使用 `typeof item + item` 拼成字符串作为 key 值来避免这个问题：

``````var array = [1, 2, 1, 1, '1'];

function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}

console.log(unique(array)); // [1, 2, "1"]
复制代码``````

然而，即便如此，我们依然无法正确区分出两个对象，比如 {value: 1} 和 {value: 2}，因为 `typeof item + item` 的结果都会是 `object[object Object]`，不过我们可以使用 JSON.stringify 将对象序列化：

``````var array = [{value: 1}, {value: 1}, {value: 2}];

function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
console.log(typeof item + JSON.stringify(item))
return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
})
}

console.log(unique(array)); // [{value: 1}, {value: 2}]
复制代码``````
• 5.ES6 Set去重

``````function unique(array) {
return Array.from(new Set(array));
}
复制代码``````
``````function unique(array) {
return [...new Set(array)];
}
复制代码``````
• 6.ES6 Map

``````function unique (arr) {
const seen = new Map()
return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}
复制代码``````

## 三、高级技巧

### 3.数组展开

• 1.递归
``````微信公众号：世界上有意思的事

function flat1 (arr) {
let result = []
arr.forEach(element => {
if (Array.isArray(element)) {
result = result.concat(flat1(element))
} else {
result.push(element)
}
});
return result
}

• 2.toString
``````function flat2 (arr) {
// 有缺陷，toString 后无法保持之前的类型
return arr.toString().split(',')
}

• 3.reduce
``````微信公众号：世界上有意思的事

function flat3 (arr) {
// 本质和 flat1 一样的，都是递归
return arr.reduce((pre, next) => {
return pre.concat(Array.isArray(next) ? flat3(next) : next)
}, [])
}

• 4.rest运算符
``````微信公众号：世界上有意思的事

function flat4 (arr) {
while (arr.some(item => Array.isArray(item))) {
// 相当于 [].concat('1', 2, [3, 4])
// concat 方法本身就会把参数中的数组展开
arr = [].concat(...arr);
}
return arr;
}

• 5.ES6 flat
``````微信公众号：世界上有意思的事

function flat5 (arr: any[]) {
// flat() 方法会移除数组中的空项
return arr.flat(Infinity)
}

### 4.拖放

``````微信公众号：世界上有意思的事

var DragDrop = function(){
var dragging = null;
function handleEvent(event){

//获取事件和目标
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);

//确定事件类型
switch(event.type){
case "mousedown":
if (target.className.indexOf("draggable") > -1){
dragging = target;
}
break;
case "mousemove":
if (dragging !== null){
//指定位置
dragging.style.left = event.clientX + "px";
dragging.style.top = event.clientY + "px";
}
break;
case "mouseup":
dragging = null;
break;
}
};
//公共接口
return {
enable: function(){
},
disable: function(){
EventUtil.removeHandler(document, "mousedown", handleEvent);
EventUtil.removeHandler(document, "mousemove", handleEvent);
EventUtil.removeHandler(document, "mouseup", handleEvent);
}
}
}();

• 1.DragDrop 对象封装了拖放的所有基本功能。这是一个单例对象，并使用了模块模式来隐藏某些实 现细节。dragging 变量起初是 null，将会存放被拖动的元素，所以当该变量不为 null 时，就知道正 在拖动某个东西。handleEvent()函数处理拖放功能中的所有的三个鼠标事件。它首先获取 event 对 象和事件目标的引用。之后，用一个 switch 语句确定要触发哪个事件样式。当 mousedown 事件发生 时，会检查 target 的 class 是否包含"draggable"类，如果是，那么将 target 存放到 dragging 中。这个技巧可以很方便地通过标记语言而非 JavaScript 脚本来确定可拖动的元素。

• 2.handleEvent()的 mousemove 情况和前面的代码一样，不过要检查 dragging 是否为 null。当 它不是 null，就知道 dragging 就是要拖动的元素，这样就会把它放到恰当的位置上。mouseup 情况 就仅仅是将 dragging 重置为 null，让 mousemove 事件中的判断失效。

• 3.DragDrop 还有两个公共方法:enable()和 disable()，它们只是相应添加和删除所有的事件处 理程序。这两个函数提供了额外的对拖放功能的控制手段。

• 4.要使用 DragDrop 对象，只要在页面上包含这些代码并调用 enable()。拖放会自动针对所有包含 "draggable"类的元素启用，如下例所示:

``````<div class="draggable" style="position:absolute; background:red"> </div>
复制代码``````

注意为了元素能被拖放，它必须是绝对定位的。

### 5.once

``````微信公众号：世界上有意思的事

function once (func) {
var done;
return function () {
if (!done) {
func.apply(null, arguments)
done = true
}
}
}

``````微信公众号：世界上有意思的事

function onlyDoOne = once(function() {
console.log('1')
})

### 6.promise

`Promise` 是一个对象，保存着未来将要结束的事件，她有两个特征:

• 1.对象的状态不受外部影响，`Promise` 对象代表一个异步操作，有三种状态，pending进行中，fulfilled已成功，rejected已失败，只有异步操作的结果，才可以决定当前是哪一种状态，任何其他操作都无法改变这个状态，这也就是promise名字的由来

• 2.一旦状态改变，就不会再变，`Promise`对象状态改变只有两种可能，从pending改到fulfilled或者从pending改到rejected，只要这两种情况发生，状态就凝固了，不会再改变，这个时候就称为定型resolved

### 7.sleep

`Promise`

1. ``````function sleep (ms) {
return new Promise((resolve) => {
window.setTimeout(resolve, ms)
})
}

sleep(1000).then(()=>{
console.log('已经 sleep 1000ms')
})
复制代码``````
2. ``````function sleep (ms) {
return new Promise((resolve) => {
window.setTimeout(resolve, ms)
})
}

// 使用async/await调用
async function test () {
var example = await sleep(1000)
console.log('已经 sleep 1000ms')
}
复制代码``````
3. ``````// 使用 generator 定义 sleep 函数
function *sleep (ms) {
yield new Promise((resolve) => {
window.setTimeout(resolve, ms)
})
}
sleep(1000).next().value.then(()=>{
console.log('已经 sleep 1000ms')
})
复制代码``````

## 四、浏览器

### 1.缓存

#### (1).按缓存位置分

• 1.Service Worker
• 1.有两种情况会导致这个缓存中的资源被清除：手动调用 API `cache.delete(resource)` 或者容量超过限制，被浏览器全部清空。
• 2.如果 Service Worker 没能命中缓存，一般情况会使用 `fetch()` 方法继续获取资源。这时候，浏览器就去 memory cache 或者 disk cache 进行下一次找缓存的工作了。注意：经过 Service Worker 的 `fetch()` 方法获取的资源，即便它并没有命中 Service Worker 缓存，甚至实际走了网络请求，也会标注为 `from ServiceWorker`
• 2.Memory Cache：tab关闭则失效
• 1.memory cache 机制保证了一个页面中如果有两个相同的请求 (例如两个 `src` 相同的 `image`，两个 `href` 相同的 `link`)都实际只会被请求最多一次，避免浪费。
• 2.在从 memory cache 获取缓存内容时，浏览器会忽视例如 `max-age=0`, `no-cache` 等头部配置。例如页面上存在几个相同 `src` 的图片，即便它们可能被设置为不缓存，但依然会从 memory cache 中读取。这是因为 memory cache 只是短期使用，大部分情况生命周期只有一次浏览而已。而 `max-age=0` 在语义上普遍被解读为“不要在下次浏览时使用”，所以和 memory cache 并不冲突。
• 3.但如果站长是真心不想让一个资源进入缓存，就连短期也不行，那就需要使用 `no-store`。存在这个头部配置的话，即便是 memory cache 也不会存储，自然也不会从中读取了。
• 3.Disk Cache：disk cache 会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存，哪些资源不可以缓存；哪些资源是仍然可用的，哪些资源是过时需要重新请求的。当命中缓存之后，浏览器会从硬盘中读取资源，虽然比起从内存中读取慢了一些，但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache
• 4.网络请求：如果一个请求在上述 3 个位置都没有找到缓存，那么浏览器会正式发送网络请求去获取内容。之后容易想到，为了提升之后请求的缓存命中率，自然要把这个资源添加到缓存中去。具体来说：
• 1.根据 Service Worker 中的 handler 决定是否存入 Cache Storage (额外的缓存位置)。
• 2.根据 HTTP 头部的相关字段(`Cache-control`, `Pragma` 等)决定是否存入 disk cache
• 3.memory cache 保存一份资源 的引用，以备下次使用。

#### (2).按失效策略分

memory cache 是浏览器为了加快读取缓存速度而进行的自身的优化行为，不受开发者控制，也不受 HTTP 协议头的约束，算是一个黑盒。Service Worker 是由开发者编写的额外的脚本，且缓存位置独立，出现也较晚，使用还不算太广泛。所以我们平时最为熟悉的其实是 disk cache，也叫 HTTP cache (因为不像 memory cache，它遵守 HTTP 协议头中的字段)。平时所说的强制缓存（强缓存），对比缓存（协商缓存），以及 `Cache-Control` 等，也都归于此类。

### 强制缓存 (也叫强缓存)

• Expires：

• HTTP1.0
• 由于是绝对时间，用户可能会将客户端本地的时间进行修改，而导致浏览器判断缓存失效，重新请求该资源。此外，即使不考虑自信修改，时差或者误差等因素也可能造成客户端与服务端的时间不一致，致使缓存失效。
• 写法太复杂了。表示时间的字符串多个空格，少个字母，都会导致非法属性从而设置失效
• Cache-control

• HTTP1.1

• 优先级高

• `max-age`：即最大有效时间

`must-revalidate`：如果超过了 `max-age` 的时间，浏览器必须向服务器发送请求，验证资源是否还有效。

`no-cache`：虽然字面意思是“不要缓存”，但实际上还是要求客户端缓存内容的，只是是否使用这个内容由后续的对比来决定。

`no-store`: 真正意义上的“不要缓存”。所有内容都不走缓存，包括强制和对比。

`public`：所有的内容都可以被缓存 (包括客户端和代理服务器， 如 CDN)

`private`：所有的内容只有客户端才可以缓存，代理服务器不能缓存。默认值。

### 对比缓存 (协商缓存)

• Last-Modified & If-Modified-Since
• 服务器通过 `Last-Modified` 字段告知客户端，资源最后一次被修改的时间
• 浏览器将这个值和内容一起记录在缓存数据库中。
• 下一次请求相同资源时时，浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 `Last-Modified` 的值写入到请求头的 `If-Modified-Since` 字段
• 服务器会将 `If-Modified-Since` 的值与 `Last-Modified` 字段进行对比。如果相等，则表示未修改，响应 304；反之，则表示修改了，响应 200 状态码，并返回数据。
• 如果资源更新的速度是秒以下单位，那么该缓存是不能被使用的，因为它的时间单位最低是秒。
• 如果文件是通过服务器动态生成的，那么该方法的更新时间永远是生成的时间，尽管文件可能没有变化，所以起不到缓存的作用。
• Etag & If-None-Match
• Etag 的优先级高于 Last-Modified
• `Etag` 存储的是文件的特殊标识(一般都是 hash 生成的)，服务器存储着文件的 `Etag` 字段。
• 之后的流程和 `Last-Modified` 一致，只是 `Last-Modified` 字段和它所表示的更新时间改变成了 `Etag` 字段和它所表示的文件 hash，把 `If-Modified-Since` 变成了 `If-None-Match`
• 服务器同样进行比较，命中返回 304, 不命中返回新资源和 200。

#### (3).Ajax 解决浏览器缓存问题

• 3.在URL后面加上一个随机数： "fresh=" + Math.random()。

• 4.在URL后面加上时间搓："nowtime=" + new Date().getTime()。

• 5.如果是使用jQuery，直接这样就可以了 \$.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。

### 2.浏览器渲染原理

#### (1).Render Tree

• 不显示（`display: none`）的元素不会被生成
• 有了`RenderTree`，我们就知道了所有节点的样式，然后计算他们在页面上的大小和位置（布局），最后把节点绘制到页面上（绘制）。
• 由于浏览器使用流式布局，对`Render Tree`的计算通常只需要遍历一次就可以完成，`table`及其内部元素除外，他们可能需要多次计算，通常要花3倍于同等元素的时间，这也是为什么要避免使用`table`布局的原因之一

#### (4).浏览器优化

• `offsetTop``offsetLeft``offsetWidth``offsetHeight`
• `scrollTop``scrollLeft``scrollWidth``scrollHeight`
• `clientTop``clientLeft``clientWidth``clientHeight`
• `width``height`
• `getComputedStyle()`
• `getBoundingClientRect()`

#### (5).减少重绘与回流

• 1.CSS

• 2.使用 `transform` 替代 `top`

• 3.使用 `visibility` 替换 `display: none` ，因为前者只会引起重绘，后者会引发回流（改变了布局

• 4.避免使用`table`布局，可能很小的一个小改动会造成整个 `table` 的重新布局。

• 5.尽可能在`DOM`树的最末端改变`class`，回流是不可避免的，但可以减少其影响。尽可能在DOM树的最末端改变class，可以限制了回流的范围，使其影响尽可能少的节点。

• 6.避免设置多层内联样式，CSS 选择符从右往左匹配查找，避免节点层级过多。

``````<div>
<a> <span></span> </a>
</div>
<style>
span {
color: red;
}
div > a > span {
color: red;
}
</style>
复制代码``````

对于第一种设置样式的方式来说，浏览器只需要找到页面中所有的 `span` 标签然后设置颜色，但是对于第二种设置样式的方式来说，浏览器首先需要找到所有的 `span` 标签，然后找到 `span` 标签上的 `a` 标签，最后再去找到 `div` 标签，然后给符合这种条件的 `span` 标签设置颜色，这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器，然后对于 HTML 来说也尽量少的添加无意义标签，保证层级扁平

• 7.将动画效果应用到`position`属性为`absolute``fixed`的元素上，避免影响其他元素的布局，这样只是一个重绘，而不是回流，同时，控制动画速度可以选择 `requestAnimationFrame`，详见探讨 requestAnimationFrame

• 8.避免使用`CSS`表达式，可能会引发回流。

• 9.将频繁重绘或者回流的节点设置为图层，图层能够阻止该节点的渲染行为影响别的节点，例如`will-change``video``iframe`等标签，浏览器会自动将该节点变为图层。

• 10.CSS3 硬件加速（GPU加速），使用css3硬件加速，可以让`transform``opacity``filters`这些动画不会引起回流重绘 。但是对于动画的其它属性，比如`background-color`这些，还是会引起回流重绘的，不过它还是可以提升这些动画的性能。

• 2.JavaScript

• 1.避免频繁操作样式，最好一次性重写`style`属性，或者将样式列表定义为`class`并一次性更改`class`属性。
• 2.避免频繁操作`DOM`，创建一个`documentFragment`，在它上面应用所有`DOM操作`，最后再把它添加到文档中。
• 3.避免频繁读取会引发回流/重绘的属性，如果确实需要多次使用，就用一个变量缓存起来。
• 4.对具有复杂动画的元素使用绝对定位，使它脱离文档流，否则会引起父元素及后续元素频繁回流。

#### (6).JS 什么时候解析？

1. `<script>`

• 渲染过程中，如果遇到 JS 就停止渲染，执行 JS 代码。

• 如果 JS 需要操作CSSOM，则会先让CSSOM构建完，再执行JS，最后构建DOM

2. `<script async>`

• 异步执行引入的 JavaScript，加载完成后就执行 JS，阻塞DOM
3. `<script defer>`

• 延迟执行。载入 JavaScript 文件时不阻塞 HTML 的解析，执行阶段被放到 HTML 标签解析完成之后。

## 五、计算机基础

### 1.计算机网络

#### (1).TCP 三次握手

• 1.第一次握手：起初两端都处于CLOSED关闭状态，Client将标志位SYN置为1，随机产生一个值seq=x，并将该数据包发送给Server，Client进入SYN-SENT状态，等待Server确认；

• 2.第二次握手：Server收到数据包后由标志位SYN=1得知Client请求建立连接，Server将标志位SYN和ACK都置为1，ack=x+1，随机产生一个值seq=y，并将该数据包发送给Client以确认连接请求，Server进入SYN-RCVD状态，此时操作系统为该TCP连接分配TCP缓存和变量；

• 3.第三次握手：Client收到确认后，检查ack是否为x+1，ACK是否为1，如果正确则将标志位ACK置为1，ack=y+1，并且此时操作系统为该TCP连接分配TCP缓存和变量，并将该数据包发送给Server，Server检查ack是否为y+1，ACK是否为1，如果正确则连接建立成功，Client和Server进入ESTABLISHED状态，完成三次握手，随后Client和Server就可以开始传输数据。

#### (2).CDN 原理

CDN的全称是Content Delivery Network，即内容分发网络。CDN的基本原理是广泛采用各种缓存服务器，将这些缓存服务器分布到用户访问相对集中的地区或网络中，在用户访问网站时，利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上，由缓存服务器直接响应

#### (4).DNS 解析

• 浏览器缓存：浏览器会按照一定的频率缓存 DNS 记录。
• 操作系统缓存：如果浏览器缓存中找不到需要的 DNS 记录，那就去操作系统中找。
• 路由缓存：路由器也有 DNS 缓存。
• ISP 的 DNS 服务器：ISP 是互联网服务提供商(Internet Service Provider)的简称，ISP 有专门的 DNS 服务器应对 DNS 查询请求。
• 根服务器：ISP 的 DNS 服务器还找不到的话，它就会向根服务器发出请求，进行递归查询（DNS 服务器先问根域名服务器.com 域名服务器的 IP 地址，然后再问.baidu 域名服务器，依次类推）

#### (5).HTTP 常用请求头

Accept 可接受的响应内容类型（Content-Types）。
Accept-Charset 可接受的字符集
Accept-Encoding 可接受的响应内容的编码方式。
Accept-Language 可接受的响应内容语言列表。
Accept-Datetime 可接受的按照时间来表示的响应内容版本
Authorization 用于表示HTTP协议中需要认证资源的认证信息
Cache-Control 用来指定当前的请求/回复中的，是否使用缓存机制。
Connection 客户端（浏览器）想要优先使用的连接类型
Content-Length 以8进制表示的请求体的长度
Content-MD5 请求体的内容的二进制 MD5 散列值（数字签名），以 Base64 编码的结果
Content-Type 请求体的MIME类型 （用于POST和PUT请求中）
Date 发送该消息的日期和时间（以RFC 7231中定义的"HTTP日期"格式来发送）
Expect 表示客户端要求服务器做出特定的行为
From 发起此请求的用户的邮件地址
Host 表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口（80），则端口号可以省略。
If-Match 仅当客户端提供的实体与服务器上对应的实体相匹配时，才进行对应的操作。主要用于像 PUT 这样的方法中，仅当从用户上次更新某个资源后，该资源未被修改的情况下，才更新该资源。
If-Modified-Since 允许在对应的资源未被修改的情况下返回304未修改
If-None-Match 允许在对应的内容未被修改的情况下返回304未修改（ 304 Not Modified ），参考 超文本传输协议 的实体标记
If-Range 如果该实体未被修改过，则向返回所缺少的那一个或多个部分。否则，返回整个新的实体
If-Unmodified-Since 仅当该实体自某个特定时间以来未被修改的情况下，才发送回应。
Max-Forwards 限制该消息可被代理及网关转发的次数。
Origin 发起一个针对跨域资源共享的请求（该请求要求服务器在响应中加入一个Access-Control-Allow-Origin的消息头，表示访问控制所允许的来源）。
Pragma 与具体的实现相关，这些字段可能在请求/回应链中的任何时候产生。
Proxy-Authorization 用于向代理进行认证的认证信息。
Range 表示请求某个实体的一部分，字节偏移以0开始。
Referer 表示浏览器所访问的前一个页面，可以认为是之前访问页面的链接将浏览器带到了当前页面。Referer其实是Referrer这个单词，但RFC制作标准时给拼错了，后来也就将错就错使用Referer了。
TE 浏览器预期接受的传输时的编码方式：可使用回应协议头Transfer-Encoding中的值（还可以使用"trailers"表示数据传输时的分块方式）用来表示浏览器希望在最后一个大小为0的块之后还接收到一些额外的字段。
User-Agent 浏览器的身份标识字符串
Via 告诉服务器，这个请求是由哪些代理发出的。
Warning 一个一般性的警告，表示在实体内容体中可能存在错误。

#### (5).TCP和UDP的区别

• 1.UDP
• 1.无连接
• 2.面向报文，只是报文的搬运工
• 3.不可靠，没有拥塞控制
• 4.高效，头部开销只有8字节
• 5.支持一对一、一对多、多对多、多对一
• 6.适合直播、视频、语音、会议等实时性要求高的
• 2.TCP
• 1.面向连接：传输前需要先连接
• 2.可靠的传输
• 3.流量控制：发送方不会发送速度过快，超过接收方的处理能力
• 4.拥塞控制：当网络负载过多时能限制发送方的发送速率
• 5.不提供时延保障
• 6.不提供最小带宽保障

#### (6).为什么三次握手四次挥手

• 1.四次挥手
• 1.因为是双方彼此都建立了连接，因此双方都要释放自己的连接，A向B发出一个释放连接请求，他要释放链接表明不再向B发送数据了，此时B收到了A发送的释放链接请求之后，给A发送一个确认，A不能再向B发送数据了，它处于FIN-WAIT-2的状态，但是此时B还可以向A进行数据的传送。此时B向A 发送一个断开连接的请求，A收到之后给B发送一个确认。此时B关闭连接。A也关闭连接。
• 2.为什么要有TIME-WAIT这个状态呢，这是因为有可能最后一次确认丢失，如果B此时继续向A发送一个我要断开连接的请求等待A发送确认，但此时A已经关闭连接了，那么B永远也关不掉了，所以我们要有TIME-WAIT这个状态。
• 当然TCP也并不是100%可靠的。
• 1.三次握手：为了防止已失效的连接请求报文段突然又传送到了服务端，因而产生错误

#### (7).websocket和ajax的区别是什么，websocket的应用场景有哪些

WebSocket的诞生本质上就是为了解决HTTP协议本身的单向性问题：请求必须由客户端向服务端发起，然后服务端进行响应。这个Request-Response的关系是无法改变的。对于一般的网页浏览和访问当然没问题，一旦我们需要服务端主动向客户端发送消息时就麻烦了，因为此前的TCP连接已经释放，根本找不到客户端在哪。 为了能及时从服务器获取数据，程序员们煞费苦心研究出来的各种解决方案其实都是在HTTP框架下做的妥协，没法子，浏览器这东西只支持HTTP，我们有什么办法。所以大家要么定时去轮询，要么就靠长连接——客户端发起请求，服务端把这个连接攥在手里不回复，等有消息了再回，如果超时了客户端就再请求一次——其实大家也懂，这只是个减少了请求次数、实时性更好的轮询，本质没变。

WebSocket就是从技术根本上解决这个问题的：看名字就知道，它借用了Web的端口和消息头来创建连接，后续的数据传输又和基于TCP的Socket几乎完全一样，但封装了好多原本在Socket开发时需要我们手动去做的功能。比如原生支持wss安全访问（跟https共用端口和证书）、创建连接时的校验、从数据帧中自动拆分消息包等等。

……但是，何苦一层叠一层地造个新轮子呢？直接使用AJAX不是更简单、更成熟吗？

#### (8).TCP/IP的网络模型

• 1.TCP/IP模型是一系列网络协议的总称，这些协议的目的是使得计算机之间可以进行信息交换，

• 2.TCP/IP模型四层架构从下到上分别是链路层，网络层，传输层，应用层

• 4.网络层负责分配地址和传送二进制数据，主要协议是IP协议，

• 5.传输层负责传送文本数据，主要协议是TCP

• 7.应用层负责传送各种最终形态的数据，是直接与用户信息打交道的层，主要协议是http，ftp等

### 2.HTTP协议

#### (1).常见的请求方法

HTTP 1.0

• 1.GET：从指定的资源请求数据
• 2.POST：向指定的资源提交要被处理的数据，例如
• 1.提交表单
• 2.将消息发布到公告板，新闻组，邮件列表，博客或类似的文章组；
• 1.类似于get请求，只不过返回的响应中没有具体的内容，只有头部
• 2.只请求资源的首部
• 3.检查超链接的有效性
• 4.检查网页是否被修改

HTTP1.1

• 1.PUT：替换或创建指定资源
• 2.DELETE：对指定资源进行删除

HTTP2.0

• 1.OPTIONS： 用于获取目的资源所支持的通信选项，比如说服务器支持的请求方式等等。

• 2.TRACE：实现沿通向目标资源的路径的消息环回（loop-back）测试 ，提供了一种实用的 debug 机制。

• 3.CONNECT

• 1.为代理服务器准备的

• 2.在 HTTP 协议中，`CONNECT` 方法可以开启一个客户端与所请求资源之间的双向沟通的通道。它可以用来创建隧道（tunnel）。例如，`CONNECT` 可以用来访问采用了 SSL (HTTPS) 协议的站点。客户端要求代理服务器将 TCP 连接作为通往目的主机隧道。之后该服务器会代替客户端与目的主机建立连接。连接建立好之后，代理服务器会面向客户端发送或接收 TCP 消息流。

• 2.幂等性：PUT，DELETE和安全Method是幂等的。
• 1.表示浏览器是会自动缓存的，以应用于后续请求。除非response中有相关策略

#### (2).GET 和 POST 的区别

• 1.get参数通过url传递，post放在request body中。

• 2.get请求在url中传递的参数是有长度限制的，而post没有。

• 3.get比post更不安全，因为参数直接暴露在url中，所以不能用来传递敏感信息。

• 4.get请求只能进行url编码，而post支持多种编码方式

• 5.get请求会浏览器主动cache，而post支持多种编码方式。

• 6.get请求参数会被完整保留在浏览历史记录里，而post中的参数不会被保留。

• 7.GET和POST本质上就是TCP链接，并无差别。但是由于HTTP的规定和浏览器/服务器的限制，导致他们在应用过程中体现出一些不同。

#### (3).HTTP 状态码

• 1xx (Informational): 收到请求，正在处理
• 2xx (Successful): 该请求已成功收到，理解并接受
• 3xx (Redirection): 重定向
• 4xx (Client Error): 该请求包含错误的语法或不能为完成
• 5xx (Server Error): 服务器错误

#### (4).301 和 302 有什么具体区别

• 301：永久移动，请求的网页已永久移动到新的位置，服务器返回此响应，会自动将请求者转到新位置

• 302：历史移动，服务器目前从不同位置的网页响应请求，但请求者应继续使用原有位置来继续以后的请求，

### 3.操作系统

#### (1).进程和线程的区别

• 1.进程，是并发执行的程序在执行过程中分配和管理资源的基本单位，是一个动态概念，竞争计算机系统资源的基本单位。

• 2.线程，是进程的一部分，一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程，也是 CPU 调度的一个基本单位。

#### (2).线程的哪些资源共享，哪些资源不共享

• 1.共享的资源有

• 1.堆：由于堆是在进程空间中开辟出来的，所以它是理所当然地被共享的；因此new出来的都是共享的（16位平台上分全局堆和局部堆，局部堆是独享的）
• 2.全局变量：它是与具体某一函数无关的，所以也与特定线程无关；因此也是共享的
• 3.静态变量：虽然对于局部变量来说，它在代码中是“放”在某一函数中的，但是其存放位置和全局变量一样，存于堆中开辟的.bss和.data段，是共享的
• 4.文件等公用资源：这个是共享的，使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式，包括信号、临界区、事件和互斥体。
• 2.独享的资源有

• 1.栈：栈是独享的
• 2寄存器：这个可能会误解，因为电脑的寄存器是物理的，每个线程去取值难道不一样吗？其实线程里存放的是副本，包括程序计数器PC

#### (3).进程间的通信方式有哪些

• 1.无名管道：半双工的通信方式，数据只能单向流动且只能在具有亲缘关系的进程间使用
• 2.高级管道：将另一个程序当作一个新的进程在当前程序进程中启动，则这个进程算是当前程序的子进程，
• 3.有名管道，：也是半双工的通信方式，但是允许没有亲缘进程之间的通信
• 4.消息队列：消息队列是有消息的链表，存放在内核中，并由消息队列标识符标识，消息队列克服了信号传递信息少，管道只能承载无格式字节流以及缓冲区大小受限的缺点
• 5.信号量：信号量是一个计数器，可以用来控制多个进程对共享资源的访问，它常作为一种锁机制，防止某进程正在访问共享资源时，其他进程也访问该资源，
• 6.信号：用于通知接受进程某个事件已经发生
• 7.共享内存：共享内存就是映射一段能被其他进程所访问的内存。这段共享内存由一个进程创建，但是多个进程可以访问，共享内存是最快的IPC 方式，往往与其他通信机制配合使用
• 8.套接字：可用于不同机器之间的进程通信

## 六、前端进阶

### 1.VUE

#### (1).vue的生命周期

Vue实例有一个完整的生命周期，也就是从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、销毁等一系列过程，我们称这是Vue的生命周期。通俗说就是Vue实例从创建到销毁的过程，就是生命周期。

• 1.实例、组件通过new Vue() 创建出来之后会初始化事件和生命周期，然后就会执行beforeCreate钩子函数，这个时候，数据还没有挂载呢，只是一个空壳，无法访问到数据和真实的dom，一般不做操作

• 2.挂载数据，绑定事件等等，然后执行created函数，这个时候已经可以使用到数据，也可以更改数据,在这里更改数据不会触发updated函数，在这里可以在渲染前倒数第二次更改数据的机会，不会触发其他的钩子函数，一般可以在这里做初始数据的获取

• 3.接下来开始找实例或者组件对应的模板，编译模板为虚拟dom放入到render函数中准备渲染，然后执行beforeMount钩子函数，在这个函数中虚拟dom已经创建完成，马上就要渲染,在这里也可以更改数据，不会触发updated，在这里可以在渲染前最后一次更改数据的机会，不会触发其他的钩子函数，一般可以在这里做初始数据的获取

• 4.接下来开始render，渲染出真实dom，然后执行mounted钩子函数，此时，组件已经出现在页面中，数据、真实dom都已经处理好了,事件都已经挂载好了，可以在这里操作真实dom等事情...

• 5.当组件或实例的数据更改之后，会立即执行beforeUpdate，然后vue的虚拟dom机制会重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染，一般不做什么事儿

• 6.当更新完成后，执行updated，数据已经更改完成，dom也重新render完成，可以操作更新后的虚拟dom

• 7.当经过某种途径调用\$destroy方法后，立即执行beforeDestroy，一般在这里做一些善后工作，例如清除计时器、清除非指令绑定的事件等等

• 8.组件的数据绑定、监听...去掉后只剩下dom空壳，这个时候，执行destroyed，在这里做善后工作也可以

#### (2).Vue 双向绑定原理

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)。

### 2.Webpack

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时，它会递归地构建一个依赖关系图(dependency graph)，其中包含应用程序需要的每个模块，然后将所有这些模块打包成一个或多个bundle。

### 3.模块化

#### (2).说一下 Commonjs、AMD 和 CMD

• 1.Commonjs：开始于服务器端的模块化，同步定义的模块化，每个模块都是一个单独的作用域，模块输出，modules.exports，模块加载require()引入模块。

• 2.AMD：中文名异步模块定义的意思。

• 1.require JS 实现了 AMD 规范

• 1.主要用于解决下述两个问题。

• 1.多个文件有依赖关系，被依赖的文件需要早于依赖它的文件加载到浏览器
• 2.加载的时候浏览器会停止页面渲染，加载文件越多，页面失去响应的时间越长。
• 2.语法：requireJS 定义了一个函数 define，它是全局变量，用来定义模块。

``````//定义模块
define(['dependency'], function(){
var name = 'Byron';
function printName(){
console.log(name);
}
return {
printName: printName
};
});

复制代码``````
``````//加载模块
require(['myModule'], function (my){
my.printName();
}
复制代码``````
• 2.总结 AMD 规范：require()函数在加载依赖函数的时候是异步加载的，这样浏览器不会失去响应，它指定的回调函数，只有前面的模块加载成功，才会去执行。因为网页在加载js的时候会停止渲染，因此我们可以通过异步的方式去加载js,而如果需要依赖某些，也是异步去依赖，依赖后再执行某些方法。

### 4.简单实现Node的Events模块

node中的Events模块就是通过观察者模式来实现的：

``````微信公众号：世界上有意思的事

var events=require('events');
var eventEmitter=new events.EventEmitter();
eventEmitter.on('say',function(name){
console.log('Hello',name);
})
eventEmitter.emit('say','Jony yu');

• 1.实现简单的Event模块的emit和on方法

``````function Events(){
this.on=function(eventName,callBack){
if(!this.handles){
this.handles={};
}
if(!this.handles[eventName]){
this.handles[eventName]=[];
}
this.handles[eventName].push(callBack);
}
this.emit=function(eventName,obj){
if(this.handles[eventName]){
for(var i=0;o<this.handles[eventName].length;i++){
this.handles[eventName][i](obj);
}
}
}
return this;
}
复制代码``````
• 2.这样我们就定义了Events，现在我们可以开始来调用：

``````var events=new Events();
events.on('say',function(name){
console.log('Hello',nama)
});

//结果就是通过emit调用之后，输出了Jony yu
events.emit('say','Jony yu');
复制代码``````
• 3.每个对象是独立的

因为是通过new的方式，每次生成的对象都是不相同的，因此：

``````var event1=new Events();
var event2=new Events();
event1.on('say',function(){
console.log('Jony event1');
});
event2.on('say',function(){
console.log('Jony event2');
})

//event1、event2之间的事件监听互相不影响
//输出结果为'Jony event1' 'Jony event2'
event1.emit('say');
event2.emit('say');
复制代码``````

### 5.性能优化

• 1.降低请求量：合并资源，减少HTTP 请求数，minify / gzip 压缩，webP，图片lazyLoad。

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

• 3.缓存：HTTP 协议缓存请求，离线缓存 manifest，离线数据缓存localStorage。

• 4.渲染：JS/CSS优化（避免使用CSS表达式），加载顺序（将CSS样式表放在顶部，把javascript放在底部），服务端渲染，pipeline。