this 关键字是 Javascript 中很特别的一个关键字,被自动定义在所有函数的作用域中。this提供了一种更优雅的方式隐式“传递”一个对象的引用。今天就来说说 this 的指向问题。

this 是在运行时,也就是说函数被调用时进行绑定,而不是在编写时进行绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数在哪里被调用。

一、调用位置

所谓 调用位置 就是 函数被调用的位置。找到这个位置并不是很简单的事情,因为有的时候,真正的调用位置会被隐藏起来。

这里要做的就是分析调用栈,即为了到达执行位置所调用的所有函数,我们关心的调用位置就在当前正在执行的函数的前一个调用中。

举个例子:

  1. function test(){
  2. // 当前调用栈是 test
  3. // 因此当前调用位置是 全局作用域
  4.  
  5. console.log("test");
  6.  
  7. bar(); // bar的调用位置
  8. }
  9.  
  10. function bar(){
  11. // 当前的调用栈是 test -> bar
  12. // 因此当前的调用位置在 test 中
  13.  
  14. console.log("bar");
  15. foo(); // foo的调用位置
  16. }
  17.  
  18. function foo(){
  19. // 当前的调用栈 test -> bar -> foo
  20. // 因此调用位置在 bar 中
  21.  
  22. console.log("foo");
  23. }
  24.  
  25. test(); // test的调用位置

那么知道调用位置之后,我们来看看 它是如何决定 this 的绑定对象的。

二、绑定规则

1、默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则的默认规则。

例子:

  1. function test(){
  2. console.log(this.a);
  3. }
  4. var a = 2;
  5. test(); //

声明在全局作用域中的变量(比如var a=2)就是全局对象的一个同名属性,即浏览器环境下的 window.a=2;

接下来,我们可以看到调用 test() 时,this.a 被解析成了全局变量 a,为什么呢?因为在这个例子中,test()是直接使用不带任何修饰的函数引用进行调用的,应用了默认规则,this 指向了全局对象。

这里要注意一个问题,如果使用了严格模式(strict mode),则不能将全局对象用于默认绑定,因此 this 会绑定到 undefined。

例子:

  1. function test(){
  2. "use strict";
  3. console.log(this.a);
  4. }
  5. var a = 2;
  6. test(); //TypeError:this is undefined

但是这里有一个非常重要的细节。虽然 this 的绑定规则完全取决于调用位置,但是只有 test() 运行在非 strict mode 下时,默认绑定才能绑定到全局对象;在严格模式下调用 test() 则不影响默认绑定。

例子:

  1. function test(){
  2. console.log(this.a)
  3. }
  4. var a =2;
  5. (function(){
  6. "use strict";
  7. foo(); //
  8. })()

比较一下上面的两个例子,发现不同 strict mode 中 this 应用默认绑定的差别。

2、隐式绑定

另一条需要考虑的规则是调用位置是否有上下文,或者说是否被某个对象拥有或者包含。

例子:

  1. function test(){
  2. console.log(this.a)
  3. }
  4.  
  5. var obj = {
  6. a:2,
  7. test:test
  8. };
  9.  
  10. obj.test(); //

当 test() 被调用时,它的前面加上了对 obj 的引用。当函数引用上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。在本例中, test() 调用时 this 被绑定到 obj,因此 this.a 即 obj.a。

在隐式绑定中需要注意两点:

a、对象属性引用链中只有上一层或者说最后一层在调用中起作用。

例子:

  1. function test(){
  2. console.log(this.a)
  3. }
  4.  
  5. var obj = {
  6. a:2,
  7. test:test
  8. };
  9.  
  10. var obj_1 = {
  11. a:4,
  12. obj:obj
  13. }
  14.  
  15. obj_1.obj.test(); //

b、隐式丢失。隐式绑定的函数丢失绑定对象,即应用默认绑定,从而把 this 绑定到全局对象上或者是 undefined(如果是严格模式)。

例子:

  1. function test(){
  2. console.log(this.a)
  3. }
  4.  
  5. var obj = {
  6. a:2,
  7. test:test
  8. };
  9.  
  10. var foo = obj.test;
  11.  
  12. var a = 4;
  13.  
  14. foo(); //

这里的 foo 实际上 引用的是 test 函数本身,当在执行 var foo = obj.test; 时,就把 obj.test 对 test 的引用地址复制给了 foo,也就是说 foo 和 obj.test 指向同一个 引用地址。所以 调用 foo 其实是一个不带任何修饰的函数调用,应用了默认绑定。关于复制的引用这里要是还是不是很明白 可以看一下,http://www.cnblogs.com/lijiayi/p/jsdeeepcopy.html 中的第一部分基本类型 和 引用类型。

3、显示绑定

这里主要就是指通过 call() 、apply() 的函数调用,实现的绑定方式。

它们的第一个参数是一个对象,是给 this 准备的,接着在调用函数时将其绑定到 this。因为你可以直接指定 this 的绑定对象,所以就叫显示绑定。

例子:

  1. function test(){
  2. console.log(this.a)
  3. }
  4.  
  5. var obj = {
  6. a:2
  7. };
  8.  
  9. test.call(obj) //

通过 test.call() ,我们可以在调用 test 时强制把它的 this 绑定到 obj 上。

这里多说一点:如果你传入了一个原始值(字符串类型,布尔值型或者数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String() 、new Boolean() 或者 new Number() )。这通常被称为 “装箱”。

在显示绑定中需要注意一点:

显示绑定中 call apply 或者 bind 中传入 null 或者 undefined ,这些值在调用时会被忽略,实际应用的是默认绑定。

例子:

  1. function test(){
  2. console.log(this.a)
  3. }
  4. var a = 2;
  5. test.call(null); //

你或许会想,我怎么会传个 null ?

一种非常常见的做法是 使用 apply 来 “展开” 数组,并当作参数传入一个函数。

例子:

  1. function test(a,b){
  2. console.log("a:"+ a + ",b:" + b)
  3. }
  4. foo.apply(null,[2,3]); // a:2,b:3

另一种做法 用bind 可以对参数进行柯里化(预先设置一些参数)

例子:

  1. function test(a,b){
  2. console.log("a:"+ a + ",b:" + b)
  3. }
  4.  
  5. var bar = test.bind(null,2);
  6. bar(3) // a:2,b:3

以上两种方法都需要传入一个参数当作 this 的绑定对象,但如果函数并不关系 this 的话,你仍然需要一个占用值,这时 null 可能是一个不错的选择。

4、new 绑定

使用 new 来调用函数,或者说是发生构造函数调用时,会自动执行下面的操作。

a、创建(或者说构造)一个全新的对象。

b、这个新对象会被执行 [[prototype]] 连接。

c、这个新对象会绑定到函数调用的 this。

d、如果函数没有返回其他对象,那么 new 表达式中的函数会自动返回这个新对象。

例子:

  1. function test(a){
  2. this.a = a
  3. }
  4.  
  5. var bar = new test(2);
  6. console.log(bar.a); //

使用 new 来调用 test() 时 ,我们会构造一个新对象并把它绑定到 test() 调用中的 this 上。

三、优先级

现在你已经知道 this 绑定的四条规则,那么当某个调用位置可以应用多个规则怎么办?

在这里我们说一下,上面四条规则的优先级:

new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定

总结:当我们在碰到 this 的时候先找到调用位置,然后按照以下顺序应用调用规则:

1、函数是否在 new 中调用,即应用的 new 绑定?如果是的话 this 绑定的是新创建的对象。

  1. var bar = new test();

2、函数是否通过 call 和 apply 调用,即应用 显示绑定?如果是的话 this 绑定的是指定的对象。

  1. test.call(obj)

3、函数是否在某个上下文中调用,即应用 隐式绑定? 如果是的话 this 绑定的是那个上下文对象。

  1. obj.test()

4、如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined ,否则绑定到全局对象。

  1. test()

四、es6 中的 this

在 es 6 中 使用了一种无法应用上面这些规则的特殊函数类型:箭头函数。

箭头函数不是使用 function 关键字定义的,而是使用被称为 “胖箭头” 的操作符 => 定义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。
例子:

  1. function test(){
  2. return (a)=>{
  3. console.log(this.a)
  4. }
  5. }
  6.  
  7. var obj1 = {
  8. a:2
  9. }
  10.  
  11. var obj2 = {
  12. a:4
  13. }
  14.  
  15. var bar = test.call(obj1);
  16. bar.call(obj2); // 2,不是4!

test 内部创建的箭头函数会捕获调用时 test() 的 this。由于 test 的 this 绑定到了 obj 1,bar(引用箭头函数) 的 this 也会绑定到 obj 1 ,箭头函数的绑定无法被修改(new也不行)。箭头函数会继承外层函数调用的 this 绑定。

引用并感谢:

你不知道的JavaScript(上卷) (炒鸡推荐大家看)

【 js 基础 】关于this的更多相关文章

  1. js 基础篇(点击事件轮播图的实现)

    轮播图在以后的应用中还是比较常见的,不需要多少行代码就能实现.但是在只掌握了js基础知识的情况下,怎么来用较少的而且逻辑又简单的方法来实现呢?下面来分析下几种不同的做法: 1.利用位移的方法来实现 首 ...

  2. js 基础

    js基础知识点总结 如何在一个网站或者一个页面,去书写你的js代码:1.js的分层(功能):jquery(tool) 组件(ui) 应用(app),mvc(backboneJs)2.js的规划():避 ...

  3. js基础练习二之简易日历

    今天学到了js基础教程3,昨天的课后练习还没来的及做,这个是类似简易日历的小案例,视频还没听完,今晚继续...... 先看效果图: 其实做过前面的Tab选项卡,这个就很好理解了,通过鼠标放在不同月份月 ...

  4. [JS复习] JS 基础知识

    项目结尾,空闲时间,又把<JS 基础知识> 这本书过了一遍,温故知新后,很多知其然不知其所以然的内容 豁然开朗. [1. 用于范围的标签] display  :inline or bloc ...

  5. JS基础(超级简单)

    1     JS基础(超级简单) 1.1 数据类型 1.1.1   基本类型: 1)        Number:特别注意:NaN的检测方法:Nan!=NaN;或者使用isNaN方法 2)       ...

  6. Node.js基础与实战

    Node.js基础与实战 Node.jsJS高级进阶 NODE原理与解析 REPL交互环境 模块与NPM Buffer缓存区 fs文件操作 Stream流 TCP&UDP 异步编程 HTTP& ...

  7. js基础到精通全面教程--JS教程

    适合阅读范围:对JavaScript一无所知-离精通只差一步之遥的人 基础知识:HTML JavaScript就这么回事1:基础知识 1 创建脚本块 1: <script language=”J ...

  8. JS基础知识总结

      js基础知识点总结 如何在一个网站或者一个页面,去书写你的js代码:1.js的分层(功能):jquery(tool) 组件(ui) 应用(app),mvc(backboneJs)2.js的规划() ...

  9. js基础篇——call/apply、arguments、undefined/null

    a.call和apply方法详解 call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象 ...

  10. js基础知识总结(2016.11.1)

    js基础知识点总结 如何在一个网站或者一个页面,去书写你的js代码:1.js的分层(功能):jquery(tool) 组件(ui) 应用(app),mvc(backboneJs)2.js的规划():避 ...

随机推荐

  1. windows转储文件(dmp)

    1. 何为转储文件      转储文件也就是我们常说的dump文件.可以把转储文件看成软件的某个时刻的一个快照.转储文件一般都是在软件出现问题时手动生成或者程序自动生成.下面我们介绍几种生成转储文件的 ...

  2. Linux 命令大全之Red Hat 7常用命令总结二

    Linux 命令大全之RedHat7常用命令笔记... ----------------------------------------------------- 征服Linux从终端开始 ----- ...

  3. 传参时Url中有中文报错

    url中如果传了中文,浏览器会报错:The header content contains invalid characters. 原因:浏览器自动把这个url进行decodeURIComponent ...

  4. StringBuffer .insert方法输出电话号码格式

    package ch11; import java.util.Scanner; /** * Created by liwenj on 2017/7/21. */public class T7 { pu ...

  5. jQuery方法输出有几个checkbox框被选中

    每选中一个多选框,输出有多少个选中 <!DOCTYPE html> <html lang="en"> <head> <meta chars ...

  6. Java序列化详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt228 以前在使用hibernate时候,domain域模型的JavaBean ...

  7. Maven(一)初识Maven

    前言 在这之前一直都有去看关于Maven的相关介绍,但是没有到真正要用的时候,自己总是以为懂了.其实真的感觉Maven并没有想象的那么简单! 那我们该怎么去学习maven呢?接下来我将从: 初步认识m ...

  8. Linux无法连接上127.0.0.1,拒绝连接,更新时提示无法下载,无法正常使用apt-get update

    你是否遇到过这种情况,在Linux以apt-get update 时更新的时候无法更新,提示一下内容 p { margin-bottom: 0.25cm; line-height: 120% } 错误 ...

  9. ★浅谈Spanking情节

  10. 第四次作业 四则运算器在C++层面上的进一步思考

    OOA(Object Oriented Analysis,面向对象分析) 从客观存在的事务和事务之间的关系,归纳出有关对象(包括对象的属性和行为)以及对象之间的联系,并将具有相同属性和行为的对象用一个 ...