JavaScript匿名函数的各种执行形式
近期在研究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
);