在JavaScript中調用一個函數將暫停當前函數的執行,傳遞控制權和參數給新函數。除了聲明時定義形參,每一個函數接收兩個附加的參數:this和arguments。參數this在面向對象中很是重要,它取決於調用的模式。在JavaScript中共有四種**調用模式:**方法調用模式、函數調用模式、構造器調用模式、和apply(),call()方法調用模式。這些模式在如何初始化關鍵參數this存在差別。本文首先要提到的是this,拋開this單獨去說這些方法是沒有意義的。而後是如何妙用call,apply,bind這些方法去改變this的指向。javascript
this在不一樣模式下的意義; 借雞下蛋之妙用call,apply; 深刻理解bind函數; this在不一樣模式下的意義:java
console.log(this.document === document); // true
// 在瀏覽器中,全局對象爲 window 對象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
複製代碼
//直接調用
function f1(){
return this;
}
f1() === window; // true
//this的值不是由函數調用設定。由於代碼不是在嚴格模式下執行,this 的值老是一個對象且默認爲全局對象
function f2(){
"use strict"; // 這裏是嚴格模式
return this;
}
f2() === undefined; // true
//在嚴格模式下,this 是在進入運行環境時設置的。若沒有定義,this的值將維持undefined狀態。也可能設置成任意值。
複製代碼
var o = {
prop: 38,
f: function() {
return this.prop;
}
};
console.log(o.f()); // logs 38
複製代碼
注意,在何處或者如何定義調用函數徹底不會影響到this的行爲。在上一個例子中,咱們在定義o的時候爲其成員f定義了一個匿名函數。可是,咱們也能夠首先定義函數而後再將其附屬到o.f。這樣作this的行爲也一致:數組
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
複製代碼
這說明this的值只與函數 f 做爲 o 的成員被調用有關係。 相似的,this 的綁定只受最靠近的成員引用的影響。在下面的這個例子中,咱們把一個方法g看成對象o.b的函數調用。在此次執行期間,函數中的this將指向o.b。事實上,這與對象自己的成員沒有多大關係,最靠近的引用纔是最重要的。瀏覽器
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // logs 42
複製代碼
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
複製代碼
在這個例子中,對象p沒有屬於它本身的f屬性,它的f屬性繼承自它的原型。可是這對於最終在o中找到f屬性的查找過程來講沒有關係;查找過程首先從p.f的引用開始,因此函數中的this指向p。也就是說,由於f是做爲p的方法調用的,因此它的this指向了p。這是JavaScript的原型繼承中的一個有趣的特性。 5. getter 與 setter 中的 this 再次,相同的概念也適用時的函數做爲一個 getter 或者 一個setter調用。做爲getter或setter函數都會綁定 this 到從設置屬性或獲得屬性的那個對象。閉包
function modulus(){
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re: 1,
im: -1,
get phase(){
return Math.atan2(this.im, this.re);
}
};
Object.defineProperty(o, 'modulus', {
get: modulus, enumerable:true, configurable:true});
console.log(o.phase, o.modulus); // logs -0.78 1.4142
複製代碼
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); // logs 38
複製代碼
// 被調用時,將關聯的元素變成藍色
function bluify(e){
console.log(this === e.currentTarget); // 老是 true
// 當 currentTarget 和 target 是同一個對象是爲 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 獲取文檔中的全部元素的列表
var elements = document.getElementsByTagName('*');
// 將bluify做爲元素的點擊監聽函數,當元素被點擊時,就會變成藍色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
複製代碼
借雞下蛋之妙用call,apply: apply fun.apply(thisArg[, argsArray])方法在指定 this 值和參數(參數以數組或類數組對象的形式存在)的狀況下調用某個函數。app
在調用一個存在的函數時,你能夠爲其指定一個 this 對象。 this指當前對象,也就是正在調用這個函數的對象。使用apply,你能夠只寫一次這個方法而後在另外一個對象中繼承它,而不用在新對象中重複寫該方法。apply 與 call() 很是類似,不一樣之處在於提供參數的方式。apply 使用參數數組而不是一組參數列表。apply 可使用數組字面量.你也可使用 arguments 對象做爲 argsArray 參數。arguments 是一個函數的局部變量。 它能夠被用做被調用對象的全部未指定的參數。 這樣,你在使用apply函數的時候就不須要知道被調用對象的全部參數。 你可使用arguments來把全部的參數傳遞給被調用對象。 被調用對象接下來就負責處理這些參數。dom
Function.prototype.construct = function (aArgs) {
var oNew = Object.create(this.prototype);
this.apply(oNew, aArgs);
return oNew;
};
//另外一種可選的方法是使用閉包
Function.prototype.construct = function(aArgs) {
var fConstructor = this, fNewConstr = function() {
fConstructor.apply(this, aArgs);
};
fNewConstr.prototype = fConstructor.prototype;
return new fNewConstr();
};
複製代碼
var animals = [
{species: 'Lion', name: 'King'},
{species: 'Whale', name: 'Fail'}
];
for (var i = 0; i < animals.length; i++) {
(function (i) {
this.print = function () {
console.log('#' + i + ' ' + this.species + ': ' + this.name);
}
this.print();
}).call(animals[i], i);
}
複製代碼
function greet() {
var reply = [this.person, 'Is An Awesome', this.role].join(' ');
console.log(reply);
}
var i = {
person: 'Douglas Crockford', role: 'Javascript Developer'
};
greet.call(i); // Douglas Crockford Is An Awesome Javascript Developer
當一個函數的函數體中使用了this關鍵字時,經過全部函數都從Function對象的原型中繼承的call()方法和apply()方法調用時,它的值能夠綁定到一個指定的對象上。
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
// The first parameter is the object to use as 'this', subsequent parameters are passed as
// arguments in the function call
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// The first parameter is the object to use as 'this', the second is an array whose
// members are used as the arguments in the function call
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
複製代碼
使用 call 和 apply 函數的時候要注意,若是傳遞的 this 值不是一個對象,JavaScript 將會嘗試使用內部 ToObject 操做將其轉換爲對象。所以,若是傳遞的值是一個原始值好比 7 或 'foo' ,那麼就會使用相關構造函數將它轉換爲對象,因此原始值 7 經過new Number(7)被轉換爲對象,而字符串'foo'使用 new String('foo') 轉化爲對象,例如函數
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
複製代碼
bind函數 fun.bind(thisArg[, arg1[, arg2[, ...]]])bind() 函數會建立一個新函數(稱爲綁定函數),新函數與被調函數(綁定函數的目標函數)具備相同的函數體(在 ECMAScript 5 規範中內置的call屬性)。當目標函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。一個綁定函數也能使用new操做符建立對象:這種行爲就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。ui
his.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX(); // 9, because in this case, "this" refers to the global object
// Create a new function with 'this' bound to module
//New programmers (like myself) might confuse the global var getX with module's property getX
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
複製代碼
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);
var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
複製代碼
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒鐘後, 調用'declare'方法
複製代碼
var slice = Array.prototype.slice;
// ...
slice.apply(arguments);
複製代碼
用 bind() 可使這個過程變得簡單。在下面這段代碼裏面,slice 是 Function.prototype 的 call() 方法的綁定函數,而且將 Array.prototype 的 slice() 方法做爲 this 的值。這意味着咱們壓根兒用不着上面那個 apply() 調用了。this
// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
// ...
slice(arguments);
複製代碼
ECMAScript 5 引入了 Function.prototype.bind。調用f.bind(someObject)會建立一個與f具備相同函數體和做用域的函數,可是在這個新函數中,this將永久地被綁定到了bind的第一個參數,不管這個函數是如何被調用的。
function f(){
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
複製代碼