资讯专栏INFORMATION COLUMN

JavaScript——this、全局变量和局部变量混谈

xiaokai / 2557人阅读

摘要:再来看书中的例子变量引用了构造函数新生成的变量,所以相当于与。而全局变量是全局对象的属性。延伸知识点最近学,发现这几个知识点最好形成思维导图来理解作用域变量声明提升全局对象全局变量引用上下文构造函数原型链与原型。

全局变量 可以先看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+"
" } 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

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

声明一个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。

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

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

相关文章

  • JavaScript——作用域、变量声明提升、局部变量混谈

    摘要:主要聊聊局部变量作用域变量声明提升作用域中有以下两种作用域全局作用域函数作用域全局作用域是函数之外最外层代码的作用域。相对于全局作用域,可以称之为局部作用域相对于全局变量可以将其成为局部变量。然而,这里的是在下一行进行声明的局部变量。 主要聊聊局部变量、作用域、变量声明提升 作用域 JavaScript中有以下两种作用域 全局作用域 函数作用域 全局作用域是函数之外(最外层代码)的...

    luffyZh 评论0 收藏0
  • [学习笔记] JavaScript 作用域链

    摘要:全局执行环境的变量对象始终是作用域链中的最后一个变量对象。综上,每个函数对应一个执行环境,每个执行环境对应一个变量对象,而多个变量对象构成了作用域链,如果当前执行环境是函数,那么其活动对象在作用域链的前端。 1.几个概念 先说几个概念:函数、执行环境、变量对象、作用域链、活动对象。这几个东东之间有什么关系呢,往下看~ 函数 函数大家都知道,我想说的是,js中,在函数内部有两个特殊...

    ?xiaoxiao, 评论0 收藏0
  • JavaScript 闯关记》之作用域闭包

    摘要:作用域和闭包是最重要的概念之一,想要进一步学习,就必须理解作用域和闭包的工作原理。全局和局部作用域的关系在函数体内,局部变量的优先级高于同名的全局变量。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。 作用域和闭包是 JavaScript 最重要的概念之一,想要进一步学习 JavaScript,就必须理解 JavaScript 作用域和闭包的工作原理。 作用域 任何...

    Jacendfeng 评论0 收藏0
  • JavaScript的作用域

    摘要:函数的作用域也可被分为全局作用域和局部作用域函数作用域,被定义在指定函数内部的函数被称为局部函数或内部函数。 作用域 变量和函数都有作用域,作用域就是变量和函数可被访问的范围,控制着变量和函数的可见性和生命周期(生命周期指一个事物开始到结束中间那一段时间)变量的作用域可被分为全局作用域和局部作用域(函数作用域),如果变量是被定义在全局作用域的话,在JavaScript代码中的任何位置都...

    aikin 评论0 收藏0
  • JavaScript-作用域、块级作用域、上下文、执行上下文、作用域链

    摘要:一旦函数执行完成,其就会从作用域链顶部移除,并且执行权会返回到函数。攀爬作用域链当不同执行上下文之间存在变量命名冲突,可以通过攀爬作用域链解决从顶部到底部。 一、作用域 在 JavaScript 中, 作用域(scope,或译有效范围)就是变量和函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期 二、全局/局部作用域 2.1 全局作用域(Global Scope) (1)不...

    Coding01 评论0 收藏0

发表评论

0条评论

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