call()、apply()、bind()

bind()方法會建立一個新函數,當這個新函數被調用時,它的this值是傳遞給bind()的第一個參數, 它的參數是bind()的其餘參數和其本來的參數.javascript

apply() 與 call() 很是類似,不一樣之處在於提供參數的方式:java

call() 方法接受的是若干個參數的列表
算法

apply() 方法接受的是一個包含多個參數的數組。apply 可使用數組字面量,如 fun.apply(this, ['eat', 'bananas']),或數組對象,如  fun.apply(this, new Array('eat', 'bananas'))。數組


 

call() 方法在使用一個指定的this值和若干個指定的參數值的前提下調用某個函數或方法.瀏覽器

語法閉包

fun.call(thisArg[, arg1[, arg2[, ...]]])

參數

thisArg:
fun函數運行時指定的this須要注意的是,指定的this值並不必定是該函數執行時真正的this值,若是這個函數處於非嚴格模式下,則指定爲nullundefinedthis值會自動指向全局對象(瀏覽器中就是window對象),同時值爲原始值(數字,字符串,布爾值)的this會指向該原始值的自動包裝對象。
arg1, arg2, ...
指定的參數列表。

示例

使用call方法調用父構造函數

在一個子構造函數中,你能夠經過調用父構造函數的 call 方法來實現繼承相似於Java中的寫法。下例中,使用 Food 和 Toy 構造函數建立的對象實例都會擁有在 Product 構造函數中添加的 name 屬性和 price 屬性,但 category 屬性是在各自的構造函數中定義的。app

function Product(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' +
                      this.name + ' with a negative price'); } } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } //等同於
function Food(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' +
                this.name + ' with a negative price'); } this.category = 'food'; } //function Toy 同上
function Toy(name, price) { Product.call(this, name, price); this.category = 'toy'; } var cheese = new Food('feta', 5); var fun = new Toy('robot', 40);

使用call方法調用匿名函數

在下例中的for循環體內,咱們建立了一個匿名函數,而後經過調用該函數的call方法,將每一個數組元素做爲指定的this值執行了那個匿名函數。這個匿名函數的主要目的是給每一個數組元素對象添加一個print方法,這個print方法能夠打印出各元素在數組中的正確索引號。固然,這裏不是必須得讓數組元素做爲this值傳入那個匿名函數(普通參數就能夠),目的是爲了演示call的用法。dom

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); }

使用call方法調用函數而且指定上下文的'this'

在下面的例子中,當調用 greet 方法的時候,該方法的 this 值會綁定到 對象。函數

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

apply() 方法在指定 this 值和參數(參數以數組或類數組對象的形式存在)的狀況下調用某個函數。oop

語法
fun.apply(thisArg[, argsArray])

thisArg( 和call同樣 )
argsArray :
一個數組或者類數組對象,其中的數組元素將做爲單獨的參數傳給 fun 函數。若是該參數的值爲null 或 undefined,則表示不須要傳入任何參數。從ECMAScript 5 開始可使用類數組對象。

可使用 arguments 對象做爲 argsArray 參數。 arguments 是一個函數的局部變量。 它能夠被用做被調用對象的全部未指定的參數。 這樣,你在使用apply函數的時候就不須要知道被調用對象的全部參數。 你可使用arguments來把全部的參數傳遞給被調用對象。 被調用對象接下來就負責處理這些參數。

從 ECMAScript 第5版開始,可使用任何種類的類數組對象,就是說只要有一個 length 屬性和[0...length) 範圍的整數屬性。例如如今可使用 NodeList 或一個本身定義的相似 {'length': 2, '0': 'eat', '1': 'bananas'} 形式的對象。

須要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受類數組對象。若是傳入類數組對象,它們會拋出異常。

示例

使用apply來連接構造器

你可使用apply來給一個對象連接構造器,相似於Java. 在接下來的例子中咱們會建立一個叫作construct的全局的Function函數,來使你可以在構造器中使用一個類數組對象而非參數列表。

Function.prototype.construct = function (aArgs) { var oNew = Object.create(this.prototype); this.apply(oNew, aArgs); return oNew; };

注意: 上面使用的Object.create()方法相對來講比較新。另外一種可選的方法是使用閉包,請考慮以下替代方法:

Function.prototype.construct = function(aArgs) { var fConstructor = this, fNewConstr = function() { fConstructor.apply(this, aArgs); }; fNewConstr.prototype = fConstructor.prototype; return new fNewConstr(); };

案例:

function MyConstructor () { for (var nProp = 0; nProp < arguments.length; nProp++) { this["property" + nProp] = arguments[nProp]; } } var myArray = [4, "Hello world!", false]; var myInstance = MyConstructor.construct(myArray); console.log(myInstance.property1); // logs "Hello world!"
console.log(myInstance instanceof MyConstructor); // logs "true"
console.log(myInstance.constructor);              // logs "MyConstructor"

注意: 這個非native的Function.construct方法沒法和一些native構造器(例如Date)一塊兒使用。 在這種狀況下你必須使用Function.bind方法(例如,想象有以下一個數組要用在Date構造器中: [2012, 11, 4];這時你須要這樣寫: new (Function.prototype.bind.apply(Date, [null].concat([2012, 11, 4])))()– -不管如何這不是最好的實現方式而且也許不應用在任何生產環境中).

使用apply和內置函數

聰明的apply用法容許你在某些原本須要寫成遍歷數組變量的任務中使用內建的函數。在接下里的例子中咱們會使用Math.max/Math.min來找出一個數組中的最大/最小值。

/* min/max number in an array */
var numbers = [5, 6, 2, 3, 7]; /* using Math.min/Math.max apply */
var max = Math.max.apply(null, numbers); /* This about equal to Math.max(numbers[0], ...) or Math.max(5, 6, ..) */
var min = Math.min.apply(null, numbers); /* vs. simple loop based algorithm */ max = -Infinity, min = +Infinity; for (var i = 0; i < numbers.length; i++) { if (numbers[i] > max) max = numbers[i]; if (numbers[i] < min) min = numbers[i]; }

可是小心:若是用上面的方式調用 apply, 你極可能會遇到方法參數個數越界的問題. 當你對一個方法傳入很是多的參數 (好比超過1W多個參數) 時, 就很是有可能會致使越界問題, 這個臨界值是根據不一樣的 JavaScript 引擎而定的 (JavaScript 核心中已經作了硬編碼  參數個數限制在65536),由於這個限制(實際上也是任何用到超大棧空間的行爲的天然表現)是未指定的. 有些引擎會拋出異常.  更糟糕的是其餘引擎會直接限制傳入到方法的參數個數,致使參數丟失. (舉個例子: 若是某個引擎限制了方法參數最多爲4個 [實際真正的參數個數限制固然要高得多了, 這裏只是打個比方], 上面的代碼中, 真正經過 apply傳到目標方法中的參數爲 5, 6, 2, 3, 而不是完整的 numbers 數組.) 若是你的參數數組可能很是大, 那麼推薦使用下面這種策略來處理: 將參數數組切塊後循環傳入目標方法:

function minOfArray(arr) { var min = Infinity; var QUANTUM = 32768; for (var i = 0, len = arr.length; i < len; i += QUANTUM) { var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len))); min = Math.min(submin, min); } return min; } var min = minOfArray([5, 6, 2, 3, 7]);

在"monkey-patching"中使用apply

Apply能夠做爲monkey-patch一個Firefox或JS庫內建函數的最好方式。對於someobject.foo 函數,你能夠用一種旁門左道的方式來修改這個函數,像這樣:

var originalfoo = someobject.foo; someobject.foo = function() { //在調用函數前幹些什麼
 console.log(arguments); //像正常調用這個函數同樣來進行調用:
  originalfoo.apply(this,arguments); //在這裏作一些調用以後的事情。
}

語法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

參數

thisArg
當綁定函數被調用時,該參數會做爲原函數運行時的 this 指向。當使用new 操做符調用綁定函數時,該參數無效。
arg1, arg2, ...
當綁定函數被調用時,這些參數加上綁定函數自己的參數會按照順序做爲原函數運行時的參數。

返回值

      返回由指定的this值和初始化參數改造的原函數拷貝

 

描述

 
bind() 函數會建立一個新函數(稱爲綁定函數),新函數與被調函數(綁定函數的目標函數)具備相同的函數體(在 ECMAScript 5 規範中內置的call屬性)。當目標函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。一個綁定函數也能使用new操做符建立對象:這種行爲就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。
 
 
 

示例

 

建立綁定函數

 

bind() 最簡單的用法是建立一個函數,使這個函數不論怎麼調用都有一樣的 this 值。JavaScript新手常常犯的一個錯誤是將一個方法從對象中拿出來,而後再調用,但願方法中的 this 是原來的對象。(好比在回調中傳入這個方法。)若是不作特殊處理的話,通常會丟失原來的對象。從原來的函數和原來的對象建立一個綁定函數,則能很漂亮地解決這個問題:

this.x = 9; var module = { x: 81, getX: function() { return this.x; } }; module.getX(); // 返回 81

var retrieveX = module.getX; retrieveX(); // 返回 9, 在這種狀況下,"this"指向全局做用域

// 建立一個新函數,將"this"綁定到module對象 // 新手可能會被全局的x變量和module裏的屬性x所迷惑
var boundGetX = retrieveX.bind(module); boundGetX(); // 返回 81

偏函數

bind()的另外一個最簡單的用法是使一個函數擁有預設的初始參數。這些參數(若是有的話)做爲bind()的第二個參數跟在this(或其餘對象)後面,以後它們會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的後面。

 
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]
 

配合 setTimeout

在默認狀況下,使用 window.setTimeout() 時,this 關鍵字會指向 window (或全局)對象。當使用類的方法時,須要 this 引用類的實例,你可能須要顯式地把 this 綁定到回調函數以便繼續使用實例。

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'方法

做爲構造函數使用的綁定函數

警告 :這部分演示了 JavaScript 的能力而且記錄了 bind() 的超前用法。如下展現的方法並非最佳的解決方案且可能不該該用在任何生產環境中。

天然而然地,綁定函數適用於用new操做符 new 去構造一個由目標函數建立的新的實例。當一個綁定函數是用來構建一個值的,原來提供的 this 就會被忽略。然而, 原先提供的那些參數仍然會被前置到構造函數調用的前面。

function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function() { return this.x + ',' + this.y; }; var p = new Point(1, 2); p.toString(); // '1,2'

var emptyObj = {}; var YAxisPoint = Point.bind(emptyObj, 0/*x*/); // 如下這行代碼在 polyfill 不支持, // 在原生的bind方法運行沒問題: //polyfill的bind方法若是加上把bind的第一個參數,即新綁定的this執行Object()來包裝爲對象,Object(null)則是{},
  那麼也能夠支持)
var YAxisPoint = Point.bind(null, 0/*x*/); var axisPoint = new YAxisPoint(5); axisPoint.toString(); // '0,5' axisPoint instanceof Point; // true axisPoint instanceof YAxisPoint; // true new Point(17, 42) instanceof YAxisPoint; // true

你知道不須要作特別的處理就能夠用new操做符 new 建立一個綁定函數。必然地,你須要知道不須要作特別處理就能夠建立一個能夠被直接調用的綁定函數,即便你更但願綁定函數是用new操做符 new來調用。

// 這個例子能夠直接在你的 javascript 控制檯運行 // ...接着上面的代碼繼續(譯註:

// 仍然能做爲一個普通函數來調用 // (即便一般來講這個不是被指望發生的)
YAxisPoint(13); emptyObj.x + ',' + emptyObj.y;   // '0,13'

若是你但願一個綁定函數只支持使用new操做符 new,或者只能直接調用它,那麼模板函數必須強制執行那限制。

快捷調用

在你想要爲一個須要特定的 this 值的函數建立一個捷徑(shortcut)的時候,bind() 方法也很好用。

你能夠用 Array.prototype.slice 來將一個相似於數組的對象(array-like object)轉換成一個真正的數組,就拿它來舉例子吧。你能夠建立這樣一個捷徑:

 
var slice = Array.prototype.slice; // ...
 slice.apply(arguments);
 

用 bind() 可使這個過程變得簡單。在下面這段代碼裏面,slice 是 Function.prototype的 call() 方法的綁定函數,而且將 Array.prototype 的 slice() 方法做爲 this 的值。這意味着咱們壓根兒用不着上面那個 apply() 調用了。

// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(unboundSlice); // ...
 slice(arguments);

Polyfill(兼容舊瀏覽器)

bind 函數在 ECMA-262 第五版才被加入;它可能沒法在全部瀏覽器上運行。你能夠部份地在腳本開頭加入如下代碼,就能使它運做,讓不支持的瀏覽器也能使用 bind() 功能。

if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }

上述算法和實際的實現算法還有許多其餘的不一樣 (儘管可能還有其餘不一樣之處,卻沒有那個必要去窮盡):

  • 這部分實現依賴於Array.prototype.slice() Array.prototype.concat(), Function.prototype.call()這些原生方法。
  • 這部分實現建立的函數的實現並無caller 以及會在 get,set或者deletion上拋出TypeError錯誤的 arguments 屬性這兩個不可改變的「毒藥」 。(假如環境支持{jsxref("Object.defineProperty")}}, 或者實現支持__defineGetter__ and__defineSetter__ 擴展)
  • 這部分實現建立的函數有 prototype 屬性。(正確的綁定函數沒有的)
  • 這部分實現建立的綁定函數全部的 length 屬性並非同ECMA-262標準一致的:它的 length 是0,而在實際的實現中根據目標函數的 length 和預先指定的參數個數可能會返回非零的 length。
相關文章
相關標籤/搜索