想要用原生js写一下bind方法,嗯,让我们先来了解一下bind方法吧,先看一波官方的解释(MDN):
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体。当新函数被调用时this值绑定到bind()的第一个参数,该参数不能被重写。绑定函数被调用时,bind()也接受预设的参数提供给原函数。
一个绑定函数也能使用new操作符创建对象:这种行为就像吧原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数。
哎呀我的妈,官方的解释真的是好绕。我们来举个栗子说:
var foo = { value:'233', getValue: function() { console.log(this.value); console.log(arguments[0], arguments[1]) } } var newGetValue = foo.getValue.bind(foo, 1); newGetValue(2); //233 //1 2
简单来说:
1、函数foo.getValue调用bind的时候,会返回一个新的函数newGetValue。
2、newGetValue和foo.getValue函数体是一毛一样的。
3、newGetValue函数被调用时的this是指向对象foo(传给bind的第一个参数)。
4、bind函数被调用时传递的参数,会在newGetValue被调用时传递,并且排在实参的最前面。
5、new newGetValue() 时,会把newGetValue当成一个构造函数,this自动被忽略,参数依旧可以传。
好了,接下来我们来聊聊原生js怎么实现?在这之前希望你了解过call、apply是如何用原生实现的。
Function.prototype.mybind = function(context) { var self = this; var args = [];//保存bind函数调用时传递的参数 for(var i = 1, len = arguments.length; i< len;i ++) { args.push(arguments[i]); } //bind()方法返回值是一个函数 return function() { //哇,新创建的函数传进来的参数可以在这里拿到哎!! var bindArgs = Array.prototype.slice.call(arguments); self.apply(context, args.concat(bindArgs)) } }
OK,我们已经实现了bind的方法指定this值,和传递参数的功能了。
接下来看一下构造函数的效果如何实现:忽略this的值,传参依旧正常。我一直在想,怎么知道调用的时候是通过构造调用的呢?
这个简单,被当做构造函数调用的时候this指向一个新创建的对象;而被当普通函数调用的时候,this是指向window的。那么问题又来了?我们如何判断this指向这个新对象呢?别忘了,构造函数new一个新对象必然关系到原型链继承这些事情,是的,bind函数不仅会复制被调函数的函数体,原型链也要复制。这样我们就看 this instanceof self ? 新对象是否继承自被调函数呀,如果是,那么就是被当做构造函数调用的。
来看代码:
Function.prototype.mybind = function (context) { if (typeof this !== "function") { throw new Error(this + "is not a function"); } var self = this; var args = []; for (var i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); } var fbound = function () { var bindArgs = Array.prototype.slice.call(arguments); self.apply(this instanceof self ? this : context, args.concat(bindArgs)); } fbound.prototype = Object.create(self.prototype); //返回的函数不仅要和 被调函数的函数体相同,也要继承人家的原型链 return fbound; }
最后,别忘了,如果调用bind方法的不是一个函数,要报错哦~
这篇博客也是我自己看了bind的原生实现之后对知识的一个梳理和总结,让我能更好的理解和掌握。如有错误,恳请指正。