hans.hu

夫天地者,万物之逆旅也;光阴者,百代之过客也。而浮生若梦,为欢几何?古人秉烛夜游,良有以也。况阳春召我以烟景,大块假我以文章。

Javascript进阶

前言
我的上一篇<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源代码中就有好几处地方使用了该方式:

Code

ECMAScript为object类型定义了一个内部属性prototype。在对象的属性解析过程中,会需要用到这个内部属性所引用的对象链--即原型链,原型链终止于链中原型为null的对象。
可以通过一个公共的prototype属性,来对与内部的prototype属性对应的原型对象进行赋值或重新定义。
例如:

function Person(first, last) {
  
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)
这里先从一个例子开始:

Code

如果大家对上述代码中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的情况下,仍可以输出参数,可以采用如下方法:

function log() {
    
if( window.console )
        console.debug.apply( console, arguments );
    
else
        alert( [].join.apply( arguments, [
' '] ) );
}

log( 'json feed received:', json );

2, 利用Array中的slice方法并通过call生成新的数组,简化操作:

var args = []; // empty array
//
 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()方法类似:

String.prototype.format = function(){
 
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源代码中的使用:

Code

 

通过前面的讲解,最后再看看下面这个例子:

var Class = {
 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

 

posted on 2009-04-11 22:31  hans.hu  阅读(5054)  评论(7编辑  收藏  举报

导航