今天偶然看到閉包,右面開始看到作用域的問題,發現跟java之類的確實是不太一樣。看到一篇超級好的文章,轉載一下。
一直對Js的作用域有點迷糊,今天偶然讀到JavaScript權威指南,立馬被吸引住了,寫的真不錯。我看的是第六版本,相當的厚,大概1000多頁,Js博大精深,要熟悉精通需要大毅力大功夫。
一:函數作用域
先看一小段代碼:
var scope="global";
function t(){
console.log(scope);
var scope="local"
console.log(scope);
}
t();
第一句輸出的是: “undefined”,而不是 “global”
第二講輸出的是:”local”
你可能會認爲第一句會輸出:”global”,因爲代碼還沒執行var scope=”local”,所以肯定會輸出“global”。
我說這想法完全沒錯,只不過用錯了對象。我們首先要區分Javascript的函數作用域與我們熟知的C/C++等的塊級作用域。
在C/C++中,花括號內中的每一段代碼都具有各自的作用域,而且變量在聲明它們的代碼段之外是不可見的。而Javascript壓根沒有塊級作用域,而是函數作用域.
所謂函數作用域就是說:-》變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有定義的。
所以根據函數作用域的意思,可以將上述代碼重寫如下:
- var scope=“global”;
- function t(){
- var scope;
- console.log(scope);
- scope=”local”
- console.log(scope);
- }
- t();
var scope="global";
function t(){
var scope;
console.log(scope);
scope="local"
console.log(scope);
}
t();
我們可以看到,由於函數作用域的特性,局部變量在整個函數體始終是由定義的,我們可以將變量聲明”提前“到函數體頂部,同時變量初始化還在原來位置。
爲什麼說Js沒有塊級作用域呢,有以下代碼爲證:
var name="global";
if(true){
var name="local";
console.log(name)
}
console.log(name);
都輸出是“local”,如果有塊級作用域,明顯if語句將創建局部變量name,並不會修改全局name,可是沒有這樣,所以Js沒有塊級作用域。
現在很好理解爲什麼會得出那樣的結果了。scope聲明覆蓋了全局的scope,但是還沒有賦值,所以輸出:”undefined“。
所以下面的代碼也就很好理解了。
- function t(flag){
- if(flag){
- var s=“ifscope”;
- for(var i=0;i<2;i++)
- ;
- }
- console.log(i);
- console.log(s);
- }
- t(true);
function t(flag){
if(flag){
var s="ifscope";
for(var i=0;i<2;i++)
;
}
console.log(i);
console.log(s);
}
t(true);
輸出:2 ”ifscope”二:變量作用域
還是首先看一段代碼:
- function t(flag){
- if(flag){
- s=”ifscope”;
- for(var i=0;i<2;i++)
- ;
- }
- console.log(i);
- }
- t(true);
- console.log(s);
function t(flag){
if(flag){
s="ifscope";
for(var i=0;i<2;i++)
;
}
console.log(i);
}
t(true);
console.log(s);
就是上面的翻版,知識將聲明s中的var去掉。
程序會報錯還是輸出“ifscope”呢?
讓我揭開謎底吧,會輸出:”ifscope”
這主要是Js中沒有用var聲明的變量都是全局變量,而且是頂層對象的屬性。
所以你用console.log(window.s)也是會輸出“ifconfig”
當使用var聲明一個變量時,創建的這個屬性是不可配置的,也就是說無法通過delete運算符刪除
var name=1 ->不可刪除
sex=”girl“ ->可刪除
this.age=22 ->可刪除
三:作用域鏈
先來看一段代碼:
- name=“lwy”;
- function t(){
- var name=“tlwy”;
- function s(){
- var name=“slwy”;
- console.log(name);
- }
- function ss(){
- console.log(name);
- }
- s();
- ss();
- }
- t();
name="lwy";
function t(){
var name="tlwy";
function s(){
var name="slwy";
console.log(name);
}
function ss(){
console.log(name);
}
s();
ss();
}
t();
當執行s時,將創建函數s的執行環境(調用對象),並將該對象置於鏈表開頭,然後將函數t的調用對象鏈接在之後,最後是全局對象。然後從鏈表開頭尋找變量name,很明顯
name是”slwy”。
但執行ss()時,作用域鏈是: ss()->t()->window,所以name是”tlwy”
下面看一個很容易犯錯的例子:
- <html>
- <head>
- <script type=“text/javascript”>
- function buttonInit(){
- for(var i=1;i<4;i++){
- var b=document.getElementById(“button”+i);
- b.addEventListener(“click”,function(){ alert(“Button”+i);},false);
- }
- }
- window.onload=buttonInit;
- </script>
- </head>
- <body>
- <button id=“button1”>Button1</button>
- <button id=“button2”>Button2</button>
- <button id=“button3”>Button3</button>
- </body>
- </html>
<html>
<head>
<script type="text/javascript">
function buttonInit(){
for(var i=1;i<4;i++){
var b=document.getElementById("button"+i);
b.addEventListener("click",function(){ alert("Button"+i);},false);
}
}
window.οnlοad=buttonInit;
</script>
</head>
<body>
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>
當文檔加載完畢,給幾個按鈕註冊點擊事件,當我們點擊按鈕時,會彈出什麼提示框呢?
很容易犯錯,對是的,三個按鈕都是彈出:”Button4”,你答對了嗎?
當註冊事件結束後,i的值爲4,當點擊按鈕時,事件函數即function(){ alert(“Button”+i);}這個匿名函數中沒有i,根據作用域鏈,所以到buttonInit函數中找,此時i的值爲4,
所以彈出”button4“。
正確的做法應該怎樣呢,再內嵌函數,現在訪問的這個i就是(function(i){})裏面這個i了,就能達到效果
function buttonInit() {
for (var i = 1; i < 4; i++) {
(function (i) {
var b = document.getElementById("button" + i);
b.addEventListener("click", function () { alert("Button" + i); }, false);
})(i);
}
}
四:with語句
說到作用域鏈,不得不說with語句。with語句主要用來臨時擴展作用域鏈,將語句中的對象添加到作用域的頭部。
看下面代碼
- person={name:“yhb”,age:22,height:175,wife:{name:“lwy”,age:21}};
- with(person.wife){
- console.log(name);
- }
person={name:"yhb",age:22,height:175,wife:{name:"lwy",age:21}};
with(person.wife){
console.log(name);
}
with語句將person.wife添加到當前作用域鏈的頭部,所以輸出的就是:“lwy”.
with語句結束後,作用域鏈恢復正常。