手写bind
原理
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
由此我们可以首先得出 bind 函数的两个特点:
- 返回一个函数
- 可以传入参数
实现
首先先在Function的原型上挂载一个自定义bind函数
Function.prototype._bind = function(){}1. 模拟实现返回一个函数
正常的使用bind的时候是返回一个函数,这里返回的函数的引用(this)依旧指向的是调用bind的函数,而不是新得返回函数, 而且 考虑到绑定函数可能是有返回值 ,所以这块还是要处理下
const obj ={ name: 'weng',};
function sayName(){ return this.name;}
const a = sayName._bind(obj);console.log(a()); //weng所以这块的this指向需要做处理。
Function.prototype._bind = function(context){ const that = this; return function(){ return that.call(context); }}这里用that存储this,之后返回一个闭包函数
2. 传参数
在执行bind的时候,bind可以传参数,而且bind返回之后的函数也可以传参数
const obj ={ name: 'weng',};
function sayName(age, address){ return `${this.name}: ${age} : ${address}`;}
const a = sayName.bind(obj, 23);
console.log(a('fujian')); // weng : 23 : fujian这里就相当于内部实现一个函数柯里化用来存储每次传进来的数据,但是没有柯里化那么牛逼,具体可以看实现函数柯里化那那篇文章
具体实现如下
Function.prototype._bind = function(context){ const that = this; const outerArgs = Array.prototype.slice.call(arguments, 1); // 存下第一次传的参数, 截掉第一个 return function(){ const innerArgs = Array.prototype.slice.call(arguments); return that.call(context, ...outerArgs.concat(innerArgs)); // 把外层参数拼接到内层的参数类数组里头 }}3. 构造函数的模拟实现
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。举个例子:
const obj = { preName: "weng",};
function Bar(name, age) { this.name = name; this.age = age;}
Bar.prototype.sayName = function(){ console.log(this.name)}
const MyBar = Bar.bind(obj, "kaimin");
const o = new MyBar(12);
console.log(o);结果如下

可以得知此时Bar函数的this指向的是o对象,就是相当于以new的形式调用了构造函数,这块的原理可以去 手写new那篇文章看看
Function.prototype._bind = function (context) { const that = this; const outerArgs = Array.prototype.slice.call(arguments, 1);
const returnFn = function () { const innerArgs = Array.prototype.slice.call(arguments); return that.call( this instanceof returnFn ? this : context, ...outerArgs.concat(innerArgs) ); }; returnFn.prototype = this.prototype; return returnFn;};以這行代碼来说
return that.call( this instanceof returnFn ? this : context, ...outerArgs.concat(innerArgs) );当作为构造函数时,this 指向实例,此时this instanceof returnFn结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
以上面的是 demo 为例,如果改成 this instanceof returnFn? null : context,实例只是一个空对象,将 null 改成 this
当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
4. 构造函数效果的优化实现
但是在这个写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:
Function.prototype._bind = function (context) { const that = this; const outerArgs = Array.prototype.slice.call(arguments, 1);
const _Transit = function(){};
const returnFn = function () { const innerArgs = Array.prototype.slice.call(arguments); return that.call( this instanceof returnFn ? this : context, ...outerArgs.concat(innerArgs) ); }; _Transit.prototype = this.prototype; returnFn.prototype = new _Transit(); return returnFn;};这里相当做了个私有变量(匿名构造函数类似的),外部是不能访问到的,所以如果后续做returnFn的原型修改,也只是改在了这个私有构造函数new出来的实例上的,这个实例的[[Prototype]]指向的是真正的this原型。
5. 最后做一个日常判定this是否为函数
if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable");}6.最终代码
Function.prototype._bind = function (context) { if(typeof this !== 'function'){ // MDN上的 throw new Error('Function.prototype.bind - what is trying to be bound is not callable"') } const that = this; const outerArgs = Array.prototype.slice.call(arguments, 1);
const _Transit = function(){};
const returnFn = function () { const innerArgs = Array.prototype.slice.call(arguments); return that.call( this instanceof returnFn ? this : context, ...outerArgs.concat(innerArgs) ); }; _Transit.prototype = this.prototype; returnFn.prototype = new _Transit(); return returnFn;};测试
let o = { address: 'fujian'}
function Person(name, age){ this.name = name; this.age = age;}
const NewPerson = Person._bind(o, 'weng');
const per1 = new NewPerson(23);
console.log(per1);最终打印的是这样的

正常打印出来是这样的,差了一个标识的问题, 这个无所谓了

let o = { address: 'fujian'}
function sayHi(name, age){ console.log(this.name = name, this.age = age, this.address);}
const hi = sayHi.bind(o, 'weng');
hi(23); // weng 23 fujian