近期在研究Pomelo源碼,這個框架基於Node.js,所以非要頻繁地與JavaScript腳本打交道不可。因此,本文中我們來總結
JavaScript語言中匿名函數的主要目的及各種存在形式。其實,匿名函數在許多語言中都有提供,這個詞語各位應該不陌生。
一、函數與匿名函數
首先,我們來看一下在javascript中正常函數定義的語法:
1234 function functionname(var1,var2,...,varX)
{
//...函數體
}
如上所示,正常函數的定義需要一個函數名來標識該函數對象,有了這個標識,我們就可以在其他地方(作用域范圍內)對它進行調用。
那麼,什麼是匿名函數呢?匿名匿名就是沒有名字,在這裡就是沒有函數名。那麼是不是可以直接把上面代碼的函數名去掉呢?答案是顯然是否定的。因為上面不是一個執行表達式,而是一個函數定義體,直接去掉函數名就會報錯。
如下面的代碼就會報"function statement requires a name"的錯誤。
<html>
<head>
<title>test</title>
<scripttype="text/javascript">
function(){
}
</script>
</head>
<body>
</body>
</html>
雖然上面的代碼是會出錯,但是我們仍然不可否認那就是一個匿名函數了,只是匿名函數的出現和執行的形式有一些要求,而上面就
是不符合使用要求的一種,詳細情況我們後面再介紹。
二、匿名函數的好處
匿名函數的好處至少有如下兩點:
1.減少全局變量(或者某作用域內變量)的污染;
2.使代碼結構更加優美,如代碼可以實現塊狀分布。
對於上述兩點,相信各位不難理解。對於第2點,我們知道其他許多語言中往往提供塊狀語言中的局部變量支持(例如C語言中的{}塊內的局部變量定義),而在JS中正可以利用匿名函數的技巧來實現類似於其他高級語言中的上述功能。
三、匿名函數的各種執行形式
1.通過括號運算符(分組運算)使匿名函數執行定義並執行定義後的函數。
(function(){}());
2.通過括號運算符(分組運算)使匿名函數執行定義,然後再執行該函數
(function(){})();
3.通過在function前面加運算符(如!,+,-等)來使匿名函數定義並執行
function(){}();
4.通過new實例化對象來執行匿名函數
new function(){};
5.同void關鍵字來使匿名函數執行
void function(){}();
其實,當我們把new和void改成其他關鍵字時,比如delete,你會發現代碼依然很好地執行。也就是說,只要我們在function前面加上合
法的關鍵字就可以讓它不報錯並執行了。
四、小結
沒有辦法,要想學習新框架肯定要適應開發者的技術背景及設計理念等等。我們的編程習慣在於我們自己,而要跑在別人後面學習只能先熟悉他的方式才能有所學。注意:上述各種javascript匿名函數使用方式在你閱讀幾乎所有基於JS的源碼時都會遇到。大家可以注意下面幾個例子。
例1:來自於Express.js(Express這個框架是基於Node.js的著名Web開發框架)
'json',
'urlencoded',
'bodyParser',
'compress',
'cookieSession',
'session',
'logger',
'cookieParser',
'favicon',
'responseTime',
'errorHandler',
'timeout',
'methodOverride',
'vhost',
'csrf',
'directory',
'limit',
'multipart',
'staticCache',
].forEach(function (name) {
Object.defineProperty(exports, name, {
get: function () {
throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see
https://github.com/senchalabs/connect#middleware.'
);
},
configurable: true
});
});
例2:來自於著名socket.io.js框架(這個長達近4000行的文件中並列定義了類似於下面的十多個匿名函數)。
注意:這個模塊文件在加載時相應的匿名函數是要執行的(而不是只定義匿名函數)
/**
* There is a way to hide the loading indicator in Firefox. If you create and
* remove a iframe it will stop showing the current loading indicator.
* Unfortunately we can't feature detect that and UA sniffing is evil.
*
* @api private
*/
var indicator = global.document && "MozAppearance" in
global.document.documentElement.style;
/**
* Expose constructor.
*/
exports['jsonp-polling'] = JSONPPolling;
/**
* The JSONP transport creates an persistent connection by dynamically
* inserting a script tag in the page. This script tag will receive the
* information of the Socket.IO server. When new information is received
* it creates a new script tag for the new data stream.
*
* @constructor
* @extends {io.Transport.xhr-polling}
* @api public
*/
function JSONPPolling (socket) {
io.Transport['xhr-polling'].apply(this, arguments);
this.index = io.j.length;
var self = this;
io.j.push(function (msg) {
self._(msg);
});
};
/**
* Inherits from XHR polling transport.
*/
io.util.inherit(JSONPPolling, io.Transport['xhr-polling']);
/**
* Transport name
*
* @api public
*/
JSONPPolling.prototype.name = 'jsonp-polling';
/**
* Posts a encoded message to the Socket.IO server using an iframe.
* The iframe is used because script tags can create POST based requests.
* The iframe is positioned outside of the view so the user does not
* notice it's existence.
*
* @param {String} data A encoded message.
* @api private
*/
JSONPPolling.prototype.post = function (data) {
var self = this
, query = io.util.query(
this.socket.options.query
, 't='+ (+new Date) + '&i=' + this.index
);
if (!this.form) {
var form = document.createElement('form')
, area = document.createElement('textarea')
, id = this.iframeId = 'socketio_iframe_' + this.index
, iframe;
form.className = 'socketio';
form.style.position = 'absolute';
form.style.top = '0px';
form.style.left = '0px';
form.style.display = 'none';
form.target = id;
form.method = 'POST';
form.setAttribute('accept-charset', 'utf-8');
area.name = 'd';
form.appendChild(area);
document.body.appendChild(form);
this.form = form;
this.area = area;
}
this.form.action = this.prepareUrl() + query;
function complete () {
initIframe();
self.socket.setBuffer(false);
};
function initIframe () {
if (self.iframe) {
self.form.removeChild(self.iframe);
}
try {
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
iframe = document.createElement('<iframe name="'+ self.iframeId +'">');
} catch (e) {
iframe = document.createElement('iframe');
iframe.name = self.iframeId;
}
iframe.id = self.iframeId;
self.form.appendChild(iframe);
self.iframe = iframe;
};
initIframe();
// we temporarily stringify until we figure out how to prevent
// browsers from turning `\n` into `\r\n` in form inputs
this.area.value = io.JSON.stringify(data);
try {
this.form.submit();
} catch(e) {}
if (this.iframe.attachEvent) {
iframe.onreadystatechange = function () {
if (self.iframe.readyState == 'complete') {
complete();
}
};
} else {
this.iframe.onload = complete;
}
this.socket.setBuffer(true);
};
/**
* Creates a new JSONP poll that can be used to listen
* for messages from the Socket.IO server.
*
* @api private
*/
JSONPPolling.prototype.get = function () {
var self = this
, script = document.createElement('script')
, query = io.util.query(
this.socket.options.query
, 't='+ (+new Date) + '&i=' + this.index
);
if (this.script) {
this.script.parentNode.removeChild(this.script);
this.script = null;
}
script.async = true;
script.src = this.prepareUrl() + query;
script.onerror = function () {
self.onClose();
};
var insertAt = document.getElementsByTagName('script')[0]
insertAt.parentNode.insertBefore(script, insertAt);
this.script = script;
if (indicator) {
setTimeout(function () {
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
document.body.removeChild(iframe);
}, 100);
}
};
/**
* Callback function for the incoming message stream from the Socket.IO server.
*
* @param {String} data The message
* @api private
*/
JSONPPolling.prototype._ = function (msg) {
this.onData(msg);
if (this.open) {
this.get();
}
return this;
};
/**
* The indicator hack only works after onload
*
* @param {Socket} socket The socket instance that needs a transport
* @param {Function} fn The callback
* @api private
*/
JSONPPolling.prototype.ready = function (socket, fn) {
var self = this;
if (!indicator) return fn.call(this);
io.util.load(function () {
fn.call(self);
});
};
/**
* Checks if browser supports this transport.
*
* @return {Boolean}
* @api public
*/
JSONPPolling.check = function () {
return 'document' in global;
};
/**
* Check if cross domain requests are supported
*
* @returns {Boolean}
* @api public
*/
JSONPPolling.xdomainCheck = function () {
return true;
};
/**
* Add the transport to your public io.transports array.
*
* @api private
*/
io.transports.push('jsonp-polling');
})(
'undefined' != typeof io ? io.Transport : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
, this
);
《JavaScript高級程序設計(第三版)》 http://www.linuxidc.com/Linux/2014-09/107426.htm
如何使用JavaScript書寫遞歸函數 http://www.linuxidc.com/Linux/2015-01/112000.htm
JavaScript核心概念及實踐 高清PDF掃描版 (邱俊濤) http://www.linuxidc.com/Linux/2014-10/108083.htm
理解JavaScript中的事件流 http://www.linuxidc.com/Linux/2014-10/108104.htm