Function.prototype.bind接口浅析

本文大部分内容翻译自 MDN内容, 翻译内容经过自己的理解。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Function.prototype.bind

Syntax

fun.bind(thisArg[, arg1[, arg2[, ...]]])
Parameters
thisArg
The value to be passed as the this parameter to the target function when the bound function is called. The value is ignored if the bound function is constructed using the new operator.
arg1, arg2, ...
Arguments to prepend to arguments provided to the bound function when invoking the target function.

bind函数,首先是所有函数的共有的属性,这个属性是一个函数,此函数为函数类(Function构造函数)的原型的一个属性(Function.prototype.xx),所以只有函数才能调用此接口,

故用法一般是定义一个函数,然后此函数调用bind接口

function XX(){}

XX.bind(thisArg[, arg1[, arg2[, ...]]])

此函数作用是,生成一个新的函数, 新函数具有和被绑定的函数具有相同功能(函数体)。

当新函数调用的时候,新函数中的this指代bind调用的第一个参数thisArg;

同时,如果bind调用时候有可选参数(arg1等),则这些参数则会作为被绑定函数默认前若干个参数。

为辅助理解,给出如下伪代码描述:

假设有函数

function XX(variable){ alert(this.variable=variable) }

则XX函数经过绑定后的效果

// var YY = XX.bind(widow, variable) 返回的函数 等价于如下代码段

function YY(Yvariable)

{

function XX(){ alert(widow.variable=variable) }  //XX函数中的 this 替换为 window, 待绑定的目标

XX(variable,  Yvariable); //XX调用,绑定的可选参数在前,YY的形参在后

}

函数绑定到一个对象

绑定之后的调用, 最后一行 boundGetX,虽然是被全局对象(window)调用,但是其中的this已经被替换为module,故其中this.XX 都会在module对象中查询。

this.x = 9;
var module = {
x: 81
};
var getX = function() { return this.x; } 

getX(); // 9, because in this case, "this" refers to the global object // Create a new function with 'this' bound to module
var boundGetX = getX.bind(module);
boundGetX(); // 81

给函数预置前导参数值

如果有种需求,需要某个函数被调用的第一个参数,是一个恒值,则需要使用封装,朴素做法

function XX() {} // 有种需求, 需要XX第一个参数恒为 1

function YY(){

var xxArgs = [1];

xxArgs.concat(Array.prototype.slice.call(arguments));  // [1,  YYarguments]

XX(xxArgs)

}

bind提供了另外一种简便方法

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]

setTimeout触发函数中this保持绑定对象

默认 setTimeout触发函数执行时候, this是绑定全局环境window, 如果想让绑定固定对象,可以使用bind接口。

如下, 对象的declare函数需要在事件到达后执行, 执行时候, declare其中的this实际上是代指LateBloomer创建的对象,但是直接作为setTimout参数则this指代window。 绑定替换为对象方法:

function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
} // Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
}; LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
}; var flower = new LateBloomer();
flower.bloom(); // after 1 second, triggers the 'declare' method

一则定时器封装应用: https://github.com/fanqingsong/Timer_Javascript/blob/master/src/timer_core.js

绑定函数作为构造函数

Warning: This section demonstrates JavaScript capabilities and documents some edge cases of the bind() method. The methods shown below are not the best way to do things and probably should not be used in any production environment.

如下例子, 将原构造函数 Point绑定到一个空对象{}上, 并生成了一个新的构造函数YAxisPoint ,此构造函数具有两个特性:

1、 其原型和Point相同的原型,即bind生成的函数,继承了待绑定函数的原型(但是你直接使用 YAxisPoint.prototype打印是undfined,只能在new之后的对象上体现)。

2、 YAxisPoint 是特定对象绑定之后的构造函数,只在普通调用情况下(YAixPoint()) 才执行this指代绑定对象的逻辑。

但是在new操作来调用的情况下, 即new YAxisPoint(), 绑定失效, 即Point中的this不指代绑定的对象。

new操作符号将按照普通构造函数逻辑,将生成一个新的对象, 将构造函数代码执行一遍,构造函数中的this指代,新建立的对象。new Construtor() 执行逻辑:

var instance = {}

Constructor.apply(instance)

instance.__proto__ = Constructor.prototype;

3、虽然Point中this不指代YAxisPoint 绑定对象(YAxisPoint 绑定啥对象也没有关系了, 但是为了避免YAxisPoint ()造成污染其他变量,尽量使用null 和空对象), 但是绑定的参数还是按照bind的定义作为前导参数传递到Point构造函数中。

修改下开头绑定的为伪代码,添加new调用的情况:

假设有函数

function XX(variable){ alert(this.variable=variable) }

则XX函数经过绑定后的效果

// var YY = XX.bind(widow, variable) 返回的函数 等价于如下代码段

function YY(Yvariable)

{

if  YY called classically then // YY普通调用模式, 由于XX已经绑定对象,需要对XX的this做替换

function XX(){ alert(widow.variable=variable) }  //XX函数中的 this 替换为 window, 待绑定的目标

else if YY called by new operator then // new YY 情况, bind不影响创建新对象

function XX(){ alert(this.variable=variable) }  //XX函数中的 this不替换,对应 被new调用的情况

end

XX(variable,  Yvariable); //XX调用,绑定的可选参数在前,YY的形参在后

}

------ MDN 实例-----

function Point(x, y) {
this.x = x;
this.y = y;
} Point.prototype.toString = function() {
return this.x + ',' + this.y;
}; var p = new Point(1, 2);
p.toString(); // '1,2' var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// not supported in the polyfill below,
// works fine with native bind:
var YAxisPoint = Point.bind(null, 0/*x*/); var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5' axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true

创建快捷方式

对于一些原型对象的函数, 如果需要引用, 往往需要些很长的一串代码, 如将array-like对象,转换为数组的 slice接口应用, 则需要如下写法:

Array.prototype.slice.apply(arguments);

进一步通过变量首先声明下slice接口, 然后调用,可以简洁点:

var slice = Array.prototype.slice;

// ...

slice.apply(arguments);

但是不能进一步将apply也放到 slice的声明中, 如果这样会有js报错, 原因为slice指向了apply函数,然后slice在window全局环境下执行, 效果相当于 window.apply,window是个对象, 不是函数,所以会有报错:

var slice = Array.prototype.slice.apply;

// ...

slice(arguments);

为了更简洁的写法,下面有请bind:

// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice); // ... slice(arguments);

现在给出进一步解释:

apply是任意函数的原型函数, 即 为 Function.prototype属性, 任意定义的一个函数 例如 function XX(){}

都可以调用apply, 即 XX.apply(obj), 此函数的意思是将 XX函数中的this替换为obj,并执行。 这里说的任意函数,当然也包括 上面例子的 Array.prototy.slice这也是函数。

function XX() { this.attr = 0}

XX(); // 由于在全局环境window中调用, this指代window对象

var obj = {}

XX.apply(obj) //将XX中的this替换为obj, 然后执行XX

// 上一行等价于, 先根据XX生成一个替换过this的函数YY, 然后再执行, 即apply有如下实现(伪代码):

function Function.prototype.apply(obj) //obj为XX函数中this替换的目标

var YYGenerator = function( caller)  // caller为 apply调用对象,  即XX

return function() {  caller.functionBody.replace( this, obj)  } // XX函数体中 this 替换为 obj

end

var YY() = YYGenerator(this);  // this 为apply调用者, 即为 XX函数

YY();

end

则apply函数经过绑定后的效果,构造过程包括三步,逐步填充slice函数体:

1、将Function.prototype.apply代码 复制到另一函数  boundapply

2、执行绑定动作, 将boundapply中 this 替换为 bind的第一个参数 unboundSlice

3、添加boundapply调用语句。

//var unboundSlice = Array.prototype.slice;

//var slice = Function.prototype.apply.bind(unboundSlice);    生成的函数等价于如下伪代码

function slice()

{

function boundapply(obj) //obj为unboundSlice函数中this替换的目标

var YYGenerator = function( caller)  // caller为 apply调用对象,  即unboundSlice

return function() {  caller.functionBody.replace( this, obj)  } // unboundSlice函数体中 this 替换为 obj

end

var YY() = YYGenerator(unboundSlice);  // this替换为unboundSlice为apply调用者, 即为 unboundSlice函数

YY();

end

boundapply(arguments);  //arguments为unboundSlice函数中this替换的目标

}

slice(arguments)

此场景不太好理解, 故给出上文描述, 其中牵扯到两个易混淆点:

1、 bind 将apply函数中 this替换为 bind的入参 Array.prototype.slice, 这个在绑定的时刻即执行。

2、 apply生效,将Array.prototype.slice函数中 this替换为arguments,并执行, 这个在slice被调用阶段执行。

3、 例子当中的 bind之后的参数名称, slice为按照应用场景命名, 实际上此名称应该是 sliceBoundApply,即实际上它函数体还是Function.prototype.apply的代码,只不过 Function.prototype.apply代码中的this固定为一个函数 Array.prototype.slice, 那么 sliceBoundApply  === Array.prototype.slice.apply (apply函数中的this指代Array.prototype.slice,因为是Array.prototype.slice对象调用了apply)

所以 sliceBoundApply(obj) ===  Array.prototype.slice.apply (obj)

通篇出现两种情况的绑定: 1、 被绑定函数中,将this作为Object使用, 则绑定的目标为Object,如”函数绑定到对象例子” 2、被绑定函数中,将this作为函数使用, 则绑定的目标为Function, 如“创建快捷方式”

绑定前后一图明义:

Function.prototype.bind接口浅析

上一篇:接口IDisposable的用法


下一篇:java interface的两个经典用法