前面两篇文章介绍了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取决于调用函数的方式,指向调用时所在函数所绑定的对象。

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

  1. 浅谈JS的作用域链(一)

    JS的执行环境 执行环境(Execution context,EC)或执行上下文,是JS中一个极为重要的概念. 在JavaScript中有三种代码运行环境: Global Code JavaScrip ...

  2. 浅谈JS的作用域链(二)

    上一篇文章中介绍了Execution Context中的三个重要部分:VO/AO,scope chain和this,并详细的介绍了VO/AO在JavaScript代码执行中的表现. 本文就看看Exec ...

  3. 浅谈 js eval作用域

    原文:浅谈 js eval作用域 就简单聊下如何全局 eval 一个代码. var x = 1; (function () { eval('var x = 123;'); })(); console. ...

  4. 浅谈js变量作用域

    变量的作用域也是前端面试题常考的一个问题,掌握下面几个规律可以帮你更好的理解js的作用域. 1.作用域优先级遵循就近原则,函数内部的作用域优先级大于外部 var a=456; var b=111; f ...

  5. 浅谈JS中的闭包

    浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...

  6. 浅谈JS之AJAX

    0x00:什么是Ajax? Ajax是Asynchronous Javascript And Xml 的缩写(异步javascript及xml),Ajax是使用javascript在浏览器后台操作HT ...

  7. JS 之作用域链和闭包

    1.JS无块级作用域 <script> function Main(){ if (1==1){ var name = "alex"; } console.log(nam ...

  8. 浅谈 js 语句块与标签

    原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...

  9. 浅谈JS面向对象

    浅谈JS面向对象 一 .什么是面向过程 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了.注重代码的过程部分. 二.什么是面向对象 最先出现在管理学 ...

随机推荐

  1. 鸟哥的 Linux 私房菜Shell Scripts篇(一)

    参考: http://linux.vbird.org/linux_basic/0340bashshell-scripts.php#script_be http://www.runoob.com/lin ...

  2. 禁用selinux

    查看selinux状态: [root@VM000000518 upload]# getenforce Enforcing 禁用: [root@VM000000518 upload]# setenfor ...

  3. 3.3Python数据处理篇之Numpy系列(三)---数组的索引与切片

    目录 (一)数组的索引与切片 1.说明: 2.实例: (二)多维数组的索引与切片 1.说明: 2.实例: 目录: 1.一维数组的索引与切片 2.多维数组的索引与切片 (一)数组的索引与切片 1.说明: ...

  4. 浅析Java中的23种设计模式

    前言 设计模式不论是在我们学习编程,还是在工作和面试过程中,都会涉及到的一个问题,所以了解和学习好设计模式,是我们每一位码农必须要具备的技能,对以后的发展和自己技能的提升都有好处. 什么是设计模式(D ...

  5. sysctl命令

    sysctl命令作用: 被用于在内核运行时动态地修改内核的运行参数,可用的内核参数在目录/proc/sys中,它包含一些TCP/ip堆栈和虚拟内存系统的高级选项,用sysctl可以读取设置超过五百个系 ...

  6. nginx跟apache访问方法

    ifconfig 在浏览器中输入ip即可访问 centos安装nginx环境 1:进入 cd /usr/local/src  //下载文件放到这个目录中 2:wget http://nginx.org ...

  7. python 播放mp3

    import os os.system('out1.mp3') 自动带开播放器

  8. Kmeans基本思想

    https://blog.csdn.net/zjc_game_coder/article/details/78595833 Kmeans算法的基本思想:看如下图: 解决小样本 .非线性及高维模式识别问 ...

  9. Codeforces Round #553 (Div. 2) D. Stas and the Queue at the Buffet 贪心+公式转化

    题意 给出n个pair (a,b) 把它放在线性序列上 1--n 上 使得  sum(a*(j-1)+b*(n-j))  最小 思路 :对式子进行合并 同类项 有:    j*(a-b)+  (-a+ ...

  10. ethereum/EIPs-158 State clearing 被EIP-161取代

    eip title author type category status created superseded-by 158 State clearing Vitalik Buterin Stand ...