不看不知道,一看吓一跳,已经整整一个月没有更新 underscore 源码解读系列文章了。前面我们已经完成了 Object ,Array,Collection 上的扩展方法的源码剖析,本文开始来解读 Function 上的扩展方法。

完整的 underscore 源码解读系列文章请移步 https://github.com/hanzichi/underscore-analysis,觉得还阔以的话,给个 star 鼓励下楼主呗 ^_^

bind 简介

Ok,今天要讲的正是 bind。关于 bind,可以先移步楼主以前写的文章 ECMAScript 5(ES5) 中 bind 方法简介备忘,有个基本的概念。

bind() 方法会创建一个新函数,当这个新函数被调用时,它的 this 值是传递给 bind() 的第一个参数, 它的参数是 bind() 的其他参数和其原本的参数。

语法是这样样子的:

fun.bind(thisArg[, arg1[, arg2[, ...]]])
  • thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
  • 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 作为默认的第一个参数,所以只需要传入两个参数即可,如果你不小心传入了三个参数,放心,也只会取前两个。

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 后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。

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]

new

使用 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}

配合 setTimeout

什么时候容易丢失 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/hanzichi.github.io/blob/master/2016/bigrender/js/bigrender.js#L179-L184

tip

bind 还能做一些有意思的事情。

通常来说,将一个类数组转为数组,我们会用 slice(ie9- 不支持)。参考 https://github.com/hanzichi/underscore-analysis/issues/14

var slice = Array.prototype.slice;

// slice.apply(arguments);
// slice.call(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);
}

2016-08-30 add:好吧,setTimeout 还能传入第三个参数,又涨姿势了 ...

for (var i = 1; i <= 5; i++) {
  setTimeout(function(num) {
    console.log(num)
  }, i * 1000, i);
}

Read more

关于 bind 的介绍就到这里,下一篇文章将结合 underscore 来讲讲如何实现一个 bind 的 polyfill。

关于 bind 你可能需要了解的知识点以及使用场景的更多相关文章

  1. Underscore _.template 方法使用详解

    为什么用「void 0」代替「undefined」 undefined 并不是保留词(reserved word),它只是全局对象的一个属性,在低版本 IE 中能被重写. 事实上,undefined ...

  2. 优雅手撕bind函数(面试官常问)

    优雅手撕bind函数 前言: 为什么面试官总爱让实现一个bind函数? 他想从bind中知道些什么? 一个小小的bind里面内有玄机? 今天来刨析一下实现一个bind要懂多少相关知识点,也方便我们将零 ...

  3. js里function的apply vs. bind vs. call

    js里除了直接调用obj.func()之外,还提供了另外3种调用方式:apply.bind.call,都在function的原型里.这3种方法的异同在stackoverflow的这个答案里说的最清楚, ...

  4. docker 系列 - Dock高阶知识点文章汇集

    docker 技术已经研究了一段时间, 基本的知识点和使用场景比较清楚了, 还有一些比较高阶的内容未曾尝试, 将来工作中如果能用到, 再深入研究吧. 这篇博文汇总了一些有用的文章链接. ======= ...

  5. 第 6 章 存储 - 039 - Data Volume 之 bind mount

    Data Volume Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统中. Data Volume 有以下特点: 1.Dat ...

  6. bind函数作用、应用场景以及模拟实现

    bind函数 bind 函数挂在 Function 的原型上 Function.prototype.bind 创建的函数都可以直接调用 bind,使用: function func(){ consol ...

  7. 面试官:能解释一下javascript中bind、apply和call这三个函数的用法吗

    一.前言    不知道大家还记不记得前几篇的文章:<面试官:能解释一下javascript中的this吗> 那今天这篇文章虽然是介绍javascript中bind.apply和call函数 ...

  8. 【数据结构】什么是AVL树

    目录 什么是AVL树 1. 什么是AVL树 2. 节点的实现 3. AVL树的调整 3.1 LL旋转 3.2 RR旋转 3.3 RL旋转 3.4 LR旋转 什么是AVL树 二叉查找树的一个局限性就是有 ...

  9. 精通Web Analytics 2.0 (4) 第二章:选择你的网络分析灵魂伴侣的最佳策略

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第二章:选择你的网络分析灵魂伴侣的最佳策略 在Web Analytics 2.0的新世界秩序中,您必须跳出"单一真理来 ...

随机推荐

  1. Java进击C#——语法之IO操作

    本章简言 上一章我们对线程同步进行讲解.了解如何去处理可能发生的脏数据.而本章就要讲有关于C#在读取IO文件的时候,常常用到的操作类.这一章的内容会比较少.但是笔者还是总结出来让读者们有一个学习的方向 ...

  2. JAVA多态

    多态是指当系统A访问系统B的服务时,系统B可以通过多种方式来提供服务,而这一切对系统A是透明的.比如动物园的饲养员能够给各种各样的动物喂食.下图显示了饲养员Feeder,食物Food和动物Animal ...

  3. 用JavaScript来实现链表LinkedList

    本文版权归博客园和作者本人共同所有,转载和爬虫请注明原文地址. 写在前面 好多做web开发的朋友,在学习数据结构和算法时可能比较讨厌C和C++,上学的时候写过的也忘得差不多了,更别提没写过的了.但幸运 ...

  4. python 数据类型---列表使用 之一

    列表的表现形式:其中的元素可以使任何数据类型,像 字符串,数字, 字典, 列表,变量 等任何类型 age = 28 name = ["Frank", "Lee" ...

  5. datatables中的Options总结(2)

    datatables中的Options总结(2) 五.datatable,列 columnDefs.targets 分配一个或多个列的列定义. columnDefs 设置列定义初始化属性. colum ...

  6. iOS 对模型对象进行归档

    归档是指一种形式的序列化,专门编写用于保存数据的任何对象都应该支持归档.使用对模型对象进行归档的技术可以轻松将复杂的对象写入文件,然后再从中读取它们. 只要在类中实现的每个属性都是标量或者都是遵循NS ...

  7. Oracle Sales Cloud:报告和分析(BIEE)小细节2——利用变量和过滤器传参(例如,根据提示展示不同部门的数据)

    在上一篇随笔中,我们建立了部门和子部门的双提示,并将部门和子部门做了关联.那么,本篇随笔我们重点介绍利用建好的双提示进行传参. 在操作之前,我们来看一个报告和分析的具体需求: [1] 两个有关联的提示 ...

  8. Google C++单元测试框架GoogleTest---AdvancedGuide(译文)下

    因为AdvancedGuide文档太长,分上下两部分,本文档接googletest--AdvancedGuide(译文)上:Google C++单元测试框架GoogleTest---AdvancedG ...

  9. React Native 之 View使用

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

  10. 网络安全——Base64编码、MD5、SHA1-SHA512、HMAC(SHA1-SHA512)哈希

    据说今天520是个好日子,为什么我想起的是502.500.404这些?还好服务器没事! 一.Base64编码 Base64编码要求把3个8位字节(3*8=24)转化为4个6位的字节(4*6=24),之 ...