目錄
- JavaScript閉包和this
- 1 閉包
- 1.1 變數作用域
- 1.2 讀取函式內部的區域變數
- 1.3 閉包概念
- 2 this
- 2.1 關鍵點:
- 2.2 四類呼叫方式
- 1)作為物件方法的呼叫
- 2)純粹的函式呼叫
- 3)作為建構式呼叫
- 4)使用apply、call、bind呼叫
- 2.3 箭頭函式中的this
- 3 樣例詳解
- 參考
JavaScript閉包和this
1 閉包
一個函式和對其周圍狀態(lexical environment,詞法環境)的參考捆綁在一起(或者說函式被參考包圍),這樣的組合就是閉包(closure),也就是說,閉包讓你可以在一個內層函式中訪問到其外層函式的作用域,在 JavaScript 中,每當創建一個函式,閉包就會在函式創建的同時被創建出來,—引自MDN
在JS中,通俗來講,閉包就是能夠讀取外層函式內部變數的函式,
1.1 變數作用域
變數的作用域為兩種:全域作用域和區域作用域
1)函式內部可以讀取全域變數
let code = 200;
function f1() {
console.log(code);
}
f1(); // 200
2)函式外部無法讀取函式內部的區域變數
function f1() {
let code = 200;
}
console.log(code); // Uncaught ReferenceError: code is not defined
1.2 讀取函式內部的區域變數
1)在函式內部再定義一個函式
function f1() {
let code = 200;
function f2() {
console.log(code);
}
}
函式f1內部的函式f2可以讀取f1中所有的區域變數,因此,若想在外部訪問函式f1中的區域變數code,可通過函式f2間接訪問,
2)為外部程式提供訪問函式區域變數的入口
function f1() {
let code = 200;
function f2() {
console.log(code);
}
return f2;
}
f1()(); // 200
1.3 閉包概念
1.2中的函式f2,就是閉包,其作用就是將函式內部與函式外部進行連接,
- 閉包訪問的變數,是每次運行上層函式時重新創建的,是相互獨立的,
function f1() {
let obj = {};
function f2() {
return obj;
}
return f2;
}
let result1 = f1();
let result2 = f1();
console.log(result1() === result2()); // false
- 不同的閉包,可以共享上層函式中的區域變數
function f() {
let num = 0;
function f1() {
console.log(++num);
}
function f2() {
console.log(++num);
}
return {f1,
f2};
}
let result = f();
result.f1(); // 1
result.f2(); // 2
從結果可以看出,閉包f1和閉包f2共享上層函式中的區域變數num,
使用閉包的注意點:
1)由于閉包會使得函式中的變數都被保存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致記憶體泄露,解決方法是,在退出函式之前,將不使用的區域變數全部洗掉,
2)閉包會在父函式外部,改變父函式內部變數的值,所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函式內部變數的值,
2 this
2.1 關鍵點:
- this始終指向呼叫該函式的物件;
- 若沒有指明呼叫的物件,則順著作用域鏈向上查找,最頂層為global(window)物件;
- 箭頭函式中的this是定義函式時系結的,與執行背景關系有關
- 簡單物件(非函式、非類)沒有執行背景關系;
- 類中的this,始終指向該實體物件;
2.2 四類呼叫方式
1)作為物件方法的呼叫
function f() {
console.log( this.code );
}
let obj = {
code: 200,
f: f
};
obj.f(); // 200
2)純粹的函式呼叫
function f() {
console.log( this.code );
}
// 此處,通過var(函式作用域)宣告的變數code會系結到window上;如果使用let(塊作用域)宣告變數code,則不會系結到window上,因此下面的2次函式呼叫f(),會輸出undefined
// let code = 200;
var code = 200;
f(); // 200
code = 404;
f(); // 404
復雜一點:
function doF(fn) {
this.code = 404;
fn();
}
function f() {
console.log(this.code);
}
let obj = {
code: 200,
f: f
};
var code = 500;
doF(obj.f); // 404
該列子中,為分析出this的指向,應找到關鍵點,哪個物件呼叫了函式f(),obj.f作為doF()的入參,將函式f傳給了doF,而doF是由window物件呼叫的,所以函式doF中的this指向window,繼而函式f中的this也指向window,
由于最終執行函式f時,其中的this指向window,所以在函式f中執行this.code = 401時,等同于window.code = 401:
function doF(fn) {
this.code = 404;
fn();
}
function f() {
this.code = 401;
console.log(this.code);
}
let obj = {
code: 200,
f: f
};
var code = 500;
doF(obj.f); // 401
3)作為建構式呼叫
code = 404
function A() {
this.code = 200
this.callA = function() {
console.log(this.code)
}
}
A() // 回傳undefined, A().callA會報錯,callA被保存在window上
var a = new A()
a.callA() // 200, callA在new A回傳的物件里
4)使用apply、call、bind呼叫
apply
var code = 404;
let obj = {
code: 200,
f: function() {
console.log(this.code);
}
}
obj.f(); // 200, 實際上是作為物件的方法呼叫
obj.f.apply(); // 404,引數為空時,默認使用全域物件global,在此處為物件window
obj.f.apply(obj); //200,this指向引數中設定的物件
call
function f() {
console.log( this.code );
}
var obj = {
code: 200
};
f.call( obj ); // 200
bind
// bind回傳一個新的函式
function f(b) {
console.log(this.a, b);
return this.a + b;
}
var obj = {
a: 2
};
var newF = f.bind(obj);
var result = newF(3); // 2 3
console.log(result); // 5
2.3 箭頭函式中的this
箭頭函式中的this是定義函式時系結的,而不是在執行函式時系結,若箭頭函式在簡單物件中,由于簡單物件沒有執行背景關系,所以this指向上層的執行背景關系;若箭頭函式在函式、類等有執行背景關系的環境中,則this指向當前函式、類,
1)箭頭函式在普通物件中
var code = 404;
let obj = {
code: 200,
getCode: () => {
console.log(this.code);
}
}
obj.getCode(); // 404
2)箭頭函式在函式中
var code = 404;
function f() {
// 若此處為let code = 200; code不會系結到函式f上,則函式getCode訪問this.code時,會輸出undefined
this.code = 200;
let getCode = () => {
console.log(this.code);
};
getCode();
}
f(); // 200
let func = new f();
console.dir(func); // func中有屬性code
3)箭頭函式在類中
var code = 404;
class Status {
constructor(code) {
this.code = code;
}
getCode = () => {
console.log(this.code);
};
}
let status = new Status(200);
status.getCode(); // 200
不管是箭頭函式還是普通函式,只要是類中,this就指向實體物件,
3 樣例詳解
1)
var code = 404;
let status = {
code : 200,
getCode : function() {
return function(){
return this.code;
};
}
};
console.log(status.getCode()()); // 404
執行status.getCode()時,回傳函式,status.getCode()()表示執行當前回傳的函式,其呼叫者為全域變數window,所以this.code為系結在window中的code,值為404,
2)
var code = 404;
let status = {
code : 200,
getCode : function() {
let that = this;
return function(){
return that.code;
};
}
};
console.log(status.getCode()()); // 200
執行status.getCode()時,this指向status,并通過區域變數that保存this的值,最后回傳值為函式,status.getCode()()表示執行回傳的函式,其that指向的status,所以回傳值為200,
3)更復雜的例子
function f() {
setTimeout(() => {
console.log(">>>" + this); // >>>[object object],陳述句5
this.code = 401;
}, 0)
console.log( this.code );
}
let obj = {
code: 200,
foo: f
};
var code = 500;
obj.foo(); // 200,陳述句1
obj.foo(); // 200,陳述句2
console.log("--" + obj.code); // --200,陳述句3
setTimeout(()=>{console.log("---" + obj.code);}, 0); // ---401,陳述句4
知識補充:函式setTimeout用于創建一個定時器,在同一個的物件上,各個定時器使用用一個編號池(這點很關鍵),不同的物件使用獨立的編號池,同一個物件上的多個定時器有不同的定時器編號,所以,setTimeout到了執行時間點時,其內部的this指向定時器所系結的物件,
結果分析:函式setTimeout中傳入的函式句柄,由于js是單執行緒執行,即使延時為0,仍需等到本次執行的所有同步代碼執行完畢,才能執行,所以在兩次執行obj.foo()的程序中,其內部的setTimeout的入參函式(也就是陳述句5)都未執行,同理,執行陳述句3時,陳述句5同樣未執行,知道執行陳述句4,當前同步代碼塊執行完畢,陳述句5執行(并且執行了2次,因為陳述句1和陳述句2分別執行1次),obj上系結的code被更新為401,最終,陳述句4的入參函式執行,輸出obj.code的值為401,
4)由上面的例子繼續擴展
function doFoo(fn) {
this.code = 404;
fn();
}
function f() {
setTimeout(() => {
console.log(">>>" + this); // >>>[object window],陳述句3
this.code = 401; // 陳述句4
}, 0)
console.log( this.code ); // 404,陳述句2
}
let obj = {
code: 200,
foo: f
};
var code = 500;
doFoo( obj.foo ); // 陳述句1
setTimeout(()=>{console.log(obj.code)}, 0); // 200,陳述句5
setTimeout(()=>{console.log(window.code)}, 0); // 401,陳述句6
結果分析:obj.foo為函式句柄,作為入參傳入函式doFoo,doFoo的呼叫房為全域變數window,所以,陳述句2、3、4中的this均指向window,
參考
- MDN閉包
- 學習JavaScript閉包—阮一峰
- 深入理解ES6箭頭函式中的this
- 一次搞定閉包和this
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/290681.html
標籤:其他
上一篇:[JavaScript] 輪播圖 利用原生js的setTimeout結合CSS3的過渡transition實作輪播圖的無縫滾動
