摘要:來點規(guī)則,有規(guī)可尋我們必須考察調用點,來判斷下面即將要說的四中規(guī)則哪一種適用。所以,在回調函數(shù)中丟失綁定是一件很常見的事情,但是還有另一種情況,接受我們回調的函數(shù)故意改變的值。
前言此文主要總結于《你不知道的JavaScript 上卷》,雖然講解this的文章已經(jīng)爛大街了,但是依舊希望,這篇文章可以幫助到那些還是對this有些疑惑的哥們
this關鍵字是JavaScript中最復雜的機制之一,它不是一個特殊的關鍵字,被自動定義在所有的函數(shù)作用域中。
老規(guī)矩,我們直接看例子:
function identify(){ console.log(this.name) return this.name.toUpperCase(); } function speak() { var gretting = "Hello I am "+identify.call(this) console.log(gretting); } var me = { name:"Neal" } var you = { name:"Nealyang" } identify.call(me); identify.call(you); speak.call(me); speak.call(you);
關于運行結果大家可以自行運行查看,如果對于this是如何工作的這里我們還是存在疑惑,那么別急,我們后面當然會繼續(xù)深入探討下,這里,先說下關于this
的一些誤解的地方
通常新手都會認為this就是指向函數(shù)本身,至于為什么在函數(shù)中引用他自己呢,可能就是因為遞歸這種情況的存在吧。但是這里,我想說,this并不是指向函數(shù)本身的
function foo(num) { console.log("foo:"+num); this.count++; } foo.count = 0; for(var i = 0;i<10;i++){ foo(i); } console.log(foo.count);
通過運行上面的代碼我們可以看到,foo函數(shù)的確是被調用了十次,但是this.count似乎并沒有加到foo.count上。也就是說,函數(shù)中的this.count并不是foo.count。
所以,這里我們一定要記住一個,就是函數(shù)中的this并不是指向函數(shù)本身的。上面的代碼修改如下:
function foo(num) { console.log("foo:"+num); this.count++; } foo.count = 0; for(var i = 0;i<10;i++){ foo.call(foo,i); } console.log(foo.count);
運行如上代碼,此時我們就可以看到foo函數(shù)中的count的確已經(jīng)變成10了
this值得是他的作用域另一種對this的誤解是它不知怎么的指向函數(shù)的作用域,其實從某種意義上來說他是正確的,但是從另一種意義上來說,這的確是一種誤解。
明確的說,this不會以任何方式指向函數(shù)的詞法作用域,作用域好像是一個將所有可用標識符作為屬性的對象,這從內部來說他是對的,但是JavaScript代碼不能訪問這個作用域“對象”,因為它是引擎內部的實現(xiàn)。
function foo() { var a = 2; this.bar(); } function bar() { console.log( this.a ); } foo(); //undefined
上面的代碼不止一處錯誤,這里不做討論,僅僅用于看代碼,首先,視圖this.bar()來視圖訪問bar函數(shù),的確他做到了。雖然只是碰巧而已。然而,寫下這段代碼的開發(fā)者視圖使用this在foo和bar的詞法作用域中建立一座橋,是的bar可以訪問foo內部變量作用域a。當然,這是不可能的,不可能使用this引用在詞法作用域中查找東西。
什么是this所以說了這么coder對this的誤解,那么究竟什么是this呢。記住,this不是在編寫時候綁定的,而是在運行時候綁定的上下文執(zhí)行環(huán)境。this綁定和函數(shù)申明無關,反而和函數(shù)被調用的方式有關系。
當一個函數(shù)被調用的時候,會建立一個活動記錄,也成為執(zhí)行環(huán)境。這個記錄包含函數(shù)是從何處(call-stack)被調用的,函數(shù)是 如何 被調用的,被傳遞了什么參數(shù)等信息。這個記錄的屬性之一,就是在函數(shù)執(zhí)行期間將被使用的this引用。
徹底明白this到底值得什么鬼 調用點為了徹底弄明白this的指向問題,我們還必須明白什么是調用點,即一個函數(shù)被調用的位置。考慮調用棧(即使我們到達當前執(zhí)行位置而被d調用的所有方法堆棧)是非常重要的,我們關心的調用點就是當前執(zhí)行函數(shù)的之前的調用
function baz() { // 調用棧是: `baz` // 我們的調用點是global scope(全局作用域) console.log( "baz" ); bar(); // <-- `bar`的調用點 } function bar() { // 調用棧是: `baz` -> `bar` // 我們的調用點位于`baz` console.log( "bar" ); foo(); // <-- `foo`的call-site } function foo() { // 調用棧是: `baz` -> `bar` -> `foo` // 我們的調用點位于`bar` console.log( "foo" ); } baz(); // <-- `baz`的調用點
上面代碼大家簡單感受下什么是調用棧和調用點,比較簡單的東西。
來點規(guī)則,有規(guī)可尋我們必須考察調用點,來判斷下面即將要說的四中規(guī)則哪一種適用。先獨立解釋下四中規(guī)則的每一種,然后再來說明下如果多種規(guī)則適用調用點時他們的優(yōu)先級。
默認綁定所謂的默認綁定,就是獨立函數(shù)的調用形式。
function foo() { console.log( this.a ); } var a = 2; foo(); // 2
為什么會是2呢,因為在調用foo的時候,JavaScript對this實施了默認綁定,所以this就指向了全局對象。
我們怎么知道這里適用 默認綁定 ?我們考察調用點來看看foo()是如何被調用的。在我們的代碼段中,foo()是被一個直白的,毫無修飾的函數(shù)引用調用的。沒有其他的我們將要展示的規(guī)則適用于這里,所以 默認綁定 在這里適用。
需要注意的是,對于嚴格模式來說,默認綁定全局對象是不合法的,this被置為undefined。但是一個很微妙的事情是,即便是所有的this綁定規(guī)則都是基于調用點的,如果foo的內容沒有嚴格模式下,默認綁定也是合法的。
隱含綁定調用點是否有一個環(huán)境對象,也成為擁有者和容器對象。
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
foo被申明,然后被obj添加到其屬性上,無論foo()是否一開始就在obj上被聲明,還是后來作為引用添加(如上面代碼所示),都是這個 函數(shù) 被obj所“擁有”或“包含”。
這里需要注意的是,只有對象屬性引用鏈的最后一層才影響調用點
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42
this綁定最讓人頭疼的地方就是隱含綁定丟失了他的綁定,其實明確了調用位置,這個也不是難點。直接看代碼
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函數(shù)引用! var a = "oops, global"; // `a`也是一個全局對象的屬性 bar(); // "oops, global"
所以如上的調用模式,我們又退回到了默認綁定模式。
還能hold住,那么接著看代碼:
function foo() { console.log( this.a ); } function doFoo(fn) { // `fn` 只不過`foo`的另一個引用 fn(); // <-- 調用點! } var obj = { a: 2, foo: foo }; var a = "oops, global"; // `a`也是一個全局對象的屬性 doFoo( obj.foo ); // "oops, global"
參數(shù)傳遞,僅僅是一種隱含的賦值,而且因為我們是傳遞一個函數(shù),他是一個隱含的引用賦值,所以最終結果和我們前一段代碼一樣。
所以,在回調函數(shù)中丟失this綁定是一件很常見的事情,但是還有另一種情況,接受我們回調的函數(shù)故意改變this的值。那些很受歡迎的事件處理JavaScript包就十分喜歡強制你的回調的this指向觸發(fā)事件的DOM元素。
不管哪一種意外改變this的方式,你都不能真正地控制你的回調函數(shù)引用將如何被執(zhí)行,所以你(還)沒有辦法控制調用點給你一個故意的綁定。我們很快就會看到一個方法,通過 固定 this來解決這個問題。
如上,我們一定要清除的是引用和調用。記住,找this,我們只看調用,別被引用所迷惑
明確綁定在JavaScript中,我們可以強制制定一個函數(shù)在運行時候的this值。是的,call和apply,他們的作用就是擴充函數(shù)賴以生存的作用域。
function foo() { console.log( this.a ); } var obj = { a: 2 }; foo.call( obj ); // 2
上面代碼,我們使用foo,強制將foo的this指定為obj
如果你傳遞一個簡單原始類型值(string,boolean,或 number類型)作為this綁定,那么這個原始類型值會被包裝在它的對象類型中(分別是new String(..),new Boolean(..),或new Number(..))。這通常稱為“boxing(封箱)”。
但是,多帶帶的依靠明確綁定仍然不能為我們先前提到的問題,提供很好的解決方案,也就是函數(shù)丟失自己原本的this綁定。
function foo() { console.log( this.a ); } var obj = { a: 2 }; var bar = function() { foo.call( obj ); }; bar(); // 2 setTimeout( bar, 100 ); // 2 // `bar`將`foo`的`this`硬綁定到`obj` // 所以它不可以被覆蓋 bar.call( window ); // 2
我們創(chuàng)建了一個函數(shù)bar(),在它的內部手動調用foo.call(obj),由此強制this綁定到obj并調用foo。無論你過后怎樣調用函數(shù)bar,它總是手動使用obj調用foo。這種綁定即明確又堅定,所以我們稱之為 硬綁定(hard binding)
new 綁定這個比較簡單,當函數(shù)前面加入new關鍵字調用的時候,其實就是當做構造函數(shù)調用的。其內部其實完成了如下事情:
一個新的對象會被創(chuàng)建
這個新創(chuàng)建的對象會被接入原型鏈
這個新創(chuàng)建的對象會被設置為函數(shù)調用的this綁定
除非函數(shù)返回一個他自己的其他對象,這個被new調用的函數(shù)將自動返回一個新創(chuàng)建的對象
總結性來一波函數(shù)是否在new中調用,如果是的話this綁定的是新創(chuàng)建的對象
var bar = new Foo();
函數(shù)是否通過call、apply或者其他硬性調用,如果是的話,this綁定的是指定的對象
var bar = foo.call(obj);
函數(shù)是否在某一個上下文對象中調用,如果是的話,this綁定的是那個上下文對象
var bar = obj.foo();
如果都不是的話,使用默認綁定,如果在嚴格模式下,就綁定到undefined,注意這里是方法里面的嚴格聲明。否則綁定到全局對象
var bar = foo();綁定例外
第一種情況就是將null和undefined傳給call、apply、bind等函數(shù),然后此時this采用的綁定規(guī)則是默認綁定
第二種情況這里舉個例子,也是面試中常常會出現(xiàn)的例子
function foo() { console.log(this.a); } var a = 2; var o = { a:3, foo:foo } var p = {a:4}; (p.foo = o.foo)();
如上調用,其實foo采用的也是默認綁定,這里我們需要知道的是,p.foo = o.foo的返回值是目標函數(shù)的引用,所以最后一句其實就是foo()
es6中的箭頭函數(shù)es6中的箭頭函數(shù)比較簡單,由于箭頭函數(shù)并不是function關鍵字定義的,所以箭頭函數(shù)不適用this的這四中規(guī)則,而是根據(jù)外層函數(shù)或者全局作用域來決定this
function foo() { // 返回一個arrow function return (a) => { // 這里的`this`是詞法上從`foo()`采用 console.log( this.a ); }; } var obj1 = { a: 2 }; var obj2 = { a: 3 }; var bar = foo.call( obj1 ); bar.call( obj2 ); // 2, 不是3!
這里foo內部創(chuàng)建的箭頭函數(shù)會自動獲取foo的this。
來一道經(jīng)典面試題吧第一題
var a=10; var foo={ a:20, bar:function(){ var a=30; console.log(this) return this.a; } }; foo.bar() (foo.bar)() (foo.bar=foo.bar)() (foo.bar,foo.bar)()
第二題
function t(){ this.x=2; } t(); console.log(window.x);
第三題
var obj = { x: 1, y: 2, t: function() { console.log(this.x) } } obj.t(); var dog={x:11}; dog.t=obj.t; dog.t(); show=function(){ console.log("show"+this.x); } dog.t=show; dog.t();
第四題
name = "this is window"; var obj1 = { name: "php", t: function() { console.log(this.name) } }; var dog1 = { name: "huzi" }; obj1.t(); dog1.t = obj1.t; var tmp = dog1.t; tmp(); //this本來指向window (dog1.t = obj1.t)(); dog1.t.call(obj1);
第五題
var number=2; var obj={ number:4, /*匿名函數(shù)自調*/ fn1:(function(){ var number; this.number*=2;//4 number=number*2;//NaN number=3; return function(){ var num=this.number; this.number*=2;//6 console.log(num); number*=3;//9 alert(number); } })(), db2:function(){ this.number*=2; } } var fn1=obj.fn1; alert(number); fn1(); obj.fn1(); alert(window.number); alert(obj.number);交流
掃碼關注我的個人微信公眾號,分享更多原創(chuàng)文章。點擊交流學習加我微信、qq群。一起學習,一起進步。共同交流上面的題目吧
歡迎兄弟們加入:
Node.js技術交流群:209530601
React技術棧:398240621
前端技術雜談:604953717 (新建)
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89780.html
摘要:代碼托管這個倉庫。假設現(xiàn)在我們需要實現(xiàn)一個點贊取消點贊的功能。如果你對前端稍微有一點了解,你就順手拈來點贊為了現(xiàn)實當中的實際情況,所以這里特易把這個的結構搞得稍微復雜一些。這里非常暴力地使用了,把兩個按鈕粗魯?shù)夭迦肓水斨小? 作者:胡子大哈原文鏈接:http://huziketang.com/blog/posts/detail?postId=58aea515204d50674934c3a...
摘要:需要有一定的基礎和的使用經(jīng)驗。這就是屬性的作用。方法接收一個新對象來重新賦值。也接收一個函數(shù),這個回調函數(shù)這里我默認有一個參數(shù),表示之前的的值,這個函數(shù)的返回值就是最新的。但是不同的是在組件內部是只讀的。 前言 寫這篇文章的主要目標是讓初學者更快的上手 React 的項目開發(fā),能有一個循循漸進的理解過程。需要有一定的 JavaScript 基礎和 NPM 的使用經(jīng)驗。不多說了,下面會按...
摘要:最近剛剛看完了你不知道的上卷,對有了更進一步的了解。你不知道的上卷由兩部分組成,第一部分是作用域和閉包,第二部分是和對象原型。附錄詞法這一章并沒有說明機制,只是介紹了中的箭頭函數(shù)引入的行為詞法。第章混合對象類類理論類的機制類的繼承混入。 最近剛剛看完了《你不知道的 JavaScript》上卷,對 JavaScript 有了更進一步的了解。 《你不知道的 JavaScript》上卷由兩部...
摘要:每一條被記錄,都需要捕捉到前一狀態(tài)和后一狀態(tài)的快照。然而,在上面的例子中中的異步函數(shù)中的回調讓這不可能完成因為當觸發(fā)的時候,回調函數(shù)還沒有被調用,不知道什么時候回調函數(shù)實際上被調用實質上任何在回調函數(shù)中進行的狀態(tài)的改變都是不可追蹤的。 前言 之前幾個項目中,都多多少少碰到一些組件之間需要通信的地方,而因為種種原因,event bus 的成本反而比vuex還高, 所以技術選型上選用了 v...
摘要:每一條被記錄,都需要捕捉到前一狀態(tài)和后一狀態(tài)的快照。然而,在上面的例子中中的異步函數(shù)中的回調讓這不可能完成因為當觸發(fā)的時候,回調函數(shù)還沒有被調用,不知道什么時候回調函數(shù)實際上被調用實質上任何在回調函數(shù)中進行的狀態(tài)的改變都是不可追蹤的。 前言 之前幾個項目中,都多多少少碰到一些組件之間需要通信的地方,而因為種種原因,event bus 的成本反而比vuex還高, 所以技術選型上選用了 v...
閱讀 644·2023-04-25 15:49
閱讀 3099·2021-09-22 15:13
閱讀 1237·2021-09-07 10:13
閱讀 3467·2019-08-29 18:34
閱讀 2556·2019-08-29 15:22
閱讀 499·2019-08-27 10:52
閱讀 677·2019-08-26 18:27
閱讀 3009·2019-08-26 13:44