一跟衅、理解js作用域
1、作用域:作用域是一套規(guī)則播歼,用于確定在何處以及如何查找變量(標(biāo)字符)伶跷。
2、LHS查詢:查找的目的是對(duì)變量進(jìn)行賦值荚恶;
3撩穿、RHS查詢:查找的目的是獲取變量的值;
(賦值操作符會(huì)導(dǎo)致LHS查詢谒撼、=操作符獲調(diào)用函數(shù)是傳入?yún)?shù)的操作會(huì)都會(huì)導(dǎo)致關(guān)聯(lián)作用域的賦值操作)
eg食寡、
function foo(a){
var b=a;
return a+b;
}
var c=foo(2);
//LHS查詢(3處)c=...、a=2(調(diào)用函數(shù)傳參的隱式變量分配)廓潜、b=..
//RHS查詢(4處)foo(2..)抵皱、=a、a..辩蛋、...b
4呻畸、JavaScript引擎會(huì)先在代碼執(zhí)行前對(duì)其進(jìn)行編譯,如var a=2分為兩個(gè)步驟:
(1)悼院、首先伤为,var a在其作用域中聲明新的變量a、這會(huì)在最開始的階段,也就是代碼執(zhí)行前進(jìn)行(聲明提前)绞愚;
(2)叙甸、接下來,a=2會(huì)查詢(LHS查詢)變量a并對(duì)其進(jìn)行賦值位衩。
LHS和RHS查詢都會(huì)在先在當(dāng)前執(zhí)行作用域中開始裆蒸,如果在當(dāng)前作用域中沒有找到所需標(biāo)識(shí)符,就會(huì)向上一級(jí)繼續(xù)查找直到頂層全局作用域糖驴;
**不成功的RHS引用會(huì)拋出referenceError異常僚祷;不成功的LHS引用會(huì)導(dǎo)致自動(dòng)隱式的創(chuàng)建一個(gè)全局變量(非嚴(yán)格模式下) **
二、詞法作用域
- 定義
贮缕。定義在詞法階段的作用域辙谜,由你寫代碼是將變量和塊作用域?qū)懺谀睦锼鶝Q定 - 查找
。作用域始終從運(yùn)行時(shí)所處的最內(nèi)部作用域開始查找跷睦,逐級(jí)向外部進(jìn)行筷弦,直到遇見第一個(gè)匹配的標(biāo)識(shí)符為止; - 詞法欺騙
抑诸。在詞法階段通過代碼欺騙和假裝成書寫烂琴,來實(shí)現(xiàn)修改詞法作用域;
蜕乡。欺騙詞法作用域會(huì)導(dǎo)致性能下降,應(yīng)盡量避免使用奸绷;
。js中會(huì)“欺騙”詞法作用域的兩種機(jī)制:
(1)eval(..)
*可接收一字符串參數(shù)层玲,并將其內(nèi)容視為在書寫時(shí)就存在于程序中的這個(gè)位置号醉;
eg:
function foo(str,a) {
eval(str);//欺騙
console.log(a,b);
// body...
}
var b=2;
foo("var b=3;",1);//1 3
*在嚴(yán)格模式的程序中,eval()在運(yùn)行時(shí)有其自己的詞法作用域辛块,即其中的聲明無法修改所在的作用域畔派;
eg:
function foo(str,a) {
"use strict";
eval(str);//欺騙
console.log(a,b);
// body...
}
function foo1(str) {
"use strict";
eval(str);//欺騙
console.log(a);//ReferenceError: a is not defined
// body...
}
foo1("var a=3;");
(2)with
。重復(fù)引用同一對(duì)象中多個(gè)屬性的快捷方式润绵;
线椰。with可以將一個(gè)沒有或有多個(gè)屬性的對(duì)象處理為為一個(gè)完全隔離的詞法作用域,因此這個(gè)對(duì)象的屬性也會(huì)被處理為定義在這個(gè)作用域中的詞法標(biāo)識(shí)符尘盼。
eg:
function foo2(obj){
with(obj){
a=2; //實(shí)際是一個(gè)LHS引用
}
}
var o1={
a:3
};
var o2={
b:3
};
foo2(o1);
console.log(o1.a);//2憨愉;o1傳遞進(jìn)去,a=2賦值操作找到o1.a并將2賦值給它
foo2(o2);
console.log(o2.a);//undefined:o2傳遞進(jìn)去卿捎,o2沒用a屬性配紫,因此不會(huì)創(chuàng)建這個(gè)屬性
console.log(a)//2----a被泄露到全局作用域
。o2午阵、foo(..)躺孝、和全局的作用域中都沒有找到標(biāo)識(shí)符a,因此當(dāng)a=2執(zhí)行時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè)全局變量(非嚴(yán)格模式)
。盡管with塊可以將一個(gè)對(duì)象處理為詞法作用域括细,但這個(gè)塊內(nèi)部的正常var聲明不會(huì)被限制在這個(gè)塊中伪很,而是被添加到with所處的函數(shù)作用域中。
eg:
function foo3(obj){
with(obj){
var a=2; //實(shí)際是一個(gè)LHS引用
}
}
var o1={
a:3
};
var o2={
b:3
};
foo3(o1);
console.log(o1.a);//2奋单;o1傳遞進(jìn)去,a=2賦值操作找到o1.a并將2賦值給它
foo3(o2);
console.log(o2.a);//undefined:o2傳遞進(jìn)去猫十,o2沒用a屬性览濒,因此不會(huì)創(chuàng)建這個(gè)屬性
console.log(a)//ReferenceError: a is not defined
三、函數(shù)作用域
- 含義:
拖云。指的是屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用(事實(shí)上在 嵌套的作用域中也可以使用) - 函數(shù)聲明和函數(shù)表達(dá)式:
贷笛。區(qū)分:看function關(guān)鍵字出現(xiàn)在聲明中的位置;如果是聲明中的第一詞宙项,那么就是一個(gè)函數(shù)聲明乏苦,否則就是一個(gè)函數(shù)表達(dá)式. -
IIEF立即執(zhí)行函數(shù)表達(dá)式
eg:
var a=2;
(function IIEF(global){
var a=3;
console.log(a);//3
console.log(global.a);//2
})(window);
console.log(a);//2
1)函數(shù)被包含在()中,因此成了一個(gè)表達(dá)式尤筐;通過后面的()可以立即執(zhí)行這個(gè)函數(shù)汇荐;
2)通過后面的()可傳入?yún)?shù),例中傳入window對(duì)象并命名為global盆繁。
掀淘。IIEF還有一種變化用途是倒置代碼的運(yùn)行順序,將需要運(yùn)行的函數(shù)放在第二位油昂,在IIEH執(zhí)行之后當(dāng)做參數(shù)傳遞進(jìn)去
eg:
var a = 2;
(function IIFE( def ) {
def(window);
})(function def( global ) {
var a = 3;
console.log( a ); // 3
console.log( global.a); //2
});
四革娄、塊作用域
- 在JavaScript中只有函數(shù)作用域,沒有塊級(jí)作用域冕碟。
for(var i = 0; i < 10; i++) {}console.log( i ); // 10
拦惋。在for循環(huán)的頭部定義了變量i,通常只是想在for循環(huán)內(nèi)部的上下文使用i安寺,而忽略了i會(huì)被綁定到外部作用域(函數(shù)或全局)厕妖。
var foo = true;if (foo) { var bar = foo * 2; }console.log( bar ); // 2
。bar變量雖然在if聲明中的上下文使用我衬,但它們最終都屬于外部作用域叹放。
- ** with**
。with也是塊級(jí)作用域的一種形式挠羔,用with從對(duì)象中創(chuàng)建出的作用域僅在with聲明中有效井仰。 - ** try/catch**
。ES3規(guī)范的try/catch的catch分句會(huì)創(chuàng)建一個(gè)塊級(jí)作用域破加,其中聲明的變量只在catch內(nèi)部有效俱恶。
try {
foo();
}
catch(err) {
var a = 0; console.log( err ); //可以執(zhí)行
}
console.log( a ); // 0;
console.log( err ); // err not found
-
let
。ES6的let可以將變量綁定到所在的任意作用域(通常是{...})。
var foo = true;
if (foo) {
let bar = foo * 2;
console.log( bar ); // 2
}
console.log( bar ); //referenceError
合是。**使用let進(jìn)行的聲明不會(huì)在塊級(jí)作用域中提升了罪。聲明的代碼被運(yùn)行前,聲明并不“存在”聪全。**
console.log( bar ); //ReferenceError
let bar = 2;
- ** const**
泊藕。 ES6引入const同樣能創(chuàng)建塊級(jí)作用域,但其值是常量难礼。
var foo = true;
if (foo) {
var a = 2;
const b = 3; // 包含在if中的塊級(jí)作用域常量
a = 3;
b = 4; // 錯(cuò)誤
}
console.log( a ); // 3
console.log( b ); // ReferenceError
五娃圆、提升(聲明提前)
eg:
foo();
function foo(){
a=2;
console.log(a);//2
var a;
}
。定義聲明如var a蛾茉;是在編譯階段進(jìn)行的讼呢;賦值聲明會(huì)留在原地等待執(zhí)行階段;
谦炬。簡(jiǎn)單講悦屏,即包含變量和函數(shù)在內(nèi)的所有聲明都會(huì)在任何代碼被執(zhí)行之前首先被處理.
。注意:函數(shù)聲明會(huì)被提升键思,當(dāng)是函數(shù)表達(dá)式不會(huì)被提升础爬。(下例中,var ber被提升稚机,但函數(shù)表達(dá)式..=function foo(){}并不會(huì)提升幕帆,故ber()拋出TypeError異常而不是ReferenceError)
eg:
ber(); //TypeError: ber is not a function
var ber=function foo(){
a=2;
console.log(a);
var a;
}
-
函數(shù)優(yōu)先
。函數(shù)聲明和變量聲明都會(huì)被提升赖条,但是函數(shù)會(huì)首先被提升失乾,然后才是變量
eg:
foo1();//a
var foo1;
function foo1(){
console.log("a");
};
foo1=function(){
console.log("b")
};
foo1()//b
。以上代碼被引擎理解為:
function foo1(){
console.log("a");
}
foo1();//a
foo1=function(){
console.log("b")
}
foo1()//b