async/await 如何捕捉异样
async function fn(){ try{ let a = await Promise.reject('error') }catch(error){ console.log(error) }}
Promise.reject
Promise.reject = function(reason) { return new Promise((resolve, reject) => reject(reason));}
如何进攻 XSS 攻打?
能够看到XSS危害如此之大, 那么在开发网站时就要做好进攻措施,具体措施如下:
- 能够从浏览器的执行来进行预防,一种是应用纯前端的形式,不必服务器端拼接后返回(不应用服务端渲染)。另一种是对须要插入到 HTML 中的代码做好充沛的本义。对于 DOM 型的攻打,次要是前端脚本的不牢靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能呈现的恶意代码状况进行判断。
- 应用 CSP ,CSP 的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行,从而避免恶意代码的注入攻打。
- CSP 指的是内容安全策略,它的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行。咱们只须要配置规定,如何拦挡由浏览器本人来实现。
- 通常有两种形式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的形式
- 对一些敏感信息进行爱护,比方 cookie 应用 http-only,使得脚本无奈获取。也能够应用验证码,防止脚本伪装成用户执行一些操作。
浏览器渲染过程的线程有哪些
浏览器的渲染过程的线程总共有五种: (1)GUI渲染线程 负责渲染浏览器页面,解析HTML、CSS,构建DOM树、构建CSSOM树、构建渲染树和绘制页面;当界面须要重绘或因为某种操作引发回流时,该线程就会执行。
留神:GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保留在一个队列中等到JS引擎闲暇时立刻被执行。
(2)JS引擎线程 JS引擎线程也称为JS内核,负责解决Javascript脚本程序,解析Javascript脚本,运行代码;JS引擎线程始终期待着工作队列中工作的到来,而后加以解决,一个Tab页中无论什么时候都只有一个JS引擎线程在运行JS程序;
留神:GUI渲染线程与JS引擎线程的互斥关系,所以如果JS执行的工夫过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。
(3)工夫触发线程 工夫触发线程属于浏览器而不是JS引擎,用来管制事件循环;当JS引擎执行代码块如setTimeOut时(也可是来自浏览器内核的其余线程,如鼠标点击、AJAX异步申请等),会将对应工作增加到事件触发线程中;当对应的事件合乎触发条件被触发时,该线程会把事件增加到待处理队列的队尾,期待JS引擎的解决;
留神:因为JS的单线程关系,所以这些待处理队列中的事件都得排队期待JS引擎解决(当JS引擎闲暇时才会去执行);
(4)定时器触发过程 定时器触发过程即setInterval与setTimeout所在线程;浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;因而应用独自线程来计时并触发定时器,计时结束后,增加到事件队列中,期待JS引擎闲暇后执行,所以定时器中的工作在设定的工夫点不肯定可能准时执行,定时器只是在指定工夫点将工作增加到事件队列中;
留神:W3C在HTML规范中规定,定时器的定时工夫不能小于4ms,如果是小于4ms,则默认为4ms。
(5)异步http申请线程
- XMLHttpRequest连贯后通过浏览器新开一个线程申请;
- 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,期待JS引擎闲暇后执行;
Vue通信
1.props和$emit2.地方事件总线 EventBus(根本不必)3.vuex(官网举荐状态管理器)4.$parent和$children当然还有一些其余方法,但根本不罕用,或者用起来太简单来。 介绍来通信的形式,还能够扩大说一下应用场景,如何应用,注意事项之类的。
什么是原型什么是原型链?
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body></body><script> function Person () { } var person = new Person(); person.name = 'Kevin'; console.log(person.name) // Kevin // prototype function Person () { } Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name)// Kevin console.log(person2.name)// Kevin // __proto__ function Person () { } var person = new Person(); console.log(person.__proto__ === Person.prototype) // true //constructor function Person() { } console.log(Person === Person.prototype.constructor) // true //综上所述 function Person () { } var person = new Person() console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true //顺便学习一下ES5得办法,能够取得对象得原型 console.log(Object.getPrototypeOf(person) === Person.prototype) // true //实例与原型 function Person () { } Person.prototype.name = 'Kevin'; var person = new Person(); person.name = 'Daisy'; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin //原型得原型 var obj = new Object(); obj.name = 'Kevin', console.log(obj.name) //Kevin //原型链 console.log(Object.prototype.__proto__ === null) //true // null 示意"没用对象" 即该处不应该有值 // 补充 function Person() { } var person = new Person() console.log(person.constructor === Person) // true //当获取person.constructor时,其实person中并没有constructor属性,当不能读取到constructor属性时,会从person的原型 //也就是Person.prototype中读取时,正好原型中有该属性,所以 person.constructor === Person.prototype.constructor //__proto__ //其次是__proto__,绝大部分浏览器都反对这个非标准的办法拜访原型,然而它并不存在于Person.prototype中,实际上,它 // 是来自与Object.prototype,与其说是一个属性,不如说是一个getter/setter,当应用obj.__proto__时,能够了解成返回了 // Object.getPrototypeOf(obj) 总结: 1、当一个对象查找属性和办法时会从本身查找,如果查找不到则会通过__proto__指向被实例化的构造函数的prototype 2、隐式原型也是一个对象,是指向咱们构造函数的原型 3、除了最顶层的Object对象没有__proto_,其余所有的对象都有__proto__,这是隐式原型 4、隐式原型__proto__的作用是让对象通过它来始终往上查找属性或办法,直到找到最顶层的Object的__proto__属性,它的值是null,这个查找的过程就是原型链</script></html>
首屏和白屏工夫如何计算
首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是onPageFinished事件。
白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。
办法1:当页面的元素数小于x时,则认为页面白屏。比方“没有任何内容”,能够获取页面的DOM节点数,判断DOM节点数少于某个阈值X,则认为白屏。 办法2:当页面呈现业务定义的错误码时,则认为是白屏。比方“网络或服务异样”。 办法3:当页面呈现业务定义的特征值时,则认为是白屏。比方“数据加载中”。
如何提⾼webpack的打包速度?
(1)优化 Loader
对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,而后对 AST 持续进行转变最初再生成新的代码,我的项目越大,转换代码越多,效率就越低。当然了,这是能够优化的。
首先咱们优化 Loader 的文件搜寻范畴
module.exports = { module: { rules: [ { // js 文件才应用 babel test: /\.js$/, loader: 'babel-loader', // 只在 src 文件夹下查找 include: [resolve('src')], // 不会去查找的门路 exclude: /node_modules/ } ] }}
对于 Babel 来说,心愿只作用在 JS 代码上的,而后 node_modules
中应用的代码都是编译过的,所以齐全没有必要再去解决一遍。
当然这样做还不够,还能够将 Babel 编译过的文件缓存起来,下次只须要编译更改过的代码文件即可,这样能够大幅度放慢打包工夫
loader: 'babel-loader?cacheDirectory=true'
(2)HappyPack
受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特地是在执行 Loader 的时候,长时间编译的工作很多,这样就会导致期待的状况。
HappyPack 能够将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来放慢打包效率了
module: { loaders: [ { test: /\.js$/, include: [resolve('src')], exclude: /node_modules/, // id 前面的内容对应上面 loader: 'happypack/loader?id=happybabel' } ]},plugins: [ new HappyPack({ id: 'happybabel', loaders: ['babel-loader?cacheDirectory'], // 开启 4 个线程 threads: 4 })]
(3)DllPlugin
DllPlugin 能够将特定的类库提前打包而后引入。这种形式能够极大的缩小打包类库的次数,只有当类库更新版本才有须要从新打包,并且也实现了将公共代码抽离成独自文件的优化计划。DllPlugin的应用办法如下:
// 独自配置在一个文件中// webpack.dll.conf.jsconst path = require('path')const webpack = require('webpack')module.exports = { entry: { // 想对立打包的类库 vendor: ['react'] }, output: { path: path.join(__dirname, 'dist'), filename: '[name].dll.js', library: '[name]-[hash]' }, plugins: [ new webpack.DllPlugin({ // name 必须和 output.library 统一 name: '[name]-[hash]', // 该属性须要与 DllReferencePlugin 中统一 context: __dirname, path: path.join(__dirname, 'dist', '[name]-manifest.json') }) ]}
而后须要执行这个配置文件生成依赖文件,接下来须要应用 DllReferencePlugin
将依赖文件引入我的项目中
// webpack.conf.jsmodule.exports = { // ...省略其余配置 plugins: [ new webpack.DllReferencePlugin({ context: __dirname, // manifest 就是之前打包进去的 json 文件 manifest: require('./dist/vendor-manifest.json'), }) ]}
(4)代码压缩
在 Webpack3 中,个别应用 UglifyJS
来压缩代码,然而这个是单线程运行的,为了放慢效率,能够应用 webpack-parallel-uglify-plugin
来并行运行 UglifyJS
,从而提高效率。
在 Webpack4 中,不须要以上这些操作了,只须要将 mode
设置为 production
就能够默认开启以上性能。代码压缩也是咱们必做的性能优化计划,当然咱们不止能够压缩 JS 代码,还能够压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,咱们还能够通过配置实现比方删除 console.log
这类代码的性能。
(5)其余
能够通过一些小的优化点来放慢打包速度
resolve.extensions
:用来表明文件后缀列表,默认查找程序是['.js', '.json']
,如果你的导入文件没有增加后缀就会依照这个程序查找文件。咱们应该尽可能减少后缀列表长度,而后将呈现频率高的后缀排在后面resolve.alias
:能够通过别名的形式来映射一个门路,能让 Webpack 更快找到门路module.noParse
:如果你确定一个文件下没有其余依赖,就能够应用该属性让 Webpack 不扫描该文件,这种形式对于大型的类库很有帮忙
setTimeout 模仿 setInterval
形容:应用setTimeout
模仿实现setInterval
的性能。
实现:
const mySetInterval(fn, time) { let timer = null; const interval = () => { timer = setTimeout(() => { fn(); // time 工夫之后会执行真正的函数fn interval(); // 同时再次调用interval自身 }, time) } interval(); // 开始执行 // 返回用于敞开定时器的函数 return () => clearTimeout(timer);}// 测试const cancel = mySetInterval(() => console.log(1), 400);setTimeout(() => { cancel();}, 1000); // 打印两次1
Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行程序?
Node 中的 Event Loop 和浏览器中的是齐全不雷同的货色。
Node 的 Event Loop 分为 6 个阶段,它们会依照程序重复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到零碎设定的阈值,就会进入下一阶段。
(1)Timers(计时器阶段):首次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(蕴含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Pending callbacks 阶段。
(2)Pending callbacks:执行推延到下一个循环迭代的I / O回调(零碎调用相干的回调)。
(3)Idle/Prepare:仅供外部应用。
(4)Poll(轮询阶段):
- 当回调队列不为空时:会执行回调,若回调中触发了相应的微工作,这里的微工作执行机会和其余中央有所不同,不会等到所有回调执行结束后才执行,而是针对每一个回调执行结束后,就执行相应微工作。执行完所有的回调后,变为上面的状况。
- 当回调队列为空时(没有回调或所有回调执行结束):但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会完结轮询阶段,进入 Check 阶段。否则会阻塞并期待任何正在执行的I/O操作实现,并马上执行相应的回调,直到所有回调执行结束。
(5)Check(查问阶段):会查看是否存在 setImmediate 相干的回调,如果存在则执行所有回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Close callbacks 阶段。
(6)Close callbacks:执行一些敞开回调,比方socket.on('close', ...)等。
上面来看一个例子,首先在有些状况下,定时器的执行程序其实是随机的
setTimeout(() => { console.log('setTimeout')}, 0)setImmediate(() => { console.log('setImmediate')})
对于以上代码来说,setTimeout
可能执行在前,也可能执行在后
- 首先
setTimeout(fn, 0) === setTimeout(fn, 1)
,这是由源码决定的 - 进入事件循环也是须要老本的,如果在筹备时候破费了大于 1ms 的工夫,那么在 timer 阶段就会间接执行
setTimeout
回调 - 那么如果筹备工夫破费小于 1ms,那么就是
setImmediate
回调先执行了
当然在某些状况下,他们的执行程序肯定是固定的,比方以下代码:
const fs = require('fs')fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0) setImmediate(() => { console.log('immediate') })})
在上述代码中,setImmediate
永远先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行结束后队列为空,发现存在 setImmediate
回调,所以就间接跳转到 check 阶段去执行回调了。
下面都是 macrotask 的执行状况,对于 microtask 来说,它会在以上每个阶段实现前清空 microtask 队列,
setTimeout(() => { console.log('timer21')}, 0)Promise.resolve().then(function() { console.log('promise1')})
对于以上代码来说,其实和浏览器中的输入是一样的,microtask 永远执行在 macrotask 后面。
最初来看 Node 中的 process.nextTick
,这个函数其实是独立于 Event Loop 之外的,它有一个本人的队列,当每个阶段实现后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其余 microtask 执行。
setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') })}, 0)process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') }) }) })})
对于以上代码,永远都是先把 nextTick 全副打印进去。
什么是中间人攻打?如何防备中间人攻打?
两头⼈ (Man-in-the-middle attack, MITM) 是指攻击者与通信的两端别离创立独⽴的分割, 并替换其所收到的数据, 使通信的两端认为他们正在通过⼀个私密的连贯与对⽅直接对话, 但事实上整个会话都被攻击者齐全管制。在两头⼈攻打中,攻击者能够拦挡通信双⽅的通话并插⼊新的内容。
攻打过程如下:
- 客户端发送申请到服务端,申请被两头⼈截获
- 服务器向客户端发送公钥
- 两头⼈截获公钥,保留在⾃⼰⼿上。而后⾃⼰⽣成⼀个伪造的公钥,发给客户端
- 客户端收到伪造的公钥后,⽣成加密hash值发给服务器
- 两头⼈取得加密hash值,⽤⾃⼰的私钥解密取得真秘钥,同时⽣成假的加密hash值,发给服务器
- 服务器⽤私钥解密取得假密钥,而后加密数据传输给客户端
Promise.all
形容:所有 promise
的状态都变成 fulfilled
,就会返回一个状态为 fulfilled
的数组(所有promise
的 value
)。只有有一个失败,就返回第一个状态为 rejected
的 promise
实例的 reason
。
实现:
Promise.all = function(promises) { return new Promise((resolve, reject) => { if(Array.isArray(promises)) { if(promises.length === 0) return resolve(promises); let result = []; let count = 0; promises.forEach((item, index) => { Promise.resolve(item).then( value => { count++; result[index] = value; if(count === promises.length) resolve(result); }, reason => reject(reason) ); }) } else return reject(new TypeError("Argument is not iterable")); });}
正向代理和反向代理的区别
- 正向代理:
客户端想取得一个服务器的数据,然而因为种种原因无奈间接获取。于是客户端设置了一个代理服务器,并且指定指标服务器,之后代理服务器向指标服务器转交申请并将取得的内容发送给客户端。这样实质上起到了对实在服务器暗藏实在客户端的目标。实现正向代理须要批改客户端,比方批改浏览器配置。
- 反向代理:
服务器为了可能将工作负载分不到多个服务器来进步网站性能 (负载平衡)等目标,当其受到申请后,会首先依据转发规定来确定申请应该被转发到哪个服务器上,而后将申请转发到对应的实在服务器上。这样实质上起到了对客户端暗藏实在服务器的作用。
个别应用反向代理后,须要通过批改 DNS 让域名解析到代理服务器 IP,这时浏览器无奈察觉到真正服务器的存在,当然也就不须要批改配置了。
正向代理和反向代理的构造是一样的,都是 client-proxy-server 的构造,它们次要的区别就在于两头这个 proxy 是哪一方设置的。在正向代理中,proxy 是 client 设置的,用来暗藏 client;而在反向代理中,proxy 是 server 设置的,用来暗藏 server。
说一下 web worker
在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行实现后,页面才变成可相应。web worker 是运行在后盾的 js,独立于其余脚本,不会影响页面的性能。 并且通过 postMessage 将后果回传到主线程。这样在进行简单操作的时候,就不会阻塞主线程了。
如何创立 web worker:
- 检测浏览器对于 web worker 的支持性
- 创立 web worker 文件(js,回传函数等)
- 创立 web worker 对象
懒加载的概念
懒加载也叫做提早加载、按需加载,指的是在长网页中提早加载图片数据,是一种较好的网页性能优化的形式。在比拟长的网页或利用中,如果图片很多,所有的图片都被加载进去,而用户只能看到可视窗口的那一部分图片数据,这样就节约了性能。
如果应用图片的懒加载就能够解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,缩小了服务器的负载。懒加载实用于图片较多,页面列表较长(长列表)的场景中。
说一说SessionStorage和localStorage还有cookie
共同点:都是保留在浏览器端、且同源的不同点: 1.cookie数据始终在同源的http申请中携带(即便不须要),即cookie在浏览器和服务器间来回传递。 cookie数据还有门路(path)的概念,能够限度cookie只属于某个门路下 sessionStorage和localStorage不会主动把数据发送给服务器,仅在本地保留。 2.存储大小限度也不同,cookie数据不能超过4K,sessionStorage和localStorage能够达到5M 3.sessionStorage:仅在以后浏览器窗口敞开之前无效; localStorage:始终无效,窗口或浏览器敞开也始终保留,本地存储,因而用作持久数据; cookie:只在设置的cookie过期工夫之前无效,即便窗口敞开或浏览器敞开 4.作用域不同 sessionStorage:不在不同的浏览器窗口中共享,即便是同一个页面; localstorage:在所有同源窗口中都是共享的;也就是说只有浏览器不敞开,数据依然存在 cookie: 也是在所有同源窗口中都是共享的.也就是说只有浏览器不敞开,数据依然存在
说一说你用过的css布局
gird布局,layout布局,flex布局,双飞翼,圣杯布局等
如何解决逾越问题
(1)CORS
上面是MDN对于CORS的定义:
跨域资源共享(CORS) 是一种机制,它应用额定的 HTTP 头来通知浏览器 让运行在一个 origin (domain)上的Web利用被准许拜访来自不同源服务器上的指定的资源。当一个资源从与该资源自身所在的服务器不同的域、协定或端口申请一个资源时,资源会发动一个跨域HTTP 申请。
CORS须要浏览器和服务器同时反对,整个CORS过程都是浏览器实现的,无需用户参加。因而实现CORS的要害就是服务器,只有服务器实现了CORS申请,就能够跨源通信了。
浏览器将CORS分为简略申请和非简略申请:
简略申请不会触发CORS预检申请。若该申请满足以下两个条件,就能够看作是简略申请:
1)申请办法是以下三种办法之一:
- HEAD
- GET
- POST
2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
若不满足以上条件,就属于非简略申请了。
(1)简略申请过程:
对于简略申请,浏览器会间接收回CORS申请,它会在申请的头信息中减少一个Orign字段,该字段用来阐明本次申请来自哪个源(协定+端口+域名),服务器会依据这个值来决定是否批准这次申请。如果Orign指定的域名在许可范畴之内,服务器返回的响应就会多出以下信息头:
Access-Control-Allow-Origin: http://api.bob.com // 和Orign始终Access-Control-Allow-Credentials: true // 示意是否容许发送CookieAccess-Control-Expose-Headers: FooBar // 指定返回其余字段的值Content-Type: text/html; charset=utf-8 // 示意文档类型
如果Orign指定的域名不在许可范畴之内,服务器会返回一个失常的HTTP回应,浏览器发现没有下面的Access-Control-Allow-Origin头部信息,就晓得出错了。这个谬误无奈通过状态码辨认,因为返回的状态码可能是200。
在简略申请中,在服务器内,至多须要设置字段:Access-Control-Allow-Origin
(2)非简略申请过程
非简略申请是对服务器有特殊要求的申请,比方申请办法为DELETE或者PUT等。非简略申请的CORS申请会在正式通信之前进行一次HTTP查问申请,称为预检申请。
浏览器会询问服务器,以后所在的网页是否在服务器容许拜访的范畴内,以及能够应用哪些HTTP申请形式和头信息字段,只有失去必定的回复,才会进行正式的HTTP申请,否则就会报错。
预检申请应用的申请办法是OPTIONS,示意这个申请是来询问的。他的头信息中的关键字段是Orign,示意申请来自哪个源。除此之外,头信息中还包含两个字段:
- Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS申请会用到哪些HTTP办法。
- Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS申请会额定发送的头信息字段。
服务器在收到浏览器的预检申请之后,会依据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是容许跨域申请,如果没有,就是不批准这个预检申请,就会报错。
服务器回应的CORS的字段如下:
Access-Control-Allow-Origin: http://api.bob.com // 容许跨域的源地址Access-Control-Allow-Methods: GET, POST, PUT // 服务器反对的所有跨域申请的办法Access-Control-Allow-Headers: X-Custom-Header // 服务器反对的所有头信息字段Access-Control-Allow-Credentials: true // 示意是否容许发送CookieAccess-Control-Max-Age: 1728000 // 用来指定本次预检申请的有效期,单位为秒
只有服务器通过了预检申请,在当前每次的CORS申请都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
在非简略申请中,至多须要设置以下字段:
'Access-Control-Allow-Origin' 'Access-Control-Allow-Methods''Access-Control-Allow-Headers'
缩小OPTIONS申请次数:
OPTIONS申请次数过多就会损耗页面加载的性能,升高用户体验度。所以尽量要缩小OPTIONS申请次数,能够后端在申请的返回头部增加:Access-Control-Max-Age:number。它示意预检申请的返回后果能够被缓存多久,单位是秒。该字段只对齐全一样的URL的缓存设置失效,所以设置了缓存工夫,在这个工夫范畴内,再次发送申请就不须要进行预检申请了。
CORS中Cookie相干问题:
在CORS申请中,如果想要传递Cookie,就要满足以下三个条件:
- 在申请中设置
withCredentials
默认状况下在跨域申请,浏览器是不带 cookie 的。然而咱们能够通过设置 withCredentials 来进行传递 cookie.
// 原生 xml 的设置形式var xhr = new XMLHttpRequest();xhr.withCredentials = true;// axios 设置形式axios.defaults.withCredentials = true;
- Access-Control-Allow-Credentials 设置为 true
- Access-Control-Allow-Origin 设置为非
*
(2)JSONP
jsonp的原理就是利用<script>
标签没有跨域限度,通过<script>
标签src属性,发送带有callback参数的GET申请,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
1)原生JS实现:
<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参一个回调函数名给后端,不便后端返回时执行这个在前端定义的回调函数 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { alert(JSON.stringify(res)); } </script>
服务端返回如下(返回时即执行全局函数):
handleCallback({"success": true, "user": "admin"})
2)Vue axios实现:
this.$http = axios;this.$http.jsonp('http://www.domain2.com:8080/login', { params: {}, jsonp: 'handleCallback'}).then((res) => { console.log(res); })
后端node.js代码:
var querystring = require('querystring');var http = require('http');var server = http.createServer();server.on('request', function(req, res) { var params = querystring.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回设置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end();});server.listen('8080');console.log('Server is running at port 8080...');
JSONP的毛病:
- 具备局限性, 仅反对get办法
- 不平安,可能会蒙受XSS攻打
(3)postMessage 跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多能够跨域操作的window属性之一,它可用于解决以下方面的问题:
- 页面和其关上的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 下面三个场景的跨域数据传递
用法:postMessage(data,origin)办法承受两个参数:
- data: html5标准反对任意根本类型或可复制的对象,但局部浏览器只反对字符串,所以传参时最好用JSON.stringify()序列化。
- origin: 协定+主机+端口号,也能够设置为"*",示意能够传递给任意窗口,如果要指定和以后窗口同源的话设置为"/"。
1)a.html:(domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe><script> var iframe = document.getElementById('iframe'); iframe.onload = function() { var data = { name: 'aym' }; // 向domain2传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); }; // 承受domain2返回数据 window.addEventListener('message', function(e) { alert('data from domain2 ---> ' + e.data); }, false);</script>
2)b.html:(domain2.com/b.html)
<script> // 接管domain1的数据 window.addEventListener('message', function(e) { alert('data from domain1 ---> ' + e.data); var data = JSON.parse(e.data); if (data) { data.number = 16; // 解决后再发回domain1 window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); } }, false);</script>
(4)nginx代理跨域
nginx代理跨域,本质和CORS跨域原理一样,通过配置文件设置申请响应头Access-Control-Allow-Origin…等字段。
1)nginx配置解决iconfont跨域
浏览器跨域拜访js、css、img等惯例动态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的动态资源服务器中退出以下配置。
location / { add_header Access-Control-Allow-Origin *;}
2)nginx反向代理接口跨域
跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是应用HTTP协定,不须要同源策略,也就不存在跨域问题。
实现思路:通过Nginx配置一个代理服务器域名与domain1雷同,端口不同)做跳板机,反向代理拜访domain2接口,并且能够顺便批改cookie中domain信息,不便以后域cookie写入,实现跨域拜访。
nginx具体配置:
#proxy服务器server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #批改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口拜访nignx时,此时无浏览器参加,故没有同源限度,上面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #以后端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; }}
(5)nodejs 中间件代理跨域
node中间件实现跨域代理,原理大抵与nginx雷同,都是通过启一个代理服务器,实现数据的转发,也能够通过设置cookieDomainRewrite参数批改响应头中cookie中域名,实现以后域的cookie写入,不便接口登录认证。
1)非vue框架的跨域 应用node + express + http-proxy-middleware搭建一个proxy服务器。
- 前端代码:
var xhr = new XMLHttpRequest();// 前端开关:浏览器是否读写cookiexhr.withCredentials = true;// 拜访http-proxy-middleware代理服务器xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);xhr.send();
- 中间件服务器代码:
var express = require('express');var proxy = require('http-proxy-middleware');var app = express();app.use('/', proxy({ // 代理跨域指标接口 target: 'http://www.domain2.com:8080', changeOrigin: true, // 批改响应头信息,实现跨域并容许带cookie onProxyRes: function(proxyRes, req, res) { res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); res.header('Access-Control-Allow-Credentials', 'true'); }, // 批改响应信息中的cookie域名 cookieDomainRewrite: 'www.domain1.com' // 能够为false,示意不批改}));app.listen(3000);console.log('Proxy server is listen at port 3000...');
2)vue框架的跨域
node + vue + webpack + webpack-dev-server搭建的我的项目,跨域申请接口,间接批改webpack.config.js配置。开发环境下,vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域。
webpack.config.js局部配置:
module.exports = { entry: {}, module: {}, ... devServer: { historyApiFallback: true, proxy: [{ context: '/login', target: 'http://www.domain2.com:8080', // 代理跨域指标接口 changeOrigin: true, secure: false, // 当代理某些https服务报错时用 cookieDomainRewrite: 'www.domain1.com' // 能够为false,示意不批改 }], noInfo: true }}
(6)document.domain + iframe跨域
此计划仅限主域雷同,子域不同的跨域利用场景。实现原理:两个页面都通过js强制设置document.domain为根底主域,就实现了同域。
1)父窗口:(domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe><script> document.domain = 'domain.com'; var user = 'admin';</script>
1)子窗口:(child.domain.com/a.html)
<script> document.domain = 'domain.com'; // 获取父窗口中变量 console.log('get js data from parent ---> ' + window.parent.user);</script>
(7)location.hash + iframe跨域
实现原理:a欲与b跨域互相通信,通过两头页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,雷同域之间间接js拜访来通信。
具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent拜访a页面所有对象。
1)a.html:(domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe><script> var iframe = document.getElementById('iframe'); // 向b.html传hash值 setTimeout(function() { iframe.src = iframe.src + '#user=admin'; }, 1000); // 凋谢给同域c.html的回调办法 function onCallback(res) { alert('data from c.html ---> ' + res); }</script>
2)b.html:(.domain2.com/b.html)
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe><script> var iframe = document.getElementById('iframe'); // 监听a.html传来的hash值,再传给c.html window.onhashchange = function () { iframe.src = iframe.src + location.hash; };</script>
<script> // 监听b.html传来的hash值 window.onhashchange = function () { // 再通过操作同域a.html的js回调,将后果传回 window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); };</script>
(8)window.name + iframe跨域
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后仍旧存在,并且能够反对十分长的 name 值(2MB)。
1)a.html:(domain1.com/a.html)
var proxy = function(url, callback) { var state = 0; var iframe = document.createElement('iframe'); // 加载跨域页面 iframe.src = url; // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy页)胜利后,读取同域window.name中数据 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域页)胜利后,切换到同域代理页面 iframe.contentWindow.location = 'http://www.domain1.com/proxy.html'; state = 1; } }; document.body.appendChild(iframe); // 获取数据当前销毁这个iframe,开释内存;这也保障了平安(不被其余域frame js拜访) function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); }};// 申请跨域b页面数据proxy('http://www.domain2.com/b.html', function(data){ alert(data);});
2)proxy.html:(domain1.com/proxy.html)
两头代理页,与a.html同域,内容为空即可。
3)b.html:(domain2.com/b.html)
<script> window.name = 'This is domain2 data!';</script>
通过iframe的src属性由外域转向本地区,跨域数据即由iframe的window.name从外域传递到本地区。这个就奇妙地绕过了浏览器的跨域拜访限度,但同时它又是平安操作。
(9)WebSocket协定跨域
WebSocket protocol是HTML5一种新的协定。它实现了浏览器与服务器全双工通信,同时容许跨域通信,是server push技术的一种很好的实现。
原生WebSocket API应用起来不太不便,咱们应用Socket.io,它很好地封装了webSocket接口,提供了更简略、灵便的接口,也对不反对webSocket的浏览器提供了向下兼容。
1)前端代码:
<div>user input:<input type="text"></div><script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script><script>var socket = io('http://www.domain2.com:8080');// 连贯胜利解决socket.on('connect', function() { // 监听服务端音讯 socket.on('message', function(msg) { console.log('data from server: ---> ' + msg); }); // 监听服务端敞开 socket.on('disconnect', function() { console.log('Server socket has closed.'); });});document.getElementsByTagName('input')[0].onblur = function() { socket.send(this.value);};</script>
2)Nodejs socket后盾:
var http = require('http');var socket = require('socket.io');// 启http服务var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end();});server.listen('8080');console.log('Server is running at port 8080...');// 监听socket连贯socket.listen(server).on('connection', function(client) { // 接管信息 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 断开解决 client.on('disconnect', function() { console.log('Client socket has closed.'); });});
闭包
首先阐明什么是闭包,闭包简略来说就是函数嵌套函数,外部函数援用来内部函数的变量,从而导致垃圾回收机制没有把以后变量回收掉,这样的操作带来了内存透露的影响,当内存透露到肯定水平会影响你的我的项目运行变得卡顿等等问题。因而在我的项目中咱们要尽量避免内存透露。
图片懒加载
实现:getBoundClientRect
的实现形式,监听 scroll
事件(倡议给监听事件增加节流),图片加载完会从 img
标签组成的 DOM 列表中删除,最初所有的图片加载结束后须要解绑监听事件。
// scr 加载默认图片,data-src 保留施行懒加载后的图片// <img src="./default.jpg" data-src="https://xxx.jpg" alt="" />let imgs = [...document.querySelectorAll("img")];const len = imgs.length;let lazyLoad = function() { let count = 0; let deleteImgs = []; // 获取以后可视区的高度 let viewHeight = document.documentElement.clientHeight; // 获取以后滚动条的地位(间隔顶部的间隔,等价于document.documentElement.scrollTop) let scrollTop = window.pageYOffset; imgs.forEach((img) => { // 获取元素的大小,及其绝对于视口的地位,如 bottom 为元素底部到网页顶部的间隔 let bound = img.getBoundingClientRect(); // 以后图片间隔网页顶部的间隔 // let imgOffsetTop = img.offsetTop; // 判断图片是否在可视区内,如果在就加载(两种判断形式) // if(imgOffsetTop < scrollTop + viewHeight) if (bound.top < viewHeight) { img.src = img.dataset.src; // 替换待加载的图片 src count++; deleteImgs.push(img); // 最初所有的图片加载结束后须要解绑监听事件 if(count === len) { document.removeEventListener("scroll", imgThrottle); } } }); // 图片加载完会从 `img` 标签组成的 DOM 列表中删除 imgs = imgs.filter((img) => !deleteImgs.includes(img));}window.onload = function () { lazyLoad();};// 应用 防抖/节流 优化一下滚动事件let imgThrottle = debounce(lazyLoad, 1000);// 监听 `scroll` 事件window.addEventListener("scroll", imgThrottle);