this全面解析(一)

在《你不知道的this》中我们排除了对于this的错误理解,并且明白了每个函数的this是在调用时绑定的,完全取决于函数的调用位置。在本节中我们主要介绍一下几个主要内容:

  1. 什么是调用位置

  2. 绑定规则

  3. this词法

调用位置

调用位置:就是函数在代码中被调用的位置(而不是声明位置)。要想回答this到底引用的是什么?只有仔细分析调用位置才能回答这个问题。
而分析调用位置最重要的就是分析调用栈。下面是调用栈的定义。
调用栈:就是为了到达当前执行位置所调用的所有函数。

    function baz(){
         //当前调用栈是:baz
         //因此,当前调用位置是全局作用域
         console.log("baz");
         bar(); //bar的调用位置
    }
    function bar(){
         //当前调用栈是:baz -> bar
         //因此,当前调用位置是在baz中
         console.log("bar");
         foo(); //foo的调用位置
    }
    function foo(){
         //当前调用栈是:baz -> bar -> foo
         //因此,当前调用位置是在baz中
         console.log("foo");
    }
    baz(); // baz的调用位置
绑定规则

我们的思路是,通过找到函数的调用位置,然后判断需要应用规则中的哪一条。便可决定this的绑定对象。关于this的绑定规则主要是以下四种:

  1. 默认绑定

  2. 隐式绑定

  3. 显式绑定

  4. new绑定

1.默认绑定

默认绑定的典型类型是:独立函数调用。 思考如下代码:

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

调用foo()时,函数应用了默认绑定,this只想全局对象window(这是在非严格模式下,若是在严格模式下会报错),所以this.a被解析成了全局变量a。所以,在不使用任何修饰的函数引用进行调用,只能使用默认绑定,无法应用其他规则。

2.隐式绑定

隐式绑定的常见形式是在调用位置具有上下文对象,或者说被某个对象拥有或者包含。看如下代码:

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

这里函数foo()是预先定义好的,然后再将其添加为obj对象的引用属性。调用位置使用obj上下文来引用函数,因此可以说函数被调用时obj对象“拥有”或者“包含”它。
无论你如何称呼这个模式,当foo()被调用时,它的前面确实是加上了obj的引用。当函数引用上下文对象时,隐式绑定规则就会把函数调用中的this绑定到这个上下文对象。所以,this.a和obj.a是一样的。

另一个需要注意的点是:对象属性引用链中只有最后一层在调用位置起作用

    function foo(){
         console.log(this.a);
    }
    var obj2 = {
         a: 100,
         foo: foo
    };
    var obj1 = {
         a: 1,
         obj2: obj2
    }
    obj1.obj2.foo(); // 100
隐式绑定一个最常见的问题

一个最常见的问题就是:隐式绑定的函数会丢失绑定对象。也就是是说它会应用默认绑定,而把this绑定到全局对象或者undifined上,取决于是否是严格模式。

    function foo(){
        console.log(this.a);
    }
    var obj ={
        a: 2,
        foo: foo
    }
    var bar = obj.foo; //函数别名
    var a = "global";
    bar(); // "global"

虽然bar是obj.foo的一个引用,但实际上,他引用的是foo函数本身, 因此, 此时的bar()其实是一个不带任何修饰的函数调用,因此,它应用了默认绑定
一种更微妙,更常见的并且更出乎意料的情况发生在传入回调函数时:

    function foo(){
        console.log(this.a);
    }
    function callBack(fn){
        fn();
    }

    var obj = {
        a: 2,
        foo: foo
    }
    var a = "global";
    callBack(obj.foo); // "global"

参数传递其实就是一种隐式赋值,这句话我们可以用下面的两段代码来详细的讲解:

    var a = 1;
    function fn(){
        alert(a); //1
        a = 2;
    }
    fn();
    alert(a); // 2

_ _ _

    var a = 1;
    function fn(a){
          alert(a); //undifined
          a = 2;
    }
    fn();
    alert(a); // 1

思考一下结果是否与你想象的一致呢?
在第一段代码中:
首先,在全局作用域中,先通过变量提升,找到了标识符a和函数fn,a此时有个默认值为undifined。然后,在执行阶段我们先将变量a赋值为1,紧跟着函数fn()执行。此时,在函数域中,依旧应用变量提升的规则,但是什么都没找到,接着执行函数内的代码:alert(a),因为在函数中并没有找到变量a。所以,通过作用域链向上层的父级作用域中查找,我们找到了a,并且此时a的值已经被赋值为1,所以,alert(a)这句的结果就是1。下一句代码:a = 2,注意a的前面没有关键字var, 即这里的a是全局的,也就是说在执行这句代码时,他修改了全局作用域中a的值,即a现在为2。最后在执行alert(a)时,自然而然a的值便是2了。

在第二段代码中:
同样,通过变量提升,我们找到了标识符a和函数fn,a此时的默认值也为undifined。开始执行,a首先被赋值为1。然后,函数执行,这里与第一段代码的不同之处在于,在函数fn中传入了参数a,那么这么做的结果就是:在函数域先运用变量提升的规则,不会像第一段代码中那样什么都找不到,而是相当于定义了一个值为undifined(调用的时候没有传入参数)的变量a,所以当执行函数域中的alert(a)时,结果就为undifined,而不会通过作用域链向上去查找,因为本函数中已经找到了,只不过是以参数的形式传入的。同理代码(a = 2)会修改a的值,即在函数域中,a的值现在为2(读者可以去尝试在函数中最后面alert一下a的值)。而在函数外执行alert(a),我们得到的结果便是1,因为该句代码是在全局中执行的,即会在全局中去查找变量a,而不会去访问函数域中的a。这也是因为,在JavaSceipt中子作用域可以访问父作用域而反过来却不行的规则。

回到我们this绑定丢失的话题上,说了这么多,我其实就是想说:参数传递其实就是一种隐式赋值参数传递其实就是一种隐式赋值参数传递其实就是一种隐式赋值,重要的事说三遍!
我们按照上面的方式来解析代码:在执行callBack(obj.foo)时,在函数作用域通过变量提升找到了参数fn,它的默认值为undifined,然后我们将参数传入,其实相当于(var fn = obj.foo),这就与前面的将其直接赋值给一个变量对等上了,然后再执行fn(),应用默认绑定,此时的this已经不指向obj了,而是指向window(严格模式)。

如果把函数传入内置的函数而不是传入你自己声明的函数,会发生什么呢?结果是一样的,没有区别:

    function foo(){
        console.log(this.a)
    }
    var obj = {
        a: 2,
        foo: foo
    }
    var a = "global";
    setTimeout(obj.foo, 1000); //"global"

JavaSceipt环境中内置的setTimeout()函数实现和下面的伪代码类似:

    function setTimeout(fn, delay){
        //等待delay秒
        fn(); //调用位置
    }

就向你们看到的那样,回调函数丢失this绑定的情况是非常常见的,并且还有一种情况this的行为会出乎我们意料:调用回调函数的函数可能会修改this。由于无法控制回调函数的执行方式,因此就没有办法控制调用位置得到期望的绑定,下一节我们会介绍如何通过固定this来“修复“这个问题。

《WCF全面解析(套装上下册)》由蒋金楠所著,是作者多年潜心研究WCF技术的心血之作,也是这些年来从事WCF开发的经验总结。书如其名,此书涵盖了WCF几乎所有的知识点,并对其底层框架进行了“庖丁解牛”式的剖析,力求将WCF的整个运行机制完整而清晰地呈现在读者面前。 《WCF全面解析(套装上下册)》上册的前四章在对WCF进行总体介绍的基础上,对构成终结点的三要素(地址、绑定和契约)进行了系统说明;随后的两章则着重剖析序列化和消息编码在WCF中的实现;第7、8章讲述了在服务寄宿和操作调用过程中,WCF的服务端和客户端框架分别为我们做了什么;第9、10章将介绍的重点落在实例化、会话和REST服务上面;在最后章中采用WCF构建了个具体的电子商务网站VM,它将指导你如何将理论应用于实践。 《WCF全面解析(套装上下册)》的下册主要涉及些所谓的“高级”话题,主要包括如何在分布式环境中处理异常(第1章);元数据的导入与导出、发布与获取如何实现(第2章);如何利用WCF对事务的支持将分布式事务引入服务(第3章);如何利用并发与限流机制提高服务的吞吐量和可用性(第4章);如何利用可靠会话机制确保消息的“使命必达”(第5章);如何利用队列服务提供离线通信的支持(第6章);第7、8章主要涉及安全的相关内容,包括传输安全、授权与审核;第9章全景展示WCF服务端和客户端的运行时框架,以及在此基础上的所有扩展可能;最后章为你带来WCF4.0几个独立的新特性。 编辑推荐 《WCF全面解析(套装上下册)》不仅适合尚未接触过WCF,希望尽快入门并进行深入研究的开发人员使用,同样也适合对WCF有定了解的开发设计人员和架构师阅读。相信不同层次的读者都能从此书中找到自己希望了解的部分。 《WCF全面解析(套装上下册)》的内容不仅适合尚未接触过WCF,希望尽快入门并进行深入研究的开发人员,同样适合对WCF具有定了解的开发设计人员和架构师。相信不同层次的读者都能从本书中找到自己希望了解的部分。阅读本书的读者需要对.NET,包括对C#和.NET Framework具有定的了解。如果读者具备了DCOM、Enterprise Library Service、.NET Remoting、Web Service、MSMQ及SOA相关的基础,对阅读此书尽快掌握WCF将大有裨益。 名人推荐我经历了COM时代,直把Don Box的《COMM本质论》奉为我的指路明灯。能把SOA机理和WCF这种特定厂商实现的技术讲得如《COM本质论》样完美透彻的,那必属Artech这本经过自己深研、实践而著的心血结晶——《WCF全面解析》。如果你想成为SOA和WCF方面的专家,那么这本书就是你的最好法宝。想想你作为专家而获得的回报,那么你对这本书购买所付出的,简直是太值了。 ——《走出软件作坊》 作者 明源软件CTO 阿朱 首先,金楠是—位工作在线的优秀的WCF技术人员,这符合我对阅读技术图书的第个要求和期待。其次,金楠的写作文笔、专业责任也给人以充分信任,这在金楠的文字中读者可以体会。这本《WCF全面解析》全面剖析了构建WCF应用所需要的各方面技术,剥丝抽茧,由浅入深,也是我非常欣赏的技术讲述方式。我相信《WCF全面解析》—书是搞WCF朋友的案头必备。 ——祝成科技与Boolan.com创始人.NET技术专家 李建忠 知识全面、论述准确、逻辑严密是本书的特点。这是本各层次开发人员都可以从中受益的书:对于初、中级开发人员,它可以帮助你获得WCF全方位的知识,系统地梳理WCF的知识结构,提升动手实践能力;对于高级开发人员,它既可以有效弥补你WCF相关知识中的盲点,又可以让你在自己熟悉的知识点上领略作者的看法和理解。 ——资深架构师 曲春雨 作者简介蒋金楠,网名Artech,现就职于某知名软件公司担任高级软件顾问。连续5届微软MVP(最有价值专家),同时也是少数的双料MVP(Solutions Architecture+Connected System)之。国内较早接触WCF的人之,2007年2月起在个人博客(http://www.cnblogs.com/artech)上发表超过两百篇深入介绍WCF的文章,成为了目前国内WCF在线资料的主要来源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值