EventLoop事件循环
同步异步编程
/*
* 进程和线程
* + 进程:可以理解为一个程序(浏览器打开一个页面就是开辟一个进程)
* + 线程:程序中具体干事的人
* 一个进程中可以包含很多的线程,一个线程同时只能做一件事情
*
* 同步编程:一件事一件事的去做,上一件事情没有完成,则无法处理下一个事情「单线程」
* 异步编程:上一件事情没有处理完成,则下一件事情可以继续去处理「多线程、基于单线程的EventLoop机制...」
*
* JS中的异步编程
* 「异步微任务」
* + promise
* + async/await 「generator」
* + requestAnimationFrame
* 「异步宏任务」
* + 定时器
* + ajax「HTTP网络请求」
* + 事件绑定
* + ...
*
* JS是单线程的,所以:
* 1. JS中大部分代码都是同步编程
* 2. 但是可以基于单线程的EventLoop(事件循环机制)实现出异步的效果
*
* 浏览器是多线程的,打开一个页面,浏览器会分配很多线程,同时处理一些事情
* + GUI渲染线程:自上而下渲染页面
* + JS引擎(渲染)线程:JS单线程是因为,浏览器只会开辟这一个线程,用来执行JS代码
* + HTTP网络请求线程:加载资源文件还是一些数据
* + 定时器监听线程:监听定时器是否到达时间
* + DOM事件监听线程:监听DOM事件的触发
* + ...
*/
setTimeout(() => {
console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
console.log(3);
}, 10);
console.log(4);
// console.time('AA');
for (let i = 0; i < 90000000; i++) {
// do soming
}
// console.timeEnd('AA'); //=>AA: 79ms 左右
console.log(5);
setTimeout(() => {
console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
console.log(8);
}, 15);
console.log(9);
setTimeout(() => {
console.log(1);
}, 0);
console.log(2);
while (1) {
// do somthing
}
console.log(3);
setTimeout(() => {
console.log(4);
}, 10);
console.log(5);
//========================
setTimeout(function () {
console.log(1);
}, 0); //异步宏任务 5ms function->1 //=>1
console.log(2); //=>2
// throw new Error('xxx');
console.log(a); //报错 Uncaught ReferenceError: a is not defined
console.log(3);
setTimeout(() => {
console.log(4);
}, 10);
setTimeout(function () {
console.log(1);
}, 0); //异步宏任务1 5ms function->1
console.log(2);
try {
console.log(a);
} catch (err) {}
console.log(3);
setTimeout(() => {
console.log(4);
}, 10); //异步宏任务1 10ms function->4
promise基础语法
/*
* ES6中新增一个内置类:Promise 承诺/约定模式,基于这种模式可以有效处理异步编程,可以解决异步编程中产生的”回调地狱“
*/
// 需求:首先从服务器端基于"/api/1"这个接口获取数据,紧接着把获取的某些数据作为参数,再基于"/api/2"接口获取其他数据;最后基于第二次请求的数据作为参数,再请求"/api/3"=>ajax串行
// 传统在实现异步操作,并且是串行的模式下,基本上都是回调函数嵌套回调函数,实现非常的恶心 ”回调地狱“
$.ajax({
url: "/api/1",
dataType: "json",
success(result){
$.ajax({
url: "/api/2",
dataType: "json",
success(result){
$.ajax({
url: "/api/3",
dataType: "json",
success(result){
}
})
}
})
}
})
设计模式就是按照一定思想有效管理代码,让代码可读性更高,维护性更强
// let p1 = new Promise(); //Uncaught TypeError: Promise resolver undefined is not a function 要求new的时候,必须传递一个函数才可以[executor]
// new Promise
// - 会立即执行传递的executor函数
// - 在executor函数中一般用来管控一个异步的操作(不写异步也可以)
// - 而且传递给executor函数两个参数:resolve reject,并且这两个参数都是函数
// - 会创建Promise类的一个实例p1
// - [[PromiseState]]promise状态:pending准备状态、fulfilled/resolved成功(兑现)、rejected失败(已拒绝)
// - [[PromiseResult]]promise值:默认是undefined,一般存储成功的结果或者失败的原因
// - p1.__proto__ -> Promise.prototype: then/catch/finally
let p1 = new Promise(function(resolve, reject){
// 执行resolve控制实例的状态为成功,传递的100是成功的结果
// resolve(100)
// 执行reject控制实例的状态为失败,传递的100是失败的原因
// reject(100)
// 如果executor代码执行报错,则实例的状态也会变为失败,并且实例的值是报错的原因
// 一但状态从pending变为resolved或者rejected,都无法再次改变其状态
})
// Promise是如何管控异步编程的?
// - new Promise的时候创建一个promise实例,此时在executor函数中管理一套异步的代码
// - 后期等异步操作成功或者失败的时候,执行resolve/reject,以此来控制promise实例的状态和结果
// - 根据状态和结果,就可以控制基于.then注入的两个方法中的哪一个去执行
// 下述代码执行的顺序
// 1. new Promise
// 2. 执行executor:设置一个定时器
// 3. then注入两个方法(注入的方法保存起来)
// -----等待1000ms
// 4. 执行定时器的回调函数:执行resolve改变promise状态和值
// 5. 通知之前基于then注入的两个方法中的第一个执行
let p1 = new Promise(function(resolve, reject){
// new Promise的时候立即执行executor函数,在executor函数中管理了一个异步编程代码[此时状态是pending];当异步操作到达指定时间,开始执行的时候(理解为异步操作成功),此时我们通过resolve,把promise状态修改为resolved
setTimeout(() => {
resolve('ok')
}, 1000)
})
p1.then(result => {
// 当p1实例的状态修改为resolved的时候,通知传递的第一个函数执行,result->[[PromiseResult]]
console.log('成功', result)
}, reason => {
// 当p1实例的状态修改为rejected的时候,通知传递的第二个函数执行,reason->[[PromiseResult]]
console.log('失败', reason)
})
let p1 = new Promise(function(resolve, reject){
console.log(1) // -> 1
resolve('ok') // 立即修改状态和值,并且通知基于then注入的方法执行(问题:then还没有执行,方法还没有注入,不知道该通知谁来执行),所以此时需要把“通知方法执行的操作”先保存起来,(放到等待任务队列中),“通知方法执行”这个操作本身是异步的,需要等待方法注入完成后再通知其执行
console.log(2) // -> 2
})
p1.then(result => {
console.log('成功', result) // -> 4
}, reason => {
console.log('失败', reason)
})
console.log(3) // -> 3
let p1 = new Promise(function(resolve, reject){
setTimeout(()=>{
resolve('ok')
console.log(1) //->(1) 再次说明,不论是否基于then注入了方法,执行resolve/reject的时候“修改状态和值”是同步的(立即处理),但是“通知对应注入的方法执行”的这个任务就是异步操作的(不会立即处理,只是把它排到等待任务队列中,当其它事情处理完,再次返回头,通知对应注入的方法执行)
}, 1000)
})
p1.then(result => {
console.log(2) // -> 4 //->(2)
}
//
/* let p1 = new Promise(function(resolve, reject){
resolve('ok')
}) */
// Promise.resolve() 创建一个状态为成功的promise实例
// Promise.reject() 创建一个状态为失败的promise实例
/*
* 执行.then方法返回一个全新的promise实例
* promise实例状态和值的分析
* 第一种情况:new Promise出来的实例
* - resolve/reject的执行控制其状态和值
* - executor函数执行失败控制其状态为失败,值为报错信息
* 第二种情况:.then返回的新实例
* - then注入的两个方法,执行报错状态为失败,值为报错信息,不报错
* - 看then的返回值,返回值为promise实例,此实例的成功或失败,直接决定.then返回实例的成功和失败,返回值不为promise实例,返回状态为成功值为当前返回值的promise实例
*/
let p1 = new Promise(function(resolve, reject){
resolve('ok')
})
let p2 = p1.then(result => {
console.log('成功', result) // 成功 'ok'
return 10
}, reason => {
console.log('失败', reason)
})
p2.then(result => {
console.log('成功', result) // 成功 10
}, reason => {
console.log('失败', reason)
})
let p1 = new Promise(function(resolve, reject){
reject('no')
})
let p2 = p1.then(result => {
console.log('成功', result)
}, reason => {
console.log('失败', reason) // 失败 no
})
p2.then(result => {
console.log('成功', result) // 成功 undefined
}, reason => {
console.log('失败', reason)
})
let p1 = Promise.resolve('ok')
let p2 = p1.then(result => {
console.log('成功', result) // 成功 'ok'
return Promise.reject('no')
}, reason => {
console.log('失败', reason)
})
p2.then(result => {
console.log('成功', result)
}, reason => {
console.log('失败', reason) // 失败 'no'
})
//对于失败的promise实例,如果没有编写方法处理结果,则会在控制台抛出异常信息(但是不会阻碍其余代码执行)
Promise.reject().then(result => {}, reason => {})
// then注入的方法没有传递,会顺延到下一个then中具备相同状态的处理函数上
Promise.reject('NO').then(/* result => result */, /* reason => Promise.reject(reason) */).then(reuslt => {
console.log('成功', result)
},reason=>{
console.log('失败', reason) //失败 'NO'
})
// 真实项目中,在多个then下,then处理的都是成功的情况,最后一个then存放失败的,这样不论是第一次还是某一次导致promise实例状态为失败的,最后都会顺延到最后一个失败的处理函数上进行处理
// - then(null, reason=>{...}) 用catch(reason=>{...})代替
Promise.reject('no').then(result=>{
console.log('成功', result)
}).then(result=>{
console.log('成功', result)
}).catch(reason=>{
console.log('失败', reason)
})
Promise.resolve('ok').then(result=>{
console.log('成功', result)
}).then(result=>{
console.log('成功', result)
return Promise.reject('no')
}).catch(reason=>{
console.log('失败', reason)
})
// Promise.all() 等待所有的promise实例都成功,整体返回的状态才是成功,只要有一个失败,整体状态就是失败
// Promise.race() 看多个实例谁先处理完,先处理完成的状态(不论成功或者失败)就是最后整体的状态
let p = Promise.all([p1, p2, p3, p4])
p.then(results => {
//都成功,p就是成功的:results是按照之前设定的顺序依次存储每一个promise的结果
}).catch(reason => {
//只要处理过程中有一个失败的,则立即结束处理,p也是失败的:谁失败的,记录谁的失败原因
})
async await
// async await ES7新增的
// async:修饰函数,最后默认让函数返回一个promise实例(函数执行报错,实例状态就是失败,值为报错信息;否则实例状态为成功,结果是return的值)-> 一般都是配合await使用,(函数中使用await,则必须基于async修饰)
async function fn(){
return 10
}
fn().then(result => {
console.log(result)
})
// await promise实例:如果设置的不是promise实例
// - 正常的值 await 10 -> await Promise.resolve(10)
// - 函数执行 await xxx() -> 首先立即执行xxx函数,接收它的返回值 -> await 返回值
// 本身是异步微任务:把当前上下文中await下面要执行的代码整体存储到异步的微任务中,当await后面的promise实例状态变为成功后,再去执行下面的代码(也就是那个异步的微任务)
function computed () {
console.log(1) //->(3)
return new Promise(resolve => {
setTimeout(() => {
resolve(2)
}, 1000)
})
}
console.log(3) // ->(1)
async function fn () {
console.log(4) // ->(2)
let result = await computed()
console.log(result) // ->(5)
console.log(5) // ->(6)
}
fn()
console.log(6) // ->(4)
// 对失败的promise实例没有做异常的处理,则控制台抛出异常信息「不会影响后续代码执行」
// + promise.catch(reason=>{})
// + await需要自己基于try catch做异常捕获
async function fn() {
try {
let result = await Promise.reject(100);
console.log(result);
} catch (err) {}
}
fn();
// Uncaught RangeError: Maximum call stack size exceeded
let i = 0;
function fn() {
console.log(++i);
fn();
}
fn();
// 不会抱错,但是会形成死循环 =>在EventLoop机制中,只有主线程空闲才会执行异步的任务
function delay(interval = 500) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, interval);
});
}
let i = 0;
async function fn() {
console.log(++i);
// await delay();
await 0;
fn();
}
fn();
同步异步和事件循环习题
async function async1() {
console.log('async1 start');
await async2();
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(function () {
console.log('promise2');
});
console.log('script end');
let body = document.body;
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(1);
});
console.log(2);
});
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
});
// => 'start' 'p1' 'p2' 'timeout1' ‘p3’ ‘p4’ 'interval'...
// 微任务1 微任务2 宏任务1 微任务3 微任务4 宏任务2...
console.log('start');
let intervalId;
Promise.resolve().then(() => { //微任务1 「找到微任务队列即可执行,因为此时promise实例已经是成功的」
console.log('p1');
}).then(() => { //微任务2 「只有微任务1的onfulfilled方法执行完,没有报错,证明其promise是成功的,才会执行微任务2」
console.log('p2');
});
setTimeout(() => { //宏任务1
Promise.resolve().then(() => { //微任务3 「找到微任务队列即可执行」
console.log('p3');
}).then(() => { //微任务4 「微任务3执行完才可以执行」
console.log('p4');
});
intervalId = setInterval(() => { //宏任务2
console.log('interval');
}, 3000);
console.log('timeout1');
}, 0);
// p1 p3 p2
Promise.resolve().then(() => {
console.log('p1');
}).then(() => {
console.log('p2');
});
Promise.resolve().then(() => {
console.log('p3');
})
// => ‘b’ ‘f’ ‘c’ ‘a’ ‘d’
// 微任务1 微任务2 微任务4 微任务3 宏任务1 宏任务2
setTimeout(() => { //宏任务1
console.log('a');
});
Promise.resolve().then(() => { //微任务1 「进入到微任务队列即执行」
console.log('b');
}).then(() => { //微任务2 「微任务1执行完即可」
return Promise.resolve('c').then(data => { //微任务4 「进入到微任务队列立即执行」
setTimeout(() => { //宏任务2
console.log('d')
});
console.log('f');
return data;
});
}).then(data => { //微任务3 「微任务2执行完,并且告知其promise实例是成功的才可以执行」
console.log(data);
});
function func1() {
console.log('func1 start'); // 3 8
return new Promise(resolve => {
resolve('OK');
});
}
function func2() {
console.log('func2 start'); // 4
return new Promise(resolve => {
setTimeout(() => {
resolve('OK');
}, 10);
});
}
console.log(1); // 1
setTimeout(async () => {
console.log(2); // 7
await func1();
console.log(3); // 9
}, 20);
for (let i = 0; i < 90000000; i++) {} //循环大约要进行80MS左右
console.log(4); // 2
func1().then(result => {
console.log(5); // 6
});
func2().then(result => {
console.log(6); // 11
});
setTimeout(() => {
console.log(7); // 10
}, 0);
console.log(8); // 5
promise A+源码
/*
* 基于原生JS实现Promise「遵循的是Promise A Plus规范」
* https://promisesaplus.com/
*/
(function () {
function Promise(executor) {
// 要求传递的executor必须是一个函数才可以
if (typeof executor !== 'function') throw new TypeError('Promise resolver ' + executor + ' is not a function');
// self->promise实例 && 初始其状态state和值value
var self = this;
self.state = 'pending';
self.value = undefined;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
var change = function change(state, value) {
// 状态只能修改一次,第二次更改无效「啥都不处理即可」
if (self.state !== 'pending') return;
self.state = state;
self.value = value;
// 通知集合中的方法执行
setTimeout(function () {
var callbacks = self.state === 'fulfilled' ? self.onFulfilledCallbacks : self.onRejectedCallbacks;
for (var i = 0; i < callbacks.length; i++) {
var item = callbacks[i];
if (typeof item === "function") {
item(self.value);
}
}
});
};
try {
executor(function resolve(result) {
change('fulfilled', result);
}, function reject(reason) {
change('rejected', reason);
});
} catch (err) {
change('rejected', err);
}
}
Promise.prototype = {
constructor: Promise,
customize: true,
/*
* .then(onfulfilled, onrejected)
* case1:如果此时promise实例已经是成功或者失败,我们创建一个异步的微任务,等待同步任务结束,执行对应的函数即可
* case2:此时状态还是pending,我们需要把onfulfilled/onrejected保存起来,当后期状态修改了(例如:resolve/reject方法执行),再次通知保存的方法执行,而这个操作也是异步微任务
*/
then: function (onfulfilled, onrejected) {
var self = this;
switch (self.state) {
case 'fulfilled':
setTimeout(function () {
onfulfilled(self.value);
});
break;
case 'rejected':
setTimeout(function () {
onrejected(self.value);
});
break;
default:
self.onFulfilledCallbacks.push(onfulfilled);
self.onRejectedCallbacks.push(onrejected);
}
},
catch: function () {}
};
// 暴露API
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = Promise;
}
if (typeof window !== 'undefined') {
window.Promise = Promise;
}
})();
let p1 = new Promise((resolve, reject) => {
// resolve(100);
// reject(0);
setTimeout(() => {
resolve(100);
console.log(2);
}, 1000);
});
p1.then(result => {
console.log('成功', result);
}, reason => {
console.log('失败', reason);
});
p1.then(result => {
console.log('成功', result);
}, reason => {
console.log('失败', reason);
});
console.log(`1`);
/*
* 基于原生JS实现Promise「遵循的是Promise A Plus规范」
* https://promisesaplus.com/
*/
(function () {
function Promise(executor) {
if (typeof executor !== 'function') throw new TypeError('Promise resolver ' + executor + ' is not a function');
var self = this;
self.state = 'pending';
self.value = undefined;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
var change = function change(state, value) {
if (self.state !== 'pending') return;
self.state = state;
self.value = value;
setTimeout(function () {
var callbacks = self.state === 'fulfilled' ? self.onFulfilledCallbacks : self.onRejectedCallbacks;
for (var i = 0; i < callbacks.length; i++) {
var item = callbacks[i];
if (typeof item === "function") {
item(self.value);
}
}
});
};
try {
executor(function resolve(result) {
change('fulfilled', result);
}, function reject(reason) {
change('rejected', reason);
});
} catch (err) {
change('rejected', err);
}
}
/* function resolvePromise(promiseNew, x, resolve, reject) {
if (x === promiseNew) throw new TypeError('Chaining cycle detected for promise #<Promise>');
if (x instanceof Promise) { //必须保证返回值是你自己构建的这个Promise类的实例
// 返回结果是一个新的promise实例
x.then(resolve, reject);
} else {
// 返回结果不是promise实例
resolve(x);
}
} */
function resolvePromise(promiseNew, x, resolve, reject) {
if (x === promiseNew) throw new TypeError('Chaining cycle detected for promise #<Promise>');
if ((x !== null && typeof x === "object") || (typeof x === "function")) {
try {
var then = x.then;
if (typeof then === "function") {
// x是一个promise(类promise)实例:x.then(resolve,reject)
then.call(x, resolve, reject);
} else {
resolve(x);
}
} catch (err) {
reject(err);
}
return;
}
resolve(x);
}
Promise.prototype = {
constructor: Promise,
customize: true,
then: function (onfulfilled, onrejected) {
// onfulfilled/onrejected如果不是函数:未来保证穿透顺延的效果,我们需要为其设置默认的函数
if (typeof onfulfilled !== "function") {
onfulfilled = function onfulfilled(value) {
return value;
};
}
if (typeof onrejected !== "function") {
onrejected = function onrejected(value) {
throw value;
};
}
var self = this,
promiseNew = null;
// 创建一个新的promise实例并且返回
// + 执行resolve/reject控制它成功或者失败
// + 到底是成功还是失败,是由onfulfilled/onrejected执行决定
// + onfulfilled/onrejected函数执行不报错,应该让其状态为成功,让其值为函数执行的返回值
// + 如果onfulfilled/onrejected返回的也是promise实例,那么promiseNew的状态和值和新返回的实例保持一致
// + 但是如果onfulfilled/onrejected返回的promise实例和promiseNew是一个东西,则直接抛出异常即可
promiseNew = new Promise(function (resolve, reject) {
switch (self.state) {
case 'fulfilled':
setTimeout(function () {
try {
var x = onfulfilled(self.value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
break;
case 'rejected':
setTimeout(function () {
try {
var x = onrejected(self.value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
break;
default:
// 把它存储到集合中,但是我们以后还要监听方法执行的结果,从而做其它事情
self.onFulfilledCallbacks.push(function (value) {
try {
var x = onfulfilled(value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
self.onRejectedCallbacks.push(function (value) {
try {
var x = onrejected(value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
});
return promiseNew;
},
catch: function (onrejected) {
var self = this;
return self.then(null, onrejected);
}
};
// 暴露API
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = Promise;
}
if (typeof window !== 'undefined') {
// window.Promise = Promise;
}
})();
let p1 = new Promise((resolve, reject) => {
// resolve(100);
reject(0);
});
let p2 = p1.then(
/* result => {
return result;
} */
/* reason=>{
throw reason;
} */
);
p2.then(result => {
console.log('成功', result);
}).then(null, reason => {
console.log('失败', reason);
});
(function () {
/* 工具类方法 */
var isPromise = function isPromise(obj) {
if ((obj !== null && typeof obj === "object") || (typeof obj === "function")) {
if (typeof obj.then === "function") {
return true;
}
}
return false;
};
var resolvePromise = function resolvePromise(promiseNew, x, resolve, reject) {
if (x === promiseNew) throw new TypeError('Chaining cycle detected for promise #<Promise>');
if (isPromise(x)) {
try {
x.then(resolve, reject);
} catch (err) {
reject(err);
}
return;
}
resolve(x);
};
/* 构造函数 */
function Promise(executor) {
if (typeof executor !== 'function') throw new TypeError('Promise resolver ' + executor + ' is not a function');
var self = this;
self.state = 'pending';
self.value = undefined;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
var change = function change(state, value) {
if (self.state !== 'pending') return;
self.state = state;
self.value = value;
setTimeout(function () {
var callbacks = self.state === 'fulfilled' ? self.onFulfilledCallbacks : self.onRejectedCallbacks;
for (var i = 0; i < callbacks.length; i++) {
var item = callbacks[i];
if (typeof item === "function") {
item(self.value);
}
}
});
};
try {
executor(function resolve(result) {
change('fulfilled', result);
}, function reject(reason) {
change('rejected', reason);
});
} catch (err) {
change('rejected', err);
}
}
/* 原型 */
Promise.prototype = {
constructor: Promise,
customize: true,
then: function (onfulfilled, onrejected) {
if (typeof onfulfilled !== "function") {
onfulfilled = function onfulfilled(value) {
return value;
};
}
if (typeof onrejected !== "function") {
onrejected = function onrejected(value) {
throw value;
};
}
var self = this,
promiseNew = null;
promiseNew = new Promise(function (resolve, reject) {
switch (self.state) {
case 'fulfilled':
setTimeout(function () {
try {
var x = onfulfilled(self.value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
break;
case 'rejected':
setTimeout(function () {
try {
var x = onrejected(self.value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
break;
default:
self.onFulfilledCallbacks.push(function (value) {
try {
var x = onfulfilled(value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
self.onRejectedCallbacks.push(function (value) {
try {
var x = onrejected(value);
resolvePromise(promiseNew, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
});
return promiseNew;
},
catch: function (onrejected) {
var self = this;
return self.then(null, onrejected);
}
};
/* 对象 */
Promise.resolve = function resolve(value) {
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function reject(value) {
return new Promise(function (_, reject) {
reject(value);
});
};
Promise.all = function all(promises) {
return new Promise(function (resolve, reject) {
try {
var index = 0,
len = promises.length,
results = [];
for (var i = 0; i < len; i++) {
(function (i) {
var item = promises[i];
if (!isPromise(item)) {
index++;
results[i] = item;
index === len ? resolve(results) : null;
return;
}
item.then(function (result) {
index++;
results[i] = result;
index === len ? resolve(results) : null;
}, function (reason) {
reject(reason);
});
})(i);
}
} catch (err) {
reject(err);
}
});
};
/* 暴露API */
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = Promise;
}
if (typeof window !== 'undefined') {
window.Promise = Promise;
}
})();
/* let p1 = Promise.resolve(10);
let p2 = new Promise(resolve => {
setTimeout(function () {
resolve(20);
}, 000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(function () {
reject(30);
}, 500);
});
let p4 = 40;
Promise.all([p1, p2, p3, p4]).then(results => {
console.log('成功', results);
}).catch(reason => {
console.log('失败', reason);
}); */
Iterator 遍历器 迭代器
/*
* 遍历器(Iterator)是一种机制(接口):为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署Iterator接口,就可以完成遍历操作,依次处理该数据结构的所有成员
* + 拥有next方法用于依次遍历数据结构的成员
* + 每一次遍历返回的结果是一个对象 {done:false,value:xxx}
* + done:记录是否遍历完成
* + value:当前遍历的结果
*
* 拥有Symbol.iterator属性的数据结构(值),被称为可被遍历的,可以基于for of循环处理
* + 数组
* + 部分类数组:arguments/NodeList/HTMLCollection...
* + String
* + Set
* + Map
* + generator object
* + ...
*
* 对象默认不具备Symbol.iterator,属于不可被遍历的数据结构
*/
let arr = [10, 20, 30, 40];
for (let item of arr) {
// for of内部是按照 iterator.next 去迭代处理,所以只有具备 Symbol.iterator 属性「也就是具备Iterator迭代规范的」,才可以基于for of遍历
console.log(item);
}
class Iterator {
constructor(assemble) {
// 挂载到实例的私有属性上:方便后期在方法中基于实例获取这些值
// assemble:数字作为索引逐级递增、拥有length属性存储集合长度
let self = this;
self.assemble = assemble;
self.index = 0;
}
next() {
let self = this,
assemble = self.assemble,
index = self.index;
if (index > assemble.length - 1) {
// 遍历结束了
return {
done: true,
value: undefined
};
}
return {
done: false,
value: assemble[self.index++]
};
}
}
let itor = new Iterator([10, 20, 30, 40]);
console.log(itor.next()); //->{done:false,value:10}
console.log(itor.next()); //->{done:false,value:20}
console.log(itor.next()); //->{done:false,value:30}
console.log(itor.next()); //->{done:false,value:40}
console.log(itor.next()); //->{done:true,value:undefined}
let obj = {
0: 10,
1: 20,
2: 30,
3: 40,
length: 4,
// [Symbol.iterator]: Array.prototype[Symbol.iterator]
/* [Symbol.iterator]: function () {
return new Iterator(this);
} */
[Symbol.iterator]: function () {
let self = this,
index = 1;
return {
next() {
if (index > self.length - 1) {
return {
done: true,
value: undefined
};
}
let result = {
done: false,
value: self[index]
};
index += 2;
return result;
}
};
}
};
for (let item of obj) {
console.log(item);
}
Generator 生成器
/*
* 生成器对象是由一个generator function返回的,并且它符合可迭代协议和迭代器协议
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Generator
*
* 普通函数 VS 生成器函数
* 生成器函数 [[IsGenerator]]:true
*
* 「把它当做一个实例 __proto__」
* 普通函数是 Function 的实例,普通函数.__proto__===Function.prototype
* 生成器函数是 GeneratorFunction 的实例,生成器函数.__proto__===GeneratorFunction.prototype -> GeneratorFunction.prototype.__proto__===Function.prototype
* ({}).toString.call(生成器函数) => "[object GeneratorFunction]"
*
* 「把它作为一个构造函数 prototype」
* 普通构造函数.prototype -> 原型对象(constructor:构造函数)
* 生成器构造函数.prototype -> 原型对象(空对象:可以自己设置内容)
* 生成器函数.prototype.__proto__ === Generator.prototype(next/return/throw/Symbol.toStringTag/Symbol.iterator...)
*/
function* func() {}
func.prototype.xxx = 'zhufeng';
// let itor = new func(); //Uncaught TypeError: func is not a constructor
let itor = func(); //=>创建func/Generator类的实例 并且是具备迭代器规范的(itor.next)
// itor.__proto__===func.prototype
// itor.__proto__.__proto__===Generator.prototype
console.log(itor);
console.log(({}).toString.call(itor)); //=>"[object Generator]"
// Generator是基于Iterator迭代器规范管理Promise或者异步编程的;Promise是基于承诺模式管理异步编程;async/await是对Generator的进一步封装「语法糖」;
function* func() {
console.log('A');
yield 1;
console.log('B');
yield 2;
console.log('C');
yield 3;
console.log('D');
return 4;
}
let itor = func();
console.log(itor.next()); //->{done:false,value:1}
console.log(itor.next()); //->{done:false,value:2}
console.log(itor.next()); //->{done:false,value:3}
console.log(itor.next()); //->{done:true,value:4} */
/*
* 创建了func的实例,但是和new执行不一样,func中的代码还没有执行
* 当后续执行itor.next才会把这些代码执行
* 并且每一次执行next遇到一个yield就结束
* + 每一次返回的结果是符合迭代器规范的
* + {done:true/false,value:yield后面的值或者是函数返回的值}
*/
// 执行next传递值,可以把传递值作为上一次yeild后的结果,但是yield后面跟的值是给每一次next执行后的value的
function* func() {
let x = yield 1;
console.log(x);
}
let itor = func();
console.log(itor.next()); //->{done:false,value:1}
console.log(itor.next(10)); //->{done:true,value:undefined}
function* func1() {
yield 1;
yield 2;
}
function* func2() {
yield 3;
yield* func1();
yield 4;
}
let itor = func2();
console.log(itor.next()); //->{done:false,value:3}
console.log(itor.next()); //->{done:false,value:1}
console.log(itor.next()); //->{done:false,value:2}
console.log(itor.next()); //->{done:false,value:4}
console.log(itor.next()); //->{done:true,value:undefined}
function* func() {
console.log(this); //=>window
yield 1;
}
let itor = func();
itor.next();
// 本身基于promise管理的异步编程:每一次执行query,首先返回promise实例,1000ms后会让promise实例状态为成功,结果是传递进来的值+1
const query = x => {
return new Promise(resolve => {
setTimeout(() => {
resolve(++x);
}, 1000);
});
};
// AJAX(异步)串行的效果:第一个异步请求成功后再去发送第二个异步请求,第二个异步请求成功再去发送第三个请求...「一般上一个请求的结果对下一个请求很重要」
/* 基于promise及then链机制管理异步编程 */
query(1).then(result1 => {
console.log(`第一个请求的结果:${result1}`);
return query(result1);
}).then(result2 => {
console.log(`第二个请求的结果:${result2}`);
return query(result2);
}).then(result3 => {
console.log(`第三个请求的结果:${result3}`);
});
/* 基于async/await实现 */
(async function () {
let result1 = await query(1);
console.log(`第一个请求的结果:${result1}`);
let result2 = await query(result1);
console.log(`第二个请求的结果:${result2}`);
let result3 = await query(result2);
console.log(`第三个请求的结果:${result3}`);
})();
/* 基于Generator管理 */
function* func(x) {
let result1 = yield query(x);
console.log(`第一个请求的结果:${result1}`);
let result2 = yield query(result1);
console.log(`第二个请求的结果:${result2}`);
let result3 = yield query(result2);
console.log(`第三个请求的结果:${result3}`);
}
let itor = func(1);
itor.next().value.then(result1 => {
itor.next(result1).value.then(result2 => {
itor.next(result2).value.then(result3 => {
itor.next(result3);
});
});
});
var isPromise = function isPromise(obj) {
if ((obj !== null && typeof obj === "object") || (typeof obj === "function")) {
if (typeof obj.then === "function") {
return true;
}
}
return false;
};
function AsyncFunc(generator, ...params) {
const itor = generator(...params);
const next = x => {
let {
value,
done
} = itor.next(x);
if (done) return;
// value.then(result => next(result));
!isPromise(value) ? value = Promise.resolve(value) : null;
value.then(next);
};
next();
}
AsyncFunc(function* (x) {
let result1 = yield query(x);
console.log(`第一个请求的结果:${result1}`);
let result2 = yield query(result1);
console.log(`第二个请求的结果:${result2}`);
let result3 = yield query(result2);
console.log(`第三个请求的结果:${result3}`);
yield 10;
console.log('OK');
}, 1);
浏览器底层渲染机制
/*
* 客户端从服务器获取到需要渲染页面的源代码后
* 开辟一个GUI渲染线程,自上而下解析代码,最后绘制出对应的页面
*
* 自上而下渲染解析的过程是同步的,但有些操作也是异步的
*
* 1. 关于css资源的加载
* - 遇到的是<style> "内嵌样式"
* "同步" 交给GUI渲染线程解析
* - 遇到的是<link> "外链样式"
* "异步" 开辟一个新的"HTTP网络请求线程"
* (同一个源下,根据不同的浏览器,最多只允许同时开辟4-7个HTTP线程 "HTTP的并发数")
* 不等待资源信息加载回来,GUI渲染线程继续向下渲染
* GUI渲染线程同步操作都处理完成后,再把基于HTTP网络请求线程请求回来的资源文件进行解析渲染
* - 遇到@import "导入样式"
* - "同步" 开辟一个新的"HTTP网络请求线程"去请求资源文件
* - 但是在资源文件没有请求回来之前,GUI渲染线程会被"阻塞",不允许其继续向下渲染
*
* 2. 遇到srcipt资源的加载
* - 默认都是"同步"的:必须基于HTTP网络线程,把资源请求回来之后,并且交给"JS渲染线程"渲染解析完成后,GUI渲染线程才能继续向下渲染,所以<script>默认也是"阻塞GUI渲染"的
* - async <script async>开辟新的HTTP网络请求线程,同时GUI渲染线程继续向下渲染解析,但是一旦资源请求回来后,会中断GUI的渲染,先把请求回来的JS进行渲染解析
* - defer <script defer>和async类似,开辟新的HTTP网络请求线程,同时GUI渲染线程继续向下渲染解析,但是不一样的是,defer和link类似,是在GUI同步代码渲染完成之后,才会渲染解析JS
*
* 3. 遇到img或者音视频资源的加载
* - 遇到这些,也会发送新的HTTP网络线程,请求加载资源文件,不会阻碍GUI的渲染,当GUI渲染完成后,才会把请求回来的资源信息进行解析
*
* Webkit浏览器预测解析:chrome的预加载扫描器html-preload-scanner通过扫描节点中的 “src” , “link”等属性,找到外部连接资源后进行预加载,避免了资源加载的等待时间,同样实现了提前加载以及加载和执行分离
*/
/*
* 页面渲染的步骤:
* - DOM TREE(DOM树):自上而下渲染完页面,整理好整个页面的DOM结构关系
* - CSSOM TREE(样式树):当把所有的样式资源请求加载回来后,按照引入css的顺序,依次渲染样式代码,生成样式树
* - RENDER TREE(渲染树):把生成的DOM树和CSSOM树结合在一起,生成渲染树(设置display:none的元素不进行处理)
* - Layout 布局/回流/重排:根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小
* - 分层处理:按照层级定位分层处理,每一个层级都会详细规划出具体的绘制步骤
* - Painting 重绘:按照每一层级计算处理的绘制步骤,开始绘制页面
*
*
*
* 前端性能优化 (CRP: 关键渲染路径)
* - 生成DOM TREE
* - 减少DOM层级嵌套
* - 不要使用"非标准"标签
* - ...
* - 生成CSSOM TREE
* - 尽可能不要使用@import(阻塞GUI渲染)
* - 如果CSS代码较少,尽可能使用"style内嵌样式"(减少HTTP网络请求)(尤其是移动端开发)
* - 如果使用link,尽可能把所有样式资源合并为一个,且压缩(减少HTTP网络请求数量,以及渲染CSS的时候,也不需要再计算依赖关系...)
* - CSS选择器链短一些(因为CSS选择器渲染是从右到左)
* - 把link等导入css的操作放在head当中(目的是:一加载页面就开始请求资源,同时GUI去生成DOM树 "css等资源预先加载")
* - ...
* - 对于其他资源的优化
* - 对于<script>,尽可能放置在页面的底部(防止其阻碍GUI渲染);对于部分<script>需要使用async或者defer
* - async是不管JS的依赖关系的,谁先获取到就先执行
* - defer不会这样,和link一样,是等待所有的<script defer>都请求回来后,按照导入顺序/依赖关系依次渲染执行
* - 对于<img>
* - 懒加载:第一次加载页面不要加载请求图片,哪怕它是异步的,但是也占据了HTTP并发数量,导致其他资源延后加载
* - base64:不用请求图片,base64码基本上代表的就是图片,而且页面渲染图片的时候速度也会很快(慎用,但是在webpack工程化中可以使用,它基于file-loader可以自动base64)(速度快是因为省去了加载、解码编码的过程)
* - 生成RENDER TREE
* - Layout/Painting:重要的优化手段(减少DOM回流/重排和重绘)
* - 第一次加载页面必然会有一次回流和重绘
* - 触发回流操作后,也必然会触发重绘;如果只是单纯重绘,则不会引发回流;性能优化点,重点在回流上
*/
// 操作DOM消耗性能? =>DOM回流
- 元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局导致渲染树重新计算布局和渲染
- 元素在视口中的大小或者位置的发生变化
- 元素的删除或者新增(以及基于display控制显示隐藏)
- 内容发生变化(比如文本变化或者图片被另一个不同尺寸的图片代替)
- 回流是根据视口的大小来计算元素的位置和大小的,所以浏览器窗口尺寸变化也会引起回流
这些操作都需要浏览器重新计算每一个元素在视口中的位置和大小(也就是重新Layout/Reflow)
- 避免DOM回流方式
// 当代浏览器的渲染队列机制:在当前上下文中,遇到一行修改样式的代码,并没有立即通知浏览器渲染,而是把其放置在渲染队列中,接下来看是否还有修改样式的代码,如果有继续放置在渲染队列中...一直到再也没有修改样式的代码或者"遇到一行获取样式的操作",这样都会刷新浏览器的渲染队列机制(也就是把现在队列中修改样式的操作,统一告诉浏览器渲染,这样只会引发一次回流)
box.style.width = '200px'
box.style.height = '400px'
box.offsetHeight //box.style.xxx window.getComputedStyle(box).xxx box.currentStyle.xxx box.clientWidth|Height|Left|Top box.offsetWidth|Height|Left|Top box.scrollWidth|Height|Left|Top... 这些获取样式的操作都会刷新浏览器渲染队列
box.style.position = 'absolute'
box.style.top = '100px'
// 1.放弃传统操作DOM的时代,基于vue/react
// 2.样式的"分离读写":把修改样式和获取样式分离开
box.style.width = '200px'
box.style.height = '400px'
box.style.position = 'absolute'
box.style.top = '100px'
box.offsetHeight
//统一修改样式也是为了分离读写,修改的时候不要有获取操作
box.style.csstext = 'width:100px;height:200px;...'
box.className = 'box'
// 缓存布局信息
box.style.width = box.offsetWidth + 10 + 'px'
box.style.height = box.offsetHeight + 10 + 'px'
let w = box.offsetWidth,
h = box.offsetHeight
box.style.width = w + 10 + 'px'
box.style.height = h + 10 + 'px'
// 3. 元素批量修改
let arr = [1,2,3]
arr.forEach(item => {
let span = document.createElement('span')
span.innerText = item
document.body.appendChild(span)
})
// 模板字符串:可能因为把原始容器的内容变为字符串和新的字符串拼接,最后再整体渲染回去,导致原始容器中的元素绑定的一些事件失效
let str = ``
arr.forEach(item => {
str += `<span>${item}</span>`
})
// document.body.innerHTML += str
document.body.innerHTML = str
// 文档碎片createDocumentFragment:临时存放元素对象的容器
let frag = document.createDocumentFragment()
arr.forEach(item => {
let span = document.createElement('span')
span.innerText = item
frag.appendChild(span)
})
document.body.appendChild(frag)
frag = null
// 4. 动画效果应用到position属性为fixed或者absolute的元素上(脱离文档流)
// 5. CSS3硬件加速(GPU加速):修改transform/opacity/filters...(浏览器硬件加速,弊端就是消耗内存)
setTimeout(() => {
// 立即回到left:0的位置
box.style.transitionDuration = '0s'
box.style.left = 0
// 刷新渲染队列
box.offsetWidth
// 回到开始位置后,再次运动到left:200位置(有动画)
box.style.transitionDuration = '0.5s'
box.style.left = '200px'
}, 1000)