MDN对于bind方法的定义:bind()方法创建一个新的函数,在调用时设置this关键字提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。读起来有些晦涩难懂。我的理解是:bind()方法创建一个新的函数,其第一个参数用来设置this指向,其余的参数序列插入到原函数的参数序列之前。所以我们来分析一下bind()方法的特点:
1、返回一个函数
2、可以传入参数

返回函数的模拟实现

首先简单模拟一下bind方法的第一个特点:

  1. // bind原函数演示
  2. var foo = {
  3. value: 1
  4. }
  5. function bar() {
  6. console.log(this.value);
  7. }
  8. var bindFoo = bar.bind(foo); // 返回一个函数
  9. bindFoo(); // 1
  10. // 模拟实现bind()方法
  11. Function.prototype.bind2 = function(context) {
  12. var self = this;
  13. return function() { // 创建一个新的函数
  14. return self.apply(context); // 考虑到绑定的函数可能有返回值
  15. }
  16. }
  17. var bind2Foo = bar.bind(foo);
  18. bar2Foo(); // 1
  19. // 带返回值的示例
  20. function baz() {
  21. return this.value;
  22. }
  23. var bind2Baz = baz.bind(foo);
  24. bind2Baz(); // 1

传参的模拟实现

在MDN定义中指出bind方法的传参比较特别,bind()方法绑定的参数放在原函数参数之前。用一个示例来了解一下

  1. var foo = {
  2. value: 1
  3. }
  4. function bar(name, age) {
  5. console.log(name);
  6. console.log(age);
  7. console.log(this.value);
  8. }
  9. var bindFoo = bar.bind(foo, 'Tom');
  10. bindFoo(18); // 'Tom' 18 1

以上函数需要传name和age两个参数,奇怪的是竟然可以在bind绑定的时候先传一个参数,执行新函数的时候再传另一个参数,那我们该怎么模拟呢,看来要对arguments进行处理了:

  1. Function.prototype.bind2 = function(context) {
  2. var self = this;
  3. // 截取bind2函数从第二个到最后一个的参数
  4. // 这里为什么要使用Array.prototype.slice.call呢?
  5. // 因为arguments不是一个真正的数组,而是类数组,没有push、shift这些操作,所以需要用Array.prototype上的方法来模拟slice
  6. var args = Array.prototype.slice.call(arguments, 1);
  7. return function() {
  8. // 此时的arguments是指bind返回的函数传入的参数
  9. var bingArgs = Array.prototype.slice.call(arguments);
  10. return self.apply(context, args.concat(bingArgs));
  11. }
  12. }
  13. var bind2Foo = bar.bind(foo, 'sam');
  14. bind2Foo(18); // 'sam' 18 1

构造函数的模拟实现

完成以上两点,最难的部分到了,因为bind还有一个特点,就是一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用的参数被提供给模拟函数。通俗的说就是bind函数返回的新函数作为构造函数的时候,bind绑定的this值会失效,但是传入的参数依然生效。用示例说明:

  1. var value = 2;
  2. var foo = {
  3. value: 1
  4. };
  5. function bar(name, age) {
  6. this.favorate = 'games';
  7. console.log(name);
  8. console.log(age);
  9. console.log(this.value);
  10. }
  11. bar.prototype.friend = 'kevin';
  12. var bindFoo = bar.bind(foo, 'sam');
  13. var obj = new bindFoo('18'); // undefined 'sam' '18'
  14. obj.favorate; // 'games'
  15. obj.friend; // 'kevin'

从这个示例可以看出,尽管在全局和foo中都声明了value值,但是依然返回了undefined,说明bind绑定的this失效了,如果大家对new操作符有了解的话,应该明白此处的this指向obj实例。所以我们可以通过修改返回的函数的原型来实现,实现如下

  1. Function.prototype.bind2 = function(context) {
  2. var self = this;
  3. var args = Array.prototype.slice.call(arguments, 1);
  4. var fBound = function() { // fBound就是bing绑定后返回的函数
  5. var bingArgs = Array.prototype.slice.call(arguments);
  6. // 当作为构造函数时,this指向实例,此时结果为true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值
  7. // 以上面的demo为例,如果改成`this instanceof fBound ? null : context`,实例只是一个空对象,将null改成this(fBound),实例就具有favorate属性了
  8. // 当作为普通函数时,this指向window,此时结果为false,将绑定函数的this指向context
  9. return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
  10. }
  11. // 修改返回函数的prototype为绑定函数的prototype,实例就可以继承绑定函数的原型中的值
  12. fBound.prototype = this.prototype;
  13. return fBound;
  14. }

但是在这个写法中,我们直接将fBound.prototype = this.prototype,我们直接修改fBound.prototype的时候,也会直接修改绑定函数的prototype。这个时候,我们可以通过一个空函数来进行中转:

  1. Function.prototype.bind2 = function(context) {
  2. var self = this;
  3. var args = Array.prototype.slice.call(arguments, 1);
  4. var fNop = function() {};
  5. var fBound = function() {
  6. var bindArgs = Array.prototype.slice.call(arguments);
  7. return self.apply(this instanceof fNop ? this : context, args.concat(bindArgs));
  8. }
  9. fNop.prototype = this.prototype;
  10. fBound.prototype = new fNop();
  11. return fBound;
  12. }

小问题优化

假如调用bind的不是函数咋办呢?我们需要对调用函数类型进行一个判断

  1. if (typeof this !== 'function') {
  2. throw new Error('Function. prototype.bind - what is trying to be bind is not callable')
  3. }

如果这个代码放在线上用,需要做个兼容

  1. Function.prototype.bind2 = Function.prototype.bind || function() {
  2. ……
  3. }

最终代码

  1. Function.prototype.bind2 = function(context) {
  2. var self = this;
  3. var args = Array.prototype.slice.call(arguments, 1);
  4. var fNop = function() {};
  5. var fBound = function() {
  6. var bindArgs = Array.prototype.slice.call(arguments);
  7. return self.apply(this instanceof fNop ? this : context, args.concat(bindArgs));
  8. }
  9. fNop.prototype = this.prototype;
  10. fBound.prototype = new fNop();
  11. return fBound;
  12. }

到这里bind函数的模拟算是完成了,这篇文章的内容参考自冴羽大神的博客,其中还有一些细节还不是很熟悉,需要多看几遍,这篇文档涉及到的知识点很多,还需要注重基础,基础扎实了,理解起这些概念才会事半功倍。
学习,任何时候开始都不算晚~~~