深入理解ES7的async/await的用法
在最開(kāi)始學(xué)習(xí)ES6的Promise時(shí),曾寫(xiě)過(guò)一篇博文 《promise和co搭配生成器函數(shù)方式解決js代碼異步流程的比較》 ,文章中對(duì)比了使用Promise和co模塊搭配生成器函數(shù)解決js異步的異同。
在文章末尾,提到了ES7的async和await,只是當(dāng)時(shí)只是簡(jiǎn)單的提了一下,并未做深入探討。
在前兩個(gè)月發(fā)布的Nodejs V7中,已添加了對(duì)async和await的支持,今天就來(lái)對(duì)這個(gè)東東做一下深入的探究。以更加優(yōu)雅的方法寫(xiě)異步代碼。
async/await是什么
async/await可以說(shuō)是co模塊和生成器函數(shù)的語(yǔ)法糖。用更加清晰的語(yǔ)義解決js異步代碼。
熟悉co模塊的同學(xué)應(yīng)該都知道,co模塊是TJ大神寫(xiě)的一個(gè)使用生成器函數(shù)來(lái)解決異步流程的模塊,可以看做是生成器函數(shù)的執(zhí)行器。而async/await則是對(duì)co模塊的升級(jí),內(nèi)置生成器函數(shù)的執(zhí)行器,不再依賴(lài)co模塊。同時(shí),async返回的是Promise。
從上面來(lái)看,不管是co模塊還是async/await,都是將Promise作為最基礎(chǔ)的單元,對(duì)Promise不很了解的同學(xué)可以先深入了解一下Promise。
對(duì)比Promise,co,async/await
下面我們使用一個(gè)簡(jiǎn)單的例子,來(lái)對(duì)比一下三種方式的異同,以及取舍。
我們采用mongodb的nodejs驅(qū)動(dòng),查詢(xún)mongodb數(shù)據(jù)庫(kù)作為例子,原因是mongodb的js驅(qū)動(dòng)已經(jīng)默認(rèn)實(shí)現(xiàn)了返回Promise,而不用我們單獨(dú)去包裝Promise了。
使用Promise鏈
MongoClient.connect(url + db_name).then(db=> {
return db.collection('blogs');
}).then(coll=> {
return coll.find().toArray();
}).then(blogs=> {
console.log(blogs.length);
}).catch(err=> {
console.log(err);
})
Promise的then()方法可以返回另一個(gè)Promise,也可以返回一個(gè)同步的值,如果返回的是一個(gè)同步值,將會(huì)被包裝成一個(gè)Promise。
上面的例子中,db.collection()將返回一個(gè)同步的值,即集合對(duì)象,但是被包裝成Promise,將會(huì)透?jìng)鞯较乱粋€(gè)then()方法。
上面一個(gè)例子,是使用的Promise鏈。
先連接數(shù)據(jù)庫(kù)MongoClient.connect()返回一個(gè)Promise,然后在then()方法里獲得數(shù)據(jù)庫(kù)對(duì)象db,然后再獲取到coll對(duì)象再返回。在下一個(gè)then()方法獲得coll對(duì)象,然后進(jìn)行查詢(xún),查詢(xún)結(jié)果返回,逐層調(diào)用then()方法,形成一個(gè)Promise鏈。
在這個(gè)Promise鏈上,如果任何一個(gè)環(huán)節(jié)出現(xiàn)異常,都會(huì)被最后的catch()捕捉到。
可以說(shuō),這個(gè)使用Promise鏈寫(xiě)的代碼,比層層調(diào)用回調(diào)函數(shù)更優(yōu)雅,流程也更明確。先獲得數(shù)據(jù)庫(kù)對(duì)象,再獲得集合對(duì)象,最后查詢(xún)數(shù)據(jù)。
但是這里有個(gè)不怎么“優(yōu)雅”的問(wèn)題,在于,每一個(gè)then()方法獲取的對(duì)象,都是上一個(gè)then()方法返回的數(shù)據(jù)。而不能跨層訪問(wèn)。
什么意思,就是說(shuō)在第三個(gè)then(blogs => {})中我們只能獲取到查詢(xún)的結(jié)果blogs,而不能使用上面的db對(duì)象和coll對(duì)象。這個(gè)時(shí)候,如果要打印出blogs列表后,要關(guān)閉數(shù)據(jù)庫(kù)db.close()怎么辦?
這個(gè)時(shí)候,可以?xún)煞N解決方法:
第一種是,使用then()嵌套。我們將Promise鏈打斷,使之嵌套,猶如使用回調(diào)函數(shù)的嵌套一般:
MongoClient.connect(url + db_name).then(db=> {
let coll = db.collection('blogs');
coll.find().toArray().then(blogs=> {
console.log(blogs.length);
db.close();
}).catch(err=> {
console.log(err);
});
}).catch(err=> {
console.log(err);
})
這里我們將兩個(gè)Promise嵌套,這樣在最后一個(gè)查詢(xún)操作里面,就可以調(diào)用外面的db對(duì)象了。但是這中方式,并不推薦。原因很簡(jiǎn)單,我們從一種回調(diào)函數(shù)地獄走向了另一種Promise回調(diào)地獄。
而且,我們要對(duì)每個(gè)Promise的異常進(jìn)行捕捉,因?yàn)镻romise沒(méi)有形成鏈。
還有一種方式, 是在每個(gè)then()方法里都將db傳過(guò)來(lái):
MongoClient.connect(url + db_name).then(db=> {
return {db:db,coll:db.collection('blogs')};
}).then(result=> {
return {db:result.db,blogs:result.coll.find().toArray()};
}).then(result=> {
return result.blogs.then(blogs=> { //注意這里,result.coll.find().toArray()返回的是一個(gè)Promise,因此這里需要再解析一層
return {db:result.db,blogs:blogs}
})
}).then(result=> {
console.log(result.blogs.length);
result.db.close();
}).catch(err=> {
console.log(err);
});
我們?cè)诿總€(gè)then()方法的返回中,都將db及其每次的其他結(jié)果組成一個(gè)對(duì)象返回。請(qǐng)注意,如果每次的結(jié)果都是一個(gè)同步的值還好說(shuō),但是如果是一個(gè)Promise值,每一個(gè)Promise都需要多做一層解析。
例如上面的一個(gè)例子,第二個(gè)then()方法返回的 {db:result.db,blogs:result.coll.find().toArray()} 對(duì)象中, blogs 是一個(gè)Promise,在下一個(gè)then()方法中,我們無(wú)法直接引用博客列表數(shù)組值,因此需要先調(diào)用then()方法解析一層,然后將兩個(gè)同步值db和blogs返回。
注意,這里涉及到了Promise的嵌套,不過(guò)一個(gè)Promise只嵌套一層then()。
這種方式,也是很蛋疼的一個(gè)方式,因?yàn)槿绻龅絫hen()方法中返回的不是同步的值,而是Promise的話,我們需要多做很多工作。而且,每次都透?jìng)饕粋€(gè)“多余”的db對(duì)象,在邏輯上也有點(diǎn)冗余。
但除此之外,對(duì)于Promise鏈的使用,如果遇到上面的問(wèn)題,好像也沒(méi)其他更好的方法解決了。我們只能根據(jù)場(chǎng)景去選擇一種“最優(yōu)”的方案,如果要使用Promise鏈的話。
鑒于Promise上面蛋疼的問(wèn)題,TJ大神將ES6中的生成器函數(shù),用co模塊包裝了一下,以更優(yōu)雅的方式來(lái)解決上面的問(wèn)題。
co搭配生成器函數(shù)
如果使用co模塊搭配生成器函數(shù),那么上面的例子可以改寫(xiě)如下:
const co = require('co');
co(function* (){
let db = yield MongoClient.connect(url + db_name);
let coll = db.collection('blogs');
let blogs = yield coll.find().toArray();
console.log(blogs.length);
db.close();
}).catch(err=> {
console.log(err);
});
co是一個(gè)函數(shù),將接受一個(gè)生成器函數(shù)作為參數(shù),去執(zhí)行這個(gè)生成器函數(shù)。生成器函數(shù)中使用 yield 關(guān)鍵字來(lái)“同步”獲取每個(gè)異步操作的值。
上面代碼在代碼形式上,比上面使用Promise鏈要優(yōu)雅,我們消滅了回調(diào)函數(shù),代碼看起來(lái)都是同步的。除了使用co和yield有點(diǎn)怪之外。
使用co模塊,我們要將所有的操作包裝成一個(gè)生成器函數(shù),然后使用co()去調(diào)用這個(gè)生成器函數(shù)??瓷先ヒ策€可以接受,但是ES的進(jìn)化是不滿足于此的,于是async/await被提到了ES7的提案。
async/await
我們先看一下使用async/await改寫(xiě)上面的代碼:
(async function(){
let db = await MongoClient.connect(url + db_name);
let coll = db.collection('blogs');
let blogs = await coll.find().toArray();
console.log(blogs.length);
db.close();
})().catch(err=> {
console.log(err);
});
我們對(duì)比代碼可以看出,async/await和co兩種方式代碼極為相似。
co換成了async,yield換成了await。同時(shí)生成器函數(shù)變成了普通函數(shù)。
這種方式在語(yǔ)義上更加清晰明了,async表明這個(gè)函數(shù)是異步的,同時(shí)await表示要“等待”異步操作返回值。
async函數(shù)返回一個(gè)Promise,上面的代碼其實(shí)是這樣:
let getBlogs = async function(){
let db = await MongoClient.connect(url + db_name);
let coll = db.collection('blogs');
let blogs = await coll.find().toArray();
db.close();
return blogs;
};
getBlogs().then(result=> {
console.log(result.length);
}).catch(err=> {
console.log(err);
})
我們定義getBlogs為一個(gè)async函數(shù),最后返回得到的博客列表最終會(huì)被包裝成一個(gè)Promise返回,如上,我們直接調(diào)用getBlogs().then()方法可獲取async函數(shù)返回值。
好了,上面我們簡(jiǎn)單對(duì)比了一下三種解決異步方案,下面我們來(lái)深入了解一下async/await。
深入async/await
async返回值
async用于定義一個(gè)異步函數(shù),該函數(shù)返回一個(gè)Promise。
如果async函數(shù)返回的是一個(gè)同步的值,這個(gè)值將被包裝成一個(gè)理解resolve的Promise,等同于return Promise.resolve(value) 。
await用于一個(gè)異步操作之前,表示要“等待”這個(gè)異步操作的返回值。await也可以用于一個(gè)同步的值。
//返回一個(gè)Promise
let timer = async functiontimer(){
return new Promise((resolve,reject) => {
setTimeout(()=> {
resolve('500');
},500);
});
}
timer().then(result=> {
console.log(result); //500
}).catch(err=> {
console.log(err.message);
});
//返回一個(gè)同步的值
let sayHi = async functionsayHi(){
let hi = await 'hello world';
return hi; //等同于return Promise.resolve(hi);
}
sayHi().then(result=> {
console.log(result);
});
上面這個(gè)例子返回是一個(gè)同步的值,字符串'hello world',sayHi()是一個(gè)async函數(shù),返回值被包裝成一個(gè)Promise,可以調(diào)用then()方法獲取返回值。
對(duì)于一個(gè)同步的值,可以使用await,也可以不使用await。效果效果是一樣的。具體用不用,看情況。
比如上面使用mongodb查詢(xún)博客那個(gè)例子, let coll = db.collection('blogs'); ,這里我們就沒(méi)有用await,因?yàn)檫@是一個(gè)同步的值。當(dāng)然,也可以使用await,這樣會(huì)顯得代碼統(tǒng)一。雖然效果是一樣的。
async函數(shù)的異常
let sayHi = async functionsayHi(){
throw new Error('出錯(cuò)了');
}
sayHi().then(result=> {
console.log(result);
}).catch(err=> {
console.log(err.message); //出錯(cuò)了
});
我們直接在async函數(shù)中拋出一個(gè)異常,由于返回的是一個(gè)Promise,因此,這個(gè)異??梢哉{(diào)用返回Promise的catch()方法捕捉到。
和Promise鏈的對(duì)比:
我們的async函數(shù)中可以包含多個(gè)異步操作,其異常和Promise鏈有相同之處,如果有一個(gè)Promise被reject()那么后面的將不會(huì)再進(jìn)行。
let count = ()=>{
return new Promise((resolve,reject) => {
setTimeout(()=>{
reject('故意拋出錯(cuò)誤');
},500);
});
}
let list = ()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve([1,2,3]);
},500);
});
}
let getList = async ()=>{
let c = await count();
let l = await list();
return {count:c,list:l};
}
console.time('begin');
getList().then(result=> {
console.log(result);
}).catch(err=> {
console.timeEnd('begin');
console.log(err);
});
//begin: 507.490ms
//故意拋出錯(cuò)誤
如上面的代碼,定義兩個(gè)異步操作,count和list,使用setTimeout延時(shí)500毫秒,count故意直接拋出異常,從輸出結(jié)果來(lái)看,count()拋出異常后,直接由catch()捕捉到了,list()并沒(méi)有繼續(xù)執(zhí)行。
并行
使用async后,我們上面的例子都是串行的。比如上個(gè)list()和count()的例子,我們可以將這個(gè)例子用作分頁(yè)查詢(xún)數(shù)據(jù)的場(chǎng)景。
先查詢(xún)出數(shù)據(jù)庫(kù)中總共有多少條記錄,然后再根據(jù)分頁(yè)條件查詢(xún)分頁(yè)數(shù)據(jù),最后返回分頁(yè)數(shù)據(jù)以及分頁(yè)信息。
我們上面的例子count()和list()有個(gè)“先后順序”,即我們先查的總數(shù),然后又查的列表。其實(shí),這兩個(gè)操作并無(wú)先后關(guān)聯(lián)性,我們可以異步的同時(shí)進(jìn)行查詢(xún),然后等到所有結(jié)果都返回時(shí)再拼裝數(shù)據(jù)即可。
let count = ()=>{
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(100);
},500);
});
}
let list = ()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve([1,2,3]);
},500);
});
}
let getList = async ()=>{
let result = await Promise.all([count(),list()]);
return result;
}
console.time('begin');
getList().then(result=> {
console.timeEnd('begin'); //begin: 505.557ms
console.log(result); //[ 100, [ 1, 2, 3 ] ]
}).catch(err=> {
console.timeEnd('begin');
console.log(err);
});
我們將count()和list()使用Promise.all()“同時(shí)”執(zhí)行,這里count()和list()可以看作是“并行”執(zhí)行的,所耗時(shí)間將是兩個(gè)異步操作中耗時(shí)最長(zhǎng)的耗時(shí)。
最后得到的結(jié)果是兩個(gè)操作的結(jié)果組成的數(shù)組。我們只需要按照順序取出數(shù)組中的值即可。
JavaScript 中最蛋疼的事情莫過(guò)于回調(diào)函數(shù)嵌套問(wèn)題。以往在瀏覽器中,因?yàn)榕c服務(wù)器通訊是一種比較昂貴的操作,因此比較復(fù)雜的業(yè)務(wù)邏輯往往都放在服務(wù)器端,前端 JavaScript 只需要少數(shù)幾次 AJAX 請(qǐng)求就可拿到全部數(shù)據(jù)。
但是到了 webapp 風(fēng)行的時(shí)代,前端業(yè)務(wù)邏輯越來(lái)越復(fù)雜,往往幾個(gè) AJAX 請(qǐng)求之間互有依賴(lài),有些請(qǐng)求依賴(lài)前面請(qǐng)求的數(shù)據(jù),有些請(qǐng)求需要并行進(jìn)行。還有在類(lèi)似 Node.js 的后端 JavaScript 環(huán)境中,因?yàn)樾枰M(jìn)行大量 IO 操作,問(wèn)題更加明顯。這個(gè)時(shí)候使用回調(diào)函數(shù)來(lái)組織代碼往往會(huì)導(dǎo)致代碼難以閱讀。
現(xiàn)在比較流行的解決這個(gè)問(wèn)題的方法是使用 Promise,可以將嵌套的回調(diào)函數(shù)展平。但是寫(xiě)代碼和閱讀依然有額外的負(fù)擔(dān)。
另外一個(gè)方案是使用 ES6 中新增的 generator,因?yàn)?generator 的本質(zhì)是可以將一個(gè)函數(shù)執(zhí)行暫停,并保存上下文,再次調(diào)用時(shí)恢復(fù)當(dāng)時(shí)的狀態(tài)。co 模塊是個(gè)不錯(cuò)的封裝。但是這樣略微有些濫用 generator 特性的感覺(jué)。
ES7 中有了更加標(biāo)準(zhǔn)的解決方案,新增了 async/await 兩個(gè)關(guān)鍵詞。async 可以聲明一個(gè)異步函數(shù),此函數(shù)需要返回一個(gè) Promise 對(duì)象。await可以等待一個(gè) Promise 對(duì)象 resolve,并拿到結(jié)果。
比如下面的例子,以往我們無(wú)法在 JavaScript 中使用常見(jiàn)的 sleep 函數(shù),只能使用 setTimeout 來(lái)注冊(cè)一個(gè)回調(diào)函數(shù),在指定的時(shí)間之后再執(zhí)行。有了 async/await 之后,我們就可以這樣實(shí)現(xiàn)了:
async function sleep(timeout) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve();
}, timeout);
});
}
(async function() {
console.log('Do some thing, ' + new Date());
await sleep(3000);
console.log('Do other things, ' + new Date());
})();
執(zhí)行此段代碼,可以在終端中看到結(jié)果:
Do some thing, Mon Feb 23 2015 21:52:11 GMT+0800 (CST)
Do other things, Mon Feb 23 2015 21:52:14 GMT+0800 (CST)
另外 async 函數(shù)可以正常的返回結(jié)果和拋出異常。await 函數(shù)調(diào)用即可拿到結(jié)果,在外面包上 try/catch 就可以捕獲異常。下面是一個(gè)從豆瓣 API 獲取數(shù)據(jù)的例子:
var fetchDoubanApi = function() {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
var response;
try {
response = JSON.parse(xhr.responseText);
} catch (e) {
reject(e);
}
if (response) {
resolve(response, xhr.status, xhr);
}
} else {
reject(xhr);
}
}
};
xhr.open('GET', 'https://api.douban.com/v2/user/aisk', true);
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send(data);
});
};
(async function() {
try {
let result = await fetchDoubanApi();
console.log(result);
} catch (e) {
console.log(e);
}
})();
async 函數(shù)的用法
同 Generator 函數(shù)一樣,async 函數(shù)返回一個(gè) Promise 對(duì)象,可以使用 then 方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到 await 就會(huì)先返回,等到觸發(fā)的異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語(yǔ)句。
下面是一個(gè)例子。
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result){
console.log(result);
});
閱讀本文前,期待您對(duì)promise和ES6(ECMA2015)有所了解,會(huì)更容易理解。本文以體驗(yàn)為主,不會(huì)深入說(shuō)明,結(jié)尾有詳細(xì)的文章引用。
第一個(gè)例子
Async/Await應(yīng)該是目前最簡(jiǎn)單的異步方案了,首先來(lái)看個(gè)例子。這里我們要實(shí)現(xiàn)一個(gè)暫停功能,輸入N毫秒,則停頓N毫秒后才繼續(xù)往下執(zhí)行。
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, time);
})
};
var start = async function () {
// 在這里使用起來(lái)就像同步代碼那樣直觀
console.log('start');
await sleep(3000);
console.log('end');
};
start();
控制臺(tái)先輸出start,稍等3秒后,輸出了end。
基本規(guī)則
async 表示這是一個(gè)async函數(shù),await只能用在這個(gè)函數(shù)里面。await表示在這里等待promise返回結(jié)果了,再繼續(xù)執(zhí)行。await 后面跟著的應(yīng)該是一個(gè)promise對(duì)象(當(dāng)然,其他返回值也沒(méi)關(guān)系,只是會(huì)立即執(zhí)行,不過(guò)那樣就沒(méi)有意義了…)
獲得返回值
await等待的雖然是promise對(duì)象,但不必寫(xiě).then(..),直接可以得到返回值。
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// 返回 ‘ok'
resolve('ok');
}, time);
})
};
var start = async function () {
let result = await sleep(3000);
console.log(result); // 收到 ‘ok'
};
捕捉錯(cuò)誤
既然.then(..)不用寫(xiě)了,那么.catch(..)也不用寫(xiě),可以直接用標(biāo)準(zhǔn)的try catch語(yǔ)法捕捉錯(cuò)誤。
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// 模擬出錯(cuò)了,返回 ‘error'
reject('error');
}, time);
})
};
var start = async function () {
try {
console.log('start');
await sleep(3000); // 這里得到了一個(gè)返回錯(cuò)誤
// 所以以下代碼不會(huì)被執(zhí)行了
console.log('end');
} catch (err) {
console.log(err); // 這里捕捉到錯(cuò)誤 `error`
}
};
循環(huán)多個(gè)await
await看起來(lái)就像是同步代碼,所以可以理所當(dāng)然的寫(xiě)在for循環(huán)里,不必?fù)?dān)心以往需要閉包才能解決的問(wèn)題。
..省略以上代碼
var start = async function () {
for (var i = 1; i <= 10; i++) {
console.log(`當(dāng)前是第${i}次等待..`);
await sleep(1000);
}
};
值得注意的是,await必須在async函數(shù)的上下文中的。
..省略以上代碼
let one2ten = [1,2,3,4,5,6,7,8,9,10];
// 錯(cuò)誤示范
one2ten.forEach(function (v) {
console.log(`當(dāng)前是第${v}次等待..`);
await sleep(1000); // 錯(cuò)誤!! await只能在async函數(shù)中運(yùn)行
});
// 正確示范
for(var v of one2ten) {
console.log(`當(dāng)前是第${v}次等待..`);
await sleep(1000); // 正確, for循環(huán)的上下文還在async函數(shù)中
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
js鼠標(biāo)及對(duì)象坐標(biāo)控制屬性詳細(xì)解析
這篇文章主要是對(duì)js鼠標(biāo)及對(duì)象坐標(biāo)控制屬性進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12
javaScript刪除對(duì)象屬性的幾種方法總結(jié)
這篇文章主要給大家介紹了關(guān)于javaScript刪除對(duì)象屬性的幾種方法的相關(guān)資料,它們?cè)诓煌瑘?chǎng)景下有不同的應(yīng)用,需要根據(jù)實(shí)際情況選擇最合適的刪除方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),下面需要的朋友可以參考下2023-06-06
原生js實(shí)現(xiàn)可兼容PC和移動(dòng)端的拖動(dòng)滑塊功能詳解【測(cè)試可用】
這篇文章主要介紹了原生js實(shí)現(xiàn)可兼容PC和移動(dòng)端的拖動(dòng)滑塊功能,結(jié)合實(shí)例形式詳細(xì)分析了javascript事件響應(yīng)及頁(yè)面元素屬性動(dòng)態(tài)操作實(shí)現(xiàn)滑塊拖動(dòng)功能的相關(guān)操作技巧,需要的朋友可以參考下2019-08-08
javascript匿名函數(shù)中的''return function()''作用
這篇文章主要介紹了javascript匿名函數(shù)中的'return function()'作用介紹,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10
詳解javascript對(duì)數(shù)組和json數(shù)組的操作
這篇文章主要介紹了javascript對(duì)數(shù)組和json數(shù)組的操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
實(shí)現(xiàn)div內(nèi)部滾動(dòng)條滾動(dòng)到底部和頂部的代碼
下面筆者就為大家分享一篇實(shí)現(xiàn)div內(nèi)部滾動(dòng)條滾動(dòng)到底部和頂部的代碼,代碼簡(jiǎn)潔,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11
JS 實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的“59秒后重新發(fā)送驗(yàn)證短信”功能
這篇文章主要介紹了JS 實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的“59秒后重新發(fā)送驗(yàn)證短信”功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
JS apply用法總結(jié)和使用場(chǎng)景實(shí)例分析
這篇文章主要介紹了JS apply用法總結(jié)和使用場(chǎng)景,結(jié)合實(shí)例形式分析了JS apply的基本功能、原理、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2020-03-03
bootstrap實(shí)現(xiàn)二級(jí)下拉菜單效果
這篇文章主要為大家詳細(xì)介紹了bootstrap實(shí)現(xiàn)二級(jí)下拉菜單效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
layer.open的自適應(yīng)及居中及子頁(yè)面標(biāo)題的修改方法
今天小編就為大家分享一篇layer.open的自適應(yīng)及居中及子頁(yè)面標(biāo)題的修改方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09

