前言

今天闲着无聊随便逛了逛MDN,忽而看到一个方法Function.prototype.bind(),突然发现除了使用这个方法之外都没有仔细琢磨过这个方法。于是乎,找到了kill time的事情-写博客。

基础知识简介

随便看看资料发现这玩意其实不简单,理解起来需要不少基础知识,在这里罗列一些,也算是一个总结和复习。

函数

下面这段话来自《JavaScript语言精粹》,名副其实地描述了函数的精髓。

调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数。除了声明时定义的形式参数,每个函数还接收两个附加的参数:this和arguments。参数this在面向对象编程中非常重要,他的值取决于调用的模式。在JavaScript里面一共有四种调用模式:方法调用模式、函数调用模式、构造器调用模式和apply调用模式。这些模式在如何初始化关键参数this上面存在差异。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用的时候,this被绑定到该对象。

var info = {
name: 'yuanzm',
sayName: function() {
console.log(this.name);
}
}
info.sayName(); //yuanzm
函数调用模式

当一个函数并非为一个对象的属性的时候,他就是被当做一个函数来调用的。此模式调用函数的时候,this被绑定到全局对象。这是语言设计上的一个错误。倘若语言设计正确,this应该是绑定到外部函数的this变量。

var name = "yuanzm"
var sayName = function() {
console.log(this.name);
}
sayName();// yuanzm
构造器调用模式

如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到新对象上。(JavaScript原型相关知识这里不做赘述)

function Info(name) {
this.name = name;
}
Info.prototype.sayName = function() {
console.log(this.name);
}
var info = new Info('yuanzm');
info.sayName();//yuanzm
Apply调用模式

根据MDN的定义

The apply() method calls a function with a given this value and arguments provided as an array (or an array-like object).

var numbers = [5, 6, 2, 3, 7];
var max = Math.max.apply(null, numbers);

类数组

一个类数组被定义为:

  • 具有:指向对象元素的数字索引下标以及 length 属性告诉我们对象的元素个数
  • 不具有:诸如 push 、 forEach 以及 indexOf 等数组对象具有的方法

符合上述定义的类数组是长下面这样子的:

var my_object = {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
length: 5
};

前面提到的arguments也是类数组。很多时候,处理类数组最省事的方法就是将它转化成数组。那么就涉及到一个非常有意思的话题:将类数组转换成数组。

将类数组转换成数组非常简单,调用Array自带的方法即可:

Array.prototype.slice.call(arguments);

其中call换成apply也是一样的。

简单解释一下,slice方法常规的调用方式为array.slice(start, end),会对array中的一段做浅复制,首先复制array[start], 一直复制到array[end]。前面提到过apply(或call)会切换一个函数调用的上下文,也就是Array.prototype.slice.call(arguments);手动绑定了需要操作的array为arguments,由于arguments和数组一样具有下标,所以这个方法也是可行的,因而产生了一个新的数组,这个数组具有普通数组的所有方法,同时具有arguments每一个下标对应的值。

bind

简介

前面说了这么多,主角终于登场了!不过为了凸显它的作用,还是需要抛出一段达不到我们需求的代码。

var name = 'yuan'
var info = {
name: "yuanzm",
sayName: function() {
console.log(this.name);
}
}
var sayName = info.sayName;
// 我们本身是希望得到yuanzm的,但是确得到了yuan这个结果。为什么会得到这个结果,前面的长篇大论起作用了。
// 如果是采用info.sayName()这种调用方式,符合函数的方法调用模式,函数内部的this是info对象;
// 如果令一个变量sayName为info.sayName,这个时候再调用函数,就是普通的函数调用模式了,结果自然为yuan。
sayName(); // yuan

那么我们使用bind就是希望最后得到的结果为yuanzm

现在我们可以好好介绍bind了。根据MDN的定义:

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

翻译过来就是,bind()方法会创建一个新函数,称为绑定函数.当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

他的语法是:

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

解决问题

有了bind,上述问题我们就能够得到想要的结果了:

var name = 'yuan'
var info = {
name: "yuanzm",
sayName: function() {
console.log(this.name);
}
}
var sayName = info.sayName.bind(info);
sayName(); // yuanzm

bind的用法在MDN上面描述得很详细,本文的目的也不是为了照搬MDN,所以这里有关bind使用的部分就到这儿,接下来我们去看看Javascript库中bind的实现

Prototype中的bind

很久之前Prototype的bind写法是下面这样的(注释为本人所加):

Function.prototype.bind = function(){
// bind作为Function的prototype属性,每一个函数都具有bind方法,其中的this指向调用该方法的函数(也即一个函数对象实例)
var fn = this;
// 前面说过,这个方法是为了将类数组转换成数组
var args = Array.prototype.slice.call(arguments);
// 数组的shift方法会移除数组的第一个元素,在bind方法的参数里面,第一个参数就是需要绑定this的对象
var object = args.shift();
return function(){
return fn.apply(object,
// 现在的args是移除了最初参数中第一个参数后的数组,也就是新函数的预设的初始参数。
// array.concat(items...)方法产生一个新数组,它包含一份array的浅复制,并把一个或者多个参数item附加在其后。
// 如果参数item是一个数组,那么它的元素会被分别添加。
// 于是这一行代码产生了一个参数数组,这个数组由预设的初始参数(如果存在)和新传递的参数组成。
args.concat(Array.prototype.slice.call(arguments)));
};
};

在最新的版本1.7.2(2015.06.23查阅官网)中https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/function.js#L115,bind是下面这样子的:

function bind(context) {
if (arguments.length < 2 && Object.isUndefined(arguments[0]))
return this; if (!Object.isFunction(this))
throw new TypeError("The object is not callable."); var nop = function() {};
var __method = this, args = slice.call(arguments, 1); var bound = function() {
var a = merge(args, arguments);
// Ignore the supplied context when the bound function is called with
// the "new" keyword.
var c = this instanceof bound ? this : context;
return __method.apply(c, a);
}; nop.prototype = this.prototype;
bound.prototype = new nop(); return bound;
}

可见,除了加了一些异常情况判断,核心代码和之前并无大差别。

结语

总得来说apply、call和bind都是为了手动绑定this对象,目的上没有什么区别。但是bind和另外两者的明显的区别是,bind会产生一个新的函数,这个函数还可以有预设的参数,这在很多时候会比apply和call更加好用。合理利用apply、call和bind会使得javaScript代码更加优雅。

参考资料

Function.prototype.apply()

JavaScript 的怪癖 8:“类数组对象”

how does Array.prototype.slice.call() work?

JavaScript’s Apply, Call, and Bind Methods are Essential for JavaScript Professionals

把玩Javascript中的bind的更多相关文章

  1. JavaScript中的bind,call和apply函数的用法和区别

    一直没怎么使用过JavaScript中的bind,call和apply, 今天看到一篇比较好的文章,觉得讲的比较透彻,所以记录和总结如下 首先要理解的第一个概念,JavaScript中函数调用的方式, ...

  2. Javascript中的bind详解

    前言 用过React的同学都知道,经常会使用bind来绑定this. import React, { Component } from 'react'; class TodoItem extends ...

  3. JavaScript中的bind方法及其常见应用

    一.bind()方法的实现 在JavaScript中,方法往往涉及到上下文,也就是this,因此往往不能直接引用.就拿最常见的console.log("info…")来说,避免书写 ...

  4. Javascript中的bind()函数

    今天看到公司大神的一段代码: function ReplaceProcessor() { this._dom = { btnReplace: $('#ro_btnReplace'), btnCompl ...

  5. javascript 中的 bind (编辑中。。。。)

    这篇文章说的非常好!http://my.oschina.net/blogshi/blog/265415 我的体会就是,函数中的this,指的是运行时,它是被哪个对象调用的.因为javascrpit的函 ...

  6. Javascript中的Bind 、Call和Apply

    看以下代码: var bind = Function.prototype.call.bind(Function.prototype.bind); 第一眼看上去,我能猜出它究竟是用来做什么的.它把x.y ...

  7. javascript 中 function bind()

    Function bind() and currying <%-- All JavaScript functions have a method called bind that binds t ...

  8. Javascript中的Bind,Call和Apply

    http://www.html-js.com/article/JavaScript-functional-programming-in-Javascript-Bind-Call-and-Apply?s ...

  9. 深入浅出 妙用Javascript中apply、call、bind

    apply.call 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向. Jav ...

随机推荐

  1. SAX解析XML文档——(二)

    SAX从上向下解析,一行一行解析.节省内存,不适合CRUD. XML文档: <?xml version="1.0" encoding="UTF-8"?&g ...

  2. AD域相关的属性和C#操作AD域

     “常规”标签  姓 Sn 名 Givename 英文缩写 Initials 显示名称 displayName 描述 Description 办公室 physicalDeliveryOfficeNam ...

  3. Python 3.x 格式化输出字符串 % & format 笔记

    Python 3.x 格式化输出字符串 % & format 笔记 python格式化字符串有%和{}两种 字符串格式控制符. 字符串输入数据格式类型(%格式操作符号) %%百分号标记 %c字 ...

  4. Kaggle 泰坦尼克

    入门kaggle,开始机器学习应用之旅. 参看一些入门的博客,感觉pandas,sklearn需要熟练掌握,同时也学到了一些很有用的tricks,包括数据分析和机器学习的知识点.下面记录一些有趣的数据 ...

  5. ARMV8 datasheet学习笔记5:异常模型

    1.前言 2.异常类型描述 见 ARMV8 datasheet学习笔记4:AArch64系统级体系结构之编程模型(1)-EL/ET/ST 一文 3. 异常处理路由对比 AArch32.AArch64架 ...

  6. ASP.NET Core Identity 实战(4)授权过程

    这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity ...

  7. svn使用小技巧

    在使用svn的时候,往往导入到eclipse中,使用eclipse进行提交修改,但是当删除文件夹的时候,eclipse会发生冲突,不能够提交. 使用一种方式,将svn上的项目跟你eclipse对应项目 ...

  8. centos7 部署 docker ce

    =============================================== 2019/4/9_第1次修改                       ccb_warlock === ...

  9. 在Mac上安装GTK(go语言GUI)

    1.在终端输入:xcode-select --install 安装command line工具, 如果安装了Xcode, 就直接跳过该步骤 2. 在终端输入:ruby -e "$(curl ...

  10. OCM_第十六天课程:Section7 —》GI 及 ASM 安装配置 _安装 GRID 软件/创建和管理 ASM 磁盘组/创建和管理 ASM 实例

    注:本文为原著(其内容来自 腾科教育培训课堂).阅读本文注意事项如下: 1:所有文章的转载请标注本文出处. 2:本文非本人不得用于商业用途.违者将承当相应法律责任. 3:该系列文章目录列表: 一:&l ...