js为什么是单线程的
单线程的特点是同一个时刻只能执行一个任务。因为一些和用户的互动和操作DOM等相关操作决定了js要使用单线程,否则多线程会带来同步问题。如果一个线程在修改DOM,另一个线程要删除DOM,就需要对这个共享资源进行加锁,使执行任务非常繁琐。
单线程的问题
单线程会带来阻塞问题,只有前一个任务执行完,后一个任务才可以执行,这样就会出现页面卡死,页面无响应,影响用户体验,所以出现了同步任务和异步任务的解决方法。
同步任务:在主线程上排队的执行任务,只有前一个完成才可以执行后一个。 异步任务:在任务队列,只有任务队列通知主线程,某个异步任务准备好了,该任务才会进入主线程进行调用。
具体运行机制:
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外有一个任务队列,只要异步任务有了运行结果,就在任务队列里面放一个事件。
- 当执行栈中的同步任务都执行完,就会开始读取任务队列里的事件,事件对应的异步任务就会结束等待状态,进行执行栈开始执行。
Event Loop
主线程不断地从任务队列读取事件,这个过程是循环不断地,所以整个运行机制又称为Event Loop(事件循环)。
宏任务
- script内的代码
- setTimeout
- setInterval
- setImmediate(Node)
- I/O(ajax请求)
微任务
- Promise(在创建 Promise 实例对象时,代码顺序执行,如果 到了执行· then 操作,该任务就会被分发到微任务队列中去。)
- process.nextTick(会比promise先执行)
- MutationObserver
循环机制的运行
-
- script内容就是一个宏任务,从script标签开始,遇到函数调用就放到执行栈中执行。
- 2.在这过程中如果遇到宏任务就把其放到一个宏任务队列中。
- 3.如果遇到微任务,就把微任务放到当前宏任务的微任务队列中(每个宏任务都有一个微任务队列)。
- 4.当前宏任务执行完准备退出执行栈前,会执行微任务队列中所有的回调函数(如果遇到新的微任务会继续加入当前微任务队列队尾)。执行完毕后清空微任务队列,退栈。
- 5.执行requestAnimationFrame回调、事件处理以及刷新页面。
- 6.从宏任务队列取出下一个宏任务开始执行(回到1)。
简而言之:执行一个宏任务,执行一队微任务
测试1
console.log('1');
setTimeout(() => {
console.log('2')
}, 1000);
new Promise((resolve, reject) => {
console.log('3');
resolve();
console.log('4');
}).then(() => {
console.log('5');
});
console.log('6');//1,3,4,6,5,2
测试2
console.log('1');
setTimeout(function() {
console.log('2');
new Promise(function(resolve) {
console.log('3');
resolve();
}).then(function() {
console.log('4')
})
setTimeout(function() {
console.log('5');
new Promise(function(resolve) {
console.log('6');
resolve();
}).then(function() {
console.log('7')
})
})
console.log('14');
})
new Promise(function(resolve) {
console.log('8');
resolve();
}).then(function() {
console.log('9')
})
setTimeout(function() {
console.log('10');
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
console.log('13')
// 1,8,13,9,2,3,14,4,10,11,12,5,6,7
https://juejin.cn/post/6913368493035356167
nodejs端的eventloop
hejialianghe.github.io/jsadvanced/…
如何实现异步编程
回调函数
异步任务必须指定回调函数,回调函数不直接调用,而是在特定的事件或者条件发生时执行的,用于对该事件进行响应。 例如AJAX的onload事件之后就会执行一个回调函数,但是回调函数不断嵌套就会形成回调地狱。
ajax("./test1.json",function(data){
console.log(data);
ajax("./test2.json",function(data){
console.log(data);
...
})
})
回调地狱缺点
- 嵌套函数存在耦合性,一旦有所改动就会牵一发动全身。
- 回调函数不能使用try catch捕获错误。(因为异步任务执行的时候,执行栈中的同步任务都已经退出了,捕获异常的函数也推出了)
- 回调函数不能直接return。(只能终止回调函数的执行,不能终止外部代码的执行。)
事件发布/订阅
const pbb = new PubSub();
ajax("./test1.json",function(data){
//第一次ajax请求成功 就发布test1Success,和请求来的数据
pbb.publish("test1Success",data);
})
//订阅test1Success的事件就会被触发
pbb.subscribe("test1Success",function(data){
console.log(data);
//去申请第二次ajax请求
ajax("./test2.json",function(data){
//第二次ajax请求成功发布消息
pbb.publish("test2Success",data);
})
})
//订阅test2Success
pbb.subscribe("test2Success",function(data){
console.log(data);
...
})
PubSub的实现
class PubSub{
constructor(){
//事件队列
this.events = {};
}
//发布事件
publish(eventName, data){
if(this.events[eventName]){
this.events[eventName].forEach(cb => {
cb.apply(this, data);//事件一旦成功发布就去执行对应队列中的回调函数
});
}
}
//订阅事件
subscribe(eventName, callback){
if(this.events[eventName]){
this.events[eventName].push(callback);
}else{
this.events[eventName] = [callback];
}
}
//取消订阅
unSubcribe(eventName, callback){
if(this.events[eventName]){
this.events[eventName] = this.events[eventName].filter(
cb => cb !== callback
);
}
}
}
nodejs中的封装(event)
let events = require("events");
const pbb = new events.EventEmitter();//创建发布订阅对象
ajax("./test1.json",function(data){
//第一次ajax请求成功 就发布test1Success,和请求来的数据
pbb.emit("test1Success",data);
})
//订阅test1Success的事件就会被触发
pbb.on("test1Success",function(data){
console.log(data);
//去申请第二次ajax请求
ajax("./test2.json",function(data){
//第二次ajax请求成功发布消息
pbb.emit("test2Success",data);
})
})
//订阅test2Success
pbb.on("test2Success",function(data){
console.log(data);
});
Generator
使用时的状态变化:
- 首先调用这个生成器函数,内部代码不会立刻执行,先被挂起。
- 调用next(),会把生成器唤醒,执行当前所在yield,并且返回一个对象,然后继续挂起。
- 直到没有可执行的代码,就返回一个结果对象{value:undefined,done:true}。
function request(url) {
makeAjaxCall(url, function(response) {
it.next(response);//第二次调用返回结果
})
}
function *foo() {
var data = yield request('http://api.example.com');
console.log(JSON.parse(data));
}
var it = foo();
it.next();//第一次调用 执行request
Promise
promise可以解决回调地狱(promise链式调用)和处理并行任务(promise.all)。
promiseA+规范
promise的使用
Promise是一个构造函数,可以传入一个函数(执行器函数 立即执行)作为参数,而这个参数函数有两个形参resolve,reject(也是函数)。
promise的属性
调用new之后 p是一个Promise实例对象,每一个实例对象都有两个属性:状态和结果
有三种可选状态
- pending(准备)
- fulfilled(已完成)
- rejected(已拒绝)
执行resolve(str)可以使状态pending->fulfilled,当前promise的值为str
执行reject(str)可以使状态pending-> rejected,当前promise的值是str
而且状态改变是一次性的(只能改变一次)
let p = new Promise((resolve,reject)=>{
resolve("成功的结果");
//reject("失败的结果");
})
console.log(p);
promise.prototype.then方法
then方法有两个参数,这两个参数都是函数,第一个函数是当状态是fulfilled时执行的(同时形参是promise的值),第二个方法是状态时rejected时执行的(同上)。
let p = new Promise((resolve,reject)=>{
resolve("成功的结果");
//reject("失败的结果");
})
let ret = p.then((value)=> {
console.log("成功时调用"+ value);
},(err)=> {
console.log("失败时调用"+ err);
})
then方法返回一个新的promise,且当前状态是pending(因为then是异步的属于微任务,会在当前宏任务执行完之后才执行,所以暂时是Pending状态)。then最后的返回值是由then中回调函数执行的结果决定。
- 如果抛出异常,则新的promise的状态就是rejected,reason就是这个异常。
- 如果返回一个非promise的任意值,新promise的状态就是fulfilled,value就是这个返回值
- 如果返回一个promise,那就是这个promise
因为then返回一个promise,所以他也可以调用then,这样就可以实现链式调用(解决回调地狱/串联执行任务)。
new Promise((resolve,reject)=>{
}).then((value)=>{
console.log("第一个promise成功")
//return 123;//返回之后这个promise的状态改成fulfilled。
console.log(a);//但是这里的代码出错就会把当前的promise的状态改为rejected
},(reason)=>{
console.log("第一个promise失败")
}).then((value)=>{
console.log("第二个promise成功")
},(reason)=>{
console.log("第二个promises失败"+reason);//可以捕获到失败的原因
})
当状态不改变的时候(pending)不会执行then。
当一个promise有多个then的时候都会调用。
promise.prototype.catch方法
catch事捕获错误的。以下三种情况(reject执行,代码出错,出动抛出错误)都会让catch捕获到错误。
const p = new Promise((resolve,reject) => {
//reject();
//console.log(a);
throw new Error('出错了')
})
let t = p.catch((reason)=>{
console.log('失败'+reason);
})
promise最常见的写法
new Promise((resolve,reject)=>{
}).then((value)=>{
console.log('成功'+value);
}).catch((reason)=>{
console.log('失败'+reason);
})
使用方法
function fn(){
return new Promise((resolve, reject)=>{
成功时调用 resolve(数据)
失败时调用 reject(错误)
})
}
fn().then(success1, fail1).then(success2, fail2)
Promise.all/Promise.race
//把多个promise包装成一个promise实例对象,只有都成功的时候才调用success1
Promise.all([promise1, promise2]).then(success1, fail1)
const p1 = Promise.resolve(1);//返回一个成功值是1的promise对象
const p2 = Promise.reject(2);
const p3 = Promise.resolve(3);
const pAll = Pomise.all([p1,p2,p3]);
pAll.then(
(value) => {
console.log('成功'+value)//[1,3]
},
(reason) => {
console.log('失败'+reason)//2
}
)
//数组中第一个确定状态的promise来调用then
Promise.race([promise1, promise2]).then(success1, fail1)
手写Promise
Promise构造函数、then、catch
//1.定义结构 不写具体实现
//ES5的自定义模块
(function(window){
//构造函数 传入一个执行器函数
function Promise(executor){
let _this = this;
_this.status = 'pending';//存储当前的状态
_this.data = undefined;//存储当前的值
_this.callbacks = [];//每一个对象都有两个回调函数 onResolved onRejected
function resolve(value){
if(_this.status !== 'pending') return;//状态只能从pending开始改
_this.status = 'fulfilled';//改状态
_this.data = value;//改值
if(_this.callbacks.length){//如果当前已经有回调函数就异步执行每一个 onResolved 并且要传vaule值
setTimeout(()=>{
_this.callbacks.forEach((callbackObj)=>{
callbackObj.onResolved(value);
})
},0);
}
}
function reject(reason){
if(_this.status !== 'pending') return;
_this.status = 'rejected';
_this.data = reason;
if(_this.callbacks.length){
setTimeout(()=>{
_this.callbacks.forEach((callbackObj)=>{
callbackObj.onRejected(reason);
})
},0);
}
}
try{
executor(resolve,reject);//立即执行executor这个函数。
}catch(err){
reject(err);//如果出错就会调用rejecte
}
}
Promise.prototype.then = function(onResolved, onRejected){
let _this = this;//保存调用Then的Promise对象
onResolved = typeof onRejected === 'function'? onRejected : value => value;//如果传入不是回调函数 自行传一个函数
onRejected = typeof onRejected === 'function'? onRejected : reason => {throw reason};//传透异常
return new Promise((resolve, reject)=>{
//执行指定的回调函数 并且改变返回promise的状态
function handle(callback){
try{
const result = callback(self.data);
if(result instanceof Promise){//3.返回的是一个promise, 返回promise就是这个promise
result.then(resolve,reject);//result成功了会调用返回promise的resolve
}else{
resolve(result);//2.返回的不是一个Promise ,返回Promise就是fulfilled
}
}catch(err){
reject(err);//1.抛出异常,返回的promise就是rejected
}
}
//判断调用者的状态
if(_this.status === 'rejected'){//如果调用者失败则异步执行onRejected 并传参
setTimeout(()=>{
handle(onRejected);
})
}else if(_this.status === 'fulfilled'){//调用者成功则执行 onResolved 应且传参
setTimeout(()=>{
handle(onResolved);
})
}else{//调用者状态是pending 保存onResolved 和 onRejected
self.callbacks.push({
onResolved(value){
handle(onResolved);
},
onRejected(reason){
handle(onRejected);
}
})
}
})
}
Promise.prototype.catch = function(onRejected){
return this.then(undefined,onRejected);
}
//返回一个成功的Promise
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
if(value instanceof Promise){//传入的是一个promise
value.then(resolve,reject);
}else{//传入的是一个普通值
resolve(value);
}
})
}
//返回一个失败的Promise
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.all = function(promises){
const values = [];//定义数组保存成功的返回值
let count = 0;
return new Promise((resolve, reject)=>{//返回一个新的promise
//遍历获取每个promise的结果
promises.forEach((p, index)=>{
if(!p instanceof Promise){//如果数组中有不是promise对象的把他视为成功
values[index] = value;
count++;
}else{
p.then(
value=>{
values[index] = value;// p成功了就存入数组中,存的顺序要和promises保持一致
count++;
if(count === promises.length){
resolve(values);//如果都成功了就把返回promise设为成功,并且传入数组
}
},
reason =>{//只要有一个失败了 返回promise就是失败
reject(reason);
})
}
})
})
}
//返回第一个完成/失败的结果
Promise.race = function(promises){
return new Promise((resolve, reject)=>{
promises.forEach((p)=>{
p.then((value)=>{
resolve(value);
},(reason)=>{
reject(reason);
})
})
})
}
window.Promise = Promise;//挂载到window下
})()//匿名立即执行函数
<!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>
<script src="./newPromise.js"></script>
<script>
new Promise((resolve, reject) => {
setTimeout(()=>{
console.log("延时1s完成")
resolve(1);
},1000);
console.log("状态改变成功")
}).then((value)=>{
console.log('onResolved1()'+value);
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
console.log('延时两秒完成')
},2000)
})
},
(reason)=>{
console.log("onRejected()" + reason);
}).then((value)=>{
console.log('onResolved2()'+ value);
throw 3;
},(reason)=>{
console.log("onRejected2()" + reason);
}).catch((reason)=>{
console.log("onRejected3()" + reason);
})
/*
状态改变成功
test.html:15 延时1s完成
test.html:21 onResolved1()1
test.html:25 延时两秒完成
test.html:32 onResolved2()2
test.html:37 onRejected3()3
*/
</script>
</body>
</html>
// ES6 的书写方式
(function(Window){
class Promise{
constructor(exector){
}
function reject(reason){
}
//定义在原型上
then(onResolved, onRejected){
}
catch(onRejected){
}
//定义在类对象上
//返回一个成功的Promise
static resolve = function(value){
}
//返回一个失败的Promise
static reject = function(reason){
}
static all = function(promises){
}
//返回第一个完成/失败的结果
static race = function(promises){
}
Window.Promise = Promise;//挂载到window下
})()//匿名立即执行函数
测试题
setTimeout(()=>{
console.log("0");
})
// 1 7 2 3 8 4 6 5 0
new Promise((resolve, reject)=>{
console.log("1");
resolve();
}).then(()=>{
console.log("2");
new Promise((resolve, reject)=>{
console.log("3");
resolve();
}).then(()=>{
console.log("4");
}).then(()=>{
console.log("5");
})
}).then(()=>{
console.log("6");
})
new Promise((resolve, reject)=>{
console.log("7");
resolve();
}).then(()=>{
console.log("8");
})
async await
async和await关键字可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
async的返回值是一个Promise对象,await右边的表达式一般是promise,但也可以不是。
await会返回fulfilled的值 ,所以需要用try{}catch(){}来获取rejected的值。
function fn1(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},1000)
})
}
async function fn2(){
let result = await fn1();
console.log(result);
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(2);
},1000)
})
}
async function fn3(){
try{
let result = await fn2();
console.log(result);
}catch(err){
console.log(err);
}
}
fn3();
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");
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout
*/
AJAX
AJAX 异步的javascript和XML,在不重新加载页面情况下,可以和服务器交换数据并且更新部分网页内容。
四个步骤
- 使用new XMLHttpRequest创建一个ajax实例对象。
- 使用open方法设定要请求的地址和请求方式。
- 使用send()发送数据
- responseText获取服务器返回的数据
get请求
let btn = document.getElementById('btn');
let username = document.getElementById('username');
let age = document.getElementById('age');
btn.onlick = function(){
let xhr = new XMLHttpRequest();
let name = username.value;
let age = username.value;
let params = 'username='+name+'&age='+age;
xhr.open('get','https://localhost:3000/get?'+params);
xhr.send();
xhr.onload = function(){
console.log(xhr.responseText);
}
}
post请求(请求格式:属性名=属性值)
let btn = document.getElementById('btn');
let username = document.getElementById('username');
let age = document.getElementById('age');
btn.onlick = function(){
let xhr = new XMLHttpRequest();
let name = username.value;
let age = username.value;
let params = 'username='+name+'&age='+age;
xhr.open('post','https://localhost:3000/post');
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.send(params);
xhr.onload = function(){
console.log(xhr.responseText);
}
}
post请求 (请求格式是 JSON)
let xhr = new XMLHttpRequest();
xhr.open('post','http://localhost:3000/json');
xhr.setRequestHeader('Content-Type','application/json');
xhr.send(JSON.stringify({name:'tom',age:19}));
xhr.onload = function(){
console.log(xhr.responseText);
}
ajax状态码
- 0 为初始化,没有调用Open
- 1 启动 ,已经调用open,没有调用send
- 2 发送,已经send,但还没有接收响应
- 3 接收,已经接收部分数据
- 4 完成,以及完全收到数据,可以在客户端使用。
let xhr = new XMLHttpRequest();
xhr.open('get','http://localhost:3000/readystate');
console.log(xhr.readyState);//1
xhr.onreadystatechange = function(){
console.log(xhr.readyState);
if(xhr.readyState === 4){
console.log(xhr.responseText);
}
}
xhr.send();
封装AJAX
<script type="text/javascript">
function ajax (options) {
// 存储的是默认值
var defaults = {
type: 'get',
url: '',
data: {},
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function () {},
error: function () {}
};
// 使用options对象中的属性覆盖defaults对象中的属性
Object.assign(defaults, options);
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 拼接请求参数的变量
var params = '';
// 循环用户传递进来的对象格式参数
for (var attr in defaults.data) {
// 将参数转换为字符串格式
params += attr + '=' + defaults.data[attr] + '&';
}
// 将参数最后面的&截取掉
// 将截取的结果重新赋值给params变量
params = params.substr(0, params.length - 1);
// 判断请求方式
if (defaults.type == 'get') {
defaults.url = defaults.url + '?' + params;
}
// 配置ajax对象
xhr.open(defaults.type, defaults.url);
// 如果请求方式为post
if (defaults.type == 'post') {
// 用户希望的向服务器端传递的请求参数的类型
var contentType = defaults.header['Content-Type']
// 设置请求参数格式的类型
xhr.setRequestHeader('Content-Type', contentType);
// 判断用户希望的请求参数格式的类型
// 如果类型为json
if (contentType == 'application/json') {
// 向服务器端传递json数据格式的参数
xhr.send(JSON.stringify(defaults.data))
}else {
// 向服务器端传递普通类型的请求参数
xhr.send(params);
}
}else {
// 发送请求
xhr.send();
}
// 监听xhr对象下面的onload事件
// 当xhr对象接收完响应数据后触发
xhr.onload = function () {
// xhr.getResponseHeader()
// 获取响应头中的数据
var contentType = xhr.getResponseHeader('Content-Type');
// 服务器端返回的数据
var responseText = xhr.responseText;
// 如果响应类型中包含applicaition/json
if (contentType.includes('application/json')) {
// 将json字符串转换为json对象
responseText = JSON.parse(responseText)
}
// 当http状态码等于200的时候
if (xhr.status == 200) {
// 请求成功 调用处理成功情况的函数
defaults.success(responseText, xhr);
}else {
// 请求失败 调用处理失败情况的函数
defaults.error(responseText, xhr);
}
}
}
ajax({
type: 'post',
// 请求地址
url: 'http://localhost:3000/responseData',
success: function (data) {
console.log('这里是success函数');
console.log(data)
}
})
</script>
fetch(现代浏览器自带,IE不自带)
fetch("http://localhost:91/",{
method:"POST",
headers:{"content-type":"application/x-www-form-urlencoded"},
body:"a=12&b=77"
}).then(
res => res.json()
).then(
data => {console.log(data)}
)
axios(默认是JSON格式)
axios({
url:'http://localhost:90?b=12',
method:'POST',
data:{a:1,b:12}
}).then(res=>{console.log(res.data)})