JS的非同步執行機制
什麼是非同步執行
為了提高Javascript程式碼的執行效率,JS對於部分函式方法採用了非同步呼叫機制(如Ajax的操作)。非同步執行的函式方法的執行並非為一個佇列挨個執行的,而是相互獨立,同時呼叫執行的,從而避免程式碼執行阻塞,減少不必要的等待時間。
我們來舉一個栗子
大部分新手程式設計時,都會按照一種線性思維的方法去設計程式碼,這就跟JS中的非同步執行機制相沖突。
如:我們在node中,希望在一個讀取文件流的操作後,將讀取到的檔案中的字串賦值給變數,str
之後再用 console.log()
方法輸出讀取到的檔案內容,這時如果按照我們的線性思維去設計程式碼,會寫出如下的操作:
// 需求:封裝一個方法,傳入一個路徑,可以讀取相對應的檔案
const fs = require('fs');
const path = require('path');
// 給定檔案路徑,返回讀取到的內容
function getFileByPath(fpath) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
var str = dataStr;
}
})
}
// 呼叫讀取檔案方法
getFileByPath(path.join(__dirname, './files/1.txt'));
console.log(str);
複製程式碼
控制檯輸出的結果為
ReferenceError: str is not defined
這就是由於我們是按照線性思維去考慮問題,理所當然的認為變數 str
的定義和賦值操作在 console.log()
操作之前,然而真實的情況是,JS在被解析之後,可以瞬間執行的操作,如 console.log()
、for迴圈
等基礎操作,都是按照佇列執行的,如:
var test = function () {
console.log('1');
}
test();
for (var i = 2; i < 5; i++) {
console.log(i);
}
console.log('6');
複製程式碼
控制檯輸出的結果為
1 2 3 4 5 6
然而讀取檔案操作是一個會導致程式碼阻塞的操作,所以JS會將其放置在非同步佇列中,執行後方程式碼,所以栗子中執行程式碼的正確順序應該是先執行console.log()
再執行 getFileByPath()
。
回撥函式
那倘若說我們就是需要有一步操作,放在讀取檔案之後再執行,而不是跳過讀取檔案操作直接執行,那該怎麼辦呢?
這就需要用“回撥函式”的思想來拯救我們。大部分人都知道回撥函式在 jQuery
中被髮揮的淋漓盡致,然而新手往往很少知道回撥函式原理,所以接下來我們仍以這個栗子為代表探討回撥函式。
我們先拋開回撥函式,用最原始的方法讓一些操作在讀取檔案操作後執行該怎麼辦呢?那就是直接改寫整個 getFileByPath()
方法:
// 需求:封裝一個方法,傳入一個路徑,可以讀取相對應的檔案
const fs = require('fs');
const path = require('path');
// 給定檔案路徑,返回讀取到的內容
function getFileByPath(fpath) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
var str = dataStr;
+ console.log(str);
}
})
}
// 呼叫讀取檔案方法
getFileByPath(path.join(__dirname, './files/1.txt'));
複製程式碼
這樣我們就可以在直行完 getFileByPath
方法之後在控制檯輸出讀取的檔案內容。但是這樣的操作並不能很好的解決我們的問題,倘若方法被封裝拿給別人使用,其他人需要更改原始碼才可以實現功能方法,很顯然這樣並不靈活,甚至還會更改該方法原有的功能。
所以我們就需要設定一個回撥函式,在非同步操作完成之後,再進行我們需要的下一步的操作。
為了理解回撥函式的原理,我們先將變更後的這一部分程式碼分理出來:
if (err)
throw err;
else {
var str = dataStr;
+ console.log(str);
}
複製程式碼
可以看出,讀取完檔案之後,會直行else
下的操作,如果我們把 var str = dataStr; console.log(str)
封裝成一個方法命名為 clg
,那我們在 else
之後執行 clg()
方法就可以實現同樣的操作:
function getFileByPath(fpath) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
clg(dataStr);
}
})
}
function clg(dataStr){
var str = dataStr;
console.log(str);
}
getFileByPath(path.join(__dirname, './files/1.txt'));
複製程式碼
這樣我們就可以將讀取檔案操作後執行的操作放在 clg
方法中就可以執行,這個 clg()
實際上就可以稱之為一個回撥函式,但是這樣還是會讓程式碼變得繁雜。
我們來看一下jQuery的回撥函式:
$('#demo').animate({"opacity":"1"}, 1000, fucntion(){... 回撥函式 ..});
jQuery將回撥函式作為一個引數傳入到方法中,所以我們只要在 getFileByPath()
方法中追加一個引數,這個引數是一個函式,我們就可以在原始碼的 else
後執行傳入的這個函式,這個函式就稱之為 "回撥函式" 。
改寫後的 getFileByPath()
方法
function getFileByPath(fpath, callback) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
callback(dataStr);
}
})
}
複製程式碼
之後我們再在呼叫的時候,在引數位寫入一個方法函式,這個方法就會被傳入getFileByPath()
方法內部,等檔案讀取操作完成之後再直行。
getFileByPath(path.join(__dirname, './files/1.txt'), function(dataStr){
var str = dataStr;
console.log(str);
});
複製程式碼
值得注意的是,我們在原始碼中設定傳入的引數位時,對回撥函式設定了一個引數
callback(dataStr);
這個dataStr
就是檔案讀取操作讀取的檔案內容,我們將個變數傳入在callback()
方法中,在呼叫getFileByPath()
時寫入的回撥函式中就可以呼叫dataStr
這個變數了。
這就是對回撥函式的簡單講解,萌新程式設計師,歡迎糾錯- ̗̀(๑ᵔ⌔ᵔ๑)
JS的非同步執行機制
什麼是非同步執行
為了提高Javascript程式碼的執行效率,JS對於部分函式方法採用了非同步呼叫機制(如Ajax的操作)。非同步執行的函式方法的執行並非為一個佇列挨個執行的,而是相互獨立,同時呼叫執行的,從而避免程式碼執行阻塞,減少不必要的等待時間。
我們來舉一個栗子
大部分新手程式設計時,都會按照一種線性思維的方法去設計程式碼,這就跟JS中的非同步執行機制相沖突。
如:我們在node中,希望在一個讀取文件流的操作後,將讀取到的檔案中的字串賦值給變數,str
之後再用 console.log()
方法輸出讀取到的檔案內容,這時如果按照我們的線性思維去設計程式碼,會寫出如下的操作:
// 需求:封裝一個方法,傳入一個路徑,可以讀取相對應的檔案
const fs = require('fs');
const path = require('path');
// 給定檔案路徑,返回讀取到的內容
function getFileByPath(fpath) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
var str = dataStr;
}
})
}
// 呼叫讀取檔案方法
getFileByPath(path.join(__dirname, './files/1.txt'));
console.log(str);
複製程式碼
控制檯輸出的結果為
ReferenceError: str is not defined
這就是由於我們是按照線性思維去考慮問題,理所當然的認為變數 str
的定義和賦值操作在 console.log()
操作之前,然而真實的情況是,JS在被解析之後,可以瞬間執行的操作,如 console.log()
、for迴圈
等基礎操作,都是按照佇列執行的,如:
var test = function () {
console.log('1');
}
test();
for (var i = 2; i < 5; i++) {
console.log(i);
}
console.log('6');
複製程式碼
控制檯輸出的結果為
1 2 3 4 5 6
然而讀取檔案操作是一個會導致程式碼阻塞的操作,所以JS會將其放置在非同步佇列中,執行後方程式碼,所以栗子中執行程式碼的正確順序應該是先執行console.log()
再執行 getFileByPath()
。
回撥函式
那倘若說我們就是需要有一步操作,放在讀取檔案之後再執行,而不是跳過讀取檔案操作直接執行,那該怎麼辦呢?
這就需要用“回撥函式”的思想來拯救我們。大部分人都知道回撥函式在 jQuery
中被髮揮的淋漓盡致,然而新手往往很少知道回撥函式原理,所以接下來我們仍以這個栗子為代表探討回撥函式。
我們先拋開回撥函式,用最原始的方法讓一些操作在讀取檔案操作後執行該怎麼辦呢?那就是直接改寫整個 getFileByPath()
方法:
// 需求:封裝一個方法,傳入一個路徑,可以讀取相對應的檔案
const fs = require('fs');
const path = require('path');
// 給定檔案路徑,返回讀取到的內容
function getFileByPath(fpath) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
var str = dataStr;
+ console.log(str);
}
})
}
// 呼叫讀取檔案方法
getFileByPath(path.join(__dirname, './files/1.txt'));
複製程式碼
這樣我們就可以在直行完 getFileByPath
方法之後在控制檯輸出讀取的檔案內容。但是這樣的操作並不能很好的解決我們的問題,倘若方法被封裝拿給別人使用,其他人需要更改原始碼才可以實現功能方法,很顯然這樣並不靈活,甚至還會更改該方法原有的功能。
所以我們就需要設定一個回撥函式,在非同步操作完成之後,再進行我們需要的下一步的操作。
為了理解回撥函式的原理,我們先將變更後的這一部分程式碼分理出來:
if (err)
throw err;
else {
var str = dataStr;
+ console.log(str);
}
複製程式碼
可以看出,讀取完檔案之後,會直行else
下的操作,如果我們把 var str = dataStr; console.log(str)
封裝成一個方法命名為 clg
,那我們在 else
之後執行 clg()
方法就可以實現同樣的操作:
function getFileByPath(fpath) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
clg(dataStr);
}
})
}
function clg(dataStr){
var str = dataStr;
console.log(str);
}
getFileByPath(path.join(__dirname, './files/1.txt'));
複製程式碼
這樣我們就可以將讀取檔案操作後執行的操作放在 clg
方法中就可以執行,這個 clg()
實際上就可以稱之為一個回撥函式,但是這樣還是會讓程式碼變得繁雜。
我們來看一下jQuery的回撥函式:
$('#demo').animate({"opacity":"1"}, 1000, fucntion(){... 回撥函式 ..});
jQuery將回撥函式作為一個引數傳入到方法中,所以我們只要在 getFileByPath()
方法中追加一個引數,這個引數是一個函式,我們就可以在原始碼的 else
後執行傳入的這個函式,這個函式就稱之為 "回撥函式" 。
改寫後的 getFileByPath()
方法
function getFileByPath(fpath, callback) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err)
throw err;
else {
callback(dataStr);
}
})
}
複製程式碼
之後我們再在呼叫的時候,在引數位寫入一個方法函式,這個方法就會被傳入getFileByPath()
方法內部,等檔案讀取操作完成之後再直行。
getFileByPath(path.join(__dirname, './files/1.txt'), function(dataStr){
var str = dataStr;
console.log(str);
});
複製程式碼
值得注意的是,我們在原始碼中設定傳入的引數位時,對回撥函式設定了一個引數
callback(dataStr);
這個dataStr
就是檔案讀取操作讀取的檔案內容,我們將個變數傳入在callback()
方法中,在呼叫getFileByPath()
時寫入的回撥函式中就可以呼叫dataStr
這個變數了。
這就是對回撥函式的簡單講解,萌新程式設計師,歡迎糾錯- ̗̀(๑ᵔ⌔ᵔ๑)