Javascript的作用域,作用域链,闭包
1,作用域和作用域鏈概念
? ? ? 作用域就是變量與函數(shù)的可訪問范圍,即作用域控制著變量與函數(shù)的可見性和生命周期。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。
? ? ? ?1.1 全局作用域,在代碼中任何一個地方都能訪問的對象擁有全局作用域,一般來說以下幾種情形擁有全局作用域:
? ? ? ? ? ? ? 最外層函數(shù)和在最外層函數(shù)外面定義的變量擁有全局作用域;
? ? ? ? ? ? ? 所有未定義直接賦值的變量自動聲明為擁有的全局作用域;
? ? ? ? ? ? ? 瀏覽器環(huán)境中,所有windows對象的屬性擁有全局作用域;
? ? ? ?1.2 局部作用域,局部作用域一般只是固定的代碼片段內(nèi)可訪問到,在JS中一般是函數(shù)作用域;
? ? ? ?1.3 作用域鏈,當(dāng)代碼在一個環(huán)境中執(zhí)行是,會常見變量對象的一個作用域鏈,作用域鏈的作用是保證隊執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中最后一個對象。
2,作用域規(guī)則
? ? ? 作用域有一套根據(jù)名稱查找變量的規(guī)則。
? ? ? ?實際情況中,通常需要同事顧及幾個作用域,當(dāng)一個塊或函數(shù)嵌套在另一塊或函數(shù)中時,就發(fā)生了作用域的嵌套。因此,在當(dāng)前作用域中無法找到某個變量時,引擎就會在外層嵌套的作用域中舉行查找,直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止,但不能通過向下搜索作用域鏈而進(jìn)入另一個執(zhí)行環(huán)境。
? ? ? ?var a = 1;
? ? ? ?function f() {
var a =2;
? ? ? ? ? ? ? var b =3;
? ? ? ? ? ? ? console.log(a);? ? ? ?
}? ? ? ? ? ??
f();? //2
? ? ? ?a;? ?//1
? ? ? ?b;? // 報錯
3,作用域工作模型
? ? ? JS作用域的工作模型是詞法作用域,詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的,因此當(dāng)詞法分析器處理代碼時會保持作用域不變(大部分情況下是這樣的)。
function f1() {alert(a)
}
f1(); function f2() {
var a = 2;
f1();
}
var a =1;
f1(); //1
f2(); //1,因為f1定義時的作用域的a為全局的a=1,說明函數(shù)不管在哪里調(diào)用都是基于它定義時的作用域;
上面代碼中,當(dāng)函數(shù)執(zhí)行時,會先查找所需的變量,作用域查找會在找到第一個匹配的標(biāo)識符時停止,在多層的嵌套作用域中可以定義同名的標(biāo)識符,這叫做"遮蔽效應(yīng)"(內(nèi)部的標(biāo)識符"遮蔽"了外部的標(biāo)識符)。拋開遮蔽效應(yīng),作用域查找始終從運(yùn)行是所處的最內(nèi)部作用域開始,逐級向外或者說向上進(jìn)行,直到遇見第一個匹配的標(biāo)識符為止。詞法作用域規(guī)則會使函數(shù)在查找變量時從函數(shù)內(nèi)部到函數(shù)使用時的作用域。所以無論函數(shù)在哪里被調(diào)用,也無論它如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時所處的位置決定。
4,函數(shù)作用域
? ? ?JS中沒有塊級作用域,而是基于函數(shù)的作用域。函數(shù)作用域的含義是指,屬于這個函數(shù)的全部變量都可以在整個函數(shù)的范圍內(nèi)使用及復(fù)用(事實上在嵌套的作用域中也可以使用)
function d() {if (true){
var a = 1;
}
console.log(a);
}
d(); //if不會生成新的作用域,所以a會泄露到當(dāng)前函數(shù)作用域
利用函數(shù)作用域的特點,我們可以在任意代碼片段外部添加包裝函數(shù),可以將內(nèi)部的變量和函數(shù)定義"隱藏"起來,外部作用域無法訪問包裝函數(shù)內(nèi)部的任何內(nèi)容。 var a = 1;
(function f() {
var a = 2;
console.log(a); //2,利用函數(shù)作用域包裝代碼,使函數(shù)內(nèi)部變量不會被外部訪問
})();
console.log(a) var a =1;
function f() {
console.log(a); //underfined,不是1
var a = 2;
console.log(a); //2
}
f();
//上面代碼解析為
function f() {
var a;
console.log(a);
a = 2;
console.log(a);
}
var a;
a = 1;
f();
這個過程就是聲明提升。
聲明提升是因為JS在執(zhí)行之前,會有一個預(yù)編譯過程,預(yù)編譯階段生成變量聲明和函數(shù)聲明,沒有初始化行為(賦值),匿名函數(shù)不參與預(yù)編譯只有在解釋執(zhí)行階段才會進(jìn)行變量初始化。
聲明提升原理
函數(shù)和變量的預(yù)解析特點:
1.函數(shù)聲明會置頂
2.變量聲明也會置頂
3.函數(shù)聲明比變量聲明更置頂:(函數(shù)在變量上面)
4.變量和賦值語句一起書寫,在js引擎解析時,會將其拆成聲明和賦值2部分,聲明置頂,賦值保留在原來位置
5.聲明過的變量不會重復(fù)聲明
(function () {
a =1;
alert(window.a);
var a = 2;
alert(a);
})();
//上面代碼解析
(function () {
var a ; //a被提升到頂端后,a=1就不是隱式聲明全局變量了,而是給a賦值操作,所以window.a未聲明為undefined;
a =1;
alert(window.a); //undefined;
a = 2;
alert(a); //2
})();
5,欺騙函數(shù)作用域
? ? ?執(zhí)行環(huán)境類型中有兩種,全局和局部函數(shù),但可以用其他方法欺騙函數(shù)作用域。
? ? eval(),Javascript中的eval(...)函數(shù)可以接受一個字符串為參數(shù),并將其中的內(nèi)容視為好像在書寫是就存在于程序中這個位置的代碼并在運(yùn)行到此位置執(zhí)行參數(shù)代碼。因為eval(...)可以在運(yùn)行期修改書寫期的詞法作用域,存在安全問題,不建議使用。
var a = 1;
function f() {
eval('var a =2;');//eval中的a遮蔽了全局中的a,讓作用域彈出的a改變了
console.log(a);
}
f();
? ?with,with語句接收一個對象參數(shù),表示with語句的對象作用域,with語句中的變量都會在這個指定的對象中查找。with會影響性能,不建議使用
?
window.onload = function () {var a = 0;
var obj = {
a:1,
b:2
};
with(obj){
a = 2; //會在obj查找a屬性
}
console.log(obj.a); //2
console.log(a); //0
with(obj){
c = 3; //沒有在指定對象中找到c,非嚴(yán)格模式下,with會在全局隱式創(chuàng)建一個全局變量
}
console.log(obj.c); //undefined
console.log(c); //3
};
6,閉包
閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn)。
?閉包的特性:
? ? ? ? ? ? 1,函數(shù)嵌套函數(shù)
2,函數(shù)內(nèi)部可以引用外部的參數(shù)和變量
? ? ? ? 3,參數(shù)和變量不會被垃圾回收機(jī)制回收
? ? ? ? 閉包的定義及其優(yōu)缺點
? ? ? ? ? ? ?閉包?是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù),創(chuàng)建閉包的最常見的方式就是在一個函數(shù)內(nèi)創(chuàng)建另一個函數(shù),通過另一個函數(shù)訪問這個函數(shù)的局部變量,閉包的缺點就是常駐內(nèi)存,會增大內(nèi)存使用量,使用不當(dāng)很容易造成內(nèi)存泄露。閉包是javascript語言的一大特點,主要應(yīng)用閉包場合主要是為了:設(shè)計私有的方法和變量。一般函數(shù)執(zhí)行完畢后,局部活動對象就被銷毀,內(nèi)存中僅僅保存全局作用域。但閉包的情況不同!
嵌套函數(shù)的閉包? ?
window.onload = function () {function aaa() {
var a = 1;
return function(){
alert(a++)
};
}
var fun = aaa();
fun();// 1 執(zhí)行后 a++,,然后a還在~
fun();// 2
fun = null;//a被回收!!
};
//閉包會使變量始終保存在內(nèi)存中,如果不當(dāng)使用會增大內(nèi)存消耗。
javascript的垃圾回收原理
(1)在JavaScript中,如果一個對象不再引用,那么這個對象就會被GC回收;
(2)如果兩個對象互相引用,而不再被第3者所引用,那么這兩個互相引用的對象也會被回收;
使用閉包的好處:
1,希望一個變量長期駐扎在內(nèi)存中
2,避免全局變量的污染
3,私有成員的存在
一,全局變量的累加
window.onload = function () {
var a = 1; function f() {
a++;
alert(a);
}
f(); //2
f(); //3
}
二,局部變量
window.onload = function () {
function f() {
var a = 1;
a++;
alert(a);
}
f();
f();
//那么怎么才能做到變量a既是局部變量又可以累加呢? ?三,局部變量的累加
window.onload = function () {
function outer(){
var x=10;
return function(){ //函數(shù)嵌套函數(shù)
x++;
alert(x);
}
}
var y = outer(); //外部函數(shù)賦給變量y;
y(); //y函數(shù)調(diào)用一次,結(jié)果為11,相當(dāng)于outer()();
y();
}
函數(shù)聲明于函數(shù)表達(dá)式
? ? ? ? ? ?在js中我們可以通過關(guān)鍵字function來聲明一個表達(dá)式:
? ? ? ? ? ?
<script>window.onload = function () {
function abc(){
alert(123);
}
abc();
}
</script>
//我們也可以通過一個"()"來將這個聲明變成一個表達(dá)式: window.onload = function () {
(function abc(){
alert(123);
})()
}
? ? 四,模塊化代碼,減少全局變量的污染
? ??
window.onload = function () {var abc = (function () { //abc為外部匿名函數(shù)的返回值
var a = 1;
return function () {
a++;
alert(a);
}
})();
abc(); //2 ;調(diào)用一次abc函數(shù),其實是調(diào)用里面內(nèi)部函數(shù)的返回值
abc(); //3
}
? ? ?5,私有成員的存在
? ? ? ??
window.onload = function () {var aaa = (function(){
var a = 1;
function bbb(){
a++;
alert(a);
}
function ccc(){
a++;
alert(a);
}
return {
b:bbb, //json結(jié)構(gòu)
c:ccc
}
})();
aaa.b(); //2
aaa.c() //3
}
? ? 六,使用匿名函數(shù)實現(xiàn)累加
? ? ?
window.onload = function () {function box(){
var age = 100;
return function(){ //匿名函數(shù)
age++;
return age;
};
}
var b = box();
alert(b());
alert(b()); //即alert(box()());
alert(b());
alert(b); // function () {
// age++;
// return age;
// }
b = null; //解除引用,等待垃圾回收
}
? ?七,在循環(huán)中直接找到對應(yīng)元素的索引
? ??
<!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" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title></title>
<script>
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){ //當(dāng)點擊時for循環(huán)已經(jīng)結(jié)束
alert(i);
};
}
}
</script>
</head>
<body>
<ul>
<li>123</li>
<li>456</li>
<li>789</li>
<li>010</li>
</ul>
</body>
</html>
? ? ?八,使用閉包改寫上面的代碼
? ??
<!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" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title></title>
<script>
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i=0;i<aLi.length;i++){
(function(i){
aLi[i].onclick = function(){
alert(i);
};
})(i);
}
};
</script>
</head>
<body>
<ul>
<li>123</li>
<li>456</li>
<li>789</li>
</ul>
</body>
</html>
轉(zhuǎn)載于:https://www.cnblogs.com/sjie0224/articles/8195570.html
總結(jié)
以上是生活随笔為你收集整理的Javascript的作用域,作用域链,闭包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 查看文件more、less
- 下一篇: TCP/IP模型层次结构