javaScript基础

1.继承javascript

一、原型链继承,将父类的实例做为子类的原型,他的特色是实例是子类的实例也是父类的实例,父类新增的原型方法/属性,子类都可以访问,而且原型链继承简单易于实现,缺点是来自原型对象的全部属性被全部实例共享,没法实现多继承,没法向父类构造函数传参。css

二、构造继承,使用父类的构造函数来加强子类实例,即复制父类的实例属性给子类,构造继承能够向父类传递参数,能够实现多继承,经过call多个父类对象。可是构造继承只能继承父类的实例属性和方法,不能继承原型属性和方法,没法实现函数服用,每一个子类都有父类实例函数的副本,影响性能前端

三、实例继承,为父类实例添加新特性,做为子类实例返回,实例继承的特色是不限制调用方法,无论是new 子类()仍是子类()返回的对象具备相同的效果,缺点是实例是父类的实例,不是子类的实例,不支持多继承vue

四、拷贝继承:特色:支持多继承,缺点:效率较低,内存占用高(由于要拷贝父类的属性)没法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)java

五、组合继承:经过调用父类构造,继承父类的属性并保留传参的优势,而后经过将父类实例做为子类原型,实现函数复用node

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

2.this指向
(1).this 指向有哪几种css3

1.默认绑定:全局环境中,this默认绑定到window。nginx

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)
3.不能转成数字
4.不能与其余类型的值进行运算
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
your symbol is ${sym}
// 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
大脑对于事情的计划方式是线性的、阻塞的、单线程的语义,可是回调表达异步流 程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度很大。难于理解的代码 是坏代码,会致使坏 bug。咱们须要一种更同步、更顺序、更阻塞的的方式来表达异步,就像咱们的大脑同样。
也是更重要的一点,回调会受到控制反转的影响,由于回调暗中把控制权交给第三 方(一般是不受你控制的第三方工具!)来调用你代码中的 continuation。能够发明一些特定逻辑来解决这些信任问题,可是其难度高于应有的水平,可能会产生更 笨重、更难维护的代码,而且缺乏足够的保护,其中的损害要直到你受到 bug 的影响才会 被发现。
咱们须要一个通用的方案来解决这些信任问题。无论咱们建立多少回调,这一方案都应可 以复用,且没有重复代码的开销。
(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等等,能够向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在何时进行调用的,就须要了解一下“事件流”的概念。
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。

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

IE只支持事件冒泡。
(2).什么是事件监听
addEventListener()方法,用于向指定元素添加事件句柄,它能够更简单的控制事件,语法为
element.addEventListener(event, function, useCapture);

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

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

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

target.addEventListener(type, listener, options: EventListenerOptions);
target.addEventListener(type, listener, useCapture: boolean);
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

(4). 事件委托以及冒泡原理
简介:事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,经过事件冒泡,父元素能够监听到子元素上事件的触发,经过判断事件发生元素DOM的类型,来作出不一样的响应。
举例:最经典的就是ul和li标签的事件监听,好比咱们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也能够有事件触发机制。
(5). 事件代理在捕获阶段的实际应用
能够在父元素层面阻止事件向子元素传播,也可代替子元素执行某些操做。
7.跨域
(1).CORS
CORS(Cross-Origin Resource Sharing,跨源资源共享) 背后的基本思想,就是使用自定义的 HTTP 头部 让浏览器与服务器进行沟通。
好比一个简单的使用 GET 或 POST 发送的请求,它没有自定义的头部,而主体内容是 text/plain。在 发送该请求时,须要给它附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端 口),以便服务器根据这个头部信息来决定是否给予响应。下面是 Origin 头部的一个示例:
Origin: http://www.nczonline.net
若是服务器认为这个请求能够接受,就在 Access-Control-Allow-Origin 头部中回发相同的源
信息(若是是公共资源,能够回发"*")。例如:
Access-Control-Allow-Origin: http://www.nczonline.net
若是没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常状况下,浏览器 会处理请求。注意,请求和响应都不包含 cookie 信息。
(2).IE
微软在 IE8 中引入了 XDR(XDomainRequest)类型。如下是 XDR 与 XHR 的一些不一样之 处。

cookie 不会随请求发送,也不会随响应返回。
只能设置请求头部信息中的 Content-Type 字段。
不能访问响应头部信息。
只支持GET和POST请求。

(3).其余浏览器
经过 XMLHttpRequest 对象实现了对 CORS 的原生支持

不能使用 setRequestHeader()设置自定义头部。
不能发送和接收 cookie。
调用 getAllResponseHeaders()方法总会返回空字符串。

(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须要后端配合返回指定格式的数据。

(5). 代理
起一个代理服务器,实现数据的转发
(6).利用 iframe

window.postMessage
Cross Frame(aba)
window.name

lovelock.coding.me/javascript/…
(7).window.postMessage
只支持到IE8及以上的IE浏览器,其余现代浏览器固然没有问题。
(8). child 与 parent 通讯
不受同源策略的限制

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

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

(9).chilid 与 child 通讯
有跨域问题,只适合站内不一样子域间的通讯(设置document.domain为同一级域名)
(10).Cross Frame
这是一个通用的方法,简单来讲是A iframe包含B iframe,在B iframe中调用了相关的接口,完成调用以后获取到结果,location.href到和A iframe位于同一个域的C iframe,在C iframe中调用A iframe中定义的方法,将B iframe中获取的结果做为参数传到要跳转的url后,在C iframe中经过location.search变量来获取变量。

(11).window.name
window对象的name属性是一个很特殊的属性,在设定了window.name以后,执行location.href跳转,window.name属性仍然不会发生变化,能够经过这种方式实现变量的传递。
8.Ajax
(1).实现一个Ajax
微信公众号:世界上有意思的事

var xhr = new XMLHttpRequest()
// 必须在调用 open()以前指定 onreadystatechange 事件处理程序才能确保跨浏览器兼容性
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
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状态

未初始化。还没有调用 open()方法。
启动。已经调用 open()方法,但还没有调用 send()方法。
发送。已经调用 send()方法,但还没有接收到响应。
接收。已经接收到部分响应数据。
完成。已经接收到所有响应数据,并且已经能够在客户端使用了。

(3).将原生的 ajax 封装成 promise
微信公众号:世界上有意思的事

const ajax = (url, method, async, data) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
// 已经接收到所有响应数据,并且已经能够在客户端使用了
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else if (xhr.status > 400) {
reject('发生错误')
}
}
}
xhr.open(url, method, async)
xhr.send(data || null)
})
}
9.垃圾回收
找出那些再也不继续使用的变 量,而后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预约的收集时间), 周期性地执行这一操做。
(1).标记清除
先全部都加上标记,再把环境中引用到的变量去除标记。剩下的就是没用的了
(2).引用计数
跟踪记录每 个值被引用的次数。清除引用次数为0的变量
⚠️会有循环引用问题 。循环引用若是大量存在就会致使内存泄露。
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({}, {
set:function(target,key,value,receiver){

}
})
能够监听动态增长的属性。例如 user.id = 1
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 操做符作了哪些事情
用 new 操做符调用构造函数实际上会经历如下 4 个步骤:

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.防抖节流
(1).节流
在 n 秒内只会执行一次,因此节流会稀释函数的执行频率
(2). 防抖
按最后一次算。好比说“中止输入5s后才发送请求”
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(){
EventUtil.addHandler(document, "mousedown", handleEvent);
EventUtil.addHandler(document, "mousemove", handleEvent);
EventUtil.addHandler(document, "mouseup", handleEvent);
},
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

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

sleep(1000).then(()=>{
console.log('已经 sleep 1000ms')
})

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')
}

// 使用 generator 定义 sleep 函数
function *sleep (ms) {
yield new Promise((resolve) => {
window.setTimeout(resolve, ms)
})
}
sleep(1000).next().value.then(()=>{
console.log('已经 sleep 1000ms')
})

4、浏览器
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 等,也都归于此类。

强制缓存 (也叫强缓存)
强制缓存直接减小请求数,是提高最大的缓存策略。 它的优化覆盖了请求、处理、响应三个阶段
能够形成强制缓存的字段是 Cache-control 和 Expires。

Expires:

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

Cache-control

HTTP1.1

优先级高

max-age:即最大有效时间
must-revalidate:若是超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效。
no-cache:虽然字面意思是“不要缓存”,但实际上仍是要求客户端缓存内容的,只是是否使用这个内容由后续的对比来决定。
no-store: 真正意义上的“不要缓存”。全部内容都不走缓存,包括强制和对比。
public:全部的内容均可以被缓存 (包括客户端和代理服务器, 如 CDN)
private:全部的内容只有客户端才能够缓存,代理服务器不能缓存。默认值。

对比缓存 (协商缓存)
对比缓存在请求数上和没有缓存是一致的,但若是是 304 的话,返回的仅仅是一个状态码而已,并无实际的文件内容,所以 在响应体体积上的节省是它的优化点。

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 解决浏览器缓存问题

1.在ajax发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。

2.在ajax发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。

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布局的缘由之一。

(2).重绘
因为节点的几何属性发生改变或者因为样式发生改变而不会影响布局的,称为重绘,例如outline, visibility, color、background-color等,重绘的代价是高昂的,由于浏览器必须验证DOM树上其余节点元素的可见性。
(3)回流
回流是布局或者几何属性须要改变就称为回流。回流是影响浏览器性能的关键因素,由于其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会致使了其全部子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流。
(4).浏览器优化
现代浏览器大多都是经过队列机制来批量更新布局,浏览器会把修改操做放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操做,即便没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。
主要包括如下属性或方法:

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 何时解析?

<script>

渲染过程当中,若是遇到 JS 就中止渲染,执行 JS 代码。

若是 JS 须要操做CSSOM,则会先让CSSOM构建完,再执行JS,最后构建DOM

<script async>

异步执行引入的 JavaScript,加载完成后就执行 JS,阻塞DOM

<script defer>

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

5、计算机基础
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 经常使用请求头
能够将http首部分为通用首部,请求首部,响应首部,实体首部

协议头
说明
Accept
可接受的响应内容类型(Content-Types)。
Accept-Charset
可接受的字符集
Accept-Encoding
可接受的响应内容的编码方式。
Accept-Language
可接受的响应内容语言列表。
Accept-Datetime
可接受的按照时间来表示的响应内容版本
Authorization
用于表示HTTP协议中须要认证资源的认证信息
Cache-Control
用来指定当前的请求/回复中的,是否使用缓存机制。
Connection
客户端(浏览器)想要优先使用的链接类型
Cookie
由以前服务器经过Set-Cookie(见下文)设置的一个HTTP协议Cookie
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
浏览器的身份标识字符串

Upgrade
要求服务器升级到一个高版本协议。

Via
告诉服务器,这个请求是由哪些代理发出的。

Warning
一个通常性的警告,表示在实体内容体中可能存在错误。

(5).OSI 七层模型
应用层:文件传输,经常使用协议HTTP,snmp,FTP ,
表示层:数据格式化,代码转换,数据加密,
会话层:创建,解除会话
传输层:提供端对端的接口,tcp,udp
网络层:为数据包选择路由,IP,icmp
数据链路层:传输有地址的帧
物理层:二进制的数据形式在物理媒体上传输数据
(5).TCP和UDP的区别

1.UDP

1.无链接
2.面向报文,只是报文的搬运工
3.不可靠,没有拥塞控制
4.高效,头部开销只有8字节
5.支持一对1、一对多、多对多、多对一
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共用端口和证书)、建立链接时的校验、从数据帧中自动拆分消息包等等。
换句话说,本来咱们在浏览器里只能使用HTTP协议,如今有了Socket,仍是个更好用的Socket。
了解了WebSocket的背景和特性以后,就能够回答它能不能取代AJAX这个问题了:
对于服务器与客户端的双向通讯,WebSocket简直是不二之选。若是不是还有少数旧版浏览器尚在服役的话,全部的轮询、长链接等方式早就该废弃掉。那些整合多种双向推送消息方式的库(如http://Socket.IO、SignalR)当初最大的卖点就是兼容全部浏览器版本,自动识别旧版浏览器并采起不一样的链接方式,如今也渐渐失去了优点——全部新版浏览器都兼容WebSocket,直接用原生的就好了。说句题外话,这点很像jQuery,在原生js难用时迅速崛起,当其余库和原生js都吸取了它的不少优点时,慢慢就不那么重要了。可是,很大一部分AJAX的使用场景仍然是传统的请求-响应形式,好比获取json数据、post表单之类。这些功能虽然靠WebSocket也能实现,但就像在本来传输数据流的TCP之上定义了基于请求的HTTP协议同样,咱们也要在WebSocket之上从新定义一种新的协议,最少也要加个request id用来区分每次响应数据对应的请求吧。
……可是,何苦一层叠一层地造个新轮子呢?直接使用AJAX不是更简单、更成熟吗?
另外还有一种状况,也就是传输大文件、图片、媒体流的时候,最好仍是老老实实用HTTP来传。若是必定要用WebSocket的话,至少也专门为这些数据专门开辟个新通道,而别去占用那条用于推送消息、对实时性要求很强的链接。不然会把串行的WebSocket完全堵死的。
因此说,WebSocket在用于双向传输、推送消息方面可以作到灵活、简便、高效,但在普通的Request-Response过程当中并无太大用武之地,比起普通的HTTP请求来反倒麻烦了许多,甚至更为低效。
每项技术都有自身的优缺点,在适合它的地方能发挥出最大长处,而看到它的几个优势就不分场合地全方位推广的话,可能会拔苗助长。
咱们本身在开发能与手机通讯的互联网机器人时就使用了WebSocket,效果很好。但并非用它取代HTTP,而是取代了原先用于通讯的基于TCP的Socket。
优势是:
原先在Socket链接后还要进行一些复杂的身份验证,同时要阻止未验证的链接发送控制指令。如今不须要了,在创建WebSocket链接的url里就能携带身份验证参数,验证不经过能够直接拒绝,不用设置状态;
原先本身实现了一套相似SSL的非对称加密机制,如今彻底不须要了,直接经过wss加密,还能顺便保证证书的可信性;
原先要本身定义Socket数据格式,设置长度与标志,处理粘包、分包等问题,如今WebSocket收到的直接就是完整的数据包,彻底不用本身处理;
前端的nginx能够直接进行转发与负载均衡,部署简单多了
(8).TCP/IP的网络模型

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

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

3.链路层的做用是负责创建电路链接,是整个网络的物理基础,典型的协议包括以太网,ADSL等,

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

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

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

2.HTTP协议
(1).常见的请求方法
HTTP 1.0

1.GET:从指定的资源请求数据
2.POST:向指定的资源提交要被处理的数据,例如

1.提交表单
2.将消息发布到公告板,新闻组,邮件列表,博客或相似的文章组;

3.HEAD

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 消息流。

全部通用服务器必须支持GET和HEAD方法。全部其余方法都是可选的。

1.安全性:在此规范定义的请求方法中,GET,HEAD,OPTIONS和TRACE方法被定义为安全的
2.幂等性:PUT,DELETE和安全Method是幂等的。
3.可缓存性:GET, HEAD, and POST。但大多数是只实现GET和HEAD可缓存

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.套接字:可用于不一样机器之间的进程通讯

6、前端进阶
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。
(1).webpack 和 gulp 区别
gulp强调的是前端开发的工做流程,咱们能够经过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),而后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。
webpack是一个前端模块化方案,更侧重模块打包,咱们能够把开发中的全部资源(图片、js文件、css文件等)都当作模块,经过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。
3.模块化
(1).如何理解前端模块化
前端模块化就是复杂的文件编程一个一个独立的模块,好比js文件等等,分红独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,因此有了commonJS 规范,AMD,CMD规范等等,以及用于js打包(编译等处理)的工具 webpack
(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');
这样,eventEmitter发出say事件,经过On接收,而且输出结果,这就是一个订阅模式的实现,下面咱们来简单的实现一个Events模块的EventEmitter。

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;
}
}
}
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');
})

//event一、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。

相关文章
相关标签/搜索