文章目錄
- 一、 前言
- 二、本節涉及知識點
- 三、重點內容
- 1、從新的角度認識this
- 2、this是函數的參數
- 3、this的值
- 4、函數的調用
- 1- 裸函數調用
- 2- 函數作為構造函數調用
- 3- 函數作為對象的方法調用
- 4- 函數顯示調用
- 5- 箭頭函數
一、 前言
第八彈內容是this。this相對來說難度不大,外面都說this背誦多過于理解,但是今天我們在這篇文章里由淺入深的理解一下this。文章更新有點慢,腦子一熱跑去學后端去了。最近趕趕進度,把之前丟的給補回來。
本系列為一周一更,計劃歷時6個月左右。從JS最基礎【變量與作用域】到【異步編程,密碼學與混淆】。希望自己能堅持下來, 也希望給準備入行JS逆向的朋友一些幫助, 我現在臉皮厚度還行。先要點贊,評論和收藏。也是希望如果本專欄真的對大家有幫助可以點個贊,有建議或者疑惑可以在下方隨時問。
先預告一下【V少JS基礎班】的全部內容,我做了一些調整。看著很少,其實,正兒八經細分下來其實挺多的,第一個月的東西也一點不少。
第一個月【變量 、作用域 、BOM 、DOM 、 數據類型 、操作符】
第二個月【函數、閉包、原型鏈、this】
第三個月【面向對象編程、 異步編程、nodejs】
第四個月【密碼學、各類加密函數】
第五個月【jsdom、vm2、express】
第六個月【基本請求庫、前端知識對接】
==========================================================
二、本節涉及知識點
函數的參數、對象、 this
==========================================================
三、重點內容
市面上很多資料都說:this的學習記憶多于理解。 只有記住了各種使用方法才能靈活運用。所以經常會有一批人上來就去背誦this的五種綁定方式:
默認綁定,隱式綁定,顯示綁定,new綁定和箭頭函數綁定。
剛開始我也是這么學習的,但是說實話,這種靠背誦學習知識點的方式并不適合爬蟲逆向。
今天,我們還是以理解為主,讓我們開始今天的this學習。
1、從新的角度認識this
首先,學習這篇文章時,請丟掉在外面學習的關于this的任何知識。無論是python中的self,還是c++中的指針。
- 在this的學習中,我們最先接觸的:
- 第一個概念就是函數。
- 函數是什么,不知道的去看我之前關于函數的章節。
- 第二個要接觸的概念就是函數的參數
- 一個函數可以傳參也可以不傳參
- 第一個概念就是函數。
function say_hello(){console.log('hello world!')
}function sum_op(a, b) {return a + b;
}// 調用示例
say_hello()
console.log(sum_op(3, 5)); // 輸出 8
但是, 有一個參數, 無論函數傳不傳參,它都在那。我們不用賦值,只要函數調用時我們就可以使用的一個參數:this
那到這里,我們都能理解的話,那我們已經能掌握this了。 如果我們能理解函數的參數,那理解this就是很簡單的事。
我們丟掉我們無法理解的這句話:this是函數執行上下文里的隱藏變量
只需要記住這句話:
- this是函數調用時的隱藏參數
- 1- this是什么:this是個參數
- 2- this的值: 對象 或 undefined
- 3- 什么時候用:函數調用時使用
我們今天的大部分內容都圍繞著這三個點展開
2、this是函數的參數
我們認識this的途徑不止從上下文中來,還可以從函數的參數的角度。
this是作為函數的隱藏參數在函數調用時傳遞。 函數在定義時我們給定一個形參,而只有在函數調用時,他才會有值。
甚至我們都不用去給他定義形參,在函數調用時,我們隨時可以使用this。hook住指定調用時的函數,隨意的就能取到this的值
3、this的值
this的值只會有兩種類型: 對象 & undefined
在嚴格模式下,裸函數調用, this的返回值就是undefined。 在非嚴格模式下,裸函數調用返回的是window。 window本身就是一個對象。 那其他情況下也是如此,this要么是對象,要么是undefined。
4、函數的調用
說了這么多,其實就是一句話的事情。 那么我們學習this到底是在學習什么呢?那就是現在要說的。 函數的調用。 我們都說了:this是函數調用時的隱藏參數,參數的值只能是對象或者undefined。 那么什么情況下this是對象什么情況下是undefined。 我們如何判斷this的值呢。 這就是我們要學習的,函數的幾種調用方式。
函數的各種調用方式:
1- 裸函數調用
“裸函數調用”其實就是最直接、最普通的函數調用方式,沒有通過對象、call/apply/new 等手段修飾,只是單純調用函數本身。
function foo() {console.log(this);
}// 裸函數調用
foo();
為什么會有嚴格和非嚴格模式下值不同的區別。
其實很簡單,嚴格模式下,是真正的裸函數調用。 沒有任何對象調用它,函數自己執行了。此時,是沒有任何對象傳遞的,所以,此時的this是undefined。而在非嚴格模式下,瀏覽器環境中,所有的屬性和方法都是掛在在window上的,所以非嚴格模式下,瀏覽器中的裸函數調用不是真正的裸函數,而是window.fn()
同理,在不同的環境中裸函數調用的this值也是不一樣的。比如在瀏覽器中this為window,而在nodejs的全局中this的值則為global
這就是this背誦點之: 默認綁定
很多課件上來就說,this有五種綁定,我們背誦一下第一種綁定:默認綁定。默認綁定就是:當一個函數被調用時,如果 沒有明確指定 this(沒有作為對象方法調用、沒有 call/apply/bind、沒有 new 構造),那么 this 會按照 默認規則綁定。
我覺得這是一種本末倒置的學習方式,如果我們要理解默認綁定,因為由以下方式理解。
什么是默認綁定: 默認綁定就是裸函數調用,沒有任何第三方對象或者方法介入。直接用函數名+() 的形式運行函數,就是裸函數調用,也就是八股文中的 this的默認綁定
到此,我們就學會了this的第一個綁定方式。它是一種綁定方式,是由裸函數調用的方式決定了它的綁定方式。
2- 函數作為構造函數調用
那函數除了裸函數調用之外,還有哪些調用方式呢。
還記得函數篇中我們提到的構造函數嗎。 除了箭頭函數,任何普通函數都可以使用new的方式進行調用,此時的函數為構造函數。 當使用 new 調用一個函數時,就變成構造函數調用:
function Person(name) {this.name = name;
}const p = new Person('Alice');
console.log(p.name); // Alice
- this 指向
- 構造函數調用時,this 永遠指向新創建的對象
- 和普通函數或方法調用不同:
-
- 普通函數(裸函數調用)→ this 根據嚴格模式決定(undefined 或 window/global)
-
- 方法調用 → this 指向點左邊對象
-
- 構造函數 → this 永遠是新對象(或被返回對象覆蓋)
為什么呢, 我們看一下new調用函數時發生了什么
- new 會做幾件事情:
- 創建一個空對象(obj)
- 將 this 指向這個新對象
- 執行構造函數的代碼
- 默認返回 this(新對象),除非顯式返回對象
所以此時的this指向是固定的。
3- 函數作為對象的方法調用
當函數作為 對象的屬性 被調用時,我們稱它為 方法調用。示例如下:
const obj = {x: 42,foo: function() {console.log(this);}
};obj.foo(); // 方法調用
this 指向: 調用時,點前面的對象
作為對象方法調用時的典型例子
1) 對象直接調用
const obj = {name: 'Alice',greet: function() {console.log(this.name);}
};obj.greet(); // Alice// 這里 this 指向 obj
2) 多層對象
const obj = {inner: {name: 'Bob',say: function() { console.log(this.name); }}
};obj.inner.say(); // Bob// 點運算符左邊是 obj.inner → this = obj.inner
3) 注意“丟失”情況
const obj = {x: 10,foo: function() { console.log(this.x); }
};const bar = obj.foo;
bar(); // undefined 或 window.x(非嚴格模式下)
原因:bar 是函數的引用,不再是對象的方法調用. 這種情況就變成了 裸函數調用
總結:
在對象調用方法的情況下一定要注意對象丟失的情況。 使用函數賦值的時候,裸函數調用和對象的方法調用不能混為一談
好, 那對象.方法的形式,是什么綁定呢。 答案是: this的隱示綁定。
當我們用對象.函數的方式調用函數,此時函數中的this就已經與該對象進行了綁定。 此時就是隱示綁定。
這就是我們背誦的this的第二個綁定方式
4- 函數顯示調用
不知道大家有沒有見過call和apply調用函數。比較經典的:
function sum(a, b, c) { return a + b + c; }
const nums = [1, 2, 3];
console.log(sum.apply(null, nums)); // 6
// 用于計算
const arr = [3, 1, 4];
const max = Math.max.apply(null, arr); // 4
// 查找最大值
其實,函數作為所謂的一等公民,他是給我們提供了很多的方便,有些已經寫好的庫,我們可以直接拿過來使用,我們對指定的對象用現有的函數,這種調用方式并不少見。
此時,我們對指定的函數進行call和apply與對象進行綁定。這種方式從this的角度看,他就是this的顯示綁定。我們直接明確的告訴大家,該函數是由該對象調用的。this也就是顯示的綁定在這個對象上。
總結:
ok,到這里大家已經學習了this的四種綁定方式了。 如此的水到渠成,完全不用背誦各種復雜的邏輯。函數怎么調用,this就是對應的綁定。 他的值也就是對應的值。
那我們最后再看一個特殊的綁定方式。
5- 箭頭函數
1) 箭頭函數的基本調用(裸函數調用)
const add = (a, b) => a + b;console.log(add(2, 3)); // 5
this 指向定義時的外層作用域(詞法綁定),而不是調用者
2)作為對象方法調用
const obj = {name: 'Alice',arrow: () => console.log(this.name)
};obj.arrow(); // undefined(this不是obj,而是定義時的外層 this)
箭頭函數不會綁定對象
無論怎么調用,this 都不會指向調用對象
如果想訪問對象屬性,需要用普通函數或外部變量
3) 作為回調函數調用
setTimeout(() => {console.log('Hello');
}, 1000);
常用于回調函數中
不會創建新的 this,繼承外層作用域的 this
避免了普通函數在回調中 this 指向全局或 undefined 的問題
4)與 call / apply / bind 的區別
const arrow = () => console.log(this);
arrow.call({ a: 1 }); // 依然指向定義時的 this
箭頭函數 不能被 call / apply 改變 this
- 總結:
- 調用方式:基本和普通函數一樣,可直接調用、作為回調或對象屬性
- 特殊點:
-
- 沒有自己的 this,繼承外層作用域
-
- 不能被顯式綁定(call/apply/bind 改變 this 無效)
-
- 適合用于回調或內部函數,避免 this 指向混亂
口語化OS:
我們其實完全可以這么理解。當上下文在被調用的函數中時: 箭頭函數本身就無法改變this,所以,此時的this就是被調用函數當前狀態的this
這句話怎么理解呢。 就是,除了箭頭函數之外的其他調用方式大家都理解了。 此時情況:
上下文正在debugger處。 在函數內部。 此時的this綁定的哪個就是哪個,這時候,函數內部有再多的箭頭函數,都不會改變this的指向。我們就記住一句話,箭頭函數無法改變this的綁定
以上就是this的五種綁定的全部內容。 今天就先到這里。如果本篇文章對大家有幫助,還望點贊關注,后續再給大家加一點this優先級的相關內容。