前端面试-异步

97 阅读2分钟
  1. 什么是单线程,和异步有什么关系
  2. 什么是 event-loop
  3. 是否用过 JQuery 的 Deferred
  4. Promise 的基本使用和原理
  5. 介绍一下 async/await(和 Promise 的区别、联系)
  6. 总结一下当前 JS 解决异步的方案

单线程,以及和异步的关系

单线程 - 只有一个线程,同一时间只能做一件事,两段JS不能同时执行

原因 - 为了避免 DOM 渲染冲突

  • 浏览器需要渲染 DOM

  • JS 也可以修改 DOM 结构

  • JS 执行的时候,浏览器 DOM 渲染会暂停

  • 两端 JS 也不能同时执行(都修改 DOM 就冲突了)

  • webworker 支持多线程,但是不能访问 DOM

异步 - 是一种“无奈”的解决方案,虽然有很多问题

// 循环运行期间,JS 执行和 DOM 渲染暂时卡顿
var i, sum = 0;
for (i = 0; i < 10000000; i++) {
    sum += i;
}
console.log(sum)

// alert 不处理,JS 执行和 DOM 渲染暂时卡顿
console.log(1);
alert('hello');
console.log(2)

解决方案 - 异步

// 方案一:setTimeout
console.log(100);
setTimeout(function () {
    console.log(200)
}, 1000)
console.log(300);
console.log(400); // 打印结果:100,300,400,200

// 方案二:ajax
console.log(100);
$.ajax({
    url: 'XXX',
    success: function(result) {
        console.log(result)    
    }
})
console.log(300);
console.log(400); // 打印结果:100,300,400,200

event-loop 事件轮询

  • 事件轮询,JS 实现异步的具体解决方案
  • 同步代码,直接执行
  • 异步函数先放到 异步队列 中
  • 待同步函数执行完毕,轮询执行 异步队列 的函数

代码如下:

setTimeout(function() {
    console.log(100)
}, 1000)
setTimeout(function() {
    console.log(200)
})
console.log(300);

// ----对以上代码进行分析

// 主线程
console.log(300)

// 异步队列
// ---立刻放入异步队列
function () {
    console.log(200)
}
// ---1000ms之后放入异步队列
function () {
    console.log(100)
}

JQuery 的 Deferred

1、JQuery 1.5 前后 ajax 的变化

  • 无法改变 JS 异步和单线程的本质
  • 只能从写法上杜绝 callback 的形式
  • 它是一种语法糖的形式,但是解耦了代码
  • 很好的体现了 开放封闭原则 (对扩展开放,对修改封闭)
  • .done(function() {});.fail(function() {});
  • .then(成功的回调,失败的回调);

代码如下:

// JQuery 1.5 前
$.ajax({
    url: 'XXX',
    success: function() {
        console.log('success1'); 
        console.log('success2'); 
        console.log('success3');  
    }
});

// JQuery 1.5 后
var ajax = $.ajax('./data.json');
ajax.done(function() {
    console.log('success1')
}).fail(function() {
    console.log('fail1')
}).done(function() {
    console.log('success2')
})

var ajax = $ajax('./data.json');
ajax.then(function() {
    console.log('success1')
}, function() {
    console.log('fail1')
});

2、使用 JQuery Deferred

  • dtd.resolve() 表示异步任务已经成功
  • dtd.reject() 表示异步任务失败或出错

代码如下:

function waitHandle() {
    var dtd = $.deferred(); // 创建一个 deferred 对象
    var wait = function (dtd) {
        var task = function () {
            console.log('执行完成');
            dtd.resolve(); // 表示异步任务已经完成
            // dtd.reject(); // 表示异步任务失败或出错
        }
        setTimeout(task, 2000)
        return dtd; // 要求返回 deferred 对象
    }
    // 注意,这里一定要返回
    return wait(dtd);
}

// 开放封闭原则--对扩展开放对修改关闭
var w = waitHandle();
w.then(function() {
    console.log('success1')
}, function() {
    console.log('fail1')
})
w.then(function() {
    console.log('success2')
}, function() {
    console.log('fail2')
});
  • dtd.promise()  // 会过滤到 resolve和reject 两个"主动修改"的方法,保留 then,done,fail "被动监听"的方法

代码如下:

function waitHandle() {
    var dtd = $.deferred(); // 创建一个 deferred 对象
    var wait = function (dtd) {
        var task = function () {
            console.log('执行完成');
            dtd.resolve(); // 表示异步任务已经完成
            // dtd.reject(); // 表示异步任务失败或出错
        }
        setTimeout(task, 2000)
        return dtd.promise(); // 
    }
    // 注意,这里一定要返回
    return wait(dtd);
}

// 开放封闭原则--对扩展开放对修改关闭
var w = waitHandle(); // promise对象
$.when(w).then(function() {
    console.log('success1')
}, function() {
    console.log('fail1')
})

Promise 的基本使用和原理

链接参考:八段代码彻底掌握 Promise

1、Promise 的语法回顾

  • new Promise()
  • resolve(); reject();
  • .then .catch

代码如下:

var p = new Promise(function(resolve, reject){
  console.log("create a promise");
  resolve("success");
});

console.log("after new Promise");

p.then(function(value){
  console.log(value);
});

2、异常捕获

  • 两类错误:
  • throw new Error('自定义错误') --- 程序逻辑之外的bug,语法错误
  • reject('记载失败')  错误返回

代码如下:

// 规定: then 只接受一个参数,最后统一用 catch 捕获异常
result.then(function (img) {
    console.log(1);
}).catch(function (err) {
    console.log(err)
})

3、串联

result.then(function (img) {
    console.log(1);
    return img;
}).then(function (img) {
    console.log(2);
}).catch(function (err) {
    console.log(err)
})

4、Promise.all & Promise.race

  • Promise.all 全部加载完成之后,统一执行
  • Promise.race 只要有一个完成

代码如下:

// Promise.all 接收一个 promise 对象的数组
Promise.all([result1, result2]).then(datas => {
    // datas 是一个数组,依次包含多个 promise 返回的内容
    console.log(datas[0]);
    console.log(datas[1]);
})

// Promise.race 接收一个包含多个 promise 对象的数组
Promise.race([result1, result2]).then(data => {
    // data 即最先执行完成的 promise 的返回值
    console.log(data);
})

5、Promise 标准 - 状态变化

  • 三种状态:pending fulfilled(resolved) rejected
  • 初始状态时 pending
  • pending 变成 fulfilled,或者 pending 变成 rejected
  • 状态变化是不可逆

6、Promise 标准 - then

  • then 方法返回一个新的 Promise 对象,因此可以进行链式操作

  • then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个参数是Promise执行失败时的回调。两个函数只会有一个被调用

  • Promise接收的函数参数是同步执行的,但then方法中的回调函数执行则是异步的

代码如下:

var p = new Promise(function(resolve, reject){
  resolve(1);
});
p.then(function(value){               //第一个then
  console.log(value);
  return value*2;
}).then(function(value){              //第二个then
  console.log(value);
}).then(function(value){              //第三个then
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){              //第四个then
  console.log(value);
  return Promise.reject('reject');
}).then(function(value){              //第五个then
  console.log('resolve: '+ value);
}, function(err){
  console.log('reject: ' + err);
})

输出的结果:

1
2
undefined
"resolve"
"reject: reject"

async/await

  • Aysnc 是基于 Promise 实现的

  • Promise 是对异步回调的封装

  • 使用了 Promise 的封装,完全是同步的写法

  • 改变不了 js 单线程,异步的本质

当前异步的解决方案

  • 事件监听
  • jQuery Deferred(callback)
  • Promise
  • Async/Await
  • Generator

链接参考:JS异步编程的六种方案