深入一点 - 使用bind的时候发生了什么呢?

2019-11-05 admin

从规范来看,Function.prototype.bind 是如何工作,以及如何来模拟bind操作。

简单示例

如下简单示例,普通对象 testObj 内部有一个b函数,接受一个普通参数,若参数为空则输出 this.a

const testObj = {
  a: 3,
  b: function(args) {
    console.log(args || this.a);
  },
};
testObj.b()
testObj.b(23)
const c = testObj.b
c()
c(23)
const c1 = testObj.b.bind(testObj, 50)
c1(70)

查看结果: -w568

testObj.b 被重新赋值给 c 后,函数的的执行上下文已经改变,导致输出为 undefined。通过上面例子,如果采用 bind 后,则可以改变 testObj的执行上下文,并可以把默认值传递到参数函数列表.

倘若在 testObj.b内,加入 console.log(arguments), 则可以看到如下输出:

50 70

bind 函数

bind函数是,Function 原型链上的函数,主要是改变函数执行上下文的同时,可以传入函数参数值,并返回新的函数

如图是mdn上的定义,

bind产生的函数就一个偏函数,就是说使用bind可以参数一个函数,然后接受新的参数。

In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity. 详细可看: https://github.com/mqyqingfeng/Blog/issues/43

规范定义

在EcmaScript的规范 15.3.4.5 中如下截图:

bind 方法需要一个或更多参数,thisArg 和(可选的)arg1, arg2, 等等,执行如下步骤返回一个新函数对象:

1\. 令 Target 为 this 值 .
2\. 如果 IsCallable(Target) 是 false, 抛出一个 TypeError 异常 .
3\. 令 A 为一个(可能为空的)新内部列表,它包含按顺序的 thisArg 后面的所有参数(arg1, arg2 等等)。
4\. 令 F 为一个新原生 ECMAScript 对象。
5\. 依照 8.12 指定,设定 F 的除了 [[Get]] 之外的所有内部方法。
6\. 依照 15.3.5.4 指定,设定 F 的 [[Get]] 内部属性。
7\. 设定 F 的 [[TargetFunction]] 内部属性为 Target。
8\. 设定 F 的 [[BoundThis]] 内部属性为 thisArg 的值。
9\. 设定 F 的 [[BoundArgs]] 内部属性为 A。
10\. 设定 F 的 [[Class]] 内部属性为 "Function"。
11\. 设定 F 的 [[Prototype]] 内部属性为 15.3.3.1 指定的标准内置 Function 的 prototype 对象。
12\. 依照 15.3.4.5.1 描述,设定 F 的 [[Call]] 内置属性。
13\. 依照 15.3.4.5.2 描述,设定 F 的 [[Construct]] 内置属性。
14\. 依照 15.3.4.5.3 描述,设定 F 的 [[HasInstance]] 内置属性。
15\. 如果 Target 的 [[Class]] 内部属性是 "Function", 则
    a. 令 L 为 Target 的 length 属性减 A 的长度。
    b. 设定 F 的 length 自身属性为 0 和 L 中更大的值。
16\. 否则设定 F 的 length 自身属性为 0.
17\. 设定 F 的 length 自身属性的特性为 15.3.5.1 指定的值。
18\. 设定 F 的 [[Extensible]] 内部属性为 true。
19\. 令 thrower 为 [[ThrowTypeError]] 函数对象 (13.2.3)。
20\. 以 "caller", 属性描述符 {[[Get]]: thrower, [[Set]]: thrower,[[Enumerable]]: false, [[Configurable]]: false}, 和 false 作为参数调用 F 的 [[DefineOwnProperty]] 内部方法。
21\. 以 "arguments", 属性描述符 {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, 和 false 作为参数调用 F 的 [[DefineOwnProperty]] 内部方法。
22\. 返回 F.
 bind 方法的 length 属性是 1。

 Function.prototype.bind 创建的函数对象不包含 prototype 属性或 [[Code]], [[FormalParameters]], [[Scope]] 内部属

规范表达过程简述如下:

  • 第1步,创建Target,并把 this 值给Target 【this为当前执行环境, 可以理解为当前函数】
  • 第3步,内部创建一个参数空的参数列表A,包含了bind参数中除了thisArg 之外的其他函数
  • 第4步,创建一个对象 F
  • 第6,7,8,9,10,11步,设置函数内部属性,这里第7步中的 [[TargetFunction]] 仅仅只有使用 bind 才会生成,第8步设置 thisArg 新的函数的 this.
  • 第12,13,14步,需要设置对象F内部方法 [[Call]], [[Construct]], [[HasInstance]], 让对象可以被调用
  • 第15步,让对象F变成 [[Function]], 并设置新的函数 length, 新的length值 范围是 0 ~ Target.length
  • 第18步,设置返回新函数可以任意添加属性
  • 第20步,设置函数描述符号 caller属性,
  • 第21步,设置函数参数描述符号, arguments
  • 最后返回对象F,也就是新执行函数。

在bind过程中,会重新设置 [[Call]] 相关函数内部方法,详细可以看规范。

isCallable 定义

Object 内部属性以及方法

用途

  • 创建绑定函数,例如react事件参数传递
  • 偏函数
  • 定时器修改this
  • 构造函数使用绑定函数
  • 快捷调用

最后

知道了过程,倘若不支持,该如何呢?来,搞一个手工的:

Function.prototype.bind2 = function bind2(thisArgs) {
  const aArgs = Array.prototype.slice.call(arguments, 1);
  const fThis = this;
  const fNOP = function() {};
  const fBound = function() {
    // 这段判断是不是使用bind返回函数继续bind
    return fThis.apply(this instanceof fBound ? this : fThis, aArgs.concat(Array.prototype.slice.call(arguments)));
  };
  // this === Function.prototype, 保证创建函数的原型链也为undefined
  if (this.prototype) fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
};

我们实现方法依赖一些属性方法:

  • Fucntion.prototype.apply
  • Function.prototype.call
  • Array.prototype.slice

而且我们实现方法有一个问题在于:

  • length 始终返回为0,并没有计算
  • 返回函数具有 prototype, 不符合规范

查看更规范的实现,点击这里

欢迎交流

[转载]原文链接:https://segmentfault.com/a/1190000020911332

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-78840.html

文章标题:深入一点 - 使用bind的时候发生了什么呢?

相关文章
为什么要选择Nodejs?Nodejs有什么好处?
什么?JavaScript还能用作服务器编程! Caleb Madrigal是来自美国密尔沃基市的一名软件顾问。四年前,他在听说“将JavaScript用作服务器端语言”这样的说法时,认为那是一个荒唐的想法。有那么多服务器端语言可供选择,为...
2015-11-12
js性能优化 如何更快速加载你的JavaScript页面
确保代码尽量简洁 不要什么都依赖JavaScript。不要编写重复性的脚本。要把JavaScript当作糖果工具,只是起到美化作用。别给你的网站添加大量的JavaScript代码。只有必要的时候用一下。只有确实能改善用户体验的时候用一下。 ...
2015-11-12
10个强大的纯CSS3动画案例分享
我们的网页外观主要由CSS控制,编写CSS代码可以任意改变我们的网页布局以及网页内容的样式。CSS3的出现,更是可以让网页增添了不少动画元素,让我们的网页变得更加生动有趣,并且更易于交互。本文分享了10个非常炫酷的CSS3动画案例,希望大家...
2015-11-16
v-charts | 饿了么团队开源的基于 Vue 和 ECharts 的图表工具
在使用echarts生成图表时,经常需要做繁琐的数据类型转化、修改复杂的配置项,v-charts的出现正是为了解决这个 痛点。基于Vue2.0和echarts封装的v-charts图表组件,只需要统一提供一种对前后端都友好的数据格式 设置简...
2018-05-24
Node.js 2014这一年发生了什么
Node.js 的 2014 年充满了不幸和争议. 这一年 Noder 们经历了太多的伤心事, 经历了漫长的等待, 经历了沉重的分裂之痛. 也许 Noder 们不想回忆14年 Node.js land 发生的事情, 但正因为痛才更有铭记的价...
2015-11-12
Angular2-primeNG文件上传模块FileUpload使用详解
近期在学习使用Angular2做小项目,期间用到很多primeNG的模块。 本系列将结合实战总结angular2-primeNG各个模块的使用经验。 文件上传模块FileUploadModule 首先要在使用该组件的模块内导入文件上传模块 ...
2017-03-09
YouTube正式默认使用HTML5视频播放器
YouTube视频网站现在默认使用HTML5播放器,这意味着更好的性能、 稳定性、 电池寿命和甚至是更好的安全性。现在用户通过Chrome、IE 11、Safari 8和Beta版本的Firefox进行浏览的时候都默认使用HTML5视频播放...
2015-11-12
从2014年的发展来展望JS的未来将会如何
<font face="寰�杞�闆呴粦, Arial, sans-serif ">2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
WebSocket断开原因分析,再也不怕为什么又断开了
阅读原文:https://wdd.js.org/websocket-… 1. 把错误打印出来 WebSocket断开的原因有很多,最好在WebSocket断开时,将错误打印出来。 在线demo地址:https://wdd.js.org/we...
2018-04-25
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
回到顶部