浅谈JS的作用域链(三)

前面两篇文章介绍了JavaScript执行上下文中两个重要属性:VO/AO和scope chain。本文就来看看执行上下文中的this。

首先看看下面两个对this的概括:

  • this是执行上下文(Execution Context)的一个重要属性,是一个与执行上下文相关的特殊对象。因此,它可以叫作上下文对象(也就是用来指明执行上下文是在哪个上下文中被触发的对象)。
  • this不是变量对象(Variable Object)的一个属性,所以跟变量不同,this从不会参与到标识符解析过程。也就是说,在代码中当访问this的时候,它的值是直接从执行上下文中获取的,并不需要任何作用域链查找。this的值只在进入上下文的时候进行一次确定。

关于this最困惑的应该是,同一个函数,当在不同的上下文进行调用的时候,this的值就可能会不同。也就是说,this的值是通过函数调用表达式(也就是函数被调用的方式)的caller所提供的。

下面就看看在不同场景中,this的值。

全局上下文

在全局上下文(Global Context)中,this总是global object,在浏览器中就是window对象。


1

2

3

4

5

6

7

8

9

10

console.log(this === window);

this.name = "Will";

this.age = 28;

this.getInfo = function(){

    console.log(this.name + " is " this.age + " years old");

};

window.getInfo();

// true

// Will is 28 years old

函数上下文

在一个函数中,this的情况就比较多了,this的值直接受函数调用方式的影响。

Invoke function as Function

当通过正常的方式调用一个函数的时候,this的值就会被设置为global object(浏览器中的window对象)。

但是,当使用"strict mode"执行下面代码的时候,this就会被设置为"undefined"。


1

2

3

4

5

6

7

8

function gFunc(){

    return this;

}

console.log(gFunc());

console.log(this === window.gFunc());

// window

// true

Invoke function as Method

当函数作为一个对象方法来执行的时候,this的值就是该方法所属的对象。

在下面的例子中,创建了一个obj对象,并设置name属性的值为"obj"。所以但调用该对象的func方法的时候,方法中的this就表示obj这个对象。


1

2

3

4

5

6

7

8

9

var obj = {

    name: "obj",

    func: function () {

        console.log(this ":" this.name);

    }

};

obj.func();

// [object Object]:obj

为了验证"方法中的this代表方法所属的对象"这句话,再看下面一个例子。

在对象obj中,创建了一个内嵌对象nestedObj,当调用内嵌对象的方法的时候,方法中的this就代表nestedObj。


1

2

3

4

5

6

7

8

9

10

11

12

var obj = {

    name: "obj",

    nestedObj: {

        name:"nestedObj",

        func: function () {

            console.log(this ":" this.name);

        }

    }           

}

obj.nestedObj.func();

// [object Object]:nestedObj

对于上面例子中的方法,通常称为绑定方法,也就是说这些方法都是个特定的对象关联的。

但是,当我们进行下面操作的时候,temp将是一个全局作用里面的函数,并没有绑定到obj对象上。所以,temp中的this表示的是window对象。


1

2

3

4

5

6

7

8

9

10

11

var name = "Will";

var obj = {

    name: "obj",

    func: function () {

        console.log(this ":" this.name);

    }

};

temp = obj.func;

temp();

//  [object Window]:Will

Invoke function as Constructor

在JavaScript中,函数可以作为构造器来直接创建对象,在这种情况下,this就代表了新建的对象。


1

2

3

4

5

6

7

8

9

10

11

function Staff(name, age){

    this.name = name;

    this.age = age;

    this.getInfo = function(){

        console.log(this.name + " is " this.age + " years old");

    };

}

var will = new Staff("Will"28);

will.getInfo();

// Will is 28 years old

Invoke context-less function

对于有些没有上下文的函数,也就是说这些函数没有绑定到特定的对象上,那么这些上下文无关的函数将会被默认的绑定到global object上。

在这个例子中,函数f和匿名函数表达式在被调用的过程中并没有被关联到任何对象,所以他们的this都代表global object。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

var context = "global";

var obj = { 

    context: "object",

    method: function () { 

        console.log(this ":" +this.context);

        

        function f() {

            var context = "function";

            console.log(this ":" +this.context);

        };

        f();

        

        (function(){

            var context = "function";

            console.log(this ":" +this.context);

        })();

    }

};

obj.method();

// [object Object]:object

// [object Window]:global

// [object Window]:global

call/apply/bind改变this

this本身是不可变的,但是 JavaScript中提供了call/apply/bind三个函数来在函数调用时设置this的值。

这三个函数的原型如下:

  • fun.apply(obj1 [, argsArray])

    • Sets obj1 as the value of this inside fun() and calls fun() passing elements of argsArray as its arguments.
  • fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
    • Sets obj1 as the value of this inside fun() and calls fun() passing arg1, arg2, arg3, ... as its arguments.
  • fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
    • Returns the reference to the function fun with this inside fun() bound to obj1 and parameters of fun bound to the parameters specified arg1, arg2, arg3, ....

下面看一个简单的例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

function add(numA, numB){

    console.log( this.original + numA + numB);

}

add(12);

var obj = {original: 10};

add.apply(obj, [12]);

add.call(obj, 12);

var f1 = add.bind(obj);

f1(23);

var f2 = add.bind(obj, 2);

f2(3);

// NaN

// 13

// 13

// 15

// 15

当直接调用add函数的时候,this将代表window,当执行"this.original"的时候,由于window对象并没有"original"属性,所以会得到"undefined"。

通过call/apply/bind,达到的效果就是把add函数绑定到了obj对象上,当调用add的时候,this就代表了obj这个对象。

DOM event handler

当一个函数被当作event handler的时候,this会被设置为触发事件的页面元素(element)。


1

2

3

4

5

var body = document.getElementsByTagName("body")[0];

body.addEventListener("click", function(){

    console.log(this);

});

// <body>…</body>

In-line event handler

当代码通过in-line handler执行的时候,this同样指向拥有该handler的页面元素。

看下面的代码:


1

2

3

4

document.write(‘<button onclick="console.log(this)">Show this</button>‘);

// <button onclick="console.log(this)">Show this</button>

document.write(‘<button onclick="(function(){console.log(this);})()">Show this</button>‘);

// window

在第一行代码中,正如上面in-line handler所描述的,this将指向"button"这个element。但是,对于第二行代码中的匿名函数,是一个上下文无关(context-less)的函数,所以this会被默认的设置为window。

前面我们已经介绍过了bind函数,所以,通过下面的修改就能改变上面例子中第二行代码的行为:

document.write(‘<button onclick="((function(){console.log(this);}).bind(this))()">Show this</button>‘); // <button onclick="((function(){console.log(this);}).bind(this))()">Show this</button>

保存this

在JavaScript代码中,同一个上下文中可能会出现多个this,为了使用外层的this,就需要对this进行暂存了。

看下面的例子,根据前面的介绍,在body元素的click handler中,this肯定是指向body这个元素,所以为了使用"greeting"这个方法,就是要对指向bar对象的this进行暂存,这里用了一个self变量。

有了self,我们就可以在click handler中使用bar对象的"greeting"方法了。

当阅读一些JavaScript库代码的时候,如果遇到类似self,me,that的时候,他们可能就是对this的暂存。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

var bar = {

    name: "bar",

    body: document.getElementsByTagName("body")[0],

    

    greeting: function(){

        console.log("Hi there, I‘m " this ":" this.name);

    },

    

    anotherMethod: function () {

        var self = this;

        this.body.addEventListener("click", function(){

            self.greeting();

        });

    }

};

  

bar.anotherMethod();

// Hi there, I‘m [object Object]:bar

同样,对于上面的例子,也可以使用bind来设置this达到相同的效果。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

var bar = {

    name: "bar",

    body: document.getElementsByTagName("body")[0],

    

    greeting: function(){

        console.log("Hi there, I‘m " this ":" this.name);

    },

    

    anotherMethod: function () {

        this.body.addEventListener("click", (function(){

            this.greeting();

        }).bind(this));

    }

};

  

bar.anotherMethod();

// Hi there, I‘m [object Object]:bar

总结

本文介绍了执行上下文中的this属性,this的值直接影响着代码的运行结果。

在函数调用中,this是由激活上下文代码的调用者(caller)来提供的,即调用函数的父上下文(parent context ),也就是说this取决于调用函数的方式,指向调用时所在函数所绑定的对象。

原文地址:https://www.cnblogs.com/cangqinglang/p/8467654.html

时间: 2024-08-03 06:55:19

浅谈JS的作用域链(三)的相关文章

浅谈 js eval作用域

就简单聊下如何全局 eval 一个代码. var x = 1; (function () { eval('var x = 123;'); })(); console.log(x); 这个代码得到的是 1 而不是 123如果想让 eval 执行的代码是全局的,那么有几种方法. var x = 1; (function () { window.eval('var x = 123;'); })(); console.log(x); 这个方法标准浏览器都可以得到 123 而IE6-8则依然是 1 相同的

浅谈js中的this关键字

浅谈js中的this关键字 全局作用域中的this 函数作用域中的this 不同函数调用方法下的this 直接调用 作为对象的方法调用 作为构造函数调用 通过call或apply方法调用 嵌套函数作用域中的this 浅谈js中的this关键字 this是JavaScript中的关键字之一,在编写程序的时候经常会用到,正确的理解和使用关键字this尤为重要.接下来,笔者就从作用域的角度粗谈下自己对this关键字的理解,希望能给到大家一些启示,权当交流之用. 全局作用域中的this 本文将以作用域由

浅谈 js 语句块与标签

原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象字面量么?怎么成了语句块了?如果在赋值语句或者表达式里用的时候,确实是对象字面量,如: var a = {}; ({toString:function(){return "hehe"}}) + "..."; 是不是很有意思..但是直接使用如: {toString: fu

浅谈JS之AJAX

0x00:什么是Ajax? Ajax是Asynchronous Javascript And Xml 的缩写(异步javascript及xml),Ajax是使用javascript在浏览器后台操作HTTP和web服务器进行数据交换(用户不知道也感觉不出来,就跟桌面应用程序似的进行数据交互),它不会导致页面重新加载,这样才有更好的用户体验. Ajax是基于以下开放标准: javascript(DOM) css html xml(json) 通俗的说就是使用了javascript(DOM)的XMLH

JS 之作用域链和闭包

1.JS无块级作用域 <script> function Main(){ if (1==1){ var name = "alex"; } console.log(name); } Main(); </script>执行结果:{}即块级作用域. alex 2.JS采用函数作为作用域链 <script> function Main(){ var innerValue = "alex"; } Main(); console.log(in

从window.console&amp;&amp;console.log(123)浅谈JS的且运算逻辑(&amp;&amp;)

从window.console&&console.log(123)浅谈JS的且运算逻辑(&&) 作者:www.cnblogs.com  来源:www.cnblogs.com  发布日期:2015-03-01 一.JS的且运算记得最开始看到window.console&&console.log(123),当时知道能起什么作用但是没有深入研究,最近在研究后总算弄明白了.要理解这个,首先得明白三个知识点第一:短路原则这个大家都非常清楚的了,在做且运算的时候,“同真

37.浅谈js原型的理解

浅谈Js原型的理解 一.js中的原型毫无疑问一个难点,学习如果不深入很容易就晕了!    在参考了多方面的资料后,发现解释都太过专业,对于很多还没有接触过面向对象    语言的小白来说,有理解不了里面的专有名词!如果你没学过c++或者Java之类的更接触底层的语言,那就不要太深究,理解会用自然可以了,当接触到更多语言时慢慢的会理解越来越深刻! 下面我就举例分享一下prototype的概念!知道对于初学者知道这些就足够了! 分析一下,上面这个例子!我们可以知道 People的类型是一个对象!按照j

浅谈 js 正则之 test 方法

原文:浅谈 js 正则之 test 方法 其实我很少用这个,所以之前一直没注意这个问题,自从落叶那厮写了个变态的测试我才去看了下这东西.先来看个东西吧. ? 1 2 3 4 5 var re = /\d/; console.log( re.test("1") ); console.log( re.test("1") ); console.log( re.test("1") ); console.log( re.test("1"

浅谈 js 下 with 对性能的影响

这几天多次看到有博主们在写 with 的文章,这货确实非常方便,但是却是个性能杀手,所以一直都是上不得台面的.那么他究竟会让效率低下到什么程度呢?先来看下 with 是如何的便捷吧.. // 正常调用 console.log(location.host); console.log(location.pathname); // 在 with 下 with (location) { console.log(host); console.log(pathname); } 如果不影响性能,确实是非常霸气