JavaScript中this的工作原理以及注意事项

  在JavaScript中,this 的概念比较复杂。除了在面向对象编程中,this 还是随处可用的。这篇文章介绍了this 的工作原理,它会造成什么样的问题以及this 的相关例子。 要根据this 所在的位置来理解它,情况大概可以分为3种:

  1、在函数中:this 通常是一个隐含的参数。

  2、在函数外(顶级作用域中):在浏览器中this 指的是全局对象;在Node.js中指的是模块(module)的导出(exports)。

  3、传递到eval()中的字符串:如果eval()是被直接调用的,this 指的是当前对象;如果eval()是被间接调用的,this 就是指全局对象。

  对这几个分类,我们做了相应的测试:

  1、在函数中的this

  函数基本可以代表JS中所有可被调用的结构,所以这是也最常见的使用this 的场景,而函数又能被子分为下列三种角色:

  • 实函数

  • 构造器

  • 方法

  1.1  在实函数中的this

  在实函数中,this 的值是取决于它所处的上下文的模式

  Sloppy模式:this 指的是全局对象(在浏览器中就是window)。





1

2

3

4

function
sloppyFunc() {

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

}

sloppyFunc();

  Strict模式:this 的值是undefined。





1

2

3

4

5

function
strictFunc() {

    ‘use strict‘;

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

}

strictFunc();

  this 是函数的隐含参数,所以它的值总是相同的。不过你是可以通过使用call()或者apply()的方法显示地定义好this的值的。





1

2

3

4

5

6

7

function
func(arg1, arg2) {

    console.log(this); // 1

    console.log(arg1); // 2

    console.log(arg2); // 3

}

func.call(1, 2, 3); // (this, arg1, arg2)

func.apply(1, [2, 3]); // (this, arrayWithArgs)

  1.2  构造器中的this

  你可以通过new 将一个函数当做一个构造器来使用。new 操作创建了一个新的对象,并将这个对象通过this 传入构造器中。





1

2

3

4

5

6

var savedThis;

function
Constr() {

    savedThis = this;

}

var inst = new
Constr();

console.log(savedThis === inst); // true

  JS中new 操作的实现原理大概如下面的代码所示(更准确的实现请看这里,这个实现也比较复杂一些):





1

2

3

4

5

function
newOperator(Constr, arrayWithArgs) {

    var
thisValue = Object.create(Constr.prototype);

    Constr.apply(thisValue, arrayWithArgs);

    return
thisValue;

}

  1.3  方法中的this

  在方法中this 的用法更倾向于传统的面向对象语言:this 指向的接收方,也就是包含有这个方法的对象。





1

2

3

4

5

6

var obj = {

    method: function
() {

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

    }

}

obj.method();

  2、作用域中的this

  在浏览器中,作用域就是全局作用域,this 指的就是这个全局对象(就像window):





1

2

3

<script>

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

</script>

  在Node.js中,你通常都是在module中执行函数的。因此,顶级作用域是个很特别的模块作用域(module
scope):





1

2

3

4

5

6

7

// `global` (not `window`) refers to global object:

console.log(Math === global.Math); // true

// `this` doesn’t refer to the global object:

console.log(this
!== global); // true

// `this` refers to a module’s exports:

console.log(this
=== module.exports); // true

  3、eval()中的this

  eval()可以被直接(通过调用这个函数名’eval’)或者间接(通过别的方式调用,比如call())地调用。要了解更多细节,请看这里





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

// Real functions

function
sloppyFunc() {

    console.log(eval(‘this‘) === window); // true

}

sloppyFunc();

function
strictFunc() {

    ‘use strict‘;

    console.log(eval(‘this‘) === undefined); // true

}

strictFunc();

// Constructors

var
savedThis;

function
Constr() {

    savedThis = eval(‘this‘);

}

var
inst = new
Constr();

console.log(savedThis === inst); // true

// Methods

var
obj = {

    method: function
() {

        console.log(eval(‘this‘) === obj); // true

    }

}

obj.method();

  4、与this有关的陷阱

  你要小心下面将介绍的3个和this 有关的陷阱。要注意,在下面的例子中,使用Strict模式(strict
mode)
都能提高代码的安全性。由于在实函数中,this 的值是undefined,当出现问题的时候,你会得到警告。

  4.1  忘记使用new

  如果你不是使用new来调用构造器,那其实你就是在使用一个实函数。因此this就不会是你预期的值。在Sloppy模式中,this 指向的就是window 而你将会创建全局变量:





1

2

3

4

5

6

7

8

9

10

function
Point(x, y) {

    this.x = x;

    this.y = y;

}

var p = Point(7, 5); // we forgot new!

console.log(p === undefined); // true

// Global variables have been created:

console.log(x); // 7

console.log(y); // 5

  不过如果使用的是strict模式,那你还是会得到警告(this===undefined):





1

2

3

4

5

6

7

function
Point(x, y) {

    ‘use strict‘;

    this.x = x;

    this.y = y;

}

var p = Point(7, 5);

// TypeError: Cannot set property ‘x‘ of undefined

  4.2 不恰当地使用方法

  如果你直接取得一个方法的值(不是调用它),你就是把这个方法当做函数在用。当你要将一个方法当做一个参数传入一个函数或者一个调用方法中,你很可能会这么做。setTimeout()和注册事件句柄(event
handlers)就是这种情况。我将会使用callIt()方法来模拟这个场景:





1

2

3

4

/** Similar to setTimeout() and setImmediate() */

function
callIt(func) {

    func();

}

  如果你是在Sloppy模式下将一个方法当做函数来调用,*this*指向的就是全局对象,所以之后创建的都会是全局的变量。





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

var counter = {

    count: 0,

    // Sloppy-mode method

    inc: function
() {

        this.count++;

    }

}

callIt(counter.inc);

// Didn’t work:

console.log(counter.count); // 0

// Instead, a global variable has been created

// (NaN is result of applying ++ to undefined):

console.log(count);  // NaN

  如果你是在Strict模式下这么做的话,this是undefined的,你还是得不到想要的结果,不过至少你会得到一句警告:





1

2

3

4

5

6

7

8

9

10

11

12

var counter = {

    count: 0,

    // Strict-mode method

    inc: function
() {

        ‘use strict‘;

        this.count++;

    }

}

callIt(counter.inc);

// TypeError: Cannot read property ‘count‘ of undefined

console.log(counter.count);

  要想得到预期的结果,可以使用bind():





1

2

3

4

5

6

7

8

9

var counter = {

    count: 0,

    inc: function
() {

        this.count++;

    }

}

callIt(counter.inc.bind(counter));

// It worked!

console.log(counter.count); // 1

  bind()又创建了一个总是能将this的值设置为counter 的函数。

  4.3 隐藏this

  当你在方法中使用函数的时候,常常会忽略了函数是有自己的this 的。这个this 又有别于方法,因此你不能把这两个this 混在一起使用。具体的请看下面这段代码:





1

2

3

4

5

6

7

8

9

10

11

12

13

14

var obj = {

    name: ‘Jane‘,

    friends: [ ‘Tarzan‘, ‘Cheeta‘
],

    loop: function
() {

        ‘use strict‘;

        this.friends.forEach(

            function
(friend) {

                console.log(this.name+‘ knows ‘+friend);

            }

        );

    }

};

obj.loop();

// TypeError: Cannot read property ‘name‘ of undefined

  上面的例子里函数中的this.name 不能使用,因为函数的this 的值是undefined,这和方法loop()中的this 不一样。下面提供了三种思路来解决这个问题:

  1、that=this,将this 赋值到一个变量上,这样就把this 显性地表现出来了(除了that,self 也是个很常见的用于存放this的变量名),之后就使用那个变量:





1

2

3

4

5

6

7

loop: function
() {

    ‘use strict‘;

    var
that = this;

    this.friends.forEach(function
(friend) {

        console.log(that.name+‘ knows ‘+friend);

    });

}

  2、bind()。使用bind()来创建一个函数,这个函数的this 总是存有你想要传递的值(下面这个例子中,方法的this):





1

2

3

4

5

6

loop: function
() {

    ‘use strict‘;

    this.friends.forEach(function
(friend) {

        console.log(this.name+‘ knows ‘+friend);

    }.bind(this));

}

  3、用forEach的第二个参数。forEach的第二个参数会被传入回调函数中,作为回调函数的this 来使用。





1

2

3

4

5

6

loop: function
() {

    ‘use strict‘;

    this.friends.forEach(function
(friend) {

        console.log(this.name+‘ knows ‘+friend);

    }, this);

}

  5、最佳实践

  理论上,我认为实函数并没有属于自己的this,而上述的解决方案也是按照这个思想的。ECMAScript 6是用箭头函数(arrow
function)
来实现这个效果的,箭头函数就是没有自己的this 的函数。在这样的函数中你可以随便使用this,也不用担心有没有隐式的存在。





1

2

3

4

5

6

7

8

loop: function
() {

    ‘use strict‘;

    // The parameter of forEach() is an arrow function

    this.friends.forEach(friend => {

        // `this` is loop’s `this`

        console.log(this.name+‘ knows ‘+friend);

    });

}

  我不喜欢有些API把this 当做实函数的一个附加参数:





1

2

3

4

5

6

7

beforeEach(function
() {  

    this.addMatchers({  

        toBeInRange: function
(start, end) {  

            ...

        }  

    });  

});

  把一个隐性参数写成显性地样子传入,代码会显得更好理解,而且这样和箭头函数的要求也很一致:





1

2

3

4

5

6

7

beforeEach(api => {

    api.addMatchers({

        toBeInRange(start, end) {

            ...

        }

    });

});

  原文链接: 2ality   翻译: 伯乐在线 -
kmokidd

JavaScript中this的工作原理以及注意事项,布布扣,bubuko.com

时间: 2024-09-30 15:37:21

JavaScript中this的工作原理以及注意事项的相关文章

javascript中闭包的工作原理

一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包.不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”.看下面这段代码: 1 2 3 4 5 6 7 function a() { var i = 0; function b() { ale

JavaScript中实现DI的原理(二)

JavaScript中实现DI的原理 在JavaScript中实现DI,看起来难,实际上原理很简单,它的核心技术是Function对象的toString().我们都知道,对一个函数对象执行toString(),它的返回值是函数的源码,知道了这一点,接下来就简单的:我获取了函数源码,然后我对函数的声明进行解析,伪码如下: var giveMe = function(config) { }; var registry = {}; var inject = function(func, thisFor

JavaScript的计时器的工作原理

最近都在看一些JavaScript原理层面的文章,恰巧看到了jQuery的作者的一篇关于JavaScript计时器原理的解析,于是诚惶诚恐地决定把原文翻译成中文,一来是为了和大家分享,二来是为了加深自己对于JavaScript的理解.原文链接:http://ejohn.org/blog/how-javascript-timers-work/ 原文翻译: 从基础层面来讲,理解JavaScript计时器的工作原理是很重要的.由于JavaScript是单线程的,所以很多时候计时器并不是表现得和我们的直

Tomcat中JSP引擎工作原理

http://blog.csdn.net/linjiaxingqqqq/article/details/7164449 JSP运行环境: 执行JSP代码需要在服务器上安装JSP引擎,比较常见的引擎有WebLogic和Tomcat.把这些支持JSP的web服务器配置好后.就可以再客户端通过浏览器来访问JSP页面了.默认端口一般是7001. JSP生命周期: JSP处理请求的方法就是把这些请求都统一看做Servlet.由于这个原因,JSP的很多功能和生命周期,都由Java Servlet技术标准定义

C++中虚函数工作原理和(虚)继承类…

转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7883531 一.虚函数的工作原理 虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数.典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式.vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl.当

Java中GC的工作原理

转文: 一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能提高整个应用程序的性能.本文将从GC的工作原理.GC的几个关键问题进行探讨,最后提出一些Java程序设计建议,如何从GC角度提高Java程序的性能. 一.GC的基本原理: GC是什么? 为什么要有GC呢? GC是垃圾收集的意思(Garbage Collection),内存处理是编程人员容易出现问题的地方,忘

简单介绍 javascript 中 __proto__ 属性的原理

在 javascript 中我们会约定俗成,如果一个方法是被 new 出来使用的,那么该方法名首字母通常会大写,例如下面代码块中的 Person.(我们也可以把 Person 看成 java 或 c# 中的类) var Person = function(name) { this.name = name; } 在 javascript 中一个类被 new 出来的具体过程如下: // 初始化一个对象 p var p = new Person(); // 初始化一个对象 p var p = {};

C++中虚函数工作原理

一.虚函数的工作原理 虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数.典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式.vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl.当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针. 

【干货理解】理解javascript中实现MVC的原理

理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程序的业务逻辑相关的数据以及对数据处理的方法.模型有对数据直接访问的权利.模型不依赖 "视图" 和 "控制器", 也就是说 模型它不关心页面如何显示及如何被操作. 视图:视图层最主要的是监听模型层上的数据改变,并且实时的更新html页面.当然也包括一些事件的注册或者aja