iT邦幫忙

DAY 1
7

Javascript面面觀系列 第 1

Javascript面面觀:核心篇《變數範圍》

其實這已經很多人知道,Javascript大全中也解釋很清楚,但是不過這一關很難精通Javascript。
不多說廢話,馬上開始吧。
變數範圍(Scope)通常是接觸一個新的程式語言時最先要理解的東西,不過Javascript的變數範圍觀念跟我們習見的稍有不同,所以會有一些陷阱。

簡單地說,Javascript是靜態變數範圍(static scoping或lexical scoping)的程式語言,也就是說,變數範圍是依照程式定義時的上下文決定的。像下面這一段程式:

 //這裡在 全域變數範圍(global)
 var v1 = 1;
 function a() {
 	//這裡在function a變數範圍,可以參考到全域
 	var v2 = 2;
 	function b() {
 		//這裡在function b變數範圍,可以參考到function a的變數範圍,也可以參考到全域
 		var v3 = 3;
 		alert(v1);
 		alert(v2);
 		alert(v3);
 		alert(v4);
 		alert(v5);
 	}
 	var v4 = 4;
 	alert(v1);
 	alert(v2);
 //	alert(v3);
 	alert(v4);
 	alert(v5);
 	b();
 }
 var v5 = 5;
 a();
 alert(v1);
 //alert(v2);
 //alert(v3);
 //alert(v4);
 alert(v5);

(註解掉的,就是會出問題的,也就是找不到的啦。)

更精確的定義,需要參考ECMA-262 Edition 3標準文件,最重要的觀念就是範圍鏈(scope chain)。

範圍鏈

ecma-262定義了三種可執行的程式碼,global, eval跟function,所有不在function中的程式碼就是global code,所有用eval()來執行的是eval code,函數中的程式碼就是function code。全域範圍的變數解析只限定在global物件;eval code的變數解析是與呼叫它的context相關;function code則比較複雜。

程式執行到function code時,會先建立一個範圍鏈,然後建立一個變數物件,把函數的參數加到這個變數物件,然後把函數中定義的變數加入到這個物件,最後把這個物件放到範圍鏈。如果函數外面還有函數,就依序把這些函數的變數物件放進範圍鏈,直到抵達global為止。

之後做變數解析時,就會到範圍鏈中找變數的定義與值。如果整個範圍鏈都找不到變數,那就產生一個變數,初始值是null。

因為範圍鏈是這樣定義的,所以就會有一個陷阱:

 function a() {
     b = 1;
 }

像這樣就會在全域變數範圍中生成一個叫做b的變數,值是1。(沿著範圍鏈沒找到這個變數的定義,所以就在最後(也就是在global)生成一個變數,然後在function a()中把他的值指派為1。因為最後都會抵達全域,所以不論巢狀的函數定義有多深,這個變數最後會在全域中定義。所以在函數中使用變數,除非你要取外層的變數來用,不然別忘了用var來定義,不然你其實是產生的一個全域變數。

this
「this是什麼」也是在進入不同code時決定的,在全域時他就是global object,但是在函數中,他依照一個特別的規則:

如果直接呼叫一個函數,函數內的this指向global object。

 function a() {
     this.setTimeout(function(){alert('test');},100);
 }
 a();

如果透過new來建立一個物件,而函數定義是這個物件的建構子,那this會指向這個物件。

 function a() {
     this.v = 'test';
     this.f = function(){
         alert(this.v);
     };
 }
 var b = new a();
 b.f();

如果透過Object與.來執行這個函數,this會指向這個Object。

 var a = {
     v: 'test',
     f: function() {alert(this.v);}
 };
 a.f();

call/apply
另外有一個特殊的方法可以讓你修改函數執行的context,就是使用函數物件的call()方法。

 var a = {
     v: 'test-a',
     f: function(){alert(this.v);}
 };
 var b = {
     v: 'test-b'
 };
 a.f.call(b);

apply的用法差不多,不同處在他第二個參數是要傳給呼叫函數的參數陣列,而call第一個參數後的參數都會當做參數傳給要呼叫的函數。

其實關於變數範圍,在Javascript大全裡面講得還蠻清楚的,google一下static scope/lexcial scope/scope chain等等關鍵字,也會找到很多資料,大家多上網找好文章吧。(回頭看了一下ECMA-262...如果真的照著講,恐怕還是很難懂)


下一篇
Javascript面面觀:核心篇《閉包》
系列文
Javascript面面觀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
alexc
iT邦高手 1 級 ‧ 2009-10-13 23:34:41

像 v: 'test' 這樣的寫法, 叫什麼
在什麼時候會用到?

fillano iT邦超人 1 級 ‧ 2009-10-14 00:04:04 檢舉

應該說是var a = {v: 'test'};

這叫做object literal或是object initialiser,上面這一段程式跑完,變數a就是一個Object物件的實例,有一個property名叫v,值是'test'。最常見的用法我想是傳遞configuration來初始化一個物件或功能。另外有一個常聽到的名稱,就是JSON。

例如:
http://jqueryui.com/demos/animate/

程式中會看到:
$("#button").toggle(
function() {
$("#effect").animate({backgroundColor: '#aa0000', color: '#fff', width: 500}, 1000);
},
function() {
$("#effect").animate({backgroundColor: '#fff', color: '#000', width: 240}, 1000);
}
);

傳給animate()的參數,就是動畫效果的設定。

fillano iT邦超人 1 級 ‧ 2009-10-14 00:17:04 檢舉

補充一下語法:
{key:value}
當中,key只要不是關鍵字,就可以不加quote。value如果是字串,那一定要加quote,數字可以不用...其實就跟=右邊的規則一樣。

另外一種是array literal,長這樣:
var a = ['test',['sub','sub1']];

關於JSON,可以參考:
http://json.org/

fillano iT邦超人 1 級 ‧ 2009-10-14 00:53:36 檢舉

另外要注意一點,JSON設計的用意是要拿來傳遞資料,所以不允許有Function物件當作value,但是這在object literal跟array literal中是允許的。

0
jamesjan
iT邦高手 1 級 ‧ 2009-10-14 08:56:30

fillano 大

var a = {
v: 'test-a',
f: function(){alert(this.v);}
};
var b = {
v: 'test-b',
};
a.f.call(b);

(上面的 b 裡面的 v 多了一個 ,)
這一段程式是否也可以改成

var b=new a();
b.v='test-b';
b.f();

兩種寫法是否有應用上的差異?

fillano iT邦超人 1 級 ‧ 2009-10-14 09:54:33 檢舉

嗯嗯,多了個逗點,在IE會出問題,謝謝你提醒。

要用new a()的話,a一定要是個function。所以有幾種做法:
第一種,就不要用new了...
var a = {};
a.v = 'test-b';
a.f = function(){alert(this.v);};
a.f();

第二種,非要用new不可的話...
var a=function(){
this.f=function(){alert(this.v);};
}
var b = new a();
b.v = 'test-b';
b.f();

第三種,function也可以移出去...
var a = function(){};
var b = new a();
b.v = 'test-b';
b.f = function(){alert(this.v);};
b.f();

Javascript很自由,不過這樣在instance動態加東西,或是在function外面加東西時沒用到prototype,動態加上去的就不會被繼承。但是藉由動態列舉然後copy property跟method給另一個物件其實很簡單,所以會用大量prototype inheritance的恐怕也不是很多。(prototype這個library我必較不熟啦,也許他們實作會用比較多正統的prototype繼承也說不定,但是我在jquery、yui3、ext其實都很少看到。)

jamesjan iT邦高手 1 級 ‧ 2009-10-14 12:25:01 檢舉

感謝!獲益良多!^^b

0
rr8r8r8r8tw
iT邦新手 5 級 ‧ 2016-08-26 17:41:36

費大我有用出來,是用你之前call back function
可是怎麼換行呢?
打兩列以上就會連在一起

function ObjectToXml()
{



var data=$('#dg').datagrid("getData");

var json=JSON.stringify(data);

var obj=new Object;


//get 4 question answer
if(data)
{


obj.firstname= data.rows.reduce(function(pre,cur)
{return pre+ cur.firstname},'');


obj.lastname=data.rows.reduce(function(pre,cur)
{return pre+ cur.lastname},'');

obj.phone=data.rows.reduce(function(pre,cur)
{return pre+ cur.phone},'');


obj.email=data.rows.reduce(function(pre,cur)
{return pre+ cur.email},'');

}

//json String
var j={"firstname":""+obj.firstname,"lastname":""+obj.lastname,"phone":""+obj.phone,"email":""+obj.email};

alert(JSON.stringify(j));





//TO XML
var xotree=new XML.ObjTree();

var xml;


var object=jQuery.parseJSON(JSON.stringify(j));

xml=xotree.writeXML(object);
alert(xml);
//document.getElementById("XML").value=xml;

}


fillano iT邦超人 1 級 ‧ 2016-08-26 18:09:02 檢舉

"\n"就可以換行。

0
rr8r8r8r8tw
iT邦新手 5 級 ‧ 2016-08-26 18:20:31

是加在這段嗎?
alert(JSON.stringify(j)+"\n")
沒有效果耶

fillano iT邦超人 1 級 ‧ 2016-08-27 15:42:29 檢舉

加在這沒用啦,JSON.stringify()出來的東西沒格式的,純粹只是給程式用。

我要留言

立即登入留言