hello,本节将介绍异步请求、EventLoop、Promise等知识点,并区分各种数组操作。本系列为个人面试使用,并没有对原理剖析很深,希望理解。
四、同步和异步
1.同步和异步的区别
同步会阻塞代码,异步不会
2.前端使用异步的场景
定时任务:setTimeout,setInterval
网络请求:ajax请求,动态img加载
事件绑定
//ajax请求
console.log('start')
$.get('./data1.json',function(data1){
console.log(data1) // 异步执行
})
console.log('end')
//img加载
console.log('start')
var img = document.createElement('img')
img.onload = function(){
console.log('loaded') // 异步执行
}
img.src = '/xxx.png'
console.log('end')
//事件绑定
console.log('start')
document.getElementById('btn1').addEventListener('click',function(){
alert('clicked') // 异步执行
})
console.log('end')
3.浏览器的EventLoop
JS在执行过程中有两种任务:
宏任务: 同步script(整体代码)、setTimeout回调函数、setInterval回调函数、I/O、UI rendering
微任务: process.nextTick、Promise回调函数、Object.observe
代码执行顺序:
1.首先JS引擎会执行一个宏任务,这个宏任务一般指主体代码本身,也就是目前的同步代码。
2.执行过程中如果遇到微任务,就把它添加到微任务任务队列中。
3.宏任务执行完成后,立即执行当前微任务队列中的微任务,直到微任务队列被清空。
4.微任务执行完成后,开始执行下一个宏任务。
5.如此循环往复,直到宏任务和微任务被清空。
console.log('script start');
setTimeout(function(){
console.log('setTimeout');
},0);
new Promise(function(resolve){
console.log('promise1');
resolve();
console.log('promise2');
}).then(function(){
console.log('promise then');
});
console.log('script end');
打印结果:script start、promise1、promise2、script end、promise then、setTimeout。
document.body.style = 'background:blue'
Promise.resolve().then(()=>{
document.body.style = 'background:black'
});
执行结果:页面会直接变黑,不会经历变蓝的阶段。
根据 Event Loop 模型,浏览器在执行第一个宏任务—主干代码时,遇到了微任务—Promise 回调函数。所以宏任务结束时,浏览器不会立即执行下一个宏任务—UI rendering,而是立即执行微任务队列,也就是 Promise 的回调函数。当微任务队列被清空后,才会去执行 UI rendering。也就是说,在页面渲染之前,document.body.style早已经在 Promise 的回调函数中被赋值为black 了。
4.Promise
Promise的优势
promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
代码风格,容易理解,便于维护
多个异步等待合并便于解决
举个例子
new Promise(
function (resolve, reject) {
// 一段耗时的异步操作
resolve('成功') // 数据处理完成
// reject('失败') // 数据处理出错
}
).then(
(res) => {console.log(res)}, // 成功
(err) => {console.log(err)} // 失败
)
解析
resolve作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
promise有三个状态:
1、pending[待定]初始状态
2、fulfilled[实现]操作成功
3、rejected[被否决]操作失败
当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
promise状态一经改变,不会再变。
Promise对象的状态改变,只有两种可能:
从pending变为fulfilled
从pending变为rejected。
这两种情况只要发生,状态就凝固了,不会再变了。
捕获错误的方法
(1)使用throw new Error()抛出错误,用catch()接收
console.log("start");
new Promise(resolve => {
setTimeout(() => {
resolve();
},2000)
}).then(() => {
console.log("promise_start");
throw new Error("test error");
}).catch(err => {
console.log("I catch:", err);
throw new Error("another error");
}).then(() => {
console.log("arrive here");
}).catch(err => {
console.log("No,I catch:", err);
})
打印结果:start、promise_start、I catch:test error、No,I catch:another error
(2)Promise对象中resolve()表示请求正确,reject()表示请求错误
console.log("start");
new Promise(resolve => {
setTimeout(() => {
reject('hi');
},2000)
}).then((res) => {
console.log(res);
},(err) => {
console.log("Error:" + err);
})
通常使用第一种方法,这样能把请求正确和请求错误区分开,不必放在一个then()中。
五、数组操作方法
1.检测数组
arr instanceof Array和Array.isArray(arr)这两种方法可以检测出变量是否为数组。
instanceof操作符通过原型链检测引用类型变量,Array对象中的isArray()方法可以判断数组,两种方法都返回boolean值。
2.转换数组
常用toString()和valueof()方法把数组转换成字符串,两个方法均不需要传入参数。调用valueof()返回数组本身,调用toString()返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。
var colors = ["red","blue","green"]; // 创建一个包含3个字符串的数组
alert(colors.toString()); // red,blue,green
alert(colors.valueof()); // red,blue,green
alert(colors); // red,blue,green
使用join()方法,可以使用不同分隔符来构建字符串。join()方法只接收一个参数用作字符串的分隔符,返回包含所有数组项的字符串。
var colors = ["red","blue","green"];
alert(colors.join(",")); //red,blue,green
alert(colors.join("||")); //red||blue||green
3.从数组头部(尾部)添加(删除)数组项
(1)栈方法
栈是一种后进先出的数据结构,可以把栈看作杯子,距离杯口近的数据是数组尾部,所以栈方法只能在数组尾部添加或删除数据。push()向数组尾部添加数据,可以接收任意数量的参数,把它们逐个添加到数组末尾,返回修改后数组的长度。pop()方法没有参数,直接从数组末尾移除最后一项,减少数组的length值,返回移除的项。
var colors = new Array();
var count = colors.push("red","green");
alert(count); //2
count = colors.push("black");
alert(count); //3
var item = colors.pop();
alert(item); //"black"
alert(colors.length); //2
(2)队列方法
队列数据结构的访问规则是先进先出,像管道一样,只能从一端进再从另一端出。unshift()方法在数组头部添加数据,可以接收任意数量的参数,返回新数组的长度。shift()方法没有参数,能够移除数组中的第一项并返回该值,减少数组的length值。
var colors = new Array();
var count = colors.push("red","green");
alert(count); //2
count = colors.push("black");
alert(count); //3
var item = colors.shift();
alert(item); //"red"
alert(colors.length); //2
count = colors.unshift("pink","yellow");
alert(count); //4
alert(colors); //["yellow","pink","green","black"]
4.数组排序
数组中有两个排序方法,reverse()和sort()。reverse()方法会反转数组项的顺序,没有参数,直接反转原数组。
sort()方法在默认情况下按升序排列数组项,为了实现排序,sort()方法会调用每个数组项的toString()方法,然后再比较字符串。即使数组中的每一项都是数值,sort()方法也比较字符串,而且会按位比较,这样“10”是小于“5”的。
var values = [0,1,5,10,15];
values.sort();
alert(values); //0,1,10,15,5
上面的排序结果明显不是我们想要的,如果希望按数值升序排列可以向sort()方法中传入函数作为参数,直接改变原数组。
var values = [0,1,5,10,15];
values.sort(compare);
alert(values); //0,1,10,15,5
function compare(value1,value2){
return value2 - value1;
}
5.操作数组
concat()方法可以基于当前数组中的所有项创建一个新数组。在没有给concat()方法传递参数的情况下,它只是复制当前数组并返回副本。如果传递给concat()方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。无论参数是什么,原数组都不会改变。
var colors = ["red","green","blue"];
var colors2 = colors.concat("yellow",["black","brown"]);
alert(colors); //red,green,blue
alert(colors2); //red,green,blue,yellow,black,brown
slice()方法能基于当前数组中的一或多个项创建一个新数组。slice()方法可以接收一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项,但不包括结束位置的项。无论参数是什么,原数组都不会改变。
var colors = ["red","green","blue","yellow","purple"];
var colors2 = colors.slice(1);
var colors3 = colors.slice(1,4);
alert(colors2); //green,blue,yellow,purple
alert(colors3); //green,blue,yellow
下面隆重介绍splice()方法。
splice()方法主要用于向数组的中部插入项,有三种使用方式:
删除: 可以删除任意数量的项,只需指定2个参数:要删除的第一项的位置和要删除的项数。
插入: 可以向指定位置插入任意数量的项,只需提供3个参数:起始位置、0、要插入的项。如果要插入多个项,可以在后面传入任意数量的项。
替换: 可以向指定位置插入任意数量的项,同时删除任意数量的项。只需指定3个参数:起始位置、要删除的项数、要插入的任意数量的项。
var colors = ["red","green","blue"];
var removed = colors.splice(0,1);
alert(colors); //green,blue
alert(removed); //red,返回的数组中只包含一项
removed = colors.splice(1,0,"yellow","orange");
alert(colors); //green,yellow,orange,blue
alert(removed); //返回的是一个空数组
removed = colors.splice(1,1,"red","purple");
alert(colors); //green,red,purple,orange,blue
alert(removed); //yellow,返回的数组中只包含一项
6.迭代数组
迭代方法都要传入函数作为参数,而且函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。以下是五种迭代方法:
every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。
forEach():对数组中的每一项运行给定函数,这个方法没有返回值。
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。
var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item,index,array){
return (item > 2);
});
alert(everyResult); //false
var someResult = numbers.some(function(item,index,array){
return (item > 2);
});
alert(someResult); //true
var filterResult = numbers.filter(function(item,index,array){
return (item > 2);
});
alert(filterResult); //[3,4,5,4,3]
var mapResult = numbers.map(function(item,index,array){
return item * 2;
});
alert(mapResult); //[2,4,6,8,10,8,6,4,2]
numbers.forEach(function(item,index,array){
//执行某些操作
});