全局变量

可以先看W3C:JavaScript 全局对象MDN:this

全局变量,W3C里说得很清楚(JavaScript Window - 浏览器对象模型):

图片描述

        alert(window.eval===eval);                // true
        alert(window.Object===Object);            // true
        alert(window.Math===Math);                // true
        alert(window.document===document);        // true
        // 我们一开始就使用那些函数方法,属性都是window对象的属性吗?

浏览器对象模型BOM (Browser Object Model) 使 JavaScript 有能力与浏览器“对话”。


还有一个DOM(文档对象模型,这是对HTML进行操作的根本)这里略过。

JavaScript就是那么奇葩,我们在函数外部声明的所有全局变量,其实都是全局对象的属性!JavaScript除了数值、布尔、字符串、null、undefined之外,都是对象(可以添加属性)

        // 预定义的全局变量、函数(方法)都是window对象的属性
        alert(typeof window.Number)            // function
        alert(typeof window.Math)            // object
        alert(typeof window.Math.random)    // function
        // 更改全局变量
        NaN = 11;
        eval = 22;
        Object = 33;
        Math = 44;
        console.log(window.Math);            // 44
        console.log(typeof window.Math)        // number     全局对象被改写
        
        alert(NaN);     // NaN
        alert(eval);    // 22
        alert(Object);  // 33
        alert(Math);    // 44
        // 这里可能有一个误区:全局对象不是window吗?
        // 实际上:window也是另外一个全局对象,但是前提是运行在浏览器端
        // window全局对象提供了与当前窗口、页面有关的诸多属性与方法。
        // 除了这些与浏览器有关的全局属性和方法,window对象还封装了JS全局对象,并向外暴露JS全局对象的属性与接口
        

看,这些预定义好的全局对象、方法我们都可以直接改写(当然没人去这么干),当然有些不能改写(NaN),不同浏览器也有不同支持。

具体可以看这两位大神的总结,相当好:JavaScript中的全局对象介绍

浅析JavaScript中两种类型的全局对象/函数


这里开始以this的应用为主,杂揉了一些知识。因为我第一次写这类感想文,编排不好后续会慢慢更改。

有关全局变量的测试:

        var a = 1;
        alert(this.a);    // 1
        alert(this);      // "[object Window]"

这里定义了一个全局变量a,之后调用this.athis。结果大家看到了。全局变量a的作用域是整个全局对象内可见,或者说变量a是全局对象的属性。不信?看下面:

        var woshiSB = 1;
        console.log(this);
        var write = "";
        for (var i in this) {
            write += i+"<br />"
        }
        document.write(write);

图片描述

看到了下面那个了?没错,还记得for-in语句干啥的嘛?遍历一个对象里的可枚举属性(图中只截取了部分全局对象(变量)),之后我们发现了了它woshiSB,这可以说在外部环境、全局上下文环境中的全局变量是全局对象的属性。反过来,我们也可以通过在全局对象里创建一个全局变量。且,如果能确定上下文(在任何函数体外部),满足这个前提条件,这个this指的就是全局对象了。且,我们可以:

        console.log(this.document === document); // true
        console.log(this.Number===Number);       // true
        // 在浏览器中,全局对象为 window 对象:
        console.log(this === window);     // true
        
        this.a = 37;                      // 此时this引用指向全局对象,所以a是全局对象的属性
        console.log(window.a);            // 37
        
        window.b = 38;                    // 同样的,全局对象window下的属性是全局变量
        console.log(this.b);              // 38
        
        this.x = 1;
        alert(this.x===window.x)          // true

this引用的几种情况(依据上下文)

第一种:

全局上下文(引用自MDN)

在全局运行上下文中(在任何函数体外部),this指代全局对象,无论是否在严格模式下。


console.log(this.document === document); // true

// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37

关于严格模式,请看真•究极大神 阮一峰:Javascript 严格模式详解


        function f1(){
          return this;
        }
        
        f1() === window; // true

不是在严格模式下执行,this的值不会在函数执行时被设置,此时的this的值会默认设置为全局对象。
这里提醒一下:严格的说,this全称叫this引用。注意引用二字。

        function f2(){
          "use strict"; // 这里是严格模式
          return this;
        }
        
        f2() === undefined; // true

在严格模式下,如果this未被执行的上下文环境定义,那么它将会默认为undefined
所谓上下文,加几个字理解,想到语文里的“联系上下文”了吗?对,this引用所处的环境,或者说它位于的作用域。

第二种:

函数上下文

在函数内部,this的值取决于函数是如何调用的。

包括引用的文章也提到:

如果这个this属于某个function,那么this指代的就是调用该function的对象。若这种情况下function只是一个普通的函数,而不是某个类的方法,那么this的指代存在两种可能:

1.在ECMAScript 3标准,以及ECMAScript 5标准的非严格模式下,this指代全局对象。

2.在ECMAScript 5标准的严格模式下,this指代undefined


  • 函数上下文 —— 对象方法中的this

        var obj = {
            x:"x",
            fn:function () {
                return this.x;
            }
        };
        console.log(obj.fn())

此时,obj.fn()中的this引用将会指向obj这个全局对象,也就是obj.x

        var obj = {x:"x"};
        function test(obj) {
            return this.x;
        };
        obj.fn = test;
        alert(obj.fn());

在何处或者如何定义调用函数完全不会影响到this的行为。我们在定义obj的时候定义了一个obj.fn()方法。但是,我们也可以首先定义函数然后再将其附属到o.f。这样做this的行为也一致。

来看另一个例子:

        var f = function (x) {
            this.y = x+1;
        };
        var a = {y:10,op:f};
        var b= {y:20,increment:f};
        
        a.op(100);                 // 通过a来调用f,this引用指向a引用的对象
        alert(a.y);                // 101
        b.increment(43);           // 通过b来调用,this引用指向b引用的对象
        alert(b.y);                // 44

f引用的匿名函数里的this引用指向调用它的对象。

  • 函数上下文 —— 构造函数中的this:

我们来看CDN的例子(改写过):

        function c(){
          this.a = 37;
        };
        c.prototype.test = "test";
        
        var o = new c();
        o.sb = "Sb";                                             // o也是对象
        console.log(o.a);                                        // logs 37
        console.log(o.__proto__===c.prototype);                  // true
        console.log(o.sb);                                       // "Sb"
        Object.prototype.op = "op";
        console.log(c.prototype.__proto__===Object.prototype)    // true
        console.log(Object.prototype.__proto__);                 // null
        console.log(Object.prototype.constructor===Object);      // true

看到这的的默认你已经懂了构造函数大部分哈。(我们先读一遍,对,首先要看懂。)

  1. 声明一个c函数,同时内建一个this引用,当然我们并不知道这个引用指向谁。将c函数作为构造函数调用,(此时:构造函数会隐式生成一个对象,对象内建的隐式链接指向构造函数的prototype对象),这个新对象的引用赋值给全局变量o,注意是引用赋值,这个变量同时也是对象,Sb那里也看到了。现在你脑子里是不是生成了一副引用指向图了呢?把它画出来你就都知道了。

    图片描述

变量o引用了新对象,则构造函数里的this引用就是指向o,这里展现的是原型链,关于原型链我还不够了解后续会继续学习;
再来看另一个:

        function c2(){
          this.a = 37;
          return {a:38};
        };

        o = new c2();
        console.log(o.a); // logs 38    

在最后的例子中(C2),因为在调用构造函数的过程中,手动的设置了返回对象,与this绑定的默认对象被取消(本质上这使得语句this.a = 37成了“僵尸”代码,实际上并不是真正的“僵尸”,这条语句执行了但是对于外部没有任何影响,因此完全可以忽略它)。———————————— 引自CDN
其实我对CDN的解释也不太懂,可以认为,重新改变o引用的对象,则也改变了this引用的对象。则this.a肯定指向新对象。


再来看书中的例子:

        var Point = function (x,y) {
            this.x = x;
            this.y = y;
        };
        var p = new Point(4,-5);
        var q = Point(3,8);
        
        console.log(typeof q);    // "undefined"

p变量引用了构造函数新生成的变量,this.xthis.y所以相当于p.xp.y。调用构造函数,传参后,就赋值等等。
而变量q,注意这里只是将Point当做普通函数调用而已,执行函数主体后,也没有返回值。So,q的值类型是undefined


再来看另一个例子:

        var obj = {
            x:3,
            doit:function () {
                console.log("method is called."+this.x);
            }
        };
        // 1
        var fn = obj.doit;            // 将obj.doit引用的Function对象赋值给全剧变量    
        fn();                         // "method is called.undefined"
        // 2
        var x = 5;
        fn();                         // "method is called.5"
        // 3
        var obj2 = {x:4,doit2:fn};
        obj2.doit2();                 // "method is called.4"  
        

全局变量obj引用对象,内含obj.x和一个方法,方法内存在this引用。

第一次调用:将对象内部doit方法赋值给全部变量fn,此时调用fn。那么我想问,是谁调用fn呢?是全局对象。而全局变量是全局对象的属性。也就是说上下文内存在的全局变量都对全局对象可见,作用域嘛。此时,既然全局对象调用函数方法,则this引用当然指向全局对象。可是,全局对象并没有属性x(或者说没有全局变量x)所以是undefined

第二次调用//2:就是反证第一次的,说明this引用的确引用了全局对象,或者我们看这个:

        var test = {
            z:"test-z",
            fn:function () {
                console.log(this.z)
            }
        };
        window.fun = test.fn;            // 全局对象的fun方法
        var z = "var z";                 // 全局变量z
        window.fun();                    // result:"var z"    this引用指向全局对象
        
        var z1 = "var z1";               // 全局变量z1
        test.z1 = "test.z1";             // 属性z1
        test.fn2 = function () {console.log(this.z1)}    
        test.fn2();                      // result:"test.z1"        this引用指向test对象

所以当第三次调用时,doit2引用的Funcion对象内部的this引用指向的是obj2对象。相当于obj2.x

这里要提一下之后要学到的applycall方法,它们作用就是显式的指定this引用要引用的对象,或者说显式指定接收方对象。


嵌套一个来试试:

        var x = "var x";
        var fn = function () {alert(this.x)};
        var obj = {
            x:"obj.x",
            fn:function () {alert(this.x)},
            obj2:{
                x:"obj2.x",
                fn2:function () {alert(this.x)}
            }
        };
        
        // 直接调用
        obj.obj2.fn2();        // obj2.x
        obj.fn();              // obj.x
        fn();                  // var z

哪怕跨级调用,由于搜索都是按作用域由内之外,所以引用的是最近的对象。所以这应该是MDN里那句话的意思了。

类似的,this 的绑定只受最靠近的成员引用的影响

再来看一个变形:

        var obj = {
            x:"obj.x",
            doit:function () {
                console.log("doit is called."+this.x);            // 这里的this.x:obj.x
                this.test.doit2();                                
                // 这里的this.x:"test.x"
                //  "true"
            },
            test:{
                x:"test.x",
                doit2:function () {
                    var x = "doit2-x";
                    console.log("doit2 is called."+this.x+" | "+this);
                    // 这里的this.x:"test.x"
                    console.log(this===window.obj.test && this===obj.test);
                    // "true"    this引用指向嵌套对象:test
                }
            }
        };
        obj.doit();                    // this.x: "obj.x"
        var x = "var z";             
        window.obj.test.doit2();    // this.x:"test.x"

最后提一下原型链中的this引用:

        var o = {
          f : function(){ 
            return this.a + this.b; 
          }
        };
        var p = Object.create(o);
        p.a = 1;
        p.b = 4;
        console.log(p.f());             // 5            
        alert(p.__proto__===o);            // true

如果该方法存在于一个对象的原型链上,那么this指向的是调用这个方法的对象,表现得好像是这个方法就存在于这个对象上一样。

在这个例子中,对象p没有属于它自己的f属性,它的f属性继承自它的原型。但是这对于最终在o中找到f属性的查找过程来说没有关系;查找过程首先从p.f的引用开始,所以函数中的this指向p也就是说,因为f是作为p的方法调用的,所以它的this指向了p。这是JavaScript的原型继承中的一个有趣的特性。


延伸知识点:

最近学JS,发现这几个知识点最好形成思维导图来理解:作用域、变量声明提升、全局对象全局变量、this引用(上下文)、new构造函数、原型链与原型......,continue learning。


Queen
139 声望20 粉丝