bind函数
bind 函数挂在 Function 的原型上
Function.prototype.bind
建立的函数都可以直接挪用 bind,运用:
function func(){
console.log(this)
}
func.bind(); // 用函数来挪用
bind 的作用:
bind() 要领挪用后会建立一个新函数。当这个新函数被挪用时,bind() 的第一个参数将作为新函数运转时的 this的值,以后的序列参数将会在通报的实参前传入作为新函数的参数。<MDN>
bind 吸收的参数
func.bind(thisArg[,arg1,arg2…argN])
- 第一个参数thisArg,当 func 函数被挪用时,该参数会作为 func 函数运转时的 this 指向。当运用 new 操作符挪用绑定函数时,该参数无效。
- [,arg1,arg2…argN] 作为实参通报给 func 函数。
bind 返回值
返回一个新函数
注重:这和函数挪用 call/apply 转变this指向有所不同。挪用call/apply 会把原函数直接实行了。
举个例子申明:
function func(){
console.log(this)
}
// 用call
func.call({a:1}); // func函数被实行了,打印:{a:1}
// 用bind
let newFunc = func.bind({}); // 返回新函数
newFunc(); // 只有当返回的新函数实行,func函数才会被实行
从以上获得以下信息:
- bind被函数挪用
- 返回一个新函数
- 能转变函数this指向
- 可以传入参数
深切bind 运用
以上知道了 bind 函数的作用以及运用体式格局,接下深切到 bind 函数的运用中,详细引见三个方面的运用,这也是以后模仿完成 bind 函数的要点。
- 转变函数运转时this指向
- 通报参数
- 返回的新函数被当作构造函数
转变函数运转时this指向
当挪用 bind 函数后,bind 函数的第一个参数就是原函数作用域中 this 指向的值。
function func(){
console.log(this);
}
let newFunc = func.bind({a:1});
newFunc(); // 打印:{a:1}
let newFunc2 = func.bind([1,2,3]);
newFunc2(); // 打印:[1,2,3]
let newFunc3 = func.bind(1);
newFunc3(); // 打印:Number:{1}
let newFunc4 = func.bind(undefined/null);
newFunc4(); // 打印:window
以上要注重,当传入为 null 或许 undefined 时,在非严厉形式下,this 指向为 window。
当传入为简朴值时,内部会将简朴的值包装成对应范例的对象,数字就挪用 Number 要领包装;字符串就挪用 String 要领包装;true/false 就挪用 Boolean 要领包装。要想取到原始值,可以挪用 valueOf 要领。
Number(1).valueOf(); // 1
String("hello").valueOf(); // hello
Boolean(true).valueOf(); // true
当屡次挪用 bind 函数时,以第一次挪用 bind 函数的转变 this 指向的值为准。
function func(){
console.log(this);
}
let newFunc = func.bind({a:1}).bind(1).bind(['a','b','c']);
newFunc(); // 打印:{a: 1}
通报的参数
从 bind 的第二个参数最先,是向原函数通报的实参。bind 返回的新函数挪用时也可以向原函数通报实参,这里就触及递次题目。
function func(a,b,c){
console.log(a,b,c); // 打印传入的实参
}
let newFunc = func.bind({},1,2);
newFunc(3)
打印效果为1,2,3。
可以看到,在 bind 中通报的参数要先传入到原函数中。
返回的新函数被当作构造函数
挪用 bind 函数后返回的新函数,也可以被当作构造函数。经由过程新函数建立的实例,可以找到原函数的原型上。
// 原函数
function func(name){
console.log(this); // 打印:经由过程{name:'wy'}
this.name = name;
}
func.prototype.hello = function(){
console.log(this.name)
}
let obj = {a:1}
// 挪用bind,返回新函数
let newFunc = func.bind(obj);
// 把新函数作为构造函数,建立实例
let o = new newFunc('seven');
console.log(o.hello()); // 打印:'seven'
console.log(obj); // 打印:{a:1}
新函数被当作了构造函数,原函数func 中的 this 不再指向传入给 bind 的第一个参数,而是指向用 new 建立的实例。在经由过程实例 o 找原型上的要领 hello 时,可以找到原函数 func 原型上的要领。
在模仿完成 bind 迥殊要注重这一块的完成,这也是口试的重点,会触及到继续。
bind函数运用场景
以上只是说了 bind 函数时怎样运用的,学会了运用,要把它放在营业场景中来处置惩罚一些现实题目。
场景一
先来一个规划:
<ul id="list">
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
需求:点击每个 li 元素,耽误1000ms后,转变 li 元素的色彩,
let lis = document.querySelectorAll('#list li');
for(var i = 0; i < lis.length; i++){
lis[i].onclick = function(){
setTimeout(function(){
this.style.color = 'red'
},1000)
}
}
以上代码点击每个 li,并不会转变色彩,由于定时器回调函数的 this 指向的不是点击的 li,而是window,(固然你也可以运用箭头函数,let之类来处置惩罚,这里议论的主假如用bind来处置惩罚)。此时就须要转变回调函数的 this 指向。能转变函数 this 指向的有:call、apply、bind。那末挑选哪个呢?依据场景来定,这里的场景是在1000ms以后才实行回调函数,所以不能挑选运用call、apply,由于它们会马上实行函数,所以这个场景应当挑选运用 bind处置惩罚。
setTimeout(function(){
this.style.color = 'red'
}.bind(this),1000)
场景二
有时会运用面向对象的体式格局来构造代码,触及到把事宜处置惩罚函数拆分在原型上,然后把这些挂在原型上的要领赋值给事宜,此时的函数在事宜触发时this都指向了元素,进而须要在函数中接见实例上的属性时,便不能找到成。
function Modal(options){
this.options = options;
}
Modal.prototype.init = function(){
this.el.onclick = this.clickHandler; // 此要领挂载原型上
}
Modal.prototype.clickHandler = function(){
console.log(this.left); // 此时点击元素实行该函数,this指向元素,不能找到left
}
let m = new Modal({
el: document.querySelector('#list'),
left: 300
})
m.init(); // 启动运用
以上代码,在 init 函数中,给元素绑定事宜,事宜处置惩罚函数挂在原型上,运用 this 来接见。当点击元素时,在 clickHandler 函数中须要拿到实例的 left 属性,但此时 clickHandler 函数中的 this 指向的是元素,而不是实例,所以拿不到。要转变 clickHandler 函数 this 的指向,此时就须要用到 bind。
Modal.prototype.init = function(){
this.el.onclick = this.clickHandler.bind(this)
}
以上场景只是 bind 运用的冰山一角,它实质要做的事变是转变 this 的指向,到达预期目标。控制了 bind 的作用以及运用的场景,在脑海中就会建立一个印象:当须要转变this指向,并不马上实行函数时,就可以想到 bind。
模仿完成
为何要本身去完成一个bind函数呢?
bind()函数在 ECMA-262 第五版才被到场;它能够没法在一切浏览器上运转(ie8以下)。
口试用,让口试官找不到谢绝你的来由
捉住 bind 运用的几个特性,把这些点逐一完成就OK,详细的点:
- 被函数挪用
- 返回新函数
- 通报参数
- 转变函数运转时this指向
- 新函数被当作构造函数时处置惩罚
被函数挪用,可以直接挂在Function的原型上,为了补缺那些不支撑的浏览器,不必再为支撑的浏览器增加,可以做以下推断:
if(!Function.prototype.bind) {
Function.prototype.bind = function(){
}
}
这类行动也叫作 polyfill,为不支撑的浏览器增加某项功用,以到达抹平浏览器之间的差异。
注重:假如浏览器支撑,轻易本身测试,可以把
if 前提去掉,或许把
bind 改一个名字。在下文预备改名字为
bind2,轻易测试。
挪用 bind 后会返回一个新的函数,当新函数被挪用,原函数随之也被挪用。
Function.prototype.bind2 = function(thisArg,...args){
let funcThis = this; // 函数挪用bind,this指向原函数
// 返回新函数
return function (...rest) {
return funcThis.apply(thisArg,[...args,...rest]/*bind2通报的实参优先于新函数的实参*/)
}
}
// 测试
function func(a,b,c){
console.log(this)
console.log(a,b,c)
}
let newFunc = func.bind2({a:1},1,2);
newFunc(3);
// 打印:{a: 1}
// 打印:1 2 3
以上这个函数已可以转变原函数 this 的指向,并通报准确递次的参数。接下来就是比较难明白的处所,当新函数被当作构造函数的状况。
须要作出两个处所的转变:
- 新返回的函数要继续原函数原型上的属性
- 原函数转变this题目。假如用new挪用,则原函数this指向应当是新函数中this的值;不然为通报的thisArg的值。
先做继续,让新函数继续原函数的原型,保持本来的原型关联。匿名函数没办法援用,所以给新函数起一个名字。
Function.prototype.bind2 = function(thisArg,...args){
let funcThis = this; // 函数挪用bind,this指向原函数
// 要返回的新函数
let fBound = function (...rest) {
return funcThis.apply(thisArg,[...args,...rest]/*bind2通报的实参优先于新函数的实参*/)
}
// 不是一切函数都有prototype属性,比方 Function.prototype就没有。
if(funcThis.prototype){
// 运用Object.create,以原函数prototype作为新对象的原型建立对象
fBound.prototype = Object.create(funcThis.prototype);
}
return fBound;
}
// 测试
function func(name){
console.log(this); // {a: 1}
this.name = name;
}
func.prototype.hello = function(){
console.log(this.name); // undefined
}
let newFunc = func.bind2({a:1});
let o = new newFunc('seven')
o.hello();
// 打印:{a: 1}
// 打印:undefined
以上代码,新建的实例 o 可以挪用到 hello 这个要领,申明继续已完成,可以接见新函数上原型要领。
接下来是关于 this 指向题目,上面例子中,运用了 new 运算符挪用函数,那末原函数中,this 应当指向实例才对。所以须要在转变 this 指向的 apply 那边对是不是是运用 new 操作符挪用的做推断。
用到的操作符是 instanceof,作用是推断一个函数的原型是不是在一个对象的原型链上,是的话返回true,不然返回false。测试以下:
function Person(){}
let p = new Person();
console.log(p instanceof Person); // true
console.log(p instanceof Object); // true
console.log(p instanceof Array); // fasle
也可以用 instanceof 在构造函数中推断是不是是经由过程 new 来挪用的。假如是用 new 来挪用,申明函数中 this 对象的原型链上存在函数的原型,会返回true。
function Person(){
console.log(this instanceof Person); // true
}
new Person();
回到我们的 bind2 函数上,当挪用 bind2 后返回了新函数 fBound,当运用 new 挪用构造函数时,实际上挪用的就是 fBound 这个函数,所以只须要在 fBound 函数中应用 instanceof 来推断是不是是用 new 来挪用即可。
Function.prototype.bind2 = function(thisArg,...args){
let funcThis = this; // 函数挪用bind,this指向原函数
// 要返回的新函数
let fBound = function (...rest) {
// 假如是new挪用的,原函数this指向新函数中建立的实例对象
// 不是new挪用,依然是挪用bind2通报的第一个参数
thisArg = this instanceof fBound ? this : thisArg;
return funcThis.apply(thisArg,[...args,...rest]/*bind2通报的实参优先于新函数的实参*/)
}
// 不是一切函数都有prototype属性,比方 Function.prototype就没有。
if(funcThis.prototype){
// 运用Object.create,以原函数prototype作为新对象的原型建立对象
fBound.prototype = Object.create(funcThis.prototype);
}
return fBound;
}
// 测试
function func(name){
console.log(this); // {a: 1}
this.name = name;
}
func.prototype.hello = function(){
console.log(this.name); // undefined
}
let newFunc = func.bind2({a:1});
let o = new newFunc('seven')
o.hello();
// 打印:{name:'seven'}
// 打印:'seven'
bind 函数源码已完成完成,愿望对你有协助。
若有误差迎接斧正进修,感谢。