今天聊一聊js中的bind方法,主要从三个维度来阐述:why——>what——>how。文章虽经我的屡次校验,对语言表述、代码书写等进行了认真审核,但仍免不了有疏漏之处,如若发现,还望指出,鄙人将审而改之,如如有不爽之处,还望轻喷,理性交流,共同进步也。html
bind是ECMAscript5新增的一个方法,ECMAscript是js的编程语言实现(详情可阅相关资料),ECMAscript5是当前主流浏览器的通用支持版本,这个版本的出现很大程度上是为了解决js这门语言在诞生到发展过程当中出现的大量问题而提出的解决方案版本,在早期,js定位为“网页小助手”语言,只负责作简单的校验表单字段小活,一度还沦为广告弹框专属语言,由于其尴尬的定位,因此js充满各类意想不到的坑,你们一直也不怎么重视它,直到基于Ajax技术的Gmail项目诞生(Gmail项目不是直接缘由,这里只是借机聊下js历史),你们才发现利用js能够作出这么多牛逼的交互,一时间,各大公司蜂拥而至,大公司的项目每每预示着项目的复杂和多人协做,当项目一复杂后你们发现js的缺点就暴露出来了,js虽然在其名里面包含了Java,但其命名只属于取巧沾光,Java面向对象编程的特性可没被js吸取,js语言更具函数式编程特性,函数为js语言的一等公民,当函数越写越多之时,管理他们的艺术就被提上了台面,为了复杂项目开发的规范化、统一化,js迫切须要引入面向对象的相关思想,但面向对象属于语言灵魂层次,js做为函数式编程使用了这么多年,不可能想改就改灵魂层次的东西,为了兼顾函数式编程的灵活和面向对象编程的规范,js开发的相关组织作了不少努力,其中一个努力就是创造出了bind、call、apply三个媒婆,这三个媒婆的共同做用就是为js的一等公民Function函数找个门当户对的人家(指明Function函数的this指向)。node
我定义了一个类:编程
var Xiaoming = {
name: '小明',
sayHi() {
console.log('hello ' + this.name);
}
}
复制代码
若是我想使用这个类的sayHi
功能,一开始,我想到的是直接拿来就用:浏览器
var Xiaoming = {
name: '小明',
sayHi() {
console.log('hello ' + this.name);
}
}
var sayHi = Xiaoming.sayHi;
sayHi(); // hello
复制代码
不出意外,将会输出hello
(在严格模式下将会直接报出cannot read property 'name'
错误),缘由就是若是直接拿来用,这里的this将会隐式指向到全局window
对象,而全局对象中并无name属性。在js中,当没有明确指定this的状况下,置于全局环境下的函数的this将会是window(注:浏览器环境下为window,node环境下为global,其它宿主环境本篇不作解释,本篇文章涉及的宿主环境都是浏览器)。app
function func() {
console.log(this.toString()); // [object Window]
}
func();
复制代码
window是全局环境下this的最终归属(若是你无家可归,你的家就是这片天地),若是咱们想给这些无家可归的可怜函数找一个归属,咱们须要一个中介来牵线搭桥,bind就是那个中介之一,bind在js中充当粘合剂的做用,他负责把指定的类和Function函数强力的粘贴在一块儿:编程语言
var Xiaoming = {
name: '小明',
sayHi() {
console.log('hello ' + this.name);
}
}
var Jack = {
name: '杰克'
};
var sayHello = Xiaoming.sayHi.bind(Jack);
sayHello(); // hello 杰克
复制代码
当咱们用bind粘贴剂把sayHi
方法和Jack类粘贴在一块儿时,sayHello
函数的this就指向Jack类了,因此输出的结果就是hello 杰克
。ide
vt. 绑;约束;装订;包扎;凝固 vi. 结合;装订;有约束力;过紧函数式编程
在汉语释义中,bind的大致意思就是绑定、结合,我我的给其在js中的定义为胶水(注意胶水二字!)。当我想给一个函数换一个新宿主之时,我就取出“bind牌胶水”把想用的函数和它的新宿主粘贴在一块儿,而后再调用这个用“bind牌胶水”粘贴的拥有新宿主的新函数。函数
The bind( ) method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.post
bind( )方法建立了一个新函数,当新函数被调用之时,将其this指向到指定的值,同时会经过bind传入一串预设参数序列供新函数使用。
在这段定义中我抽出了几个细节:
var Xiaoming = {
name: '小明',
sayHi() {
console.log('hello ' + this.name);
}
}
var Jack = {
name: '杰克'
};
var sayHello = Xiaoming.sayHi.bind(Jack);
console.log(xiaoming); // {name: "小明", sayHi: ƒ}
复制代码
var Xiaoming = {
name: '小明',
sayHi() {
console.log('hello ' + this.name);
}
}
var Jack = {
name: '杰克'
};
var sayHello = Xiaoming.sayHi.bind(Jack);
sayHello(); // hello 杰克
Jack.name = '皆可'; // 改变新宿主的name属性
sayHello(); // hello 皆可 <—— 当新宿主发生改变时,对应的输出也会受影响
/* 并无把函数绑定到新宿主上 */
Jack.sayHi(); // error: Uncaught TypeError: Jack.sayHi is not a function
Jack.sayHello(); // error: Uncaught TypeError: Jack.sayHello is not a function
复制代码
“bind牌胶水”的主要做用是给指定函数绑定指定this,第一个参数即指定的新宿主,其后的剩余参数为预设参数,既然第一个参数已经达到了目的,为何还要在其后加一些预设参数呢?这里要注意参数的预设二字,预设表示预先设定给新函数的参数,经过bind预设的参数将会比新函数本身设定的参数预先使用。看代码:
var obj1 = {
name: 'han',
sayHi(word1, word2) {
console.log('hello' + this.name + ',' + word1 + ',' + word2);
}
};
var obj2 = {
name: '李'
};
var func = obj1.sayHi.bind(obj2, '早上好');
func('good morning'); // hello 李,早上好, good morning
func(); // hello 李,早上好, undefined
复制代码
经过代码咱们发现这个预设参数和默认参数有点相似(但其实彻底不是一回事!),由于经过bind预设的参数老是先被调用,而使用新函数时自定义的参数老是等预设参数调用后再被调用,相似栈的概念(先进先出)。这个预设参数的设计,我我的以为略显尴尬,多是由于js的函数以前没有默认参数的设定致使的吧(不甚了解)?这里用默认参数我的以为会更合适。
在改变this指向的方法中,存在着三个方法:bind、call、apply,bind属于“静态绑定”,做为胶水,bind只负责粘贴函数,不负责粘贴以后的函数的运行,但call和apply却不是,他们给函数绑定this后还把绑定后的函数给当场运行了。由于这个特性,咱们在给事件绑定函数时只能使用bind来进行this的绑定(由于给事件绑定的函数不须要咱们手动执行,它是在事件被触发时由JS 内部自动执行的),看代码:
<button id="btn">Click Me</button>
复制代码
var obj = {
thing: '搞点事情'
};
function onBtnClick() {
console.dir('我被点击了,我想' + this.thing);
}
var btn = document.getElementById('btn');
btn.addEventListener('click', onBtnClick.bind(obj)); // 我想搞点事情
复制代码
在js编程中,常常会出现使用var that = this;
的hack黑魔法来给函数找回自我,具体场景以下:
var obj = {
datas: ['jack'],
resolveDatas: function() {
var that = this;
this.datas.forEach(function(val) {
that.name = val;
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
console.log(that); // {datas: Array(1), resolveDatas: ƒ, name: "jack"}
});
console.log(this.name); // jack
}
}
obj.resolveDatas();
复制代码
在上面的代码中,当在内部函数内部使用匿名函数时,this将会指向到全局window对象,为了不这个问题,在函数内部经过var that = this
声明了一个变量,而后在forEach的匿名函数中调用,为何要这样使用?这是由于在没出现ES6的箭头函数以前,js存在着一个“任性this”,关于js中this的复杂度,在《你不知道的js(上卷)》中写到:
this 关键字是js 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在 全部函数的做用域中。可是即便是很是有经验的js 开发者也很难说清它到底指向 什么。 任何足够先进的技术都和魔法无异。 ——Arthur C. Clarke
关于js中this的指向黑魔法问题这里只略提,具体可查阅相关权威资料。在上面的代码中,咱们发现一个哭笑不得的现实:好好的一个函数,咋包了一层函数后就找不到设想中的那个this了呢?本觉得本身把this指向到了当前的obj对象,一到用的时候就直接“认贼做父”了,把this指向到了window对象,what the hell?,如何避免这种悲剧?让咱们有请“bind牌胶水”隆重登场,做为专业的“this硬绑定”方法,bind用起来妥妥的:
var obj = {
datas: ['jack'],
resolveDatas: function() {
this.datas.forEach((function(val) {
this.name = val;
console.log(this); // {datas: Array(1), resolveDatas: ƒ, name: "jack"}
}).bind(this));
console.log(this.name); // jack
}
}
obj.resolveDatas();
复制代码
在上面的代码中,咱们经过“bind牌胶水”把真正想用的对象粘贴给了匿名函数,从而让匿名函数可以坚持自我,但在这里我的以为这种硬绑定是一种笨拙的hack方法,由于针对这种诡异问题居然要用胶水进行“修补”,我的以为其实很low,因此不予提倡,Es6提出箭头函数才是专业应对该问题的合适方案:
var obj = {
datas: ['jack'],
resolveDatas: function() {
this.datas.forEach((val) => {
this.name = val;
console.log(this); // {datas: Array(1), resolveDatas: ƒ, name: "jack"}
});
console.log(this.name); // un
}
}
obj.resolveDatas();
复制代码
关于bind的使用方,我这里只列举出了两个,更多的使用场景还有不少,能够查阅相关资料。
花了好几天时间终于写完了这篇文章,但愿相关内容能给你们带来一些启发和感悟。通常讲bind的时候都会把call和apply放在一块儿聊,我本有此意,但考虑到本身的啰嗦话语,内容过长,因此仍是分开讲解,下一篇文章我来聊聊apply和call方法(由于这两个方法就是孪生兄弟,因此一块儿讲再合适不过了),而后到下篇再对bind、apply、call三者进行对比来阐述,若知后事如何,且听下回分解!