看過[阮一峯]()的關於 this 的教程,講了不少比較好的例子,但沒有對其本質的東西解釋清楚,並且部分例證存在問題;因而,打算重寫本章節,從this
的本質入手;javascript
本文爲做者的原創做品,轉載需註明出處;html
this
能夠理解爲一個指針,指向調用對象;java
官網定義編程
先來看第一段官方的解釋,windows
In JavaScript, as in most object-oriented programming languages,
this
is a special keyword that is used within methods to refer to the object on which a methodis being invoked
. The value of this is determined using a simple series of steps:數組
- If the function is invoked using
Function.call
orFunction.apply
, this will be set to the first argument passed to call/apply. If the first argument passed to call/apply is null or undefined, this will refer to the global object (which is the window object in Web browsers).- If the function being invoked was created using
Function.bind
, this will be the first argument that was passed to bind at the time the function was created.- If the function is
being invoked
as a method ofan object
, this will refer to that object.- Otherwise, the function is being invoked as a
standalone function
not attached to any object, and this will refer to the global object.
大體翻譯以下,this
是這麼一個特殊的關鍵字,它是用來指向一個當前正在被調用( a being invoked )方法的調用對象的;( 等等,這句話其實隱藏了一個很是關鍵的信息,那就是this
是在運行期
生效的,怎麼生效的?在運行期
,this
被賦值,將某個對象賦值給this
,與聲明期
無關,也就是說,this
是運行期相關的 );this
的賦值場景,概括起來,分爲以下四種狀況,瀏覽器
Function.call
或者Function.apply
調用執行.... bla..bla..Function.bind
... bla...bla關聯調用
),在運行期
,會將該 object 的引用賦值給該方法的this
。obj.func()
,這個時候,func()
方法內部的this
將會被賦值爲obj
對象的引用,也就是指向obj
;獨立方法
被調用(is being invoked as a standalone function
not attached to any object ),那麼該方法內部的this
將會被賦值爲全局對象(在瀏覽器端就是 windows )獨立方法 ( standalone function )
?在運行期,若是func
方法被obj
關聯調用的,既是經過obj.func()
的方式,那麼它就不是standalone
的;若是是直接被調用,沒有任何對象關聯,既是經過func()
調用,那麼這就是standalone
的。官網定義 2閉包
再來看另一句很是精煉的描述,來加深理解app
The this keyword is relative to the execution context, not the declaration context.
this
關鍵字與運行環境
有關而與聲明環境
無關;(補充,而做用域鏈
和閉包
是在函數的聲明期
建立的,參考建立時機)函數
補充,是如何與函數的運行期
相關的,參考this 指針運行時賦值
法則 #3 和 #4,大多數狀況都很是容易理解,有幾種狀況須要特別注意,
函數嵌套
須要注意的是object
對象中的函數內部再次嵌套函數的狀況,
var name = "windows"; var obj = { name:"object", f1:function(){ console.log("this: "+this.name) function f2(){ console.log("this: " + this.name) } f2(); } };
執行
> obj.f1(); this: object this: windows
能夠看到,在運行期
,被調用函數 f1() 中的this
指向 obj_,而被調用函數 _f2() 中的this
指向的是 windows ( global object );由於 f1 函數在當前的運行時
中是經過 obj.f1() 進行的關聯調用,因此,根據定義 #3,在當前的運行期間
,_f1()_ 內部的 this
是指向 obj 對象的( 經過將 obj 的引用直接賦值給 this
),而, f2 函數在運行期
是沒有與其它 object 進行關聯調用,因此,在當前的運行時期
,_f2_ 是一個 standalone
的函數,因此,根據定義 #4,在當前的運行期間
,_f2()_ 的內部this
是指向 windows 的。(注意,這裏我反覆強調當前運行期間
,是由於this
是在運行時被賦值的,因此,要特別注意的是,即便某個函數的定義不變,但在不一樣的執行環境(運行環境)中,this
是會發生變化;)
可見,要判斷this
在運行期
到底指的是什麼,並無那麼容易,可是,只要緊緊的把握好兩點,就能夠迎刃而解,
this
是運行期
相關的this
是在運行期
被賦值的,因此,它的值是在運行期動態肯定的。this
是否與其它對象關聯調用
關聯調用
指的是 javascript 的一種語法,既是調用語句顯式的寫爲obj.func()
,另外須要注意的是,_javascript_ 方法的調用不會隱式的隱含 this。只要沒有顯式的關聯調用
,那麼就是standalone
的調用,就符合法則 #4,因此,this
指向 _Global Object_。注意,this
定義中所指的Object
指的是 javascript 的 Object
類型,既是經過
var o1 = {}; var o2 = new Object(); var o3 = Object.create(Object.prototype);
這樣的方式構建出來的對象;
備註,最開始,本身有個思惟的誤區,認爲既然 javascript 一切皆爲對象,那麼this
指針是指向對象
的,那麼是否是也能夠指向Function
,Number
等對象?答案是否認的。
起初,我是按照上面的邏輯來理解的,直到當我總結到bind 是如何實現的小節後,發現Function
對象在調用方法屬性bind
的時候,bind
方法內部的this
指向的是Function
,這才恍然大悟,this
的Object
其實是能夠指向任何 javascript Object
的,包括 Object_、_Function 等。
咱們來看這樣一個例子,
var C = "王麻子"; var A = { name: '張三', describe: function () { return '姓名:'+ this.name; } }; var B = { name: '李四' }; // 執行, > A.describe(); '張三' > B.describe = A.describe; > B.describe() '李四' > var describe = A.describe; > describe(); '王麻子'
能夠看到,雖然 A.describe 方法的定義不變,可是其運行時環境發生了變化,_this_ 的指向也就發生了變化。
> B.describe = A.describe; > B.describe() '李四'
在運行時,至關於運行的是 B 的 describe 方法
> var describe = A.describe; > describe(); '王麻子'
在運行時,至關於運行的是 windows 的 describe 方法
常常寫 Java 代碼的緣由,常常會習慣性的認爲只要在對象方法裏面調用某個方法或者屬性,隱含了 this
,好比
public class Person{ String name; public String getName(){ return name; } public String getName2(){ return this.name; } }
而 Javascript 實際上並無這種隱含的表達方式;詳細驗證過程參考將函數賦值-standalone
從this 是什麼章節中,爲了方便對 #3 進行描述,我起了個名字叫作 關聯調用 ;那麼有些狀況看似是 _關聯調用_,實則否則;
咱們有一個標準的對象,定義以下,
var name = "windows"; var obj = { name: "obj", foo: function () { console.log("this: "+ this.name); } };
經過標準的 關聯調用 的方式,咱們進行以下的調用,
> obj.foo() 'this: obj'
根據法則 #3 既 關聯調用 的定義,獲得 this -> obj_;若是事事都如此的簡單,如此的標準,那可就行了,總會有些讓人費解的狀況,如今來看看以下的一些特殊的例子,加深對 _關聯調用 的理解。
> var fooo = obj.foo > fooo(); 'this: windows'
輸出的 windows_,既是 _this -> global object_,而不是咱們指望的 _obj_;爲何?緣由是,_obj.foo 實際上是 foo 函數的函數地址,經過 var fooo = obj.foo 將該函數的地址賦給了變量 _fooo_,那麼當執行
> fooo();
的時候,fooo()
執行的是是一個standalone
的方法,根據法則 #4,因此該方法內部的this
指向的是 Global Object_;注意,_obj.foo 表示函數 foo 的入口地址,因此,變量 fooo 等價與 foo 函數。
備註:因爲受到寫 Java 代碼習慣的緣由,很容易將這裏解釋爲默認執行的是this.fooo()
,_fooo()_ 的調用隱含了this
,所以就會想到,因爲this
指向的 Global Object_,因此這裏固然返回的就是this: windows
;可是,這樣解釋,是不對的
,由於 _Javascript 壓根沒有這種隱含this
的概念,參看用例,
var name = "windows"; var o = { name : "o", f2 : function(){ console.log( "o -> f2"); console.log( "this: "this.name ); }, f : function(){ console.log("f.this -> " + this.name); var f2 = function(){ console.log( "f -> f2"); console.log( this.name ); } f2(); // f -> f2 this.f2(); // o -> f2 } }
能夠看到,在 o.f() 函數中,若是 f2() 的調用隱含了this
,那麼 f2() 和 this.f2() 二者調用應該是等價的;可是,在實際執行過程當中,_f2()_ 和 this.f2() 執行的是兩個大相徑庭的方法,所以 f2() ≠ this.f2()_,因此 _f2() 並無隱示的表示爲 _this.f2()_;
> (obj.foo = obj.foo)() 'this: windows'
首先,當即執行 foo 函數,而後將 foo 函數賦值給對象 obj 對象的 foo 屬性;等價於執行以下的代碼,
var name = "windows"; var obj = { name : "obj" }; (obj.foo = function () { console.log("this: " + this.name); })();
輸出,
'this: windows'
能夠看到,_this_ -> _global object_,這裏爲何指向的是 _global object_?其實這裏的當即執行過程,就是執行的以下代碼,
(function () { console.log("this: " + this.name); }());
由此能夠看出,實際上進行一個匿名函數
的當即執行;也就是說執行過程當中並無使用 關聯調用_,而是一次 _standalone 函數的自身調用,因此根據法則 #4,_this_ -> _global object_。執行完之後,將該匿名函數賦值給 _obj.foo_。
再次執行,
> obj.foo(); 'this: obj'
此次執行的過程是一次標準的 關聯調用 過程,因此根據法則 #3,_this_ -> _obj_。
> (false || obj.foo)() 'windows'
等價於執行,
(false || function () { console.log("this: " + this.name); })()
原理和函數賦值變種-匿名 standalone 函數當即執行 一致,等價於當即執行以下的匿名函數
(function () { console.log("this: " + this.name); })()
其實,把這個例子再作一個細微的更改,其中邏輯就看得更清楚了,爲 foo 函數添加一個返回值 return true
var name = "windows"; var obj ={ name: "obj", foo: function () { console.log("this: "+ this.name); return true; } };
再次執行,
> (false || obj.foo)() 'windows' true
可見,_obj.foo_ 函數執行之後,返回 _true_。上述代碼其實等價於執行以下的代碼,
(false || function () { console.log("this: " + this.name); return true; })()
var counter = { count: 0, inc: function () { 'use strict'; this.count++; } }; function callIt(callback) { callback(); } > callIt(counter.inc) TypeError: Cannot read property 'count' of undefined
能夠看到,把一個定義有this
關鍵字的函數做爲其它函數的回調函數,是危險的,由於this
在運行期
會被從新賦值,上述例子很直觀的描述了這一點,之因此報錯,是由於this
指向了 _Global Object_。要解決這樣的問題,可使用bind
,調用的時候改成
> callIt(counter.inc.bind(counter)) 1
var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ console.log(this.name); }, waitShowName : function(){ setTimeout(this.showName, 1000); } }; // 執行, > nameObj.waitShowName(); 'Tom' undefined
setTimeout(this.showName, 1000);
將 nameObj.showName 函數做爲回調函數參數傳遞給 setTimeout_;那麼爲何當 _setTimeout 執行回調的時候,_nameObj.showName_ 方法返回的是 undefined 呢?爲何不是返回全局對象對應的 name Bob_?緣由只有一個,那就是 _setTimeout 有本身的 this 對象,而它沒有 name 屬性,而在回調 showName 函數的時候,_showName_ 函數中的 this 正是 setTimeout 上下文中的 this_,而該 _this 並無定義 name 屬性,因此這裏返回 _undefined_。
var o = new Object(); o.f = function () { console.log(this === o); } o.f() // true,獲得指望的結果 this -> o
可是,若是將f
方法指定給某個click
事件,this
的指向發生了改變,
$('#button').on('click', o.f);
點擊按鈕之後,返回的是false
,是由於在執行過程當中,this
再也不指向對象o
了而改成指向了按鈕的DOM
對象了;Sounds Good,但問題是,怎麼被改動的?看了一下 jQuery 的源碼,_event.js_,摘錄重要的片斷以下,
function on( elem, types, selector, data, fn, one ) { ....... if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } ....... }
o.f 函數的地址賦值給 fn 參數,_fn_ -> origFn_,最後是經過origFn.apply( this, arguments );
來調用 _o.f 函數的,而這裏的 this 就是當前的 DOM 對象,既是這個按鈕 button_;經過這樣的方式,在執行過程當中
,經過回調函數 _$("button").on(...) 成功的將新的 this 對象 button 注入了 o.f 函數。那麼如何解決呢?參看function.prototype.apply())
的小節#3,動態綁定回調函數。
var obj = { name: '張三', times: [1, 2, 3], print: function () { this.times.forEach(function (n) { console.log(this.name); }); } }; > obj.print(); 'undefined' 'undefined' 'undefined'
這裏咱們指望的是,依次根據數組 times 的長度,輸出 obj.name 三次,可是實際運行結果是,數組雖然循環了三次,可是每次輸出都是 _undefined_,那是由於匿名函數
function(n){ console.log(this.name); }
做爲數組 times 的方法 forEach 的回調函數執行,在 forEach 方法內部該匿名函數必然
是做爲 standalone 方法執行的,因此,this
指向了 _Global Object_;
進一步,爲何「在 forEach 方法內部該匿名函數必然
是做爲 standalone 方法執行的」?爲何必然
是做爲 standalone 方法執行?是由於不能在 forEach 函數中使用 this.fn() 的方式來調用該匿名回調函數( fn 做爲參數引用該匿名回調函數 ),由於若是這樣作,在運行時期會報錯,由於在 forEach 函數的 this 對象中找不到 fn 這樣的屬性,而該 this 對象指向的是 obj.times 數組對象。所以,獲得結論「在 forEach 方法內部該匿名函數必然
是做爲 standalone 方法執行的」
解決辦法,使用 bind
obj.print = function () { this.times.forEach(function (n) { console.log(this.name); }.bind(this)); }; > obj.print() '張三' '張三' '張三'
將 obj 對象做爲 this
綁定到該匿名函數上,而後再做爲回調函數參數傳遞給 forEach 函數,這樣,在 forEach 函數中,用 standalone 的方式調用 fn 的時候,_fn_ 中的 this
指向的就是數組對象 obj 對象,這樣,咱們就能順利的輸出 obj.name 了。
有上述描述可知,this
的值在運行時
根據不一樣上下文環境有不一樣的值,所以咱們說this
的值是變化的,這就給咱們的編程帶來了麻煩,有時候,咱們指望,獲得一個固定的this
。Javascript 提供了call
、apply
以及bind
這三個方法,來固定this
的指向;這三個方法存儲在 function.prototype 域中,
總結起來,就是解決函數在調用的時候,如何解決this
動態變化的問題。
調用格式,
func.call(thisValue, arg1, arg2, ...)
第一個參數是在運行時用來賦值給 func 函數內部的 this 的。
經過f.call(obj)
的方式調用函數,在運行時,將 obj 賦值給 _this_;
var obj = {}; var f = function () { return this; }; f() === this // true f.call(obj) === obj // true
call
方法的參數是一個對象,若是參數爲 空_、_null 或者 _undefined_,則使用默認的全局對象;
var n = 123; var obj = { n: 456 }; function a() { console.log(this.n); } > a.call() 123 > a.call(null) 123 > a.call(undefined) 123 > a.call(window) 123 > a.call(obj) 456
若是call
方法的參數是一個原始值,那麼這個原始值會自動轉成對應的包裝對象,而後賦值給 this
var f = function () { return this; }; > f.call(5) [Number: 5]
call
方法能夠接受多個參數,第一個參數就是賦值給 this 的對象,
var obj = { name : 'obj' } function add(a, b) { console.log(this.name); return a + b; } > add.call(obj, 1, 2) obj 3
call
方法能夠調用對象的原生方法;
var obj = {}; obj.hasOwnProperty('toString') // false // 「覆蓋」掉繼承的 hasOwnProperty 方法 obj.hasOwnProperty = function () { return true; }; obj.hasOwnProperty('toString') // true Object.prototype.hasOwnProperty.call(obj, 'toString') // false
方法 hasOwnProperty 是對象 obj 從 Object.prototype 中繼承的方法,若是一旦被覆蓋,就不會獲得正確的結果,那麼,咱們可使用call
的方式調用原生方法,將 obj 做爲 this 在運行時調用,這樣,變通的,咱們就能夠調用 obj 對象所繼承的原生方法了。
總結起來,和call
同樣,就是解決函數在調用的時候,如何解決this
動態變化的問題。
apply
方法的做用與call
方法相似,也是改變this
指向,而後再調用該函數。惟一的區別就是,它接收一個數組做爲函數執行時的參數,使用格式以下。
func.apply(thisValue, [arg1, arg2, ...])
apply
方法的第一個參數也是this
所要指向的那個對象,若是設爲null或undefined,則等同於指定全局對象。第二個參數則是一個數組,該數組的全部成員依次做爲參數,傳入原函數。原函數的參數,在
call
方法中必須一個個添加,可是在apply
方法中,必須以數組形式添加
function f(x,y){ console.log(x+y); } f.call(null,1,1) // 2 f.apply(null,[1,1]) // 2
找出數組最大的元素
var a = [10, 2, 4, 15, 9]; Math.max.apply(null, a) // 15
將數組的空元素變爲 undefined
Array.apply(null, ["a",,"b"]) // [ 'a', undefined, 'b' ]
空元素
與undefined
的差異在於,數組的forEach
方法會跳過空元素,可是不會跳過undefined
。所以,遍歷內部元素的時候,會獲得不一樣的結果。
var a = ['a', , 'b']; function print(i) { console.log(i); } a.forEach(print) // a // b Array.apply(null, a).forEach(print) // a // undefined // b
綁定回調函數的對象
函數回調場景-2咱們看到this
被動態的更改成了 DOM 對象 _button_,這每每不是咱們所指望的,因此,咱們能夠再次綁定回調函數來固定this
,以下,
var o = new Object(); o.f = function () { console.log(this === o); } var f = function (){ o.f.apply(o); // 或者 o.f.call(o); }; $('#button').on('click', f);
這樣,咱們用 f 函數封裝原來的回調函數 o.f_,並使用apply
方法固定住this
,使其永遠指向 _object o
,這樣,就達到了this
不被動態修改的目的。
總結起來,其實就是在把函數做爲參數傳遞的時候,如何解決this
動態變化的問題。
在認識關聯調用 - 容易混淆的場景中,咱們濃墨重彩的描述了將函數賦值
之後,致使this
在運行期發生變化的種種場景,並且在編程過程中,也是很是容易致使問題的場景;那麼有沒有這麼一種機制,即使是在函數賦值
後,在運行期依然可以保護並固定住個人this
?答案是有的,那就是bind
。下面,咱們來看一個例子,
var d = new Date(); d.getTime() // 1481869925657
咱們使用語句 d.getTime() 經過對象 d 關聯調用函數 getTime()_,根據法則 #3,函數 _getTime() 內部的 this
指向的是對象 d_,而後從 _d 對象中成功獲取到了時間。可是,咱們稍加改動,將對象 d 中的函數 getTime 賦值給另一個變量,在執行呢?
var print = d.getTime; print() // Uncaught TypeError: this is not a Date object.
Wow~, 畫風突變,得不到時間了,並且還拋出了一個程序異常,好玩,你的程序所以崩潰.. 這就是this
在執行期動態變化所致使的,當咱們將函數 d.getTime 賦值給 print_,而後語句 _print() 表示將函數 getTime 做爲 standalone 的函數在運行期
調用,因此,內部的this
發生變化,指向了 _Global Object_,也所以,咱們得不到時間了,但咱們獲得一個意想不到的異常..
Ok, 別怕,孩子,bind
登場了,
var print = d.getTime.bind(d); print() // 148186992565
在 賦值過程當中_,將函數經過bind
語法綁定this
對象 _d 之後,再賦值給一個新的變量;這樣,即使 print() 再次做爲 standalone 的函數在運行期
調用,this
的指向也再也不發生變化,而是固定的指向了對象 _d_。
if(!('bind' in Function.prototype)){ Function.prototype.bind = function(){ var fn = this; // 當前調用 bind 的當前對象 fn ( fn.bind(..) ) var context = arguments[0]; // 用來綁定 this 對象的參數 var args = Array.prototype.slice.call(arguments, 1); var fnbound = function(){ return fn.apply(context, args); } return fnbound; } }
給Function
對象的prototype
原型中新增一個屬性bind
,該bind
是一個 function 函數;這裏要特別特別注意,每次bind
調用之後,返回的是一個新的function
,
var fnbound = function(){ return fn.apply(context, args); } return fnbound;
經過 fnbound 函數套一層原函數 fn 做爲閉包,而後返回這個新的 function _fnbound_;大部分教程就是這樣介紹即止了;其實,我想問的是,爲何bind
要這麼設計,直接返回fn.apply(context, args);
不是挺好嗎?爲何還要在外面套一層新函數 _fnbound_?Ok,這裏我就來試圖解釋下緣由吧;
採用反證法,若是,咱們不套這麼一層新函數 _fubound_,看看,會怎樣?因而,咱們獲得以下的實現,
if(!('bind' in Function.prototype)){ Function.prototype.bind = function(){ var fn = this; // 當前調用 bind 的當前對象 fn ( fn.bind(..) ) var context = arguments[0]; // 用來綁定 this 對象的參數 var args = Array.prototype.slice.call(arguments, 1); return fn.apply(context, args); } }
直接返回fn.apply(context, args)
,oh,頓時,我明白了,fn.apply(...)
這是一條執行命令啊,它會當即執行 fn_,將 _fn 執行的結果返回.. 而咱們這裏的bind
的初衷只是擴充 fn 函數的行爲(既綁定this
對象),而後返回一個函數的引用
,而正式由於咱們沒法在綁定之後,直接返回原有函數的引用,因此,這裏,咱們才須要建立一個新的函數並返回這個新的函數的引用,已達到bind
的設計目的。Ok,這下總算是清楚了。
obj.print = function () { this.times.forEach(function (n) { console.log(this.name); }.bind(this)); };
可見,咱們能夠直接改匿名函數執行bind
,而後在將其賦值給某個對象;更詳細的用例參考函數回調場景 3 - 數組對象方法的回調
var altwrite = document.write; altwrite("hello");
在瀏覽器運行這個例子,獲得錯誤Uncaught ReferenceError: alwrite is not defined
,這個錯誤並無真正保留底層的緣由,真正的緣由是,_document_ 對象的 write 函數再執行的時候,內部this
指向了 Global Object
爲了解決上述問題,咱們能夠bind
document 對象,
altwrite.bind(document)("hello")
注意這裏的寫法,altwrite.bind(document)
返回的是一個Function
,因此能夠直接跟參數調用。
除了綁定this
對象意外,還能夠綁定函數中的參數,看以下的例子,
var add = function (x, y) { return x * this.m + y * this.n; } var obj = { m: 2, n: 2 }; var newAdd = add.bind(obj, 5); newAdd(5); // 20
add.bind(obj, 5);
除了綁定 add 函數的this
對象爲 obj 之外,將其固定
爲 obj 之外,還綁定了 add 函數的第一個參數 x_,並將其固定
爲 _5_;這樣,獲得的 _newAdd 函數只能接收一個參數,那就是 y 了,由於 x 已經被bind
綁定且固定了,因此能夠看到,隨後執行的語句newAdd(5)
傳遞的其實是 y 參數。
若是bind
方法的第一個參數是 null 或 _undefined_,等於將this
綁定到全局對象,函數運行時this
指向 _Global Object_。
var name = 'windows'; function add(x, y) { console.log(this.name); return x + y; } var plus = add.bind(null, 5); // 綁定了 x 參數 > plus(10) // 賦值的是 y 參數,因而執行的是 5 + 10 'windows' 15
首先,
> [1, 2, 3].push(4) 4 // 輸出新增後數組的長度
等價於
Array.prototype.push.call([1, 2, 3], 4)
第一個參數 [1, 2, 3] 綁定 push 函數的this
關鍵字,第二個參數 _4_,是須要被添加的值。
補充一下
爲何說這裏是等價的?咱們來解讀一下
> [1, 2, 3].push(4) 4 // 輸出新增後數組的長度
的執行過程,_[1, 2, 3]_ 做爲數組對象,調用其原型中的 Array.prototype.push 方法,很明顯,採用的是關聯調用
,所以 push 函數內部的 this 指向的是數組對象 _[1, 2, 3]_;而這裏,咱們經過
Array.prototype.push.call([1, 2, 3], 4)
這樣的調用方式,只是換湯不換藥,一樣是執行的數組中的原型方法 _push_,只是this
的傳遞方式不一樣而已,這裏是經過bind
直接將this
賦值爲數組對象 _[1, 2, 3]_,而不是經過以前的關聯調用
;因此,兩種調用方式是等價的。
補充完畢
再次,
call 方法調用的是 Function 對象的原型方法既 Function.prototype.call(...)_,那麼咱們再來將它 _bind 一下,看看會有什麼結果
> var push = Function.prototype.call.bind(Array.prototype.push); > push([1, 2, 3], 4); 4 // 返回數組長度 // 或者寫爲 > var a = [1, 2, 3]; > push(a, 4); 4 > a [1, 2, 3, 4]
咱們獲得了一個具有數組 push 操做的一個新的函數 push(...) ( 注: bind 每次回返回一個新的函數 );
那是爲何呢?
能夠看到,背後的核心是,
push([1, 2, 3], 4);
等價於執行
Array.prototype.push.call([1, 2, 3], 4)
因此,咱們得證實Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)
與Array.prototype.push.call([1, 2, 3], 4)
兩個函數的執行過程
是等價的( 注意,爲何比較的是執行過程等價
,由於call
函數是當即執行的,而bind
返回的是一個函數引用,因此必須比較二者的執行
過程 );其實,要證實這個問題,最直接方法就是去查看函數Function.prototype.call
的源碼,惋惜,我在官網 MDN Function.prototype.call() 上面也沒有看到源碼;那麼這裏,其實能夠作一些推理,
Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)
經過bind
,這裏返回一個新的 call 函數,該函數綁定了 Array.prototype.push Function 對象作爲其this
對象;那麼Function.prototype.call
函數內部會怎麼執行呢?我猜測應該就是執行this.apply(context, params)
之類的,this
表示的是 Array.prototype.push_,context
表示的既是這裏的數組對象 _[1, 2, 3]_, params
表示的既是這裏的參數 _4
Array.prototype.push.call([1, 2, 3], 4)
同理,由上述Function.prototype.call
函數內部的執行過程是執行this.apply(context, params)
的推斷來看,this
依然是指向的 Array.prototype.push_,context
表示的既是這裏的數組對象 _[1, 2, 3]_, params
表示的既是這裏的參數 _4_;因此,這裏的調用方式與 _Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) 的方式等價;因此,咱們得出以下結論,
Array.prototype.push.call([1, 2, 3], 4) <=> Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) <=> push([1, 2, 3], 4)
bind
方法每運行一次,就返回一個新函數
,這會產生一些問題。好比,監聽事件的時候,不能寫成下面這樣。
element.addEventListener('click', o.m.bind(o));
上面代碼中,click 事件綁定bind
方法新生成的一個匿名函數。這樣會致使沒法取消綁定,因此,下面的代碼是無效的。
element.removeEventListener('click', o.m.bind(o));
正確的方法是寫成下面這樣,使得 add 和 remove 使用的是同一個函數的引用。
var listener = o.m.bind(o); element.addEventListener('click', listener); // ... element.removeEventListener('click', listener);
使用嚴格模式
,該部分能夠參考阮一峯的教程嚴格模式,說得很是詳細;不過應用到面向對象編程裏面,主要就是爲了不this
在運行期
動態指向 _Global Object_,若是發生這類的狀況,報錯;例如
function f() { 'use strict'; this.a = 1; }; f();// 報錯,this未定義
當執行過程當中,發現函數 f 中的this
指向了 _Global Object_,則報錯。
構造函數比較特別,_javascript_ 解析過程不一樣於其它普通函數;
假如咱們有以下的構造函數,
var Person = function(name, age){ this.name = name; this.age = age; }
當 javascript 語法解析器解析到以下語句之後,
var p = new Person('張三', 35);
實際上執行的是,
function new( /* 構造函數 */ constructor, /* 構造函數參數 */ param1 ) { // 將 arguments 對象轉爲數組 var args = [].slice.call(arguments); // 取出構造函數 var constructor = args.shift(); // 建立一個空對象,繼承構造函數的 prototype 屬性 var context = Object.create(constructor.prototype); // 執行構造函數 var result = constructor.apply(context, args); // 若是返回結果是對象,就直接返回,則返回 context 對象 return (typeof result === 'object' && result != null) ? result : context; }
備註:_arguments_ 可表示一個函數中全部的參數,也就是一個函數全部參數的結合。
下面,咱們一步一步的來分析該構造函數的實現,弄清楚this
指的是什麼,
constructor
就是 Person 構造函數,
context
var context = Object.create(constructor.prototype);
經過 constructor.prototype 建立了一個新的對象,也就是 Person.prototype 的一個實例 _Person.prototype isntance_;
constructor.apply(context, args);
注意,這步很是關鍵,_context_ 做爲 constructor 構造函數的this
,因此
var Person = function(name, age){ this.name = name; this.age = age; }
中的this
在執行過程當中指向的實際上就是該 context 對象。
result
是constructor.apply(context, args);
方法調用的返回值,咱們當前用例中,_Person_ 構造函數並無返回任何東西,因此,這裏是 _null_。
return (typeof result === 'object' && result != null) ? result : context;
new
方法的最後返回值,若是 result 不爲 null_,則返回 _result 不然返回的是 context_;咱們這個用例,當初始化構造函數完成之後,返回的是 _context 既 _Person.prototype instance_,也就是構造函數中的this
指針;這也是大多數構造函數應用的場景。
var Obj = function (p) { this.p = p; }; Obj.prototype.m = function() { return this.p; };
執行,
> var o = new Obj('Hello World!'); > o.p 'Hello World!' > o.m() 'Hello World!'
說實話,當我第一次看到這個例子的時候,_o.p_ 還好理解,_o_ 就是表示構造函數 Obj 內部的this
對象,是一個經過 Object.create(Obj.prototype) 獲得的一份 Obj.prototype 的實例對象;可是,當我看到 o.m 的時候,仍是有點懵逼,_Obj.prototype_ 並非表明的this
呀,_Object.create(Obj.prototype)_ 纔是( 既 Obj.prototype instance ),因此在 Obj.prototype 上定義的 m 方法,怎麼能夠經過 o.m() 既經過 Obj.prototype instance 來調用呢?( 注意,關係 o -> Object.create(Obj.prototype) -> Obj.prototype instance -> this != Obj.prototype ) 當理解到 prototype
的涵義有,才知道,_Obj.prototype instance_ 會繼承 Obj.prototype 中的公共屬性的,因此,這裏經過 Obj.prototype 對象定義的 m 函數能夠經過 Object.prototype instance 進行調用。
本文轉載自筆者的私人博客,傷神的博客,http://www.shangyang.me/2017/...
[Javascript中this關鍵字詳解](
http://www.cnblogs.com/justan...
jQuery Fundamentals Chapter - The this keyword