... Function.prototype.bind = function(obj) { var args = Array.prototype.slice.call(arguments, 1); //记录下所有第一次传入的参数 var self = this; return function () { self.apply(obj, args.concat(Array.prototype.slice.call(arguments))); } }
当使用bind进行绑定之后,即不能再通过call,apply进行修正this指向,所以bind绑定又称为硬绑定。
3. 使用new关键字进行绑定
在js中,函数有两种调用方式,一种是直接进行调用,一种是通过new关键字进行构造调用。
function fun(){console.log("function called")} //直接调用 fun() //function called //构造调用 var obj = new fun() //function called
那普通的调用和使用new关键字的构造调用之间,又有哪些区别呢?
准确的来说,就是new关键字只是在调用函数的基础上,多增加了几个步骤,其中就包括了修正this指针到return回去的对象上。
var a = 5; function Fun() { this.a = 10; } var obj = new Fun(); obj.a //10
几种绑定方式的优先级比较
以下面这个例子来进行几种绑定状态的优先级权重的比较
var obj1 = { info: "this is obj1", getInfo: () => console.log(this.info) } var obj2 = { info: "this is obj2", getInfo: () => console.log(this.info) }
1. call,apply和默认指向比较
首先很显然,根据使用频率来想,使用call和apply会比直接调用的优先级更高。
obj1.getInfo() //this is obj1 obj2.getInfo() //this is obj2 obj1.getInfo.call(obj2) //this is obj2 obj2.getInfo.call(obj1) //this is obj1
使用call和apply相比于使用new呢?
这个时候就会出现问题了,因为我们没办法运行类似 new function.call(something)这样的代码。所以,我们通过bind方法返回一个新的函数,再通过new判断优先级。
var obj = {} function foo(num){ this.num = num; } var setNum = foo.bind(obj); setNum(10); obj.num //10 var obj2 = new setNum(20); obj.num //10 obj2.num //20
通过这个例子我们可以看出来,使用new进行构造调用时,会返回一个新的对象,并将this修正到这个对象上,但是它并不会改变之前的对象内容。
那么问题来了,上面我们写的bind的polyfill明显不具备这样的能力。而在MDN上有一个bind的polyfill方法,它的方法如下:
if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "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 || this, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
上面的polyfill首先判断需要绑定的对象是否为函数,防止使用Function.prototype.bind.call(something)时,something不是一个函数造成未知错误。之后让需要返回的fBound函数继承自this,返回fBound。
特殊情况
当然,在某些情况下,this指针指向也存在一些意外。
箭头函数
ES6中新增了一种定义函数方式,使用"=>"进行函数的定义,在它的内部,this的指针不会改变,永远指向最外层的词法作用域。
var obj = { num: 1, getNum: function () { return function () { //this丢失 console.log(this.num); //此处的this指向window } } } obj.getNum()(); //undefined var obj2 = { num: 2, getNum: function () { return () => console.log(this.num); //箭头函数内部绑定外部getNum的this,外部this指向调用的对象 } } obj2.getNum()(); //2
软绑定
上面提供的bind方法可以通过强制修正this指向,并且再不能通过call,apply进行修正。如果我们希望即能有bind效果,但是也能通过call和apply对函数进行二次修正,这个时候就需要我们重写一个建立在Function.prototype上的方法,我们给它起名为"软绑定"。
if (!Function.prototype.softBind) { Function.prototype.softbind = function (obj) { var self = this; var args = Array.prototype.slice.call(arguments, 1); return function () { return self.apply((!this || this === (window || global)) ? obj : this, args.concat(Array.prototype.slice.call(arguments))); } } }
bind,call的妙用