[转载][JavaScript]你所想像不到的 JavaScript
转载自: 爱德华日志
这不是一篇教你如何在网页中应用 JavaScript 的教材。这是一篇让你明了看似简单的 JavaScript,其实其核心语法功能强大。我将在这篇文章中说明 JavaScript 物件导向的特性,并说明如何透过这些特性,达到传统程式语言 (C++/Java) 所难以完成的功能。
Everything is Object in JavaScript
开宗明义:在 JavaScript 中任何东西都是物件:变量是物件、函式是物件,常数也是物件。证明方式:
alert (typeof('abc')); alert (typeof(123)); var ary = [123, "abc"]; alert (typeof(ary));
上面的 typeof 会显示物件资料型态。得到结果分别是 string, number, object。
Create Objects in JavaScript
既然任何东西都是物件,那建立物件的方法就多了。直接看个例子:
var main = new Object; // 建立新物件 main.x = 123; // 设定物件成员变量(属性)之一 main["y"] = "XYZ"; // 设定物件成员变量(属性)之二 alert(main["x"]); // 取得物件属性并输出 alert(main.y);
可以看到在 JavaScript 中,main.x 与 main["x"] 这两种语法是通用的。其实在其他语言中,这两种表示法的语意并不相同。我稍后再作说明。
List All Members in an Object
这是 JavaScript 的必杀技,使用 JavaScript 的人务必要学会这个技巧。底下函式可以传回一个物件的所有成员的字串表达式,包括物件中的属性及方法。在物件导向程式设计中,这种技术叫 reflection。
function listMember(main) { var s = ""; for( key in main ) // 使用 in 运算子列举所有成员 s += key + ": " + main[key] + "\n"; return s; }
范例码中的 key 会对应到物件中的属性名称,如 “x” 或 “y”,而 main[key] 则对应到属性值。
说这项技巧是必杀技的原因是,你可以透过这项技巧,将物件封装的黑箱打开来,看看里面藏有什么东西。我常用这项技巧来看看 IE 与 Mozilla 的 Dom 物件模型有何不同。试试看下面呼叫范例,就可以知道这项技巧的强大了:
var ary = [123, "abc"]; alert (listMember(ary)); alert (listMember(document.location));
Construct Object with Initial Value
要在建立物件的同时指定物件初始值,必须先透过 function 建立一个“原型物件”(或称为 constructor),再透过 new 运算子建立新物件。例如以下程式码会建立一个二维阵列的原型,再产生一个新的二维物件。
function Array2DVar(x,y) { // 定义二维阵列原型 this.length = x; this.x = x; // x 维度长度 this.y = y; // y 维度长度 for(var i = 0; i < this.length; i++) // 初始各元素值为 null this[i] = new Array(y); // this 代表物件本身 } var a2dv = new Array2DVar(10, 10); // 建立新的 10*10 的二维阵列 a2dv[1][3] = "ABC"; // 设定二维阵列元素值 a2dv[2][6] = "XYZ"; a2dv[9][9] = 1000; alert( a2dv[1][3]); // 取得二维阵列元素值,并显示出来 alert( a2dv[2][6]); alert( a2dv[9][9]);
Initial Array Object
在 JavaScript 中阵列也是物件 (其实近代多数语言中阵列也都是物件,只有像 C 或 Assembly 这类古老的语言才不把阵列看成物件),因此也可以用 constructor 的语法来建构。当然 JavaScript 还提供了 [] 语法,以更方便建构阵列,范例如下:
a = new Array("abc", "xyz", 1000); // constructor 语法,或 a = ["abc", "xyz", 1000]; // 阵列标准语法
阵列的元素可以是简单的资料、其他物件,或是函数。举个例子来在阵列里面放函式:
b = [ // 使用函式作为阵列元素 function () { alert("这个好玩!") }, function () { alert("再按一次离开!") }, function () { alert("再来一次!") }, function () { alert("最后一次!") } ]; for (var i = 0; i < b.length ; i++) b[i]();
最后一个 for 循环是个有趣的应用。由于 b 阵列中现在存放的所有元素都是函式,因此我们可以对 b 的每个元素进行呼叫。
Object as Association Array
关联阵列 (Assocation Array) 又称作 Map 或 Dictionary,是一种物件容器,其中可以放置许多的 key-value pair,以存取子物件。在JavaScript 中,物件本身就可以作为关连阵列。以关联阵列的方式初始化物件的范例如下:
obj1 = {"a" : "Athens" , "b" : "Belgrade", "c" : "Cairo"}; alert(obj1["a"]); // 显示 Athens obj2 = { name: "Edward", showName: function() { alert(this.name); } // 使用函式作为物件属性 } obj2.showName(); // 显示 Edward obj2.age = 23; // 属性可以动态加入
其 中 obj1 储存了三个子元素,其键 (key) 为 “a”, “b” 与 “c”,而值 (value) 为 “Athens”, “Belgrade” 与 “Cairo”。obj2 中 showName 键所对应的值为 function,因此 obj2.showName() 即为函式呼叫。
Object as Return Value
虽然 Javascript 的函式只能传回一个变量,但您却可以将传回值设定为物件,达到传回 1个以上变量值的效果
function a () { return [32, 17]; } b = a(); alert( b ); // 或 alert(a()); function pixel () { return {"x": 32, "y":17}; } point = pixel (); alert (point.x + "\n" + point.y); // 或 alert (pixel().x + "\n" + pixel().y);
Delegation Function Object
函式也是物件,只是其中包含的是程式的逻辑。这项特性可拿来作为委任式的程式设计,亦即使用委任函式当作另一函式的参数:
function doloop(begin, end, func) { // 这个函式是个 iterator for (var i = begin; i < end; i++) { func(i); } } function func1(i) { // 印出 ** n ** document.writeln("** " + i + " **<br>"); } doloop(1, 10, func1); // 印出 10 行 ** n ** doloop(20, 29, function(i) { // 印出 10 行 ## n ## document.writeln("## " + i + " ##<br>"); } );
Object = Properties + Behaviors
古有明训:程式 = 资料结构 + 算法。而物件是建构程式的基本单位,自然的具有相同的性质。物件除了有属性 (property),也可具有操作 (behavior),也就是函式。
假如我们要使用一维阵列来模拟二维阵列,那么就无法使用 ary[x][y] 这种表示法来设定或取得阵列成员。不过我可以定义一个 set 方法来设定成员变量,而以 get 方法来取得成员变量值。原型函式定义如下:
function Array2D(x,y){ // 以一维阵列模拟二维阵列的原型物件 this.length = x * y; // 阵列总长 this.x = x; // x 维度长度 this.y = y; // y 维度长度 for(var i = 0; i < this.length; i++) // 初始各元素值为 null this[i] = null; this.get = function(x,y){ // 成员函式:取得阵列第 [x,y]个元素值 return this[x*this.x + y]; } this.set = function(x,y,value){ // 成员函式:设定阵列第 [x,y] 个元素值 this[x*this.x + y] = value; } }
我们接着来使用它:
var a2d = new Array2D(10, 10); // 建立新的“二维”阵列 a2d.set(1, 3, "ABC"); // 设定“二维”阵列元素值 a2d.set(2, 6, "XYZ"); a2d.set(9, 9, 1000); alert( a2d.get(1,3) ); // 取得“二维”阵列元素值,并显示出来 alert( a2d.get(2,6) ); alert( a2d.get(9,9) );
Member Function Outside of Constructor
我们也可以将物件成员函式写于原型物件之外。以下的 Array2D 物件与上一个范例中的 Array2D 物件有相同的作用,只不过这次是写在原型物件之外。
function Array2D(x,y){ // 以一维阵列模拟二维阵列的原型物件 this.length = x * y; // 阵列总长 this.x = x; // x 维度长度 this.y = y; // y 维度长度 for(var i = 0; i < this.length; i++) // 初始各元素值为 null this[i] = null; this.get = Array2DGet; // 用这种方式把成员函式挂进来 this.set = Array2DSet; } function Array2DGet(x,y){ // 成员函式:取得阵列第 [x,y] 个元素值 return this[x*this.x + y]; } function Array2DSet(x,y,value){ // 成员函式:设定阵列第 [x,y] 个元素值 this[x*this.x + y] = value; }
Dynamic Object Function
这里说明如何为一个已定义物件,动态的加上其他操作的方法。
如果一物件已定义完成,而您也使用它来建立了新的物件,这时候您想为原型物件增加新的操作 (而不修改原型物件的原始码),让所有该物件的复本都能使用该操作,该如何达成呢?方法是使用物件的 prototype 属性。以下这个例子,为 Array 这类 Object 在执行期加入一个 max 方法,以取得阵列元素之最大值 (修改自微软 jscript.chm之范例):
function array_max(){ // 定义求取 Array 最大值之函式 var i, max = this[0]; for (i = 1; i < this.length; i++){ if (max < this[i]) max = this[i]; } return max; } Array.prototype.max = array_max; // 在 Array 原型中加入 max 函式
上面的程式码,首先建立一个 array_max 方法,以求取阵列之最大元素。接着将这个方法设定给 Array 原型物件。
var x = new Array(1, 2, 3, 4, 5, 6); // 透过 Array 建构子建立一阵列,想求取 x 中某一元素之最大值 var y = x.max( ); // 取得 x 之最大元素
Dynamic Mix in
假如物件 dynamic 有 method1, method2 两个函式;而另一物件 main 有 methodA 及 methodB 两个函式。现在我想把 dynamic 的所有特性 (feature) 汇入到 main 中,我们可以在 main 中加上一个 imports 函式:
function main(){ // main 之建构子 // ... this.imports = function (object) { if( typeof object == "object") for( value in object ) this[value] = object[value]; } // ... } obj = new main(); main.imports(new dynamic()); // 汇入 dynamic 物件之所有功能
这个 imports 函式可以动态的为 main 加上另一物件的所有操作。这种 Mix in 的功能可是 C++/Java 的 static type 语言所望尘莫及的。
近期回响