本文共 4291 字,大约阅读时间需要 14 分钟。
bind 函数对于写react的人来说并不陌生。哦!是的,没错我的朋友,它的一个用处就是用来改变函数this指向的。如果细究一下bind的实现,发现里面还是有不少东西的,我们今天展开讨论一下。
在说bind之前呢,我们还要先来讲讲我们的老熟人this。说到this,我们在也有提到过,this的工作方式。今天我们再来看看它的四种绑定规则
1.默认绑定
独立函数调用时,this
指向全局对象,如果使用严格模式,那么全局对象无法使用默认绑定,this
绑定至undefined
并抛错(TypeError: this is undefined)
2.隐式绑定
当函数作为引用属性被添加到对象中,隐式绑定规则会把函数调用中的this
绑定到这个上下文对象
3.显示绑定
运用apply call 方法,在调用函数时候绑定this,也就是指定调用的函数的this值
4.new绑定
就是使用new操作符的时候的this绑定
上述四条规则优先级由上到下依次递增。
由于js多样的绑定规则,带来了绑定隐式丢失问题,即函数中的this
丢失绑定对象,即它会应用第 1 条的默认绑定规则,从而将this
绑定到全局对象或者undefined
上。
例如:绑定至上下文对象的函数被赋值给一个新的函数,然后调用这个新的函数时
var obj = { a: 2, foo: function () { console.log(this.a) }}var a = 2setTimeout(obj.foo, 0) // 2
还记得我们当年我们是怎么做的吗?
...var me = this;return function () { me.xxx()}...
还有就是用call 或者apply来显示的绑定:
function foo() { console.log( this.a); }var obj = { a: 2 };var bar = function() { foo.call(obj); };bar(); // 2setTimeout(bar, 100); // 2
由于这种用法太多了,所以呢ES5的时候给出了一个方法,自从有了它,前端工程师的生活似乎好过了很多,一个bind可以解决很多问题。
给出的定义是:
bind()方法创建一个新的函数, 当这个新函数被调用时其this置为提供的值,其参数列表前几项置为创建时指定的参数序列。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
bind() 函数会创建一个新 绑定函数, 绑定函数与被调函数具有相同的函数体(在 ECMAScript 5 中)。调用 绑定函数通常会导致执行 包装函数 绑定函数也可以使用new运算符构造:这样做就好像已经构造了目标函数一样。提供的 this值将被忽略,而前置参数将提供给模拟函数
总的来说bind有如下三个功能点:
其实有时候去研究这些JS API的实现还是蛮好玩的,你能学到很多知识。今天我们就手摸手写一下吧。
从bind的定义描述中可以看到,我们要写的这个函数的输入输出基本确定了:
// 定义这个方法为myBindFunction.prototype.myBind = function(thisArg) { if (typeof this !== 'function') { return; } var _self = this; var args = Array.prototype.slice.call(arguments, 1) //从第二个参数截取 return function() { return _self.apply(thisArg, args.concat(Array.prototype.slice.call(arguments))); // 注意参数的处理 }}
我们来测试一下:
function foo(name) {this.name = name;}var obj = {}//上下文 功能 donevar bar = foo.myBind(obj)bar('jack')console.log(obj.name) //'jack'// 参数 功能 donevar tar = foo.myBind(obj, 'rose');tar()console.log(obj.name) //'rose'// new 功能 errorvar alice = new bar('alice')console.log(obj.name) //alice obj name should be 'jack'console.log(alice.name) //undefined, alice name should be 'alice'
可以看到使用new
实例化被绑定的方法,上下文还指向了传入的obj,这个方法有点问题,我们需要考虑到的是在myBind的实现里面,需要检测new的操作
我们先考虑一下new操作符在调用构造函数时做了哪些操作?
比如说 var a = new b()
{}
.__proto__ = b.prototype
所以我们做了如下修改:
Function.prototype.myBind = function(thisArg) { if (typeof this !== 'function') { return; } var _self = this; var args = Array.prototype.slice.call(arguments, 1) var fnBound = function () { // 检测 New // 如果当前函数的this指向的是构造函数中的this 则判定为new 操作 var _this = this instanceof _self ? this : thisArg; return _self.apply(_this, args.concat(Array.prototype.slice.call(arguments))); } // 为了完成 new操作 // 还需要做一件事情 执行原型 链接 (思考题,为什么? fnBound.prototype = this.prototype; return fnBound;}
测试OK了:
function foo(name) {this.name = name;}var obj = {};var bar = foo.myBind(obj);bar('Jack');console.log(obj.name); // Jackvar alice = new bar('Alice');console.log(obj.name); // Jackconsole.log(alice.name); // Alice
这个用例中我们来讨论一下
bar的this指向:
所以fnBound.prototype = this.prototype 是有必要的,!!但注意 这个原型赋值是有问题的:
原因我在这里先不说了留给各位讨论啦。
Function.prototype.myBind = function(thisArg) { if (typeof this !== 'function') { return } var _self = this var args = Array.prototype.slice.call(arguments, 1) var fnNop = function () {} // 定义一个空函数 var fnBound = function () { var _this = this instanceof _self ? this : thisArg return _self.apply(_this, args.concat(Array.prototype.slice.call(arguments))) } // 维护原型关系 if (this.prototype) { fnNop.prototype = this.prototype; } fnBound.prototype = new fnNop(); return fnBound;}
这里我们创建了一个空函数来做中间人,承接原函数的原型丢给绑定函数,好了问题就搞完了,是不是很多知识点慢慢看吧,我去踢球去了。
转载地址:http://rjkei.baihongyu.com/