Function.prototype.bind及其polyfill分析

本文深入探讨JavaScript的Function.prototype.bind方法,详细解释如何绑定this、提前传入参数、结合使用及与call/apply的区别。同时,文章通过实例介绍了bind的polyfill实现,揭示了其内部涉及的闭包和原型链机制,以此巩固JavaScript基础知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Function.prototype.bind执行会返回一个新的函数,并将this关键字设置为指定的值。并可以在执行该返回的函数之前传入参数。

语法

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

  • thisArg:表示返回的函数中this的指向。
  • [, arg1[, arg2[, ...]]]:表明参数是可选的。

使用方法

绑定this

var obj = {
  name: 'real',
  getName: function() {
    console.log(this.name)
  }
}
var name = 'window';

obj.getName(); // "real"

var getName = obj.getName;
getName(); // "window";

var getName2 = obj.getName.bind(obj);
getName2(); // "real";

上述getName2方法中this指向了obj

偏函数(提前传入参数)

var log = function log(...args) {
  console.log(args);
}
log(1,2,3); // [1, 2, 3]

var log2 = log.bind(null, 0);
log2(1,2,3); // [0, 1, 2, 3]

提前传入函数0。

结合setTimeout

setTimeout(fn, delay)。其中fn中的this默认指向的是全局window/global

function Foo(name){ this.name = name; };
Foo.prototype.getName = function(){ console.log(this.name); }
Foo.prototype.declare = function() { setTimeout(this.getName, 100); }

var name = 'window';
var foo = new Foo('real');
foo.declare(); // 'window'

setTimeout(this.getName, 100);中的this指向的foo对象,可以找到Foo.prototype.getName。没能执行预期结果是因为Foo.prototype.getName中的this指向了window。如果改成:

setTimeout(this.getName.bind(this), 100);

可以取得预期效果,打印real

结合new

这里大致说一下。你不知道的javascript 上卷一书中提到this有四种绑定方式。
① 默认绑定到window

var name = 'real';
console.log(this.name);

②隐式绑定到所在对象

var obj = {
  name: 'real',
  getName: function() { return this.name; }
}
console.log(obj.getName()) // 'real';

③显示绑定Function.prototype.apply/call及其变种Function.prototype.bind
new绑定。

function Foo(name) { this.name = name; }
var foo = new Foo('real');
console.log(foo.name); // 'real'

其中,优先级从①到④依次增高。

Function.prototype.apply/call区别

区别在于Function.prototype.bind不会执行被绑定的函数,并且返回一个新的函数。而Function.prototype.apply/call会执行被绑定的函数,并且被绑定的函数中的this发生偏移,并且传入参数。

polyfill的理解

bind 函数在 ECMA-262 第五版(es5)才被加入;它可能无法在所有浏览器上运行。你可以部分地在脚本开头加入以下代码,就能使它运作,让不支持的浏览器也能使用 bind() 功能。

if (!Function.prototype.bind) { 
  Function.prototype.bind = function(oThis) {
    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 aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

以一个demo为例。

function foo(name) { this.name = name; } 
var obj = { name: 'real'};
var bar = foo.bind(obj);

bar('123'); // 注1
console.log(obj.name); // 123

var baz = new bar('abc'); // 注2
console.log(baz.name); // 'abc'

接下来逐步分析。

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');
    }

这个很好理解。一般我们使用bind的方式是fn.bind(obj)。这里的this就是fn函数。上述demo中的foo

if (this.prototype) {
  // Function.prototype doesn't have a prototype property
  fNOP.prototype = this.prototype; 
}

同理这里的this也是指上述demo中的foo。所以foo.prototype存在,设置fNOP.prototype = this.prototype;

接下来就是最关键的地方了:

var aArgs   = Array.prototype.slice.call(arguments, 1),
    fToBind = this,
    fNOP    = function() {},
    fBound  = function() {
      return fToBind.apply(this instanceof fNOP
             ? this
             : oThis,
             aArgs.concat(Array.prototype.slice.call(arguments)));
    };

首先要明确的是bind函数返回的就是fBound函数,也就是外部的bar函数。并且fToBind = this,这里的this指向的还是还是demo中的foo函数。

最核心的这一行

this instanceof fNOP ? this : oThis, // 注3

的作用是区分fBound函数的调用方式。

如果是通过上述demo中的bar('123');这种方式调用,那么注3中的this就是window,this instanceof fNOP ? this : oThis返回oThis,也就是demo中的obj对象。那么就相当于执行foo.apply(obj, 123),其结果就是改变了obj.name的值。

如果是通过var baz = new bar(‘abc’)这种方式调用。我们知道,使用new一个构造函数的过程中,会返回一个对象。并且构造函数中的this(在这里也就是foo中的this)会和该返回的对象baz绑定。所以this instanceof fNOP ? this : oThis中的this就是该对象baz。由于fBound.prototype = new fNOP();,所以fBound.prototype.__proto__指向FNOP.prototype。并且上面提到,bar函数就是fBound函数。所以baz是bar的一个实例,也就是fBound的一个实例。所以会有baz.__proto__指向fBound.prototype。并且fBound.prototype.__proto__指向FNOP.prototype,后者继续指向foo.prototype…所以baz的原型链上层是存在FNOP.prototype的。换言之,this instanceof fNOP ? this : oThis就返回this,也就是baz这个新new出来的对象。所以bar.name的结果是abc

总结

主要讲了bind函数的基本用法,this的多种绑定方式。并且polyfill中大量使用了闭包和原型链的技巧,都是js中的基础。想想看,我们的js基础真的扎实吗!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值