Function.prototype.bind及其polyfill分析

来源:互联网 发布:linux run parts 编辑:程序博客网 时间:2024/06/05 09:37

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'); // 注1console.log(obj.name); // 123var baz = new bar('abc'); // 注2console.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基础真的扎实吗!

原创粉丝点击