函式中的this的四種繫結形式 — 大家準備好瓜子,我要講故事啦~~
函式中的this的四種繫結形式 — 大家準備好瓜子,我要講故事啦~~
【故事】有一個年輕人叫"迪斯"(this),有一天,迪斯不小心穿越到一個叫 “伽瓦斯克利”(javascript)的 異世界,此時此刻迪斯身無分文, 他首先要做的事情就是——找到他的住宿的地方——呼叫函式的物件
this的預設繫結
【故事——線路1】如果迪斯(this)直到天黑前都沒有找到能收留自己的住所,他眼看就要過上非洲難民的生活, 這時候,一位樂善好施的魔法師村長——window救世主一般地出現了:先住在我家吧!
當一個函式沒有明確的呼叫物件的時候,也就是單純作為獨立函式呼叫的時候,將對函式的this使用預設繫結:繫結到全域性的window物件
function fire () {
console.log(this === window)
}
fire(); // 輸出true
上面的例子我相信對大多數人都很簡單,但有的時候我們把例子變一下就會具有迷惑性:
function fire () {
// 我是被定義在函式內部的函式哦!
function innerFire() {
console.log(this === window)
}
innerFire(); // 獨立函式呼叫
}
fire(); // 輸出true
函式 innerFire在一個外部函式fire裡面宣告且呼叫,那麼它的this是指向誰呢?仍然是window
許多人可能會顧慮於fire函式的作用域對innerFire的影響,但我們只要抓住我們的理論武器——沒有明確的呼叫物件的時候,將對函式的this使用預設繫結:繫結到全域性的window物件,便可得正確的答案了
下面這個加強版的例子也是同樣的輸出true
var obj = {
fire: function () {
function innerFire() {
console.log(this === window)
}
innerFire(); // 獨立函式呼叫
}
}
obj.fire(); //輸出 true
【注意】在這個例子中, obj.fire()的呼叫實際上使用到了this的隱式繫結,這就是下面我要講的內容,這個例子我接下來還會繼續講解
【總結】 凡事函式作為獨立函式呼叫,無論它的位置在哪裡,它的行為表現,都和直接在全域性環境中呼叫無異
this的隱式繫結
【故事——線路2】 迪斯(this)穿越來異世界“伽瓦斯克利”(javascript)的時候,剛好身上帶了一些錢,於是他找到一個旅館住宿了下來
當函式被一個物件“包含”的時候,我們稱函式的this被隱式繫結到這個物件裡面了,這時候,通過this可以直接訪問所繫結的物件裡面的其他屬性,比如下面的a屬性
var obj = {
a: 1,
fire: function () {
console.log(this.a)
}
}
obj.fire(); // 輸出1
現在我們需要對平常司空見慣的的程式碼操作做一些更深的思考,首先,下面的這兩段程式碼達到的效果是相同的:
// 我是第一段程式碼
function fire () {
console.log(this.a)
}
var obj = {
a: 1,
fire: fire
}
obj.fire(); // 輸出1
// 我是第二段程式碼
var obj = {
a: 1,
fire: function () {
console.log(this.a)
}
}
obj.fire(); // 輸出1
fire函式並不會因為它被定義在obj物件的內部和外部而有任何區別,也就是說在上述隱式繫結的兩種形式下,fire通過this還是可以訪問到obj內的a屬性,這告訴我們:
1. this是動態繫結的,或者說是在程式碼執行期繫結而不是在書寫期
2. 函式於物件的獨立性, this的傳遞丟失問題
(下面的描述可能帶有個人的情感傾向而顯得不太嚴謹,但這是因為我希望閱讀者儘可能地理解我想表達的意思)
隱式繫結下,作為物件屬性的函式,對於物件來說是獨立的
基於this動態繫結的特點,寫在物件內部,作為物件屬性的函式,對於這個物件來說是獨立的。(函式並不被這個外部物件所“完全擁有”)
我想表達的意思是:在上文中,函式雖然被定義在物件的內部中,但它和“在物件外部宣告函式,然後在物件內部通過屬性名稱的方式取得函式的引用”,這兩種方式在性質上是等價的(而不僅僅是效果上)
定義在物件內部的函式只是“恰好可以被這個物件呼叫”而已,而不是“生來就是為這個物件所呼叫的”
借用下面的隱式繫結中的this傳遞丟失問題來說明:
var obj = {
a: 1, // a是定義在物件obj中的屬性 1
fire: function () {
console.log(this.a)
}
}
var a = 2; // a是定義在全域性環境中的變數 2
var fireInGrobal = obj.fire;
fireInGrobal(); // 輸出 2
上面這段簡單程式碼的有趣之處在於:這個於obj中的fire函式的引用( fireInGrobal)在呼叫的時候,行為表現(輸出)完全看不出來它就是在obj內部定義的,其原因在於:我們隱式繫結的this丟失了!!從而 fireInGrobal呼叫的時候取得的this不是obj,而是window
上面的例子稍微變個形式就會變成一個可能困擾我們的bug:
var a = 2;
var obj = {
a: 1, // a是定義在物件obj中的屬性
fire: function () {
console.log(this.a)
}
}
function otherFire (fn) {
fn();
}
otherFire(obj.fire); // 輸出2
在上面,我們的關鍵角色是otherFire函式,它接受一個函式引用作為引數,然後在內部直接呼叫,但它做的假設是引數fn仍然能夠通過this去取得obj內部的a屬性,但實際上, this對obj的繫結早已經丟失了,所以輸出的是全域性的a的值(2),而不是obj內部的a的值(1)
在一串物件屬性鏈中,this繫結的是最內層的物件
**在隱式繫結中,如果函式呼叫位置是在一串物件屬性鏈中,this繫結的是最內層的物件。**如下所示:
var obj = {
a: 1,
obj2: {
a: 2,
obj3: {
a:3,
getA: function () {
console.log(this.a)
}
}
}
}
obj.obj2.obj3.getA(); // 輸出3
this的顯式繫結:(call和bind方法)
【故事——線路3】 迪斯(this)穿越來異世界“伽瓦斯克利”(javascript),經過努力的打拼,積累了一定的財富,於是他買下了自己的房子
上面我們提到了this的隱式繫結所存在的this繫結丟失的問題,也就是對於 “ fireInGrobal = obj.fire”
fireInGrobal呼叫和obj.fire呼叫的結果是不同的,因為這個函式賦值的過程無法把fire所繫結的this也傳遞過去。這個時候,call函式就派上用場了
call的基本使用方式:fn.call(object)
fn是你呼叫的函式,object引數是你希望函式的this所繫結的物件。
fn.call(object)的作用:
1.即刻呼叫這個函式(fn)
2.呼叫這個函式的時候函式的this指向object物件
例子:
var obj = {
a: 1, // a是定義在物件obj中的屬性
fire: function () {
console.log(this.a)
}
}
var a = 2; // a是定義在全域性環境中的變數
var fireInGrobal = obj.fire;
fireInGrobal(); // 輸出2
fireInGrobal.call(obj); // 輸出1
原本丟失了與obj繫結的this引數的fireInGrobal再次重新把this綁回到了obj
但是,我們其實不太喜歡這種每次呼叫都要依賴call的方式**,我們更希望:能夠一次性 返回一個this被永久繫結到obj的fireInGrobal函式,這樣我們就不必每次呼叫fireInGrobal都要在尾巴上加上call那麼麻煩了。**
怎麼辦呢?聰明的你一定能想到,在fireInGrobal.call(obj)外面包裝一個函式不就可以了嘛!
var obj = {
a: 1, // a是定義在物件obj中的屬性
fire: function () {
console.log(this.a)
}
}
var a = 2; // a是定義在全域性環境中的變數
var fn = obj.fire;
var fireInGrobal = function () {
fn.call(obj) //硬繫結
}
fireInGrobal(); // 輸出1
如果使用bind的話會更加簡單
var fireInGrobal = function () {
fn.call(obj) //硬繫結
}
可以簡化為:var fireInGrobal = fn.bind(obj);
call和bind的區別是:在繫結this到物件引數的同時:
1.call將立即執行該函式
2.bind不執行函式,只返回一個可供執行的函式
【其他】:至於apply,因為除了使用方法,它和call並沒有太大差別,這裡不加贅述
在這裡,我把顯式繫結和隱式繫結下,函式和“包含”函式的物件間的關係比作買房和租房的區別。
因為this的緣故
在隱式繫結下:函式和只是暫時住在“包含物件“的旅館裡面,可能過幾天就又到另一家旅館住了
在顯式繫結下:函式將取得在“包含物件“裡的永久居住權,一直都會”住在這裡“
new繫結
【故事】 迪斯(this)組建了自己的家庭,並生下多個孩子(通過建構函式new了許多個物件)
執行new操作的時候,將建立一個新的物件,並且將建構函式的this指向所建立的新物件
function foo (a) {
this.a = a;
}
var a1 = new foo (1);
var a2 = new foo (2);
var a3 = new foo (3);
var a4 = new foo (4);
console.log(a1.a); // 輸出1
console.log(a2.a); // 輸出2
console.log(a3.a); // 輸出3
console.log(a4.a); // 輸出4
相關文章
- ton函式函式hash的兩種形式函式
- 定義函式的兩種形式及區別函式
- python的四大函式講解Python函式
- 如何講好資料管理的故事
- caffe中各種cblas的函式使用總結函式
- SAP UI5 資料繫結中的工廠函式UI函式
- 試圖追趕3A標準的《重生邊緣》,想在多人對戰中講好故事
- JS中this的4種繫結規則JS
- React中this值繫結和事件函式傳參React事件函式
- jquery繫結未來新建立函式的方法onjQuery函式
- 為複合函式和反函式做好準備函式
- 我和 TiDB 的故事 | 毫無準備地不期而遇,卻想說與你相遇好幸運TiDB
- echarts 繫結事件處理函式Echarts事件函式
- 技術總監的故事告訴大家,要學會say【NO!】
- STM32延時函式的四種方法函式
- 在WPF中一種較好的繫結Enums資料方法
- 講個鬼故事:這臺機器人終於“強”成了我要的模樣機器人
- 【譯】關於四種快取的故事快取
- 聽說你們春節要出去玩?我要學好Flutter為升職加薪做準備了!Flutter
- JS中建立函式的幾種方式JS函式
- javascript中bind繫結接收者與函式柯里化JavaScript函式
- 如果我是小白,學Python要準備什麼呢?Python
- Vue 中實現雙向繫結的 4 種方法Vue
- spark中的聚合函式總結Spark函式
- JS 中的函式 this 指向總結JS函式
- 【翻譯】WPF中的資料繫結表示式
- 準備應用導數來分析函式函式
- 老爸講的故事
- 函式計算支援 MySQL 例項繫結函式MySql
- 我與外企上司的四個職場故事
- Day10 函式基礎+函式三種定義形式 + 函式的返回值、物件和引數 + 可變長引數函式物件
- 我們的20年 | 講述雲安全背後的故事
- 準備好提升您的ITSM了嗎?
- 我的新書出版啦!和大家聊聊寫書的酸甜苦辣新書
- React元件方法中為什麼要繫結thisReact元件
- 那些年,我們看不懂的那些Kotlin標準函式Kotlin函式
- 看了好幾遍的bind函式,一定講的明明白白函式
- 我的故事