资讯专栏INFORMATION COLUMN

this全面解析(二)

iflove / 1697人阅读

摘要:在传统的面向类的语言中,构造函数是类中的一些特殊方法,使用初始化类是会调用类中的构造函数。

在上一节中我们详细介绍了this的两种绑定方式,默认绑定隐式绑定,在这一节我们继续介绍this的另外两种绑定方式显示绑定new绑定。那么,我们要解决的问题当然就是上一节中我们提到的:this丢失!

显式绑定

在隐式绑定中,我们必须在一个对象的内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接绑定到这个对象上。那么如果我们不想在每个对象内部包含函数引用,而想在每个对象上强制调用函数,该怎么做呢?
这时就需要 call(绑定this, 其他参数...)apply(绑定this, 其他参数...)方法出场了,这两个方法的第一个参数都是给this准备的,不同之处在于其他参数的形式上,他们两的其他参数对比如下:

call(绑定this, "参数1","参数2","参数3","参数4");
apply(绑定this, ["参数1","参数2","参数3","参数4"]);

apply的其他参数是以数组序列形式存在的,它会在执行时将其解析成单个的参数再依次的传递到调用的函数中,这有什么用处呢?加入我们有一个数组:

var arr = [1,2,3,4,5,6];

现在我要找到其中的最大值,当然这里有很多方法了。既然这里讲到apply那么我们就用apply方法来解决这个问题。如果想要找到一组数中最大的一个,有一个简单的方法,使用Math.max(...)。但是,该方法并不能找出一个数组中的最大值,也就是说:

Math.max(1,2,3,4,5); // 可以找到最大值5
Math.max([1,2,3,4,5]); // 这就不行了,因为不接受以数组作为参数

我们的做法就是通过:

Math.max.apply(null, [1,2,3,4,5]); //得到数组中的最大值5

还有很多其他方面的用处,比如push等等,似乎有点跑题了!!!
不过我想说的就是通过call()和apply()这两种方法我们可以显式的绑定this到指定的对象!

    function foo(){
        console.log(this.a);
    }
    var obj = {
        a: 2
    }
    foo.call(obj);//2

但是,显式绑定仍旧无法解决this丢失绑定的问题。

硬绑定

显式绑定的一个变种可以解决这个问题。

    function foo(){
        console.log(this.a);
    }
    var obj = {
            a: 2
         }
    var bar = function(){
        foo.call(obj);
    }
    bar();// 2
    setTimeout(bar, 100); // 2
    bar.call(window); // 2

看看它是如何工作的:我们创建了一个函数bar(),并在他的内部手动调用foo.call(obj)。因此,强制把foo的this绑定到了obj,无论之后如何调用函数bar,它总会手动在obj上调用foo。这样的形式我们称之为硬绑定
硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值:

    function foo(something){
        console,log(this.a, something);
        return this.a + something;
    }
    var obj = {
        a: 2
    }
    var bar = function(){
        return foo.apply(obj, arguments);
    }
    var b = bar(3); //2, 3
    console.log(b); //5

另一个常用的方法是创建一个可以重复使用的辅助函数:

    function foo(something){
        console,log(this.a, something);
        return this.a + something;
    }
    //简单的辅助函数
    function bind(fn, obj){
        return function(){
            return fn.apply(obj, arguments);
        }
    }
    var obj = {
        a:2
    }
    
    var bar = bind(foo, obj);

    var b = bar(); //2, 3
    console.log(b); //5

硬绑定是一种非常常用的模式,所以ES5提供了内置的方法Function.prototype.bind,它的用法如下:

    function foo(something){
        console,log(this.a, something);
            return this.a + something;
    }
    var obj = {
        a:2
    }
    var bar = foo.bind(obj);
    var b = bar(3); //2, 3
    console.log(b); //5

bind(...)会返回一个硬编码的心函数,它会把指定的参数设置为this的上下文并调用原始函数。

new 绑定

第四条规则,也是最后一条规则,在讲解他之前我们首先要澄清一个非常常见的关于javascript中函数和对象的误解。
在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用new初始化类是会调用类中的构造函数。通常的形式是这样:

someThinges = new MyClass(...)

javascript中也有个new操作符,但javascript中的new操作符的机制与面向类的语言完全不同。首先我们重新定义一下JavaScrit中的“构造函数”。在Javascript中,构造函数只是一些使用new操作符时被调用的函数。它并不会属于某个类,也不会实例化一个类。实际上它甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。
举例来说,思考一下Number()作为构造函数时的行为,ES5.1中这样描述它:

Number构造函数
当Number在new表达式中被调用时,它是一个构造函数:它会初始化新建的对象。

所以,包括内置对象函数在内的所有函数都可以用new来调用,这种函数被称为构造函数调用,这有个非常细微的区别:实际上并不存在所位的“构造函数”,只有对于函数的“构造调用”。
使用new来调用函数,会自动执行下面的操作:

创建一个全新的对象

这个新对象会被执行[[prototype]]连接(之后会细说)

这个新对象会绑定到函数调用的this

如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个对象。

    function foo(a){
        this.a = a
    }

    var bar = new foo(2);
    console.log(bar) // foo {a: 2}
    console.log(bar.a); //2

使用new 来调用foo(...)时,我们会构造一个新的对象,并把它绑定到foo(...)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法。我们称之为new绑定。

箭头函数

我们之前介绍的四条规则已经可以包含所有正常是有的函数。但是在ES6中介绍了一种无法使用这些规则的特殊函数类型:箭头函数
箭头函数不是使用function关键字定义的,而是使用“ => ”定义。箭头函数不使用this的四种标准规则,而是根据外层作用域(函数或全局)来决定this。

    function foo(){
        //返回一个箭头函数
        return (a) =>{
            //this继承自foo()
            console.log(this.a);
        }
    }
    var obj1 = {
        a: 2
    }
    var obj2 = {
        a: 3
    }
    var bar = foo.call(obj1);
    bar.call(obj2); //2  不是3

foo()内部创建的箭头函数会捕获调用时foo()的this,由于foo()的this绑定到obj1,bar的this也会绑定到obj1上,而且箭头函数的绑定无法被修改!
箭头函数最常用与回调函数中,例如事件处理器或者定时器:

    function foo(){
        setTimeot(()=>{
            //这里的this在词法上继承自foo(),也就是说只要foo()绑定到了obj1上,箭头函数的this也就绑定到了obj1上
            console.log(this.a)
        },100)
    }
    var obj1 = {
            a: 2
        }
    foo.call(obj1); //2

箭头函数可以像bind(..)一样确保函数的this被绑定到指定的对象,此外,其重要性还体现在他用更常见的词法作用域取代了传统的this机制。实际上在,ES6之前我们就已经使了用一种几乎和箭头函数完全一样的模式。

    function foo(){
        console.log(this); //Object {a: 2}
        
        var self = this; //词法作用域捕获this
        
        setTimeout(function(){
        console.log(this); // Window {external: Object, chrome: Object, document: document, obj1: Object, obj2: Object…}
       
       console.log(self.a);
        }, 100);
    }
    var obj1 = {
                a: 2
            }
    foo.call(obj1); //2

我分别在这段代码中foo()的内部,和setTimeout()的内部加了两行代码console.log(this),当调用foo()函数并将其this绑定到obj1上时(即执行foo.call(obj1)),foo()内的this此时是Object {a: 2},说明foo()函数中的this已经绑定到了obj1上,setTimeout()内的结果是Window...,如果你看了上一节《this全面解析(一)》的内容应该会很好理解,因为在setTimeout()方法中,函数传参相当于隐式赋值,调用方式自然运用默认规则setTimeout()方法中函数的this指向window。为了让我们得到预期的结果,我们将foo()中的this保存下来(即var self = this),然后通过词法作用域的在setTimeout()方法中的函数中引用self变量。读者可以自行测试,如果不这样做得出的结果会是什么(undifined吗?自行验证一下吧!)
好吧!一不小心又啰嗦的讲了这么多。虽然,self = this和箭头函数看起来都可以取代bind(),但本质上来说,他们想取代的是this机制。

小结

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到后就可以顺序应用下面这四条规则来判断this的绑定对象

是否由new调用?绑定到新创建的对象

是否由call()或apply()调用?绑定到指定的对象

是否由上下文对象调用?绑定到那个上下文对象

默认:严格模式undifined,非严格绑定到全局对象

ES6中的箭头函数不会使用四条标准的绑定规则,而是根据词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到了什么),这其实和ES6之前代码中的self = this 机制一样。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79002.html

相关文章

  • 全面解析JS中的this

    摘要:当我们不想再对象内部间接包含引用函数,而像在某个对象上强制调用函数。我们可以用中内置的和的方法来实现,这两个方法的第一个参数是一个对象,是给准备的,接着再调用函数时将其绑定到。 this是什么 在javascript中,每个执行上下文可以抽象成一组对象showImg(https://segmentfault.com/img/bVuKR7); 而this是与执行上下文相关的特殊对象,任何...

    calx 评论0 收藏0
  • 关于this全面解析(下)

    摘要:关于的全棉解析上的文章地址判断函数是否在中调用绑定如果是的话绑定的是新创建的对象。显而易见,这种方式可能会导致许多难以分析和追踪的。默认在严格模式下绑定到,否则绑定到全局对象。 关于this的全棉解析(上)的文章地址 判断this 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。 bar = new foo() 函数是否通过call、apply(显式绑定...

    philadelphia 评论0 收藏0
  • 关于this全面解析(上)

    摘要:关于的全面解析下页面链接的调用位置调用位置就是函数在代码中被调用的位置而不是声明的位置,寻找调用位置就是寻找函数被调用的位置,最重要的是分析调用栈就是为了到达当前执行位置所调用的所有函数。因此,调用函数时被绑定到这个对象上,所以和是一样的。 关于this的全面解析(下)页面链接 this的调用位置 调用位置就是函数在代码中被调用的位置(而不是声明的位置),寻找调用位置就是寻找函数被调用...

    caige 评论0 收藏0
  • 【进阶3-2期】JavaScript深入之重新认识箭头函数的this

    摘要:箭头函数的寻值行为与普通变量相同,在作用域中逐级寻找。题目这次通过构造函数来创建一个对象,并执行相同的个方法。 我们知道this绑定规则一共有5种情况: 1、默认绑定(严格/非严格模式) 2、隐式绑定 3、显式绑定 4、new绑定 5、箭头函数绑定 其实大部分情况下可以用一句话来概括,this总是指向调用该函数的对象。 但是对于箭头函数并不是这样,是根据外层(函数或者全局)作用域(...

    Rainie 评论0 收藏0
  • Java杂记17—String全面解析

    摘要:所以也就是说在没有的基础上,执行代码会在串池中创建一个,也会在堆内存中再出来一个。不可变性的优点安全性字符串不可变安全性的考虑处于两个方面,数据安全和线程安全。 摘要: String基本特性,String源码,为什么String不可变? 前言 基于字符串String在java中的地位,关于String的常识性知识就不多做介绍了,我们先来看一段代码 public class Test {...

    jeffrey_up 评论0 收藏0

发表评论

0条评论

iflove

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<