bind()
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
— MDN
bind 方法与 call / apply 最大的不同就是前者返回一个绑定上下文的函数,而后两者是直接执行了函数。
来个例子说明下:
letvalue=2;letfoo={value: 1};functionbar(name,age){return{value: this.value,name: name,age: age}};bar.call(foo,"Jack",20);// 直接执行了函数// {value: 1, name: "Jack", age: 20}letbindFoo1=bar.bind(foo,"Jack",20);// 返回一个函数bindFoo1();// {value: 1, name: "Jack", age: 20}letbindFoo2=bar.bind(foo,"Jack");// 返回一个函数bindFoo2(20);// {value: 1, name: "Jack", age: 20}通过上述代码可以看出 bind 有如下特性:
- 1、指定
this - 2、传入参数
- 3、返回一个函数
- 4、柯里化
模拟实现:
Function.prototype.bind=function(context){// 调用 bind 的不是函数,需要抛出异常if(typeofthis!=="function"){thrownewError("Function.prototype.bind - what is trying to be bound is not callable");}// this 指向调用者varself=this;// 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数varargs=Array.prototype.slice.call(arguments,1);// 实现第3点,返回一个函数returnfunction(){// 实现第4点,这时的arguments是指bind返回的函数传入的参数// 即 return function 的参数varbindArgs=Array.prototype.slice.call(arguments);// 实现第1点returnself.apply(context,args.concat(bindArgs));}}但还有一个问题,bind 有以下一个特性:
一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
来个例子说明下:
letvalue=2;letfoo={value: 1};functionbar(name,age){this.habit='shopping';console.log(this.value);console.log(name);console.log(age);}bar.prototype.friend='kevin';letbindFoo=bar.bind(foo,'Jack');letobj=newbindFoo(20);// undefined// Jack// 20obj.habit;// shoppingobj.friend;// kevin上面例子中,运行结果 this.value 输出为 undefined ,这不是全局 value 也不是 foo 对象中的 value ,这说明 bind 的 this 对象失效了,new 的实现中生成一个新的对象,这个时候的 this 指向的是 obj 。
这个可以通过修改返回函数的原型来实现,代码如下:
Function.prototype.bind=function(context){// 调用 bind 的不是函数,需要抛出异常if(typeofthis!=="function"){thrownewError("Function.prototype.bind - what is trying to be bound is not callable");}// this 指向调用者varself=this;// 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数varargs=Array.prototype.slice.call(arguments,1);// 创建一个空对象varfNOP=function(){};// 实现第3点,返回一个函数varfBound=function(){// 实现第4点,获取 bind 返回函数的参数varbindArgs=Array.prototype.slice.call(arguments);// 然后同传入参数合并成一个参数数组,并作为 self.apply() 的第二个参数returnself.apply(thisinstanceoffNOP ? this : context,args.concat(bindArgs));// 注释1}// 注释2// 空对象的原型指向绑定函数的原型fNOP.prototype=this.prototype;// 空对象的实例赋值给 fBound.prototypefBound.prototype=newfNOP();returnfBound;}注释1 :
- 当作为构造函数时,
this 指向实例,此时 this instanceof fBound 结果为 true ,可以让实例获得来自绑定函数的值,即上例中实例会具有 habit 属性。 - 当作为普通函数时,
this 指向 window ,此时结果为 false ,将绑定函数的 this 指向 context
注释2 :
- 修改返回函数的
prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值,即上例中 obj 可以获取到 bar 原型上的 friend - 至于为什么使用一个空对象
fNOP 作为中介,把 fBound.prototype 赋值为空对象的实例(原型式继承),这是因为直接 fBound.prototype = this.prototype 有一个缺点,修改 fBound.prototype 的时候,也会直接修改 this.prototype ;其实也可以直接使用ES5的 Object.create() 方法生成一个新对象,但 bind 和 Object.create() 都是ES5方法,部分IE浏览器(IE < 9)并不支
注意:bind() 函数在 ES5 才被加入,所以并不是所有浏览器都支持,IE8 及以下的版本中不被支持,如果需要兼容可以使用 Polyfill 来实现
详情可前往 深度解析bind原理、使用场景及模拟实现 查看
补充:柯里化
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
varadd=function(x){returnfunction(y){returnx+y;};};varincrement=add(1);varaddTen=add(10);increment(2);// 3addTen(2);// 12add(1)(2);// 3这里定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。所以说 bind 本身也是闭包的一种使用场景。
柯里化是将 f(a,b,c) 可以被以 f(a)(b)(c) 的形式被调用的转化。JavaScript 实现版本通常保留函数被正常调用和在参数数量不够的情况下返回偏函数这两个特性。
bind()
bind方法与call / apply最大的不同就是前者返回一个绑定上下文的函数,而后两者是直接执行了函数。来个例子说明下:
通过上述代码可以看出
bind有如下特性:this模拟实现:
但还有一个问题,
bind有以下一个特性:来个例子说明下:
上面例子中,运行结果
this.value输出为undefined,这不是全局value也不是foo对象中的value,这说明bind的this对象失效了,new的实现中生成一个新的对象,这个时候的this指向的是obj。这个可以通过修改返回函数的原型来实现,代码如下:
注释1 :
this指向实例,此时this instanceof fBound结果为true,可以让实例获得来自绑定函数的值,即上例中实例会具有habit属性。this指向window,此时结果为false,将绑定函数的this指向context注释2 :
prototype为绑定函数的prototype,实例就可以继承绑定函数的原型中的值,即上例中obj可以获取到bar原型上的friendfNOP作为中介,把fBound.prototype赋值为空对象的实例(原型式继承),这是因为直接fBound.prototype = this.prototype有一个缺点,修改fBound.prototype的时候,也会直接修改this.prototype;其实也可以直接使用ES5的Object.create()方法生成一个新对象,但bind和Object.create()都是ES5方法,部分IE浏览器(IE < 9)并不支注意:
bind()函数在 ES5 才被加入,所以并不是所有浏览器都支持,IE8及以下的版本中不被支持,如果需要兼容可以使用 Polyfill 来实现详情可前往 深度解析bind原理、使用场景及模拟实现 查看
补充:柯里化
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
这里定义了一个
add函数,它接受一个参数并返回一个新的函数。调用add之后,返回的函数就通过闭包的方式记住了add的第一个参数。所以说bind本身也是闭包的一种使用场景。柯里化是将
f(a,b,c)可以被以f(a)(b)(c)的形式被调用的转化。JavaScript 实现版本通常保留函数被正常调用和在参数数量不够的情况下返回偏函数这两个特性。