前言
我的上一篇<Javascript简述>对JavaScript做了浅尝辄止的描述,并没有深入讲解其细节内容。本文则会从下面几方面的内容对JavaScript做一些整理与深入的讲解:
1. object
2. this & closure
3. call & apply
4. arguments
深入了解这些概念将使你在JavaScript的使用上更加的游刃有余。
一、object
JavaScript是弱类型的脚本语言,所以声明变量时不用指定类型。
在JavaScript中有三个基本类型boolean,number,string与特殊类型null(空/无效),undefined(无定义),引用类型则是object。
数组和函数都是实现为object类型的对象。一般情况下在JavaScript中可以这样定义对象,类似perl中的hash:
var functions = {
show:function(){
alert("Show"); },
hide:function(){
alert("Hide"); }
};
这里有两种方式调用其中的函数:
1, functions.show();
2, functions["show"]();
显然用第二种方式可以让我们在程序中动态生成函数调用方式,有更大的自由性,充分体现出JavaScript动态语言的特性。
例如:
functions[condition?"show":"hide"](); // 根据condition条件决定调用show/hide
在jQuery源代码中就有好几处地方使用了该方式:
// Save the old toggle function
_toggle: jQuery.fn.toggle,
toggle: function( fn, fn2 ){
var bool = typeof fn === "boolean";
return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
this._toggle.apply( this, arguments ) :
fn == null || bool ?
this.each(function(){
var state = bool ? fn : jQuery(this).is(":hidden");
jQuery(this)[ state ? "show" : "hide" ]();
}) :
this.animate(genFx("toggle", 3), fn, fn2);
},
fadeTo: function(speed,to,callback){
return this.animate({opacity: to}, speed, callback);
},
ECMAScript为object类型定义了一个内部属性prototype。在对象的属性解析过程中,会需要用到这个内部属性所引用的对象链--即原型链,原型链终止于链中原型为null的对象。
可以通过一个公共的prototype属性,来对与内部的prototype属性对应的原型对象进行赋值或重新定义。
例如:
this .first = first;
this .last = last;
}
Person.prototype.fullName = function () {
return this .first + ' ' + this .last;
}
Person.prototype.toString = function (){
return ' [Person: ' + this .fullName() + ' ] ' ;
};
var simon = new Person( ' Simon ' , ' Willison ' );
document.write(simon.fullName());
二、this与闭包(closure)
这里先从一个例子开始:
//V2.1 - Based on http://www.openjs.com/scripts/jx/ - released under BSD license
// 2.1 version by Chris Maunder (http://www.codeproject.com/)
// - use XMLHttpRequest as 1st priority + use window.XMLHttpRequest in test, allow loading
// indicator to be specified. Also added '$'. Added custom error handler. Added optional
// load parameters removed bind method
jx = {
http:false, //HTTP Object
format:'text',
callback:function(data){},
indicator:false,
customError:false,
customhandler:false,
$: function(id) {return document.getElementById(id);},
error: function(code){alert('An error occurred (status '+code.toString()+')')},
//Create a xmlHttpRequest object - this is the constructor.
getHTTPObject : function() {
var http = false;
if (window.XMLHttpRequest) {
try {http = new XMLHttpRequest();}
catch (e) {http = false;}
} else if(typeof ActiveXObject != 'undefined') {
try {http = new ActiveXObject("Msxml2.XMLHTTP");}
catch (e) {
try {http = new ActiveXObject("Microsoft.XMLHTTP");}
catch (E) {http = false;}
}
}
return http;
},
// This function is called from the user's script.
//Arguments -
// url - The url of the serverside script that is to be called. Append all the arguments to
// this url - eg. 'get_data.php?id=5&car=benz'
// callback - Function that must be called once the data is ready.
// loadingId - id of 'loading' HTML element to be displayed
// errCallback - custom error callback
// format - The return type for this function. Could be 'xml','json' or 'text'. If it is json,
// the string will be 'eval'ed before returning it. Default:'text'
// method - GET or POST. Default 'GET'
// customHandler - custom error callback
load : function (url,callback /*,loadingId,errCallback,format,method,customHandler*/) {
var method='GET';
var format='text';
this.init(); //The XMLHttpRequest object is recreated at every call - to defeat Cache problem in IE
if(!this.http||!url) return;
//XML Format need this for some Mozilla Browsers
if (this.http.overrideMimeType) this.http.overrideMimeType('text/xml');
this.callback=callback;
if(arguments[2])this.indicator=this.$(arguments[2]);
if(arguments[3])this.customError=arguments[3];
if(arguments[4])method = arguments[4].toUpperCase();
if(arguments[5])format = arguments[5].toLowerCase();
if(arguments[6])this;customhandler = arguments[6]
var ths = this; //Closure
var now = "cache-bust=" + new Date().getTime(); // IE cache kill
url += (url.indexOf("?")+1) ? "&" : "?";
url += now;
var parameters = null;
if(method=="POST") {
var parts = url.split("\?");
url = parts[0];
parameters = parts[1];
}
this.http.open(method, url, true);
if(method=="POST") {
this.http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
this.http.setRequestHeader("Content-length", parameters.length);
this.http.setRequestHeader("Connection", "close");
}
if(this.customhandler) { //If a custom handler is defined, use it
this.http.onreadystatechange = this.customhandler;
} else {
this.http.onreadystatechange = function () {
if(!ths) return;
var http = ths.http;
if (ths.indicator)ths.indicator.style.display='';
if (http.readyState == 4) {//4 = document loaded.
if (ths.indicator)ths.indicator.style.display='none';
if(http.status == 200||http.status==0) {
var result = "";
if(http.responseText) result = http.responseText;
if(ths.format.charAt(0) == "j") { // JSON -> eval result first
result = result.replace(/[\n\r]/g,""); //\n's in JSON string cause errors in IE
result = eval('('+result+')');
} else if(ths.format.charAt(0) == "x") { //XML
result = http.responseXML;
}
if(ths.callback) ths.callback(result); // process
} else {
if(ths.customError)ths.customError(http.status); else ths.error(http.status);
}
}
}
}
this.http.send(parameters);
},
init : function() {this.http = this.getHTTPObject();}
}
如果大家对上述代码中this与闭包的用法有疑惑的话,可以继续往下看。
讲到这里就不得不提及JavaScript的作用域(scope)。所有的JavaScript代码都是在一个执行环境中被执行,有自己的作用域。这部分内容相对来说比较复杂,读者有兴趣可以详细参考<Javascript 闭包>。
JavaScript中的this就是被调用对象的引用。形象的说,是当前执行环境的上下文对象;简单的说,就是函数的拥有者Owner(这点对理解Event Handling很重要)。
闭包并不是JavaScript特有的概念,Martin Fowler早些年就发表了一篇关于闭包的文章(中文版)。
闭包是具有闭合作用域 的匿名函数。简单来说就是在function内定义的function,內部的function可以存取外部function內定义的变量。
现在来看看上面说的那个例子:
1、定义了一个jx对象,由于是作为Ajax使用的,所以它提供的一些属性都是为了自定义参数使用的。
2、我们关注它的load方法:
this.init(); // 对XMLHttpRequest对象进行初始化
var ths = this; // 在这里把jx对象本身保存到ths中是为了onreadystatechange函数中可以正确使用到jx对象中的属性(否则onreadystatechange内部的this指向的是http对象而不是我们要使用的jx对象)。
this.http.onreadystatechange = function () {
if(!ths) return;
var http = ths.http;
if (ths.indicator)ths.indicator.style.display='';
if (http.readyState == 4) {//4 = document loaded.
if (ths.indicator)ths.indicator.style.display='none';
if(http.status == 200||http.status==0) {
......
if(ths.callback) ths.callback(result); // process
}
......
}
}
这里还要强调一下JavaScrpt的Event Handling里this的使用:
假设Html页面上有元素<div id="prompt"">Hello World!</div>,我们可以通过两种机制给div元素添加上下面的click事件:
// 在Html页面上定义click函数
function fn_click(){
this.style.color = "#cc0000"; // 此时this指向的是函数的拥有者--页面,确切说是JavaScript的window对象
}
1, Copying
var divP = document.getElementById("prompt");
divP.onclick = fn_click;
顾名思义,Copying机制是通过把fn_click函数拷贝给div的onclick属性。因此事件执行后this指向的就是触发事件的div元素,故该函数在div点击后可以正常运行。
【注:这里给读者提个疑问--我们应该如何更改fn_click中的this指向,使其指向的是你需要的对象上而不是触发事件的div元素?这篇文章对此有精彩的论述:JavaScript's Slippery this Reference and Other Monsters in The Closet】
2, Referring
Referring机制则是找到引用的fn_click函数后再执行它。
<div id="prompt" οnclick="javascript:fn_click();">Hello World!</div>
大家可以想想其结果是什么呢?
由于其采用的是Referring机制,故fn_click函数中的this指向的是全局对象window,那么显然onclick后会弹出错误--style不是window对象的属性。
为了解决这个问题,可以修改为:
function fn_click(obj){
obj.style.color = "#cc0000";
}
<div id="prompt" οnclick="javascript:fn_click(this);">Hello World!</div>
三、call与apply
apply
Allows you to apply a method of another object in the context of a different object (the calling object).
call
Allows you to call (execute) a method of another object in the context of a different object (the calling object).
作用都是将函数绑定到另外一个对象上去运行,两者只是在定义参数方式有所区别:
var result = fun.apply(thisArg[, argsArray]);
var result = fun.call(thisArg[, arg1[, arg2[, ...]]]);
这里的参数差别就决定了apply在需要传参数的应用上更有优势。
请看下面的两个例子:
1, 为了保证在没有window.console的情况下,仍可以输出参数,可以采用如下方法:
if ( window.console )
console.debug.apply( console, arguments );
else
alert( [].join.apply( arguments, [ ' ' ] ) );
}
log( 'json feed received:', json );
2, 利用Array中的slice方法并通过call生成新的数组,简化操作:
// copy all other arguments we want to "pass through"
for ( var i = 2 ; i < arguments.length; i ++ )
{
args.push(arguments[i]);
}
func.apply(obj, args);
// 通过刚才的介绍,我们可以简化为:
var args = [].slice.call(arguments, 2 );
func.apply(obj, args);
四、arguments
在JavaScript函数代码中可以使用特殊的对象arguments来实现不定参数的效果。
它以类数组的形式保存了当前函数调用的参数,但是它实际上并不是数组,使用arguments instanceof Array会返回"false",不过我们可以使用下标获取其值以及长度length属性(表示调用参数的数目)。此外arguments还有个非常有用的属性callee,它表示对当前调用函数对象自身的引用,特别是可以用它来调用自身的匿名函数。
注:
1, 网上有不少方法说明arguments不是数组,大家有兴趣可以去看看。
2, 函数属于引用类型,有自己的属性和方法,其中length声明了函数期望的参数个数。
函数名.length或arguments.callee.length // 形参个数
arguments.length // 实参个数
请看下面这个经典例子--与C#中String.Format()方法类似:
var args = arguments; // 将参数保存到args中,以便于在stringobject.replace函数中被使用
return this .replace( / \{(\d+)\} / g,
function (m,s,i,t){
return args[s]; // s是模式中子表达式匹配的字符串,正是{0},{1}中的0,1
});
}
var formats = " {0} is {1}! " ;
document.write(formats.format( " hans " , " chinese " ));
其在jQuery源代码中的使用:
// Mozilla, Opera and webkit nightlies currently support this event
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", function(){
document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
jQuery.ready();
}, false );
// If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", function(){
if ( document.readyState === "complete" ) {
document.detachEvent( "onreadystatechange", arguments.callee );
jQuery.ready();
}
});
通过前面的讲解,最后再看看下面这个例子:
create : function () {
return function () { this .initialize.apply( this , arguments); }
}
};
var vehicle = Class.create();
vehicle.prototype = {
initialize : function (type){
this .type = type;
},
showSelf : function (){
return ' this vehicle is ' + this .type;
}
};
var moto = new vehicle( ' Moto ' );
log.info(moto.showSelf());
现在,大家可以看明白这个例子吗?
五、References
http://www.javascriptkit.com/jsref/
http://www.quirksmode.org/js/this.html
http://www.blueidea.com/tech/web/2007/4855.asp
http://blog.csdn.net/mumuTiger/archive/2008/03/25/2217731.aspx
http://www.cn-cuckoo.com/wordpress/wp-content/uploads/2007/08/JavaScriptClosures.html