首頁  >  文章  >  web前端  >  JS 作用域與作用域鏈詳解_基礎知識

JS 作用域與作用域鏈詳解_基礎知識

WBOY
WBOY原創
2016-05-16 16:05:101424瀏覽

(1)作用域

一個變數的作用域(scope)是程式原始碼中定義的這個變數的區域。

1. 在JS中使用的是詞法作用域(lexical scope)

不在任何函數內宣告的變數(函數內省略var的也算全域)稱為全域變數(global scope)

函數內宣告的變數具有函數作用域(function scope),屬於局部變數

局部變數優先權高於全域變數

複製程式碼 程式碼如下:

var name="one";
function test(){
  var name="two";
  console.log(name); //two
}
test();

函數內省略var的,會影響全域變量,因為它實際上已經被重寫成全域變數

複製程式碼 程式碼如下:

var name="one";
function test(){
  name="two";
}
test();
console.log(name); //two

函數作用域,就是說函數是作用域的基本單位,js不像c/c 那樣有區塊級作用域 例如 if  for 等

複製程式碼 程式碼如下:

function test(){
  for(var i=0;i     if(i==5){
      var name = "one";
    }
  }
  console.log(name); //one
}
test();  //因為是函數層級作用域,所以可以存取name="one"

當然了,js裡邊還使用到了高階函數,其實可以理解成巢狀函數

複製程式碼 程式碼如下:

function test1(){
  var name = "one";
  return function (){
    console.log(name);
  }
}
test1()();

test1()之後將呼叫外層函數,傳回了一個內層函數,再繼續(),就對應呼叫執行了內層函數,所以就輸出 ”one"

巢狀函數涉及到了閉包,後面再談..這裡內層函數可以存取到外層函數中聲明的變數name,這就涉及到了作用域鏈機制

2. JS中的陳述提前

js中的函數作用域是指在函數內宣告的所有變數在函數體內始終是可見的。而且,變數在宣告之前就可以使用了,這種情況就叫做宣告提前(hoisting)

tip:宣告提前是在js引擎預編譯時就進行了,在程式碼被執行之前已經有宣告提前的現象產生了

複製程式碼 程式碼如下:

var name="one";
function test(){
  console.log(name);  //undefined
  var name="two";
  console.log(name); //two
}
test();

上邊就達到下面的效果

複製程式碼 程式碼如下:

var name="one";
function test(){
  var name;
  console.log(name);  //undefined
  name="two";
  console.log(name); //two
}
test();

再試試把var去掉?這是函數內的name已經變成了全域變量,所以不再是undefined

複製程式碼 程式碼如下:

var name="one";
function test(){
  console.log(name);  //one
  name="two";
  console.log(name); //two
}
test();

3. 值得注意的是,上面提到的都沒有傳參數,如果test有參數,又如何呢?

複製程式碼 程式碼如下:

function test(name){
  console.log(name);  //one
  name="two";
  console.log(name); //two
}
var name = "one";
test(name);
console.log(name); // one

之前說過,基本型別是按值傳遞的,所以傳進test裡面的name其實只是一個副本,函數回傳之後這個副本就被清除了。
千萬不要以為函數裡邊的name="two"把全域name修改了,因為它們是兩個獨立的name

(2)作用域鏈

上面提到的高階函數就牽涉到了作用域鏈

複製程式碼 程式碼如下:

function test1(){
  var name = "one";
  return function (){
    console.log(name);
  }
}
test1()();

1. 引入一大段話來解釋:
每一段js程式碼(全域程式碼或函數)都有一個與之關聯的作用域鏈(scope chain)。

這個作用域鍊是一個物件清單或是鍊錶,這組物件定義了這段程式碼中「作用域中」的變數。

當js需要查找變數x的值的時候(這個過程稱為變數解析(variable resolution)),它會從鏈的第一個物件開始查找,如果這個物件有一個名為x的屬性,則會直接使用這個屬性的值,如果第一個物件中沒有名為x的屬性,js會繼續尋找鏈上的下一個物件。如果第二個物件仍然沒有名為x的屬性,則會繼續尋找下一個,以此類推。如果作用域鏈上沒有任何一個物件含有屬性x,那麼就認為這段程式碼的作用域鏈上不存在x,最後拋出一個引用錯誤(ReferenceError)異常。

2. 作用域鏈舉例:

在js最頂層程式碼中(也就是不包括任何函數定義內的程式碼),作用域鏈由一個全域物件組成。

在不包含巢狀的函數體內,作用域鏈上有兩個對象,第一個是定義函數參數和局部變數的對象,第二個是全域對象。

在一個嵌套的函數體內,作用域上至少有三個物件。

3. 作用域鏈建立規則:

當定義一個函數時(注意,是定義的時候就開始了),它實際上保存一個作用域鏈。

當呼叫這個函數時,它會建立一個新的物件來儲存它的參數或局部變量,並將這個物件加入儲存到那個作用域鏈上,同時建立一個新的更長的表示函數呼叫作用域的「鏈」。

對於巢狀函數來說,情況又有所變化:每次呼叫外部函數的時候,內部函數又會重新定義一次。因為每次呼叫外部函數的時候,作用域鏈都是不同的。內部函數在每次定義的時候都要微妙的差異---在每次呼叫外部函數時,內部函數的程式碼都是相同的,而且關聯這段程式碼的作用域鏈也不相同。

 (tip: 把上面三點理解好,記住了,最好還要能用自己的話說出來,不然就背下來,因為面試官就直接問你:請描述一下作用域鏈... )

舉個作用域鏈的實用例子:

複製程式碼 程式碼如下:

var name="one";
function test(){
  var name="two";
  function test1(){
    var name="three";
    console.log(name);  //three
  }
  function test2(){
    console.log(name);  // two
  }
  test1();
  test2();
}
test();

上邊是個巢狀函數,對應的應該是作用域鏈上有三個物件
那麼在呼叫的時候,就需要找出name的值,就在作用域鏈上找

當成功呼叫test1()的時候,順序為 test1()->test()->全域物件window 因為在test1()上就找到了name的值three,所以完成搜尋回傳

當成功呼叫test1()的時候,順序為test2()->test()->全域物件window  因為在test2()上沒找到name的值,所以找test()中的,找到了name的值two,就完成搜尋回傳

還有一個例子有時候我們會犯錯的,面試的時候也常常被騙到。

複製程式碼 程式碼如下:

http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
http://www.w3.org/1999/xhtml">
<頭>
<スクリプトタイプ="text/javascript">
関数 buttonInit(){
    for(var i=1;i         var b=document.getElementById("ボタン" i);
        b.addEventListener("クリック",function(){
            alert("ボタン" i); //都是 Button4
        },false);
    }
}
window.onload=buttonInit;








何ですか?
作用領域链中の量の寻找规による:

复制代 代码如下:

b.addEventListener("クリック",function(){
            alert("ボタン" i);
        },false);

ここには関数が 1 つありますが、これは匿名関数であり、関数でもありますが、作用ドメイン上に 1 つのオブジェクトがあり、この関数は量 i に達するまで使用され、必然的に作用ドメイン上で検出されます。 查找顺序是これ匿名関数 -->外部関数buttonInit() -->全局对象window

匿名関数数中找不到i,自然跑到了buttonInit(), ok,在中找到了,

今回の注釈イベントはすでに終了しました。関数作用領域内の量の作用領域は継続的に確認できるため、次のイベントに解放される必要はありません。

当匿名関数数要使用i的候,注册イベント完了,i已经变成了4,所以都是Button4

那怎么解决呢?

给它传进去吧,次回循環時,再使用一匿名関数数,把里边的i传进去,秘密関数数的规则如代码

复制代 代码如下:
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > "> <頭>
<スクリプトタイプ="text/javascript">
関数 buttonInit(){
    for(var i=1;i         (関数(data_i){
        var b=document.getElementById("ボタン" data_i);
        b.addEventListener("クリック",function(){
            alert("ボタン" data_i);
        },false);
        })(i);
    }
}
window.onload=buttonInit;









このボタン1..2..3了

4. 上記は、作用領域の基本的な説明であり、さらに、この句は、作用領域を一時的に展開するために使用できます (非推奨使用)

语法形如:

with(オブジェクト)

ステートメント

これに加えて、オブジェクトを作用ドメインの部分に追加し、その後ステートメントを実行し、最後に作用ドメインを元の状態に戻します

简单用法:

表内の各項目の値と比較

一般可以我们直接蠷

复制代码代码如下:

var f = document.forms[0];
f.name.value = "";
f.age.value = "";
f.email.value = "";

with導入後(withを使うと色々問題が起きるので上記のフォームを使いましょう)

コードをコピーします コードは次のとおりです:

with(document.forms[0]){
f.name.value = "";
f.age.value = "";
f.email.value = "";
}

さらに、オブジェクト o が x 属性を持つ場合、o.x = 1;
次に、

を使用します

コードをコピーします コードは次のとおりです:

with(o){
x = 2;
}

は o.x = 2;
に変換できます。 o が属性 x を定義しない場合、その関数は x = 2 と同等になります。

with は o の属性を読み取るためのショートカットを提供しますが、o 自体が持たない属性を作成することはできません。

以上がこの記事の全内容です。JavaScript を学習する皆さんのお役に立てれば幸いです。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn