前言
我的上一篇<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源代码中就有好几处地方使用了该方式:
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)
这里先从一个例子开始:
如果大家对上述代码中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" onclick="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" onclick="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源代码中的使用:
通过前面的讲解,最后再看看下面这个例子:
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