不看不知道,一看嚇一跳,已經整整一個月沒有更新 underscore 源碼解讀系列文章了。前面咱們已經完成了 Object ,Array,Collection 上的擴展方法的源碼剖析,本文開始來解讀 Function 上的擴展方法。javascript
完整的 underscore 源碼解讀系列文章請移步 https://github.com/hanzichi/u...,以爲還闊以的話,給個 star 鼓勵下樓主唄 ^_^html
Ok,今天要講的正是 bind。關於 bind,能夠先移步樓主之前寫的文章 ECMAScript 5(ES5) 中 bind 方法簡介備忘,有個基本的概念。java
bind() 方法會建立一個新函數,當這個新函數被調用時,它的 this 值是傳遞給 bind() 的第一個參數, 它的參數是 bind() 的其餘參數和其本來的參數。git
語法是這樣樣子的:github
fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg 當綁定函數被調用時,該參數會做爲原函數運行時的 this 指向。當使用 new 操做符調用綁定函數時,該參數無效。canvas
arg1, arg2, ... (可選)當綁定函數被調用時,這些參數加上綁定函數自己的參數會按照順序做爲原函數運行時的參數。數組
bind 的第一個參數會做爲原函數運行時的 this 指向,很少說;而第二個開始的參數是可選的,當綁定函數被調用時,這些參數加上綁定函數自己的參數會按照順序做爲原函數運行時的參數。怎麼理解?閉包
function fn(a, b, c) { return a + b + c; } var _fn = fn.bind(null, 10); var ans = _fn(20, 30); // 60
fn 函數須要三個參數,_fn 函數將 10 做爲默認的第一個參數,因此只須要傳入兩個參數便可,若是你不當心傳入了三個參數,放心,也只會取前兩個。app
function fn(a, b, c) { return a + b + c; } var _fn = fn.bind(null, 10); var ans = _fn(20, 30, 40); // 60
這有啥用呢?若是某些函數,前幾個參數已經 「內定」 了,咱們即可以用 bind 返回一個新的函數。也就是說,bind() 能使一個函數擁有預設的初始參數。這些參數(若是有的話)做爲 bind() 的第二個參數跟在 this 後面,以後它們會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的後面。dom
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]
使用 bind 返回的結果仍是個 function,是個 function 就能夠被 new 運算符調用,那麼結果呢?規範中說的很清楚了,當使用 new 操做符調用綁定函數時,bind 的第一個參數無效。
function Person(name, age) { this.name = name; this.age = age; } var _Person = Person.bind({}); var p = new _Person('hanzichi', 30); // Person {name: "hanzichi", age: 30}
通常咱們不會去這麼用,可是若是要寫個 bind 的 polyfill(http://caniuse.com/#search=bind),仍是須要考慮用 new 調用的狀況。
咱們也能夠設置默認值(參考上一小節),原先提供的那些參數仍然會被前置到構造函數調用的前面。
function Person(name, age) { this.name = name; this.age = age; } var _Person = Person.bind(null, 'hanzichi'); var p = new _Person(30); // Person {name: "hanzichi", age: 30}
何時容易丟失 this 指向?恩,setTimeout 是一個場景,很容易把 this 指向 window,固然,setInterval 也是同樣。當使用對象的方法時,須要 this 引用對象,你可能須要顯式地把 this 綁定到回調函數以便繼續使用對象。
var canvas = { render: function() { this.update(); this.draw(); }, update: function() { // ... }, draw: function() { // ... } }; window.setInterval(canvas.render, 1000 / 60);
用 canvas 寫特效或者作遊戲時常常會碰到相似的問題。上面的代碼是有問題的,render 方法中的 this 其實被指向了 window!咱們能夠用 bind,顯式地把 this 綁定到回調函數以便繼續使用該對象。
window.setInterval(canvas.render.bind(canvas), 1000);
相似的狀況還有 dom 的事件監聽,一不當心可能 this 就被指向了 dom 元素。能夠參考下之前作 bigrender 時寫的這部分代碼 https://github.com/hanzichi/h...。
bind 還能作一些有意思的事情。
一般來講,將一個類數組轉爲數組,咱們會用 slice(ie9- 不支持)。參考 https://github.com/hanzichi/u...
var slice = Array.prototype.slice; // slice.apply(arguments); // slice(arguments, 1);
bind 能讓調用變的更加簡單。
// same as "slice" in the previous example var unboundSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(unboundSlice); // ... slice(arguments); // slice(arguments, 1);
再舉個相似的例子,好比說咱們要添加事件到多個節點,for 循環固然沒有任何問題,咱們還能夠 「剽竊」 forEach
方法:
Array.prototype.forEach.call(document.querySelectorAll('input[type="button"]'), function(el){ el.addEventListener('click', fn); });
更進一步,咱們能夠用 bind 將函數封裝的更好:
var unboundForEach = Array.prototype.forEach , forEach = Function.prototype.call.bind(unboundForEach); forEach(document.querySelectorAll('input[type="button"]'), function (el) { el.addEventListener('click', fn); });
一樣相似的,咱們能夠將 x.y(z) 變成 y(x,z) 的形式:
var obj = { num: 10, getCount: function() { return this.num; } }; var unboundBind = Function.prototype.bind , bind = Function.prototype.call.bind(unboundBind); var getCount = bind(obj.getCount, obj); console.log(getCount()); // 10
再舉個栗子。每隔一秒在控制檯打印 1-5,看起來是道考察閉包的經典題目。
for(var i = 1; i <= 5; i++) { !function(i) { setTimeout(function() { console.log(i); }, i * 1000); }(i); }
ES6 下能用 let
:
for(let i = 1; i <= 5; i++) { setTimeout(function() { console.log(i); }, i * 1000); }
也能夠用 bind,瞬間逼格提高:
for(var i = 1; i <= 5; i++) { setTimeout(console.log.bind(console, i), i * 1000); }
關於 bind 的介紹就到這裏,下一篇文章將結合 underscore 來說講如何實現一個 bind 的 polyfill。