在 JavaScript 中 this
實際上是一顆語法糖,可是這糖有毒。this
致命的地方在於它的指向每每不能直觀肯定。但願下面能夠一步步去掉有毒的糖衣。安全
f.call(thisVal, ...args)
指定 this
調用函數的方式有三種,用 Function.prototype.call
調用能夠指定 this
:閉包
定義 function f(...args){/*...*/}
app
調用 f.call(thisVal, ...args);
框架
例一函數
function greet(){ console.log('Hello, ' + this); } // 手動指定 `greet` 中的 `this`: greet.call('ngolin'); // Hello, ngolin
例二this
function whoAreYou(){ console.log("I'm " + this.name); } whoAreYou.call({name: 'Jane'}); // I'm Jane
this
自動指定先接受函數 f
的正確調用方式是 f.call(thisVal, ...args);
, 而後就能夠把 f(...args);
理解成語法糖。prototype
可是不用 f.call(thisVal, ...args)
, this
怎樣動態指定?code
1、函數(function)對象
// 1. 在非嚴格模式下:window f(); // 解糖爲 f.call(window); // 2. 但在嚴格模式下:undefined f(1, 2, 3); // 解糖爲 f.call(undefined, 1, 2, 3);
1、方法(method)ip
// 不管是在嚴格仍是非嚴格模式: obj.m(1, 2, 3); // 解糖爲 obj.m.call(obj, 1, 2, 3); obj1.obj2.m(...args); // obj1.obj2.m.call(obj1.obj2, ...args); obj1.obj2....objn.m(); // obj1.obj2....objn.m.call(obj1.obj2....objn);
經過上面的例子,分別演示了函數 f(..args)
和方法 obj1.obj2....objn.m(..args)
怎樣自動指定 this
.
嚴格區分函數(function)和方法(method)這兩個概念有利於清晰思考,由於它們在綁定 this
時發生的行爲徹底不同。同時函數和方法能夠相互賦值(轉換),在賦值先後,惟一發生變化的是綁定 this
的行爲(固然這種變化在調用時纔會體現)。下面先看函數轉方法,再看方法轉函數。
函數聲明(function f(){}
)和函數表達式(var f = function(){};
)有一些微妙的區別,可是兩種方式在調用時綁定this
行爲徹底同樣,下面在嚴格模式下以函數表達式爲例:
var f = function(){ console.log(this.name); }; var obj1 = { name: 'obj 1', getName: f; }; var obj2 = { name: 'obj 2', getName: f; }; // 函數 `f` 轉方法 `obj1.getName` obj1.getName();// 'obj 1' => obj1.getName.call(obj1) // 不認爲函數轉方法 obj2.getName.call(obj1);// 'obj 1'(不是 'obj 2')
將函數轉成方法一般不太容易出錯,由於起碼在方法中 this
可以有效地指向一個對象。函數轉成方法是一個模糊的說法,實際上能夠這樣理解:
JavaScript 不能定義一個函數,也不能定義一個方法,是函數仍是方法,要等到它執行才能肯定;當把它當成函數執行,它就是函數,當把它當成方法執行,它就是方法。因此只能說執行一個函數和執行一個方法。\
\
這樣理解可能有些極端,可是它可能有助於避免一些常見的錯誤。由於關係到this
怎樣綁定,重要的是在哪裏調用(好比在obj1
,obj2
... 上調用)以及怎樣調用(好比以f()
,f.call()
... 的方式),而不是在哪裏定義。
可是,爲了表達的方便,這裏仍然會使用定義函數和定義方法這兩種說法。
將方法轉成函數比較容易出錯,好比:
var obj = { name: 'obj', show: function(){ console.log(this.name); } }; var _show = obj.show; _show(); // error!! => _show.call(undefined) button.onClick = obj.show; button.onClick(); // error!! => button.onClick.call(button) (function(cb){ cb(); // error!! =>cb.call(undefined) })(obj.show);
當一個對象的方法使用了 this
時,若是這個方法最後不是由這個對象調用(好比由其餘框架調用),這個方法就可能會出錯。可是有一種技術能夠將一個方法(或函數)綁定(bind)在一個對象上,從而不管怎樣調用,它都可以正常執行。
先看這個obj.getName
的例子:
var obj = { getName: function(){ return 'ngolin'; } }; obj.getName(); // 'ngolin' obj.getName.call(undefined); // 'ngolin' obj.getName.call({name: 'ngolin'}); // 'ngolin' var f = obj.getName; f(); // 'ngolin' (function(cb){ cb(); // 'ngolin' })(obj.getName);
上面的例子之因此能夠成功是由於 obj.getName
根本沒有用到 this
, 因此 this
指向什麼對 obj.getName
都沒有影響。
這裏有一種技術把使用 this
的方法轉成不使用 this
的方法,就是建立兩個閉包(即函數),第一個閉包將方法(method)和對象(obj)捕獲下來並返回第二個閉包,而第二個閉包用於調用並返回 obj.method.call(obj);
. 下面一步步實現這種技術:
第一步 最簡單的狀況下:
function method(){ obj.method.call(obj); } method(); // correct, :))
存在的缺陷:
obj.method
obj.method
,好比 obj.method = null;
obj
,好比 obj = null
第二步 在方法有參數有返回的狀況下:
function method(a, b){ return obj.method.call(obj, a, b); } method(a, b); // correct, :))
存在的缺陷:
obj.method
第三步 一個傳遞參數更好的辦法:
function method(){ return obj.method.apply(obj, arguments); } method(a, b); // correct, :))
仍存在兩個安全隱患。
第四步 更加安全的方式:
var method = (function(){ return function(){ return obj.method.apply(obj, arguments); }; })(obj.method, obj); method(a, b); // correct, :))
第五步 抽象出一個函數,用於將方法綁定到對象上:
function bind(method, obj){ return function(){ return method.apply(obj, arguments); }; } var obj = { name: 'ngolin', getName: function(){ return this.name; } }; var method = bind(obj.getName, obj); method(); // 'ngolin'
Function.prototype.bind
這種方法很常見,後來 ECMAScript 5 就增長了 Function.prototype.bind
, 好比:
var binded = function(){ return this.name; }.bind({name: 'ngolin'}); binded(); // 'ngolin'
具體來講,Function.prototype.bind
這樣工做:
var bindedMethod = obj.method.bind(obj); // 至關於: var bindedMethod = (function(){ return function(){ return obj.method.apply(obj, arguments); }; })(obj.method, obj);
更多使用 Function.prototype.bind
的例子:
var f = obj.method.bind(obj); button.onClick = obj.method.bind(obj); document.addEventListener('click', obj.method.bind(obj));
一 在定義對象時有沒有 this
?
obj = { firstName: 'First', lastName: 'Last', // `fullName` 能夠獲得預期結果嗎? fullName: this.firstName + this.lastName } // 或者: function makePoint(article){ if(article.length <= 144) return article; return article.substr(0, 141) + '...'; } obj = { fulltext: '...a long article go here...', // `abstract` 呢? abstract: makePoint(this.fulltext) }
二 在方法內的 this
都是同一對象嗎?
obj = { count: 3, field: 'field', method: function(){ function repeat(){ if(this.count > 100){ return this.field.repeat(this.count % 100); } this.field.repeat(this.count); }.bind(this); // 這個呢? return repeat(); } }