Javascript通过bind()掌控this

canca8年前 (2016-08-01)JavaScript326
摘要
.bind()创建了一个函数,当这个函数在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。
    最近博客的更新明显跟不上脚步,不是因为最近什么都没看不知道写什么,而是因为最近工作比较忙看了好多东西以至于一时无从下手。东西要整理的太多了,还有很多不足啊,给自己打打气,一点点来~不能懈怠!

    今天看到公司大神的一段代码:

function ReplaceProcessor() {
this._dom = {
btnReplace : $('#ro_btnReplace'),
btnComplete: $('#ro_btnComplete')
};
// Bind events
this._dom.btnReplace.on('click', this._onReplace.bind(this));
this._dom.btnComplete.on('click', this._onComplete.bind(this));
}

ReplaceProcessor.prototype._onReplace = function(){
// code
this._dom.btnComplete.html("OK");
}

这里面最后两行代码是向DOM节点上绑定事件,"this._onReplace.bind(this)"明显就是绑定的执行函数,在不知道具体作用的情况下猜测一下bind()的作用可能和call或者apply类似,用来改变function执行时的上下文环境,不知道理解的对不对所以找资料来印证一下。

    先上官网的解释,不管我们个人的解释是多么的接地气,官方API到底还是比较靠谱的:
bind方法会创建一个新函数,称为绑定函数.当调用这个绑定函数时,绑定函数会以创建它时传入bind方法的第一个参数作为this,传入bind方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数.

这个解释多读几次还是很靠谱的,不是很难懂。从功能描述上看和call以及apply还是有区别的,应用的场景不太一样,bind主要是为了改变函数内部的this指向,这个是在ECMA5以后加入的,所以IE8一下的浏览器不支持,当然有兼容的办法,不过坦白说首先对于IE8以下实在无爱,其次那种情况下估计你也没什么心情用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 && oThis ? this : oThis || window,
aArgs.concat(Array.prototype.slice.call(arguments)));
};

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();

return fBound;
};}

东西就是这么个东西,最主要的还是应用的场景,什么情况下使用。本文一开始代码中使用.bind(this)的效果就相当于将事件绑定的callback抽出来写,但是同时还维持了函数中的this指向。本来事件绑定的处理函数一般是一个匿名函数,这里相当于单独抽出来从而使结构更清晰的同时,this指向的是ReplaceProcessor的实例。

    这里列举三部分的代码来说明bind能为我们做些什么,同时它的好处在哪里。

    (一)事件处理函数

    所谓的事件处理函数其实就是绑定事件后的那个callback,这里如果用上bind你的代码应该会简洁优雅一些,我在开篇列出的那段代码里就是这样做的。

var logger = {
x: 0,
updateCount: function(){
this.x++;
console.log(this.x);
}
}

// 下面两段代码的实现是一样的

document.querySelector('button').addEventListener('click', function(){
logger.updateCount();
});

document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));

  如何,这就是我之前说的,本来通常情况下处理函数都要用一层匿名函数包裹一下,才能维持处理函数本身的this.这里直接通过.bind(logger)人为的将其执行时的this指向logger对象。

.bind()创建了一个函数,当这个函数在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。

(二)setTimeout

function LateBloomer() {
this.petalCount = Math.ceil( Math.random() * 12 ) + 1;
}

// declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout( this.declare.bind( this ), 1000 );
};

LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' + this.petalCount + ' petals!');
};

看一下这里this.dclare.bind(this),相当于将LateBloomer的实例对象传递到declare中,是不是setTimeout简洁了很多,同时不会破坏其他执行函数的结构。

    (三)请完整阅读下面的代码

//设立一个简单地对象作为“上下文”
var context = { foo: "bar" };

//一个在this上下文中指向foo变量的函数
function returnFoo () {
return this.foo;
}

// 变量在作用域中不存在,因此显示undefined
returnFoo(); // => undefined

// 如果我们把它绑定在context上下文中
var bound = returnFoo.bind(context);

// 现在的作用域中有这个变量了
bound(); // => "bar"

//
// 这就是Function.prototype.bind的作用.
//由于returnFoo也是函数,因此它继承了function的原型
//
// 如果你觉得享受,接着往下读,下面更精彩
//

// 有许多方法将函数绑定在一个上下文中
// Call和Apply让你能在上下文中调用函数
returnFoo.call(context); // => bar
returnFoo.apply(context); // => bar

// 将函数添加到对象中
context.returnFoo = returnFoo;
context.returnFoo(); // => bar

//
// 现在我们来玩一点诡异的东西
//

// Array.prototype 中有一个叫做slice的方法
// 对一个数组调用slice,可以返回一个从start index到end index的数组
[1,2,3].slice(0,1); // => [1]

// 因此我们把Array.slice赋值给一个本地变量slice
var slice = Array.prototype.slice;

//现在的slice是"自由的",由于Array.prototype中的slice一般指定了上下文
//或者默认为this,此时slice将不起作用
slice(0, 1); // => TypeError: can't convert undefined to object
slice([1,2,3], 0, 1); // => TypeError: ...

// 但是如果我们使用call或者apply,slice又将在一个上下文中执行
slice.call([1,2,3], 0, 1); // => [1]

// Apply和Call差不多,知识参数要放在一个数组中
slice.apply([1,2,3], [0,1]); // => [1]

// 使用call没错了,那么能不呢使用bind呢?
// 没错,我们来把"call"绑定在slice上
slice = Function.prototype.call.bind(Array.prototype.slice);

// 现在slice可以把第一个参数作为上下文了
slice([1,2,3], 0, 1); // => [1]

//
// 很酷,对吧。现在再来完成一件事
//

// 现在我们对bind本身做一件刚才对silce做的事
var bind = Function.prototype.call.bind(Function.prototype.bind);

// 在这里总结一下,好好想想
// 发生了什么事? 我们改变了call,
// 返回一个接收一个函数和一个上下文作为ic桉树的函数
//并且返回了一个完全绑定的函数

// 回到最初的例子
var context = { foo: "bar" };
function returnFoo () {
return this.foo;
}

// 现在来使用神奇的"bind"函数
var amazing = bind(returnFoo, context);
amazing(); // => bar

 最后第三部分的代码来自一段译文:https://variadic.me/posts/2013-10-22-bind-call-and-apply-in-javascript.html,代码很好所以忍不住拿来用,十分感谢.

    补充一段代码,关于Ajax的回调中,如何保持this:

$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: {'info': info}
})
.done((function(data) {
if(data.status){
// 这里this指向的是外层bind进来的this
this._data.process_type = info.process_type;
}else{
uUnique.noticeBox.showWarning(data.message);
}
}).bind(this));

 OK,感觉说到这里bind是做什么的应该么可以理解了,最近打算写写如何在没有MVC的库的状态下,写出带有MVC思想的代码~GO.

相关文章

JavaScript 函数原型对象

    以下是一篇本人在Qzone里写下的文章,现在放到这里来,欠丑了。希望对一些初学者有一点点帮助。     今天给大家说说Ja...

《Dom Scripting》读书笔记

  《Dom Scripting》读书笔记 Canca         最近看了一本《Jeremy Keith: DOM Scri...

javascript MailTo 邮件技巧

调用email的方法 //<a href="mailto:talantlee@126.com">Email</a>window.location.href="mail...

javascript 中面向对象編程 (类的继承)

// 人的基類var Person=new ( function(){  var sex;  var ...

javascript 弹出式窗体详解

1>window.prompt(text, value) 簡單的基與模態窗體的對話框,(返回你輸入)   var v=window.prompt("提示","請輸入你的名字"...

javascript 中面向对象编程 (类的构造)

javascript 中面向对象编程 (类的构造)

不論是用java,還是c#,又或是vb,構建一個對象都很簡單,都可以采用 classobj =new classobj()的方法構造一個類,然后使用其中的屬性以及方法,其實javascript也是一樣...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。