前端路由回退时,如果页面中还有资源在pending, 页面不会跟着立即跳转

资源型会 阻塞 回退, xhr fetch 不会阻塞;
解决
1. 通过replace 代替 回退
2. 路由变化时,
2.1watch:{
$route(to,from){
console.log(to.path);
}
},
2.2 window.addEventListener(‘popstate’, function() { // 监听回退按钮
console.log(‘我监听到了回退事件’); // 在回退时进行某种操作。
},false);

option 跨域 请求详解

什么情况下需要 CORS ?

跨域资源共享标准( cross-origin sharing standard )允许在下列场景中使用跨域 HTTP 请求:

功能概述

跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

简单请求

某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”:

预检请求 

与前述简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS   方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。 不属于简单请求都将有预检请求。

附带身份凭证的请求

Fetch 与 CORS 的一个有趣的特性是,可以基于  HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息cookie,需要设置 XMLHttpRequest 的某个特殊标志位。 如 设置 withCredentials 标志设置为 true

附带身份凭证的请求与通配符

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。

这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 http://foo.example,则请求将成功执行。

另外,响应首部中也携带了 Set-Cookie 字段,尝试对 Cookie 进行修改。如果操作失败,将会抛出异常。

preload 和 prefetch

preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源
prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源
prefetch ,它的作用是告诉浏览器加载下一页面可能会用到的资源,注意,是下一页面,而不是当前页面。因此该方法的加载优先级非常低,也就是说该方式的作用是加速下一个页面的加载速度

preload 好处:
1、将加载和执行分离开,不阻塞渲染和document的onload事件
2、提前加载指定资源,不再出现依赖的font字体隔了一段时间才刷出的情况
3、Preload 有 as 属性,浏览器可以设置正确的资源加载优先级,这种方式可以确保资源根据其重要性依次加载, 所以,Preload既不会影响重要资源的加载,又不会让次要资源影响自身的加载;浏览器能根据 as 的值发送适当的 Accept 头部信息;浏览器通过 as 值能得知资源类型,因此当获取的资源相同时,浏览器能够判断前面获取的资源是否能重用
4、如果忽略 as 属性,或者错误的 as 属性会使 preload 等同于 XHR 请求,浏览器不知道加载的是什么,因此会赋予此类资源非常低的加载优先级

preload 二次获取
1、如果对所 preload 的资源不使用明确的 “as” 属性,将会导致二次获取
2、 preload 字体不带 crossorigin 会二次获取! 确保对 preload 的字体添加 crossorigin 属性,否则字体文件会被下载两次,这个请求使用匿名的跨域模式。这个建议也适用于字体文件在相同域名下,也适用于其他域名的获取(比如说默认的异步获取)

webpack
webpack 4.6.0+ adds support for prefetching and preloading.

1 preload 加载平行于 主要加载项目, prefetch 在主要加载项完成后加载
2 preload 中等优先度,会立即下载, prefetch 会在空闲(idle)时下载
3 preload 内容 通过 父chunk 会被立即用到, prefetch 内容会在将来用到

prefetch 案例: HomePage component 里有 LoginBotton component , 点击 按钮是会加载LoginModal
LoginButton.js
[cc lang=”js”]import(/* webpackPrefetch: true */ ‘LoginModal’);[/cc]

result in

prelaod 案例: ChartComponent 需要 比较大的 ChartingLibrary, 加载完页面就需要用到ChartingLibrary 里的 LoadingIndicator
ChartComponent.js
[cc lang=”js”]import(/* webpackPreload: true */ ‘ChartingLibrary’);[/cc]

https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf
https://medium.com/webpack/link-rel-prefetch-preload-in-webpack-51a52358f84c

阻止父元素 滚动, 阻止下拉刷新

chrome 新 css 属性 overscroll-behavior:
auto – 默认。元素的滚动会传播给祖先元素。
contain – 阻止滚动链接。滚动不会传播给祖先,但会显示元素内的原生效果。
none – 和 contain 一样,但它也可以防止节点本身的滚动效果(例如 Android 炫光或 iOS 回弹)。

背景历史
滚动边界和滚动链接(scroll boundary & scroll chaining)
滚动到底部时,滚动停止,因为我们到达了”滚动边界”
在 Web 页面中滚动并不会停止,而是继续滚动抽屉后面的内容,我们称这种行为叫滚动链接(scroll chaining)

历史处理
浮层出现
body 加入 overflow: hidden;
浮层隐藏 css 取消

禁用下拉刷新 pull-to-refresh

[cc lang=”css”]body {
/* Disables pull-to-refresh but allows overscroll glow effects. */
overscroll-behavior-y: contain;
}[/cc]

禁用炫光和回弹效果

[cc lang=”css”]body {
/* 禁用默认的下拉刷新和边界效果
但是依然可以进行滑动导航 */
overscroll-behavior-y: none;
}[/cc]

async/await/promise/ setTimeout/ event loop

promise:
[cc lang=”javascript”]
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error(‘fail’)), 3000)
})

const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})

p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
[/cc]
上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

!!! 注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。如下
[cc lang=”js”]
new Promise((resolve, reject) => {
resolve(1); //这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
如果加上return语句就不会打印2
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
[/cc]
promise then 方法
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
then里的 回调函数完成以后,会将返回结果作为参数,传入下一次回调函数。如果回调函数返回promise,这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用
promise catch 方法
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数.
建议用catch 不要用then 第二个方法
!!! catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法

promise.all([ p1, p2, p3 ]).then().catch

promise.race([ p1, p2, p3]).then().catch
那个率先改变的 Promise 实例的返回值,就传递给then或catch的回调函数
[cc lang=”js”]
const p = Promise.race([
fetch(‘/resource-that-may-take-a-while’),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error(‘request timeout’)), 5000)
})
]);

p
.then(console.log)
.catch(console.error);
//上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数

[/cc]

1.async 函数返回的是一个 Promise 对象,async 函数没有返回值,它会返回 Promise.resolve(undefined)

2 await 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await就忙起来了,它会阻塞asycn函数内后面的代码,等着Promise对象resolve,然后得到 resolve 的值,作为 await 表达式的运算结果
3。1 js 执行机制 event loop
首先判断JS是同步还是异步,同步就进入主进程,异步就进入event table
异步任务在event table中注册函数,当满足触发条件后,被推入event queue
同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中
3.2event loop (2)
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick

!! 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
[cc lang=”javascript”]
async function async1() {
console.log( ‘async1 start’ )
await async2() // return promise 此时返回的Promise会被放入到回调队列中等待,await会让出线程
//等外面执行完成后再回到这生成promise.resolve(undefined) 又被放到micro-task, 再次让出线程跳出async
console.log( ‘async1 end’ )
}

async function async2() {
console.log( ‘async2’)
}

console.log( ‘script start’ )

setTimeout( function () {
console.log( ‘setTimeout’ )
}, 0 )

async1();

new Promise( function ( resolve ) {
console.log( ‘promise1’ )
resolve(); // 回调then 会放入micro-task 让出线程
console.log(34)
} ).then( function () {
console.log( ‘promise2’ )
return Promise.resolve();
} ).then(function(){console.log(11)
return Promise.resolve();
}).then(function(){console.log(22)})

console.log( ‘script end’ )

//script start
//async1 start
//async2
//promise1
// 34
//script end
//promise2
//async1 end
//11
//22
// setTimeout
[/cc]

javascript 事件流

DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段

1).尽管“DOM2级事件”标准规范明确规定事件捕获阶段不会涉及事件目标,但是在IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两次机会在目标对象上面操作事件。

2).并非所有的事件都会经过冒泡阶段 。所有的事件都要经过捕获阶段和处于目标阶段,但是有些事件会跳过冒泡阶段:如,获得输入焦点的focus事件和失去输入焦点的blur事件。

//事件代理同时绑定了li和span,当点击span的时候,li和span都会冒泡

//解决方法一、span的事件处理程序中阻止冒泡
$(document).on('click', 'span', function(e){
alert('li span');
e.stopPropagation();
})
//解决方法二 li的事件处理程序中检测target元素
$(document).on('click', 'li', function (e) {
if (e.target.nodeName == 'SPAN') {
e.stopPropagation();
return;
}
alert('li li');
});

注意: 1. dom.addEventListener中e.currentTarget 是指dom而 jQuery on 方法 e.currentTarget与该方法接收的第二个参数有关
对象this始终等于e.currentTarget的值,而e.target则只是包含事件的实际目标,
2. addEventListener(‘click’, handle, false), 第三个参数 是否在捕获阶段调用事件处理程序,
第三个参数 或者是 options 一个指定有关 listener 属性的可选参数对象。可用的选项如下:
capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
mozSystemGroup: 只能在 XBL 或者是 Firefox’ chrome 使用,这是个 Boolean,表示 listener 被添加到 system group。

分离出第三方库、自定义公共模块、webpack运行文件

1、抽离webpack运行文件

[cc lang=”javascript” tab-size=”2″]
entry: {
first: ‘./src/first.js’,
second: ‘./src/second.js’,
vendor: Object.keys(packagejson.dependencies)//获取生产环境依赖的库
},

//先来抽离webpack运行文件,修改webpack配置文件:

plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: [‘vendor’,’runtime’],
filename: ‘[name].js’
}),
]

[/cc]

其实上面这段代码,等价于下面这段:

[cc lang=”javascript” tab-size=”2″]

plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ‘vendor’,
filename: ‘[name].js’
}),
new webpack.optimize.CommonsChunkPlugin({
name: ‘runtime’,
filename: ‘[name].js’,
chunks: [‘vendor’]
}),
]

[/cc]

上面两段抽离webpack运行文件代码的意思是创建一个名为runtime的commons chunk进行webpack运行文件的抽离,其中source chunks是vendor.js。

2、抽离第三方库和自定义公共模块

要在vendor.js中把第三方库单独抽离出来,上面也说到了有两种方法。

第一种方法minChunks设为Infinity,修改webpack配置文件如下:

[cc lang=”javascript” tab-size=”2″]

plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: [‘vendor’,’runtime’],
filename: ‘[name].js’,
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: ‘common’,
filename: ‘[name].js’,
chunks: [‘first’,’second’]//从first.js和second.js中抽取commons chunk
}),
]

[/cc]

这时候的vendor.js就纯白无瑕,只包含第三方库文件,common.js就是自定义的公共模块,runtime.js就是webpack的运行文件。

第二种方法把它们分离开来,就是利用minChunks作为函数的时候,说一下minChunks作为函数两个参数的含义:
module:当前chunk及其包含的模块
count:当前chunk及其包含的模块被引用的次数
minChunks作为函数会遍历每一个入口文件及其依赖的模块,返回一个布尔值,为true代表当前正在处理的文件(module.resource)合并到commons chunk中,为false则不合并。

[cc lang=”javascript” tab-size=”2″]
const config = {
entry: {
first: ‘./src/first.js’,
second: ‘./src/second.js’,
//vendor: Object.keys(packagejson.dependencies)//获取生产环境依赖的库
},
output: {
path: path.resolve(__dirname,’./dist’),
filename: ‘[name].js’
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ‘vendor’,
filename: ‘[name].js’,
minChunks: function (module,count) {
console.log(module.resource,`引用次数${count}`);
//”有正在处理文件” + “这个文件是 .js 后缀” + “这个文件是在 node_modules 中”
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, ‘./node_modules’)) === 0
)
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: ‘runtime’,
filename: ‘[name].js’,
chunks: [‘vendor’]
}),
]
}

[/cc]

参考文章