bind函数_关于ES5的bind

一、bind的概念

由MDN定义可知:bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

例子:

var module = {
  x: 42,
  getX: function() {
    return this.x;
  }
}
console.log(module.getX());        // 输出42
 
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined

var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());    // expected output: 42

e77a89abe1c6c3094a761c0bcf08b47a.png

我们与apply()和call()在例子中对比一下: 可见bind与另两者的区别在于它返回的是原函数而并非带参执行原函数。

function foo(x,y) {
  console.log(x, y, this)
}
foo.call(100, 1, 2)      // 1,2,Number {100}
foo.apply(true, [3,4])  // 3,4,Boolean {true}
foo.apply(null)    // undefined,undefined,window
foo.apply(undefined)   // undefined,undefined,window
foo.bind(true)     // 返回foo函数
foo.bind(null)     // 同上
foo.bind(null)()   // undefined,undefined,window
foo.bind(true)()   // undefined undefined Boolean {true}
foo.bind()()       // undefined,undefined,window

aa398b018fbe31229f95f51b4bd315af.png

二、bind的作用:改变this指向、对参数柯里化

2.1 改变this指向(同call、apply)

2.2 对参数进行柯里化

function add(a,b,c) {
  return a + b + c
}
var fun1 = add.bind(undefined, 10)   // 实际上返回add(10, b, c) {..}函数
fun1(1,2)  // 执行add(10, 1, 2) {..}函数得到13

var fun2 = fun1.bind(undefined, 20)   // 实际上返回add(10, 20, c) {..}函数
fun2(10)  // 执行add(10,20,10)得到40

0cbc5a0096f6258880c3ab2a8b3760f7.png

实际工程中类似的使用实例:

比如定义一个可传参决定颜色、大小、其他选项的配置函数,但颜色和大小是公共配置基本通用的就不用每次都往传参里写,为里方便重用通用配置项,借助bind的curry功能我们就可以默认使用“标配”,执行时只用传最后一个参数otherOptions就行。

10eb02c7eeb10d1d7ecf979ea2e38352.png
function getConfig(colors, size, otherOptions) {
  console.log(colors, size, otherOptions);
}
var defaultConfig = getConfig.bind(null, "#fff", "1024*768")

defaultConfig("123")      // #fff, 102*768, 123
defaultConfig("2333")     // #fff, 102*768, 2333

三、手写实现bind功能

我们可以通过一个示例来试试看原生的bind对于使用new的情况是如何的:

function animal(name) {
    this.name = name
}
var obj = {}

var cat = animal.bind(obj)
cat('lily')
console.log(obj.name)  //lily

var tom = new cat('tom')
console.log(obj.name)  //lily
console.log(tom.name)  //tom

试验结果发现,obj.name依然是lily而没有变成tom,所以就像MDN描述的那样,如果绑定函数cat是通过new操作符来创建实例对象的话,this会指向创建的新对象tom,而不再固定绑定指定的对象obj

由于 bind()是ES5新增的方法,故仅支持IE9以上及Chrome等现代浏览器,在老版本浏览器想要实现需自己Polyfill 。

下面是MDN的Polyfill:

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() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };
    // aArgs就是调用bind时传入的初始化参数(剔除了第一个参数oThis)。将aArgs与绑定函数执行时的实参arguments通过concat连起来作为参数传入,就实现了bind函数初始化参数的效果。
    // 维护原型关系
    if (this.prototype) {
      // 当执行Function.prototype.bind()时, this为Function.prototype 
      // this.prototype(即Function.prototype.prototype)为undefined
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

四、apply、call 实现

apply 的实现:

Function.prototype.myApply= function(context){
    context = context || window; // 参数默认值并不会排除null,所以重新赋值
    context.fn = this; //1.将函数挂载到传入的对象
    var arg = [...arguments].splice(1)[0];  //2.取参数
    if(!Array.isArray(arg)) {
        throw new Error('apply的第二个参数必须是数组')  //3.限制参数类型为数组
    }    
    context.fn(arg) //4.执行对象的方法
    delete context.fn; //5.移除对象的方法
}

var obj = {
    name:'obj'
}
function sayName(arr){
    console.log(this.name,arr)
}
sayName.myApply(obj,[1,2,3]) //obj [1, 2, 3]

call实现:与apply的唯一区别就是参数格式不同

Function.prototype.myCall= function(context){
    context = context || window; // 参数默认值并不会排除null,所以重新赋值
    context.fn = this;//1.将函数挂载到传入的对象
    var arg = [...arguments].splice(1);//2.取参数
    context.fn(...arg) //3.执行对象的方法
    delete context.fn; //4.移除对象的方法
}
var obj = {
    name:'obj1'
}
function sayName(){
    console.log(this.name,...arguments)
}
sayName.myCall(obj,1,2,3,5) //obj1 1,2,3,5

本文引用参考:

Function.prototype.bind()​developer.mozilla.org
df6a24ee82d3e8ae089797d77a9a800b.png
[JavaScript]函数属性arguments,JavaScript深入浅出教程-慕课网​www.imooc.com
59e89a051bcacbdbda0c1802bbc57ca4.png
js函数this理解?手写apply、call、bind就够了​segmentfault.com 如何手写call、apply、bind?-JavaScript中文网-JavaScript教程资源分享门户​www.javascriptcn.com
e292ceaaa210b7f4653023a9f391bd03.png
手写bind()函数,理解MDN上的标准Polyfill​blog.csdn.net 手动实现bind函数​segmentfault.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值