此文为进阶理解,如果不熟悉标题中的arguments,aplly,call,bind,先去看他们的作用与基本用法
这里将简易版的手写bind放在开头,方便快速查阅
Function.prototype._bind = function (){
var slice = Array.prototype.slice
var thatFunc = this
var thatArg = arguments[0]
var args = slice.apply(arguments,1)
return function (){
// 这里调用slice.apply(arguments)是因为arguments是类数组不是真实数组
var funcArgs = args.concat(slice.apply(arguments))
return thatFunc.apply(thatArg,funcArgs)
}
}
一、函数属性&arguments
function foo(x,y,z){
arguments.length // 2 (只传入了两个参数)
// 注意,arguments是类数组对象,是没有join,slice这些数组对象的方法的
arguments[0] // 1
arguments[0] = 10 // 此时x change to 10
// 注意,如果是在严格模式下,aruguments则是形参的副本,赋值不会改变真实的形参
arguments[2] = 100
//此时z still undefined (没有传入第三个参数,所以z为undefined)
arguments.callee === foo // true (arguments.callee指向当前arguments指向的函数)
// 注意,严格模式下arguments.callee是禁止使用的,会报错
}
foo(1,2)
console.log(foo.length) // 3 (返回形参的个数,形参有xyz所以返回3)
console.log(foo.name) // "foo" (返回函数名)
二、apply/call方法
function foo(x,y){
// 'use strict'
console.log(this,x,y)
}
/* 第一个参数不是对象会转换为对象,比如100就变成了包装类Number(100) */
foo.call(100,1,2) // Number(100),1,2
foo.apply(true,[3,4]) // Boolean(true),3,4
/* 第一个参数如果是null或undefined,会转换为全局对象,对于浏览器就是window对象,对于nodeJS就是Global对象 */
foo.apply(null) // window,undefined,undefined
foo.apply(undefined) // window,undefined,undefined
/* 如果是在严格模式下,null或undefined则不会转换,打印的this就是null或者undefined */
三、bind方法
this.x=9
var module = {
x:81,
getX:function(){return this.x}
}
module.getX() // 81
var getX = module.getX
getX() // 9
var boundGetX = getX.bind(module)
boundGetX() // 81
四、bind与currying(柯里化)
function add(a,b,c){
return a+b+c
}
var func = add.bind(undefined,100) // 绑定add的this为window,第一个参数(add中的a)为100
func(1,2) // 103
var func2 = func.bind(undefined,200) // 绑定func的this为window,第一个参数(add中的b)为200
func2(10) // 310
function getConfig(colors,size,otherOptions){
console.log(colors,size,otherOptions)
}
// 比如在某个模块下colors和size的参数都是一样的,要改动的只有otherOptions,则可用bind柯里化
// 这样使得代码更加清晰,更加方便重用
var defaultConfig = getConfig.bind(null,'#CC0000','1024 * 768')
defaultConfig("123") // #CC0000 1024 * 768 123
defaultConfig("456") // #CC0000 1024 * 768 456
五、bind与new
如果 bind 得到的函数用作构造函数(带 new 关键字使用),则 bind 不生效,也就是说new的优先级是高于bind的
function foo(){
this.b = 100
return this.a
}
var func = foo.bind({a:1})
func() // 1
new func() // {b:100}
function foo(something) {
this.a = something
}
var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
var baz = new bar(3);
console.log(obj1.a); //2
console.log(baz.a); //3
六、bind方法模拟(polyfill)(来源MDN)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
因为较旧的浏览器通常也是较慢的浏览器,所以比大多数人认识到的要重要的是创建性能填充,以使过时的浏览器中的浏览体验的恐怖程度略微降低。
因此,下面介绍了Function.prototype.bind()填充的两个选项:
-
第一个小得多,性能更高,但是在使用new运算符时不起作用。
-
第二个函数较大,性能较低,但是允许在绑定函数上使用new运算符。
通常,在大多数代码中,很少见到在绑定函数上使用了new,因此通常最好选择第一个选项。(不过参考第五点,正常情况下new的优先级是高于bind的,只是简易版的polyfill没有实现这个功能)
第一个
// 与 `new (funcA.bind(thisArg, args))`不兼容
if (!Function.prototype.bind) (function(){ /* 如果没有Function.prototype.bind方法 */
var slice = Array.prototype.slice; /* 数组对象的slice方法 */
Function.prototype.bind = function() { /* 给Function.prototype绑定一个bind方法 */
var thatFunc = this, thatArg = arguments[0]; /* this为调用bind方法的对象,aruguments[0]为绑定的对象 */
var args = slice.call(arguments, 1); /* arguments只是类数组,不是数组对象,本身是没有slice方法的 */
if (typeof thatFunc !== 'function') { /* 如果调用bind的对象不是一个function */
throw new TypeError('Function.prototype.bind - ' +
'what is trying to be bound is not callable');
}
return function(){
var funcArgs = args.concat(slice.call(arguments))
/* 这个arguments已经是本函数作用域中的arguments了,不是上文的arguments */
/* 用slice.call(arguments)将其转化为数组对象并且与之前固定的args进行拼接 */
return thatFunc.apply(thatArg, funcArgs);
/* thatFunc、args是foo.bind(thatFunc,args),即bind固定的对象和参数*/
/* arguments是(foo.bind(thatFunc,args))(arguments),即调用已经bind修改指向后的函数时传入的参数 */
};
};
})();
去除掉各种判断条件的核心代码:
Function.prototype._bind = function () {
var slice = Array.prototype.slice
var thatFunc = this,
thatArg = arguments[0]
var args = slice.call(arguments, 1)
return function () {
var funcArgs = args.concat(slice.call(arguments))
return thatFunc.apply(thatArg, funcArgs);
}
}
第二个(兼容new操作符的版本)
// 兼容 `new (funcA.bind(thisArg, args))`(满足bind()方法new改变this优先级大于bind的性质)
if (!Function.prototype.bind) (function(){
var ArrayPrototypeSlice = Array.prototype.slice;
Function.prototype.bind = function(otherThis) {
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 baseArgs= ArrayPrototypeSlice.call(arguments, 1),
baseArgsLength = baseArgs.length,
fToBind = this,
fNOP = function() {},
fBound = function() {
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments);
return fToBind.apply(
fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
/* 如果是new 构造函数().bind(otherThis,baseArgs),则this指向构造函数而不是bind绑定的对象 */
/* 这里笔者也不太懂是不是这个意思,欢迎大家在评论区讨论一下 */
);
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
})();