ホームページ  >  記事  >  ウェブフロントエンド  >  jsスコープとスコープチェーンの概念の理解と使い方_基礎知識

jsスコープとスコープチェーンの概念の理解と使い方_基礎知識

WBOY
WBOYオリジナル
2016-05-16 17:37:101108ブラウズ

(1) 範囲

変数のスコープとは、プログラムのソースコード内で定義された変数の領域です。

1. JS で字句スコープが使用されます

関数内で宣言されていない変数 (関数内で var が省略されている場合もグローバルとみなされます) は、グローバル変数 (グローバル スコープ) と呼ばれます
関数内で宣言された変数は関数スコープを持ち、ローカル変数です

ローカル変数はグローバル変数よりも優先されます

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 には if for などの c/c のようなブロックレベルのスコープがありません。

function test(){
 for(var i=0;i<10;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()の後、外側の関数が呼び出され、内側の関数が返され、Continue()すると、それに応じて内側の関数が呼び出されて実行されるため、「one」
が出力されます。 ネストされた関数にはクロージャが含まれます。これについては後で説明します。ここで、内側の関数は外側の関数で宣言された変数名にアクセスできます。これにはスコープ チェーン メカニズムが関係します

2. 事前にJSで宣言

js の関数スコープは、関数内で宣言されたすべての変数が関数本体内で常に表示されることを意味します。また、宣言前に変数を使用することもできます。この状況をホイスティング
と呼びます。 ヒント: 事前宣言は、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 をもう一度削除してみますか?これはグローバル変数になった関数内の名前なので、未定義ではなくなりました

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

test();

3. テストにパラメータがある場合は、上記のパラメータが渡されないことに注意してください。

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

var name = "one";
test(name);
console.log(name); // one

前に述べたように、基本型は値によって渡されるため、テストに渡される名前は実際には単なるコピーであり、関数が返された後にこのコピーはクリアされます。
関数内の name="two" がグローバル名を変更するとは考えないでください。これらは 2 つの独立した名前であるためです

(2) スコープチェーン

上記の高度な機能にはスコープ チェーンが含まれます

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

1. 大きな段落で説明します。
js コードの各部分 (グローバル コードまたは関数) には、スコープ チェーンが関連付けられています。

このスコープ チェーンは、オブジェクトのリストまたはリンクされたリストであり、このオブジェクトのグループは、このコードの「スコープ内」の変数を定義します。

js が変数 x の値を見つける必要がある場合 (このプロセスは変数解決と呼ばれます)、このオブジェクトに x という名前の属性がある場合、この属性の値は次のようになります。最初のオブジェクトに x という名前の属性がない場合、js はチェーン内の次のオブジェクトの検索を続けます。 2 番目のオブジェクトに x という名前の属性がまだない場合は、引き続き次のオブジェクトの検索が続きます。スコープ チェーン内のオブジェクトに属性 x が含まれていない場合、x はこのコードのスコープ チェーンに存在しないとみなされ、最終的に ReferenceError 例外がスローされます。

2. スコープチェーンの例:

js のトップレベル コード (つまり、関数定義が含まれていないコード) では、スコープ チェーンはグローバル オブジェクトで構成されます。

ネストを含まない関数本体には、スコープ チェーン上に 2 つのオブジェクトがあります。1 つ目は関数パラメータとローカル変数を定義するオブジェクトで、2 つ目はグローバル オブジェクトです。

ネストされた関数本体では、スコープ内に少なくとも 3 つのオブジェクトがあります。

3. スコープチェーン作成ルール:

関数が定義されると (定義されたときに関数が開始されることに注意してください)、実際にはスコープ チェーンが保存されます。

この関数が呼び出されると、そのパラメーターまたはローカル変数を格納するための新しいオブジェクトが作成され、そのオブジェクトがそのスコープ チェーンに追加され、関数呼び出しスコープの新しいより長い表現「チェーン」が作成されます。

入れ子関数の場合は、状況が再び変わります。外部関数が呼び出されるたびに、内部関数が再定義されます。外部関数が呼び出されるたびにスコープ チェーンが異なるためです。内部関数は定義されるたびに微妙に異なる必要があります。内部関数のコードは外部関数が呼び出されるたびに同じであり、このコードに関連付けられたスコープ チェーンも異なります。

(ヒント: 上記の 3 つのポイントをよく理解して、覚えておいてください。自分の言葉で言うのが最善です。そうしないと、面接官が直接「スコープ チェーンについて説明してください」と質問するため、暗記する必要があります。 )

スコープチェーンの実際的な例:

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,就完成搜索返回

还有一个例子有时候我们会犯错的,面试的时候也经常被骗到。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<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); //都是 Button4
    },false);
  }
}
window.onload=buttonInit;
</script>
</head>
<body>
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>

为什么?
根据作用域链中变量的寻找规则:

b.addEventListener("click",function(){ 
      alert("Button"+i);
    },false);

这里有一个函数,它是匿名函数,既然是函数,那就在作用域链上具有一个对象,这个函数里边使用到了变量i,它自然会在作用域上寻找它。
查找顺序是 这个匿名函数 -->外部的函数buttonInit() -->全局对象window

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

这时注册事件已经结束了,不要以为它会一个一个把i放下来,因为函数作用域之内的变量对作用域内是一直可见的,就是说会保持到最后的状态

当匿名函数要使用i的时候,注册事件完了,i已经变成了4,所以都是Button4

那怎么解决呢?

给它传值进去吧,每次循环时,再使用一个匿名函数,把for里边的i传进去,匿名函数的规则如代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript">
function buttonInit(){
  for(var i=1;i<4;i++){
    (function(data_i){
    var b=document.getElementById("button"+data_i);
    b.addEventListener("click",function(){ 
      alert("Button"+data_i);
    },false);
    })(i);
  }
}
window.onload=buttonInit;
</script>
</head>
<body>
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>

这样就可以 Button1..2..3了

4.上述就是作用域链的基本描述,另外,with语句可用于临时拓展作用域链(不推荐使用with)

语法形如:

with(object)

statement

这个with语句,将object添加到作用域链的头部,然后执行statement,最后把作用域链恢复到原始状态

简单用法:

比如给表单中各个项的值value赋值

一般可以我们直接这样

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本身没有的属性。

要理解变量的作用域范围就得先理解作用域链
用var关键字声明一个变量时,就是为该变量所在的对象添加了一个属性。
作用域链:由于js的变量都是对象的属性,而该对象可能又是其它对象的属性,而所有的对象都是window对象的属性,所以这些对象的关系可以看作是一条链
链头就是变量所处的对象,链尾就是window对象

看下面的代码:

复制代码 代码如下:

function t() {
var a;
function t2() {
var b;
}
}

js中函数也是对象,所以变量a所在的对象是t,t又在window对象中,所以a的作用域链如下
t--window
那么b所以在的对象即t2,t2又包含在t中,t又在window对象,所以b的作用域链如下
t2--t--window
明白了作用域链下面就开始变量的作用域分析了
1 javascript 没有var的变量都为全局变量,且为window对象的属性
复制代码 代码如下:

function test1() {
//执行这个句的时候它会找作用域对象,这个函数就是作用域链中的第一个对象,但这个对象中没有相关的var语句
//于里就找作用域链的第二个对象,即全局对象,而全局对象中也没有相关的var语句
//由于没有相关的var语句,js隐式在函数地声明了变量即var all;
all = 30;
alert(all);
}
test1();
alert(all);
alert(window.all);

2 関数内で定義された変数 (関数内の関数を除く) は関数全体で有効です
コードをコピー コードは次のとおりです:

function test2() {
var t = 0; //forの条件で変数を定義します。今回の変更のスコープチェーンオブジェクトはこの関数
です。 //つまり、関数全体で有効です
for (var i = 0; i t = 私; }
アラート(i); }
テスト2();

3 関数内の変数は、同じ名前のグローバル変数を置き換えます



コードをコピー
コードは次のとおりです: var t = "bb"; 関数 test() { // t を実行すると、最初にスコープ チェーン オブジェクトが見つかります。関数内で定義されているため、この関数はそのスコープ チェーンの最初のオブジェクトです。
//そして、このオブジェクトには t の定義があるので、 t はローカル変数であり、グローバル変数 t を置き換えます
//この時点では t が定義されているだけですが、代入はありません。代入は次の行にあるため、ここでは unknown が出力されます。
アラート(t); var t = "aa"; アラート(t); }
テスト();


4 ブロックスコープなし




コードをコピー

コードは次のとおりです:
以下の内容は、オンライン ブログを読んでまとめたものであり、重要なポイントだけを覚えておいてください。同時に、私を共有させてくれたブロガーにとても感謝しています。巨人の肩!
1.




コードをコピー

コードは次のとおりです:


var temp = (function(){
var name="テスト"; return function(){ アラート(名前); } })(); 上記のコードスニペットはjserでよく見かける書き方で、伝説のクロージャです。 ご存知のとおり、temp() を呼び出すと「test」が表示されます。このプロセスは次の 3 つの理論に基づいて説明できます。 1) js スコープは関数の区切り文字にのみ関連しており、関数のネストによってスコープ チェーンが形成されます。 2) スコープ チェーンの作成ルールは、前の環境のスコープ チェーンをコピーし、チェーンの先頭に環境変数オブジェクトへのポインタを置くことです。 3) Javascript では、オブジェクトが参照されなくなった場合、そのオブジェクトは GC によってリサイクルされます。 2 つのオブジェクトが相互に参照し、サードパーティによって参照されなくなった場合、相互に参照している 2 つのオブジェクトもリサイクルされます。
上記の 3 つの項目を読んでもまだ理解できない場合は、次の理論に基づいたコードの詳細な説明を読んでください:
まず、外側の関数は実行後に破棄されますが、外側の関数のスコープ チェーンは内側の関数のスコープ チェーンにコピーされ、内側の関数のスコープ チェーンの一部を形成します。これはコピーではなく、コピーであることに注意してください。参照(第 2 条による)であるため、内部関数は引き続き name にアクセスできます。返された内部関数は temp によって参照されるため、実行後に外部関数が破棄された場合、内部関数は外部関数の一部であるにもかかわらず依然として存在します。 、3 番目の根拠と同様に、第三者によって引用されました。伝説のクロージャもこの原則です
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。