bind函数作用、运用场景以及模仿完成

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函数才会被实行

从以上获得以下信息:

  1. bind被函数挪用
  2. 返回一个新函数
  3. 能转变函数this指向
  4. 可以传入参数

深切bind 运用

以上知道了 bind 函数的作用以及运用体式格局,接下深切到 bind 函数的运用中,详细引见三个方面的运用,这也是以后模仿完成 bind 函数的要点。

  1. 转变函数运转时this指向
  2. 通报参数
  3. 返回的新函数被当作构造函数

转变函数运转时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,详细的点:

  1. 被函数挪用
  2. 返回新函数
  3. 通报参数
  4. 转变函数运转时this指向
  5. 新函数被当作构造函数时处置惩罚

被函数挪用,可以直接挂在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 的指向,并通报准确递次的参数。接下来就是比较难明白的处所,当新函数被当作构造函数的状况。

须要作出两个处所的转变:

  1. 新返回的函数要继续原函数原型上的属性
  2. 原函数转变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 函数源码已完成完成,愿望对你有协助。

若有误差迎接斧正进修,感谢。

    原文作者:戎马
    原文地址: https://segmentfault.com/a/1190000017543088
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞