let和const指令及引發的知識補充
- ES6 let和const指令
-
- 1、let指令
-
- 基本用法
- 不存在變量提升
- 暫時性死區
- 2、塊級作用域
-
- 沒有塊級作用域帶來的不合理場景
- ES6塊級作用域
- 3、const
-
- 基本用法
- 本質
- ES6 聲明變量的六種方法
- 4、頂層對象的屬性
- 5、globalThis 對象
- 6、知識補充
-
- 閉包
-
- 什麼是閉包?
- 閉包的特性
- 閉包的作用
- 閉包的坑
- 立即執行函數
-
- 立即執行函數的寫法
- 立即執行函數的作用:
- 與立即執行函數相關的面試題:
- 立即執行函數使用的場景
- 立即執行函數的參數
- 立即執行函數的傳回值
- 總結立即執行函數有哪些作用?
ES6 let和const指令
1、let指令
基本用法
ES6 新增了 let
指令,用來聲明變量。它的用法類似于var,但是所聲明的變量,隻在let指令所在的代碼塊内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
for(let i = 0;i < 10; i++){
//...
}
console.log(i);// ReferenceError: i is not defined
下面的代碼如果使用var,最後輸出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
上面代碼中,變量是
i
指令聲明的,在全局範圍内都有效,是以全局隻有一個變量
var
。每一次循環,變量
i
的值都會發生改變,而循環内被賦給數組
i
的函數内部的
a
,裡面的i指向的就是全局的
console.log(i)
。也就是說,所有數組
i
的成員裡面的
a
,指向的都是同一個
i
,導緻運作時輸出的是最後一輪的
i
的值,也就是10。
i
上面的代碼塊涉及到閉包,見下方閉包。
如果使用let,聲明的變量僅在塊級作用域内有效,最後輸出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
上面代碼中,變量是
i
let
聲明的,目前的i隻在本輪循環有效,是以每一次循環的i其實都是一個新的變量,是以最後輸出的是6。你可能會問,如果每一輪循環的變量i都是重新聲明的,那它怎麼知道上一輪循環的值,進而計算出本輪循環的值?這是因為
JavaScript 引擎内部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。
2019/09/10了解: 每個i的作用域都隻在目前循環内生效,比如
i=0
隻在
a[i] = function(){
console.log(i);
};(i=0)
這個範圍内生效,當
i=1
時又是一個新的作用域,不能作用于i=0這個作用域内,是以
a[0]()
的值為0,同理可得
a[6]()
的值為6.
另外,for循環還有一個特别之處,就是設定循環變量的那部分是一個父作用域,而循環體内部是一個單獨的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
上面代碼正确運作,輸出了 3 次。這表明函數内部的變量
abc
與循環變量
i
不在同一個作用域,有各自單獨的作用域。
i
不存在變量提升
指令會發生變量提升現象,即變量可以在聲明之前使用,值為
var
undefined
。這種現象多多少少是有些奇怪的,按照一般的邏輯,變量應該在聲明語句之後才可以使用。
為了糾正這種現象,let指令改變了文法行為,它所聲明的變量一定要在聲明後使用,否則報錯。
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;
上面代碼中,變量用
foo
指令聲明,會發生變量提升,即腳本開始運作時,變量
var
已經存在了,但是沒有值,是以會輸出
foo
。變量
undefined
用
bar
指令聲明,不會發生變量提升。這表示在聲明它之前,變量
let
是不存在的,這時如果用到它,就會抛出一個錯誤。
bar
簡單了解變量提升就是在變量定義之前使用變量,浏覽器不報錯而顯示
undefined
。let不存在變量提升,隻要在定義前使用變量就會報錯。
暫時性死區
隻要塊級作用域記憶體在 let
指令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代碼中,存在全局變量,但是塊級作用域内
tmp
又聲明了一個局部變量
let
,導緻後者綁定這個塊級作用域,是以在
tmp
聲明變量前,對
let
tmp
指派會報錯。
ES6明确規定,如果區塊中存在
和
let
const
指令,這個區塊對這些指令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。
總之,在代碼塊内,使用
指令聲明變量之前,該變量都是不可用的。這在文法上,稱為“暫時性死區”(temporal dead zone,簡稱TDZ)。
let
還有一些暫時性死區的細節說明可見阮一峰ECMAScript6入門:let暫時性死區說明
2、塊級作用域
沒有塊級作用域帶來的不合理場景
ES5 隻有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合理的場景。
第一種場景,内層變量可能會覆寫外層變量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上面代碼的原意是,代碼塊的外部使用外層的
if
變量,内部使用内層的
tmp
變量。但是,函數f執行後,輸出結果為
tmp
,原因在于變量提升,導緻内層的
undefined
變量覆寫了外層的
tmp
tmp
變量。
第二種場景,用來計數的循環變量洩露為全局變量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代碼中,變量 i
隻用來控制循環,但是循環結束後,它并沒有消失,洩露成了全局變量。
ES6塊級作用域
let
實際上為 JavaScript 新增了塊級作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函數有兩個代碼塊,都聲明了變量,運作後輸出 5。這表示外層代碼塊不受内層代碼塊的影響。如果兩次都使用
n
定義變量
var
n
,最後輸出的值才是 10。
ES6 允許塊級作用域的任意嵌套。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 報錯
}}}};
上面代碼使用了一個五層的塊級作用域,每一層都是一個單獨的作用域。第四層作用域無法讀取第五層作用域的内部變量。
内層作用域可以定義外層作用域的同名變量。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
塊級作用域的出現,實際上使得獲得廣泛應用的匿名立即執行函數表達式(匿名 IIFE)不再必要了。
// IIFE 寫法
(function () {
var tmp = ...;
...
}());
// 塊級作用域寫法
{
let tmp = ...;
...
}
什麼是立即執行函數
3、const
基本用法
const
聲明一個隻讀的常量。一旦聲明,常量的值就不能改變。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
上面代碼表明改變常量的值會報錯。聲明的變量不得改變值,這意味着,
const
一旦聲明變量,就必須立即初始化,不能留到以後指派。
const
const foo;
// SyntaxError: Missing initializer in const declaration
上面代碼表示,對于const來說,隻聲明不指派,就會報錯。的作用域與
const
指令相同:隻在聲明所在的塊級作用域内有效。
let
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const
指令聲明的常量也是不提升,同樣存在暫時性死區,隻能在聲明的位置後面使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
上面代碼在常量MAX聲明之前就調用,結果報錯。
const聲明的常量,也與let一樣不可重複聲明。
var message = "Hello!";
let age = 25;
// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;
本質
實際上保證的,并不是變量的值不得改動,而是變量指向的那個記憶體位址所儲存的資料不得改動。對于簡單類型的資料(數值、字元串、布爾值),值就儲存在變量指向的那個記憶體位址,是以等同于常量。但對于複合類型的資料(主要是對象和數組),變量指向的記憶體位址,儲存的隻是一個指向實際資料的指針,
const
隻能保證這個指針是固定的(即總是指向另一個固定的位址),至于它指向的資料結構是不是可變的,就完全不能控制了。是以,将一個對象聲明為常量必須非常小心。
const
const foo = {};
// 為 foo 添加一個屬性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only
上面代碼中,常量儲存的是一個位址,這個位址指向一個對象。不可變的隻是這個位址,即不能把
foo
foo
指向另一個位址,但對象本身是可變的,是以依然可以為其添加新屬性。
下面是另一個例子。
const a = [];
a.push('Hello'); // 可執行
a.length = 0; // 可執行
a = ['Dave']; // 報錯
上面代碼中,常量a是一個數組,這個數組本身是可寫的,但是如果将另一個數組指派給
a
,就會報錯。
如果真的想将對象當機,應該使用
方法。
Object.freeze
const foo = Object.freeze({});
// 正常模式時,下面一行不起作用;
// 嚴格模式時,該行會報錯
foo.prop = 123;
上面代碼中,常量
foo
指向一個當機的對象,是以添加新屬性不起作用,嚴格模式時還會報錯。
除了将對象本身當機,對象的屬性也應該當機。下面是一個将對象徹底當機的函數。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES6 聲明變量的六種方法
ES5 隻有兩種聲明變量的方法:
var
指令和
function
指令。ES6 除了添加
let
和
const
指令,後面章節還會提到,另外兩種聲明變量的方法:
import
指令和
class
指令。是以,ES6 一共有 6 種聲明變量的方法。
4、頂層對象的屬性
頂層對象,在浏覽器環境指的是window對象,在 Node 指的是global對象。ES5 之中,頂層對象的屬性與全局變量是等價的。
window.a = 1;
a // 1
a = 2;
window.a // 2
上面代碼中,頂層對象的屬性指派與全局變量的指派,是同一件事。
頂層對象的屬性與全局變量挂鈎,被認為是 JavaScript語言最大的設計敗筆之一。這樣的設計帶來了幾個很大的問題,首先是沒法在編譯時就報出變量未聲明的錯誤,隻有運作時才能知道(因為全局變量可能是頂層對象的屬性創造的,而屬性的創造是動态的);其次,程式員很容易不知不覺地就建立了全局變量(比如打字出錯);最後,頂層對象的屬性是到處可以讀寫的,這非常不利于子產品化程式設計。另一方面,window對象有實體含義,指的是浏覽器的視窗對象,頂層對象是一個有實體含義的對象,也是不合适的。
ES6為了改變這一點,一方面規定,為了保持相容性,指令和
var
指令聲明的全局變量,依舊是頂層對象的屬性;另一方面規定,
function
指令、
let
指令、
const
class
指令聲明的全局變量,不屬于頂層對象的屬性。也就是說,從
ES6 開始,全局變量将逐漸與頂層對象的屬性脫鈎。
var a = 1;
// 如果在 Node 的 REPL 環境,可以寫成 global.a
// 或者采用通用方法,寫成 this.a
window.a // 1
let b = 1;
window.b // undefined
上面代碼中,全局變量a由var指令聲明,是以它是頂層對象的屬性;全局變量b由let指令聲明,是以它不是頂層對象的屬性,傳回undefined。
5、globalThis 對象
JavaScript語言存在一個頂層對象,它提供全局環境(即全局作用域),所有代碼都是在這個環境中運作。但是,頂層對象在各種實作裡面是不統一的。
- 浏覽器裡面,頂層對象是window,但 Node 和 Web Worker 沒有window。
- 浏覽器和 Web Worker裡面,self也指向頂層對象,但是 Node 沒有self。
- Node 裡面,頂層對象是global,但其他環境都不支援。
同一段代碼為了能夠在各種環境,都能取到頂層對象,現在一般是使用 this
變量,但是有局限性。
- 全局環境中,
會傳回頂層對象。但是,Node 子產品和 ES6 子產品中,
this
傳回的是目前子產品。
this
- 函數裡面的
,如果函數不是作為對象的方法運作,而是單純作為函數運作,
this
會指向頂層對象。但是,嚴格模式下,這時
this
會傳回
this
。
undefined
- 不管是嚴格模式,還是普通模式,
,總是會傳回全局對象。但是,如果浏覽器用了 CSP(Content Security Policy,内容安全政策),那麼
new Function('return this')()
、
eval
這些方法都可能無法使用。
new Function
綜上所述,很難找到一種方法,可以在所有情況下,都取到頂層對象。下面是兩種勉強可以使用的方法。
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
現在有一個提案,在語言标準的層面,引入作為頂層對象。也就是說,任何環境下,
globalThis
globalThis
都是存在的,都可以從它拿到頂層對象,指向全局環境下的this。
墊片庫
模拟了這個提案,可以在所有環境拿到
global-this
。
globalThis
6、知識補充
閉包
什麼是閉包?
閉包的官方概念:閉包是指有權通路另一個函數作用域中的變量的函數。
菜鳥教程對閉包的了解:閉包是一種保護私有變量的機制,在函數執行時形成私有的作用域,保護裡面的私有變量不受外界幹擾。直覺的說就是形成一個不銷毀的棧環境。
百度經驗簡單明了的閉包了解:閉包就是能夠讀取其他函數内部變量的函數。由于在javascript中,隻有函數内部的子函數才能讀取局部變量,是以閉包可以了解成定義在一個函數内部的函數。在本質上,閉包是将函數内部和函數外部連接配接起來的橋梁。
我的了解:閉包在javaScript中的含義是能通路另一個作用域中變量的函數,表現形式是所有函數内部定義的函數,因為隻要是函數内部定義的函數,就可以通路他父作用域内的變量,但是函數内部定義的函數雖可以通路父作用域内的值,但是不會将其傳回,是以現在使用閉包通常是在函數内部定義函數,子函數将父函數的某個值傳回,使得在全局範圍或者祖範圍内可以得到原本無法擷取的父函數的值。
舉個例子:
function nv(){
var n=10;
function subNv(){return n;}
return subNv;
}
var subNv=nv();
subNv();//10
當有個函數
nv
的内部有個局部變量
n
,這個變量無法在外面直接得到時,我們通過在這個函數裡面在定義一個函數
subNv
,并且用這個定義的函數去得到這個局部變量,最後我們在傳回這個子函數
subNv
,就能通過傳回的這個函數得到局部變量n。這裡的
subNv
就是閉包。
事實上,每個函數都可以被了解成是一個閉包,因為每個函數都在其所在域中維護了某種私有聯系。但在大多數時候,該作用域在函數體執行完之後就自行銷毀了,除非遇到閉包,導緻作用域被保持。(出自《Javascript面向對象程式設計指南》,暫時未了解)
閉包的特性
csdn中的文章介紹了閉包的三個特性:
①函數嵌套函數 ②函數内部可以引用函數外部的參數和變量 ③參數和變量不會被垃圾回收機制回收
閉包的作用
閉包最常見的作用是在父函數中定義子函數,子函數傳回父函數的某個變量,使其在父函數外也可以通路父函數的變量擷取值。閉包最常見的使用時getter和setter。如下代碼段所示:
var getValue,setValue;
(function(){
var secret = 0;
getValue = function(){
return secret;
};
setValue = function(v){
secret = v;
};
}());
在代碼段中定義了一個立即執行函數,在其中定義了全局函數getValue()和setValue(),這兩個函數有權通路父作用域也就是立即執行函數中的secret,是以這兩個函數是閉包。getValue()傳回secret的值,setValue設定secret的值,以此來確定局部變量secret的不可直接通路性。
閉包的坑
循環中的閉包是閉包中最常見的坑,參考以下代碼:
function F(){
var arr = [],i;
for(i = 0;i < 3; i++){
arr[i] = function(){
return i;
};
}
return arr;
}
var arr = F();
arr[0]();//3
arr[1]();//3
arr[2]();//3
為什麼輸出都是3呢?因為
arr[i] = function(){return i;};
嵌套在函數F()内,是以循環的3個
arr[i] = function(){return i;};
是三個閉包,他們的
i
取的是父元件中的
i
,又由于建立函數
var arr = F();
時,子函數不會被立即執行,是以這三個閉包并不會記錄當時
i
的值,隻會将它們指向一個共同的局部變量
i
。當通過
arr[0]()
取值時,這時的
i
在F()函數中已經是固定值3了,是以三個閉包傳回的值都是3。
怎麼避免這種情況的發生呢?在ES6中的let出來之前,使用立即執行函數,換一種閉包形式,代碼如下:
function F(){
var arr = [],i;
for(i = 0;i < 3; i++){
arr[i] = (function(x){
return function(){
return x;
};
};)(i)
}
return arr;
}
var arr = F();
arr[0]();//0
arr[1]();//1
arr[2]();//2
使用立即執行函數,給立即執行函數傳入目前的
i
,這樣全局變量
i
就被指派給了局部變量
x
,
x
被保護在立即執行函數内,也隻作用于立即執行函數内,是以每次調用
arr[i]()
就可以得到不同的值。
在let出來之後,隻需将for循環内的
i
使用let聲明就可以:
function F(){
var arr = [];
for(let i = 0;i < 3; i++){
arr[i] = function(){
return i;
};
}
return arr;
}
var arr = F();
arr[0]();//3
arr[1]();//3
arr[2]();//3
因為let隻作用于目前作用域内,當
i=0
時,i的作用域為for循環中的判斷條件,
arr[i] = function(){return i;};
是一個閉包,取到了父函數for中
i
的值。這個
i
隻作用于目前作用域,等價于
{
let i=0;
arr[i] = function(){
return i;
}
}
{
let i=1;
arr[i] = function(){
return i;
}
}
{
let i=2;
arr[i] = function(){
return i;
}
}
立即執行函數
立即執行函數的寫法
有時,我們定義函數之後,立即調用該函數,這時不能在函數的定義後面直接加圓括号,這會産生文法錯誤。産生文法錯誤的原因是,
function
這個關鍵字,既可以當做語句,也可以當做表達式,比如下邊:
//語句
function fn() {};
//表達式
var fn = function (){};
為了避免解析上的歧義,JS引擎規定,如果
function
出現在行首,一律解析成語句。是以JS引擎看到行首是
function
關鍵字以後,認為這一段都是函數定義,不應該以原括号結尾,是以就報錯了。
解決方法就是不要讓
function
出現在行首,讓JS引擎将其了解為一個表達式,最簡單的處理就是将其放在一個圓括号裡,比如下邊:
(function(){
//code
}())
(function (){
//code
})()
上邊的兩種寫法,都是以圓括号開頭,引擎會意味後面跟的是表達式,而不是一個函數定義語句,是以就避免了錯誤,這就叫做"立即調用的函數表達式"。
立即執行函數,還有一些其他的寫法(加一些小東西,不讓解析成語句就可以),比如下邊:
(function () {alert("我是匿名函數")}()) //用括号把整個表達式包起來
(function () {alert("我是匿名函數")})() //用括号把函數包起來
!function () {alert("我是匿名函數")}() //求反,我們不在意值是多少,隻想通過文法檢查
+function () {alert("我是匿名函數")}()
-function () {alert("我是匿名函數")}()
~function () {alert("我是匿名函數")}()
void function () {alert("我是匿名函數")}()
new function () {alert("我是匿名函數")}()
立即執行函數的作用:
- 不必為函數命名,避免了污染全局變量
- 立即執行函數内部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量
- 封裝變量
總而言之:立即執行函數會形成一個單獨的作用域,我們可以封裝一些臨時變量或者局部變量,避免污染全局變量
與立即執行函數相關的面試題:
<body>
<ul id="list">
<li>公司簡介</li>
<li>聯系我們</li>
<li>營銷網絡</li>
</ul>
<script>
var list = document.getElementById("list");
var li = list.children;
for(var i = 0 ;i<li.length;i++){
li[i].onclick=function(){
alert(i); // 結果總是3.而不是0,1,2
}
}
</script>
</body>
為什麼
alert
總是3? 通過
for
循環給每個元素綁定
alert
事件,循環結束,
i
值為3.
onclick
指向一個匿名函數,當事件發生,顯示彈框,
i
是最後的
i
,而不是綁定時的值。原因是在函數内定義函數,會形成閉包,内部函數可以通路包含函數内的變量(這裡是
i
),但是内部函數通路的外部函數執行結束後的
i
值,而不是綁定那一刻的值,因為那一刻,事件還未發生。
那麼怎麼解決這個問題呢,可以用立即執行函數,給每個
li
建立一個獨立的作用域,在立即執行函數執行的時候,
i
的值從0到2,對應三個立即執行函數,這3個立即執行函數裡邊的
j
分别是0,1,2是以就能正常輸出了,看下邊例子:
<body>
<ul id="list">
<li>公司簡介</li>
<li>聯系我們</li>
<li>營銷網絡</li>
</ul>
<script>
var list = document.getElementById("list");
var li = list.children;
for(var i = 0 ;i<li.length;i++){
(function(j){
li[j].onclick = function(){
alert(j);
})(i); 把實參i指派給形參j
}
}
</script>
</body>
也可以使用ES6的塊級作用域解決整個問題
<body>
<ul id="list">
<li>公司簡介</li>
<li>聯系我們</li>
<li>營銷網絡</li>
</ul>
<script>
var list = document.getElementById("list");
var li = list.children;
for(let i = 0 ;i<li.length;i++){
li[i].onclick=function(){
alert(i); // 結果是0,1,2
}
}
</script>
</body>
立即執行函數使用的場景
- 你的代碼在頁面加載完成之後,不得不執行一些設定工作,比如時間處理器,建立對象等等。
- 所有的這些工作隻需要執行一次,比如隻需要顯示一個時間。
- 但是這些代碼也需要一些臨時的變量,但是初始化過程結束之後,就再也不會被用到,如果将這些變量作為全局變量,不是一個好的注意,我們可以用立即執行函數——去将我們所有的代碼包裹在它的局部作用域中,不會讓任何變量洩露成全局變量,看如下代碼: 比如上面的代碼,如果沒有被包裹在立即執行函數中,那麼臨時變量todaydom,days,today,year,month,date,day,msg都将成為全局變量(初始化代碼遺留的産物)。用立即執行函數之後,這些變量都不會在全局變量中存在,以後也不會其他地方使用,有效的避免了污染全局變量。
ES6 let和const指令及知識補充ES6 let和const指令
立即執行函數的參數
(function(j){
//代碼中可以使用j
})(i)
如果立即執行函數中需要全局變量,全局變量會被作為一個參數傳遞給立即執行函數(上例中的i就是一個全局變量,i代表的是實參,j是i在立即執行函數中的形參)。
立即執行函數的傳回值
像其他函數一樣,立即執行函數也可以有傳回值。除了可以傳回基本類型值以外,立即執行函數也能傳回任何類型的值,比如對象,函數。
上例中立即執行函數的傳回值被指派給了一個變量result,這個函數簡單的傳回了res的值,這個值事先被計算并被存儲在立即執行函數的閉包中。
在五中,如果在以後的代碼中我需要msg這個值,我也可以傳回一個包含msg的對象,友善在以後代碼中使用(這樣五中的一些臨時變量也沒有暴露在外面)。
總結立即執行函數有哪些作用?
1、改變變量的作用域(建立一個獨立的作用域)
<body>
<ul id="list">
<li>公司簡介</li>
<li>聯系我們</li>
<li>營銷網絡</li>
</ul>
<script>
var list = document.getElementById("list");
var li = list.children;
for(var i = 0 ;i<li.length;i++){
(function(j){
li[j].onclick = function(){
alert(j);
})(i); 把實參i指派給形參j
}
}
</script>
</body>
改變變量i的作用域,把全局變量i以參數的形式傳遞到立即執行函數中,在立即執行函數中定義變量i的形參變量j,變量j就是在立即執行函數的作用域中。(給每個li建立了一個作用域塊,點選的時候尋找自由變量j,在立即執行塊中找到)
2、封裝臨時變量
在上面的代碼中,可以封裝臨時變量,避免全局變量的污染。也可以傳回一個在全局中需要的變量(用return)。