很多人当谈到 JavaScript 中的
的时候会感到头疼,因为在 JavaScript 中,
- this
是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的
- this
关键字有能力具备多重含义,带来灵活性的同时,也为初学者带来不少困惑。
- this
每个函数调用都有与之相关的作用域和上下文。首先需要澄清的问题是上下文和作用域是不同的概念。很多人经常将这两个术语混淆。
作用域 (scope) 是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。而上下文(context) 是用来指定代码某些特定部分中
的值。
- this
从根本上说,作用域是基于函数 (function-based) 的,而上下文是基于对象 (object-based) 的。换句话说,作用域是和每次函数调用时变量的访问有关,并且每次调用都是独立的。上下文总是被调用函数中关键字
的值,是调用当前可执行代码的对象的引用。说的通俗一点就是:
- this
取值,是在函数真正被调用执行的时候确定的,而不是在函数定义的时候确定的。
- this
无论是否在严格模式下,在全局执行上下文中(在任何函数体外部)
都指向全局对象。当然具体的全局对象和宿主环境有关。
- this
在浏览器中,
对象同时也是全局对象:
- window
- console.log(this === window); // true
NodeJS 中,则是
对象:
- global
- console.log(this); // global
由于其运行期绑定的特性,JavaScript 中的
含义要丰富得多,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:作为函数调用,作为对象方法调用,作为构造函数调用,和使用
- this
或
- apply
调用。下面我们将按照调用方式的不同,分别讨论
- call
的含义。
- this
作为函数直接调用时,要注意 2 种情况:
在非严格模式下执行函数调用,此时
默认指向全局对象。
- this
- function f1(){
- return this;
- }
- //在浏览器中:
- f1() === window; //在浏览器中,全局对象是window
- //在Node中:
- f1() === global;
在严格模式下,
将保持他进入执行上下文时的值,所以下面的
- this
并不会指向全局对象,而是默认为 undefined 。
- this
- 'use strict'; // 这里是严格模式
- function test() {
- return this;
- };
- test() === undefined; // true
在 JavaScript 中,函数也是对象,因此函数可以作为一个对象的属性,此时该函数被称为该对象的方法,在使用这种调用方式时,内部的
指向该对象。
- this
- var Obj = {
- prop: 37,
- getProp: function() {
- return this.prop;
- }
- };
- console.log(Obj.getProp()); // 37
上面的例子中,当
被调用时,方法内的
- Obj.getProp()
将指向
- this
对象。值得注意的是,这种行为根本不受函数定义方式或定义位置的影响。在前面的例子中,我们在定义对象
- Obj
的同时,将成员
- Obj
定义了一个匿名函数。但是,我们也可以首先定义函数,然后再将其附加到
- getProp
。所以,下面的代码和上面的例子是等价的:
- Obj.getProp
- var Obj = {
- prop: 37
- };
- function independent() {
- return this.prop;
- }
- Obj.getProp = independent;
- console.log(Obj.getProp()); // logs 37
JavaScript 非常灵活,现在我们把对象的方法赋值给一个变量,然后直接调用这个函数变量又会发生什么呢?
- var Obj = {
- prop: 37,
- getProp: function() {
- return this.prop;
- }
- };
- var test = Obj.getProp console.log(test()); // undefined
可以看到,这时候
指向全局对象,这个例子
- this
只是引用了
- test
函数,也就是说这个函数并不作为
- Obj.getProp
对象的方法调用,所以,它是被当作一个普通函数来直接调用。因此,
- Obj
指向全局对象。
- this
我们来看看下面这个例子:
- var prop = 0;
- var Obj = {
- prop: 37,
- getProp: function() {
- setTimeout(function() {
- console.log(this.prop) // 结果是 0 ,不是37!
- },
- 1000)
- }
- };
- Obj.getProp();
正如你所见,
中的
- setTimeout
向了全局对象,这里不是把它当作函数的方法使用吗?这一点经常让很多初学者疑惑;这种问题是很多异步回调函数中也会普遍会碰到,通常有个土办法解决这个问题,比如,我们可以利用 闭包 的特性来处理:
- this
- var Obj = {
- prop: 37,
- getProp: function() {
- var self = this;
- setTimeout(function() {
- console.log(self.prop) // 37
- },
- 1000)
- }
- };
- Obj.getProp();
其实,
和
- setTimeout
都只是在全局上下文中执行一个函数而已,即使是在严格模式下:
- setInterval
- 'use strict';
- function foo() {
- console.log(this); // Window
- }
- setTimeout(foo, 1);
记住
和
- setTimeout
都只是在全局上下文中执行一个函数而已,因此 this 指向全局对象。 除非你实用箭头函数,
- setInterval
方法等办法修复。至于解决方案会在后续的文章中继续讨论。
- Function.prototype.bind
JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。作为又一项约定通用的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。
当一个函数用作构造函数时(使用
关键字),它的
- new
被绑定到正在构造的新对象,也就是我们常说的实例化出来的对象。
- this
- function Person(name) {
- this.name = name;
- }
- var p = new Person('愚人码头');
- console.log(p.name); // "愚人码头"
如果构造函数具有返回对象的
语句,则该返回对象将是
- return
表达式的结果。
- new
- function Person(name) {
- this.name = name;
- return { title : "前端开发" };
- }
- var p = new Person('愚人码头');
- console.log(p.name); // undefined
- console.log(p.title); // "前端开发"
相应的,JavaScript 中的构造函数也很特殊,如果不使用
调用,则和普通函数一样, this 仍然执行全局:
- new
- function Person(name) {
- this.name = name;
- console.log(this); // Window
- }
- var p = Person('愚人码头');
在箭头函数中,
与封闭词法上下文的
- this
保持一致,也就是说由上下文确定。
- this
- var obj = {
- x: 10,
- foo: function() {
- var fn = () = >{
- return () = >{
- return () = >{
- console.log(this); //{x: 10, foo: ƒ} 即 obj
- console.log(this.x); //10
- }
- }
- }
- fn()()();
- }
- }
- obj.foo();
是一个匿名函数,无论如何, 这个函数中的 this 指向它被创建时的上下文(在上面的例子中,就是
- obj.foo
对象)。这同样适用于在其他函数中创建的箭头函数:这些箭头函数的 this 被设置为外层执行上下文。
- obj
- // 创建一个含有bar方法的obj对象,bar返回一个函数,这个函数返回它自己的this,
- // 这个返回的函数是以箭头函数创建的,所以它的this被永久绑定到了它外层函数的this。
- // bar的值可以在调用中设置,它反过来又设置返回函数的值。
- var obj = {
- bar: function() {
- var x = (() = >this);
- return x;
- }
- };
- // 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
- // x所指向的匿名函数赋值给fn。
- var fn = obj.bar();
- // 直接调用fn而不设置this,通常(即不使用箭头函数的情况)默认为全局对象,若在严格模式则为undefined
- console.log(fn() === obj); // true
- // 但是注意,如果你只是引用obj的方法,而没有调用它(this是在函数调用过程中设置的)
- var fn2 = obj.bar;
- // 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
- console.log(fn2()() == window); // true
在上面的例子中,一个赋值给了
的函数(称为匿名函数 A),返回了另一个箭头函数(称为匿名函数 B)。因此,函数 B 的 this 被永久设置为
- obj.bar
(函数 A)被调用时的
- obj.bar
。当返回的函数(函数 B)被调用时,它 this 始终是最初设置的。在上面的代码示例中,函数 B 的
- this
被设置为函数 A 的
- this
,即
- this
,所以它仍然设置为
- obj
,即使以通常将
- obj
设置为
- this
或全局对象(或者如前面示例中全局执行上下文中的任何其他方法)进行调用。
- undefined
我们回到上面 setTimeout 的坑:
- var prop = 0;
- var Obj = {
- prop: 37,
- getProp: function() {
- setTimeout(function() {
- console.log(this.prop) // 结果是 0 ,不是37!
- },
- 1000)
- }
- };
- Obj.getProp();
通常情况我,我们在这里期望输出的结果是
,用箭头函数解决这个问题相当简单:
- 37
- var Obj = {
- prop: 37,
- getProp: function() {
- setTimeout(() = >{
- console.log(this.prop) // 37
- },
- 1000)
- }
- };
- Obj.getProp();
相同的概念在定义在原型链中的方法也是一致的。如果该方法存在于一个对象的原型链上,那么
指向的是调用这个方法的对象,就好像该方法本来就存在于这个对象上。
- 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
在这个例子中,对象
没有属于它自己的 f 属性,它的 f 属性继承自它的原型。但是这对于最终在
- p
中找到
- o
属性的查找过程来说没有关系;查找过程首先从
- f
的引用开始,所以函数中的
- p.f
指向
- this
。也就是说,因为 f 是作为 p 的方法调用的,所以它的
- p
指向了
- this
。这是 JavaScript 的原型继承中的一个有趣的特性。
- p
你也会看到下面这种形式的老代码,道理是一样的:
- function Person(name) {
- this.name = name;
- }
- Person.prototype = {
- getName: function() {
- return this.name
- }
- };
- var p = new Person('愚人码头');
- console.log(p.getName()); // "愚人码头"
再次,相同的概念也适用时的函数作为一个
或者 一个
- getter
调用。用作
- setter
或
- getter
的函数都会把
- setter
绑定到正在设置或获取属性的对象。
- this
- function sum() {
- return this.a + this.b + this.c;
- }
- var o = {
- a: 1,
- b: 2,
- c: 3,
- get average() {
- return (this.a + this.b + this.c) / 3;
- }
- };
- Object.defineProperty(o, 'sum', {
- get: sum, enumerable: true, configurable: true});
- console.log(o.average, o.sum); // logs 2, 6
当函数被用作事件处理函数时,它的
指向触发事件的元素(一些浏览器在使用非
- this
的函数动态添加监听函数时不遵守这个约定)。
- addEventListener
- // 被调用时,将关联的元素变成蓝色
- function bluify(e) {
- console.log(this === e.currentTarget); // 总是 true
- // 当 currentTarget 和 target 是同一个对象是为 true
- console.log(this === e.target);
- this.style.backgroundColor = '#A5D9F3';
- }
- // 获取文档中的所有元素的列表
- var elements = document.getElementsByTagName('*');
- // 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
- for (var i = 0; i < elements.length; i++) {
- elements[i].addEventListener('click', bluify, false);
- }
当代码被内联 on-event 处理函数调用时,它的 this 指向监听器所在的 DOM 元素:
- <button onclick="alert(this.tagName.toLowerCase());">
- Show this
- </button>
上面的 alert 会显示
。注意只有外层代码中的
- button
是这样设置的:
- this
- <button onclick="alert((function(){return this})());">
- Show inner this
- </button>
在这种情况下,没有设置内部函数的
,所以它指向
- this
/
- global
对象(即非严格模式下调用的函数未设置
- window
时指向的默认对象)。
- this
JavaScript 中函数也是对象,对象则有方法,
和
- apply
就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即
- call
绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。让我们看一个具体的例子:
- this
- function Point(x, y){
- this.x = x;
- this.y = y;
- this.moveTo = function(x, y){
- this.x = x;
- this.y = y;
- }
- }
- var p1 = new Point(0, 0);
- p1.moveTo(1, 1);
- console.log(p1.x,p1.y); //1 1
- var p2 = {x: 0, y: 0};
- p1.moveTo.apply(p2, [10, 10]);
- console.log(p2.x,p2.y); //10 10
在上面的例子中,我们使用构造函数生成了一个对象
,该对象同时具有
- p1
方法;使用对象字面量创建了另一个对象
- moveTo
,我们看到使用
- p2
可以将
- apply
的方法 apply 到
- p1
上,这时候
- p2
也被绑定到对象
- this
上。另一个方法
- p2
也具备同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的:
- call
- function Point(x, y){
- this.x = x;
- this.y = y;
- this.moveTo = function(x, y){
- this.x = x;
- this.y = y;
- }
- }
- var p1 = new Point(0, 0);
- p1.moveTo(1, 1);
- console.log(p1.x,p1.y); //1 1
- var p2 = {x: 0, y: 0};
- p1.moveTo.call(p2, 10, 10); // 只是参数不同
- console.log(p2.x,p2.y); //10 10
ECMAScript 5 引入了
。调用
- Function.prototype.bind
会创建一个与
- f.bind(someObject)
具有相同函数体和作用域的函数,但是在这个新函数中,
- f
将永久地被绑定到了
- this
的第一个参数,无论这个函数是如何被调用的。
- bind
- function f(){
- return this.a;
- }
- //this被固定到了传入的对象上
- var g = f.bind({a:"azerty"});
- console.log(g()); // azerty
- var h = g.bind({a:'yoo'}); //bind只生效一次!
- console.log(h()); // azerty
- var o = {a:37, f:f, g:g, h:h};
- console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
上面我们已经讲了使用箭头函数填 setTimeout 的坑,这次我们使用
方法来试试:
- bind
- var prop = 0;
- var Obj = {
- prop: 37,
- getProp: function() {
- setTimeout(function() {
- console.log(this.prop) // 37
- }.bind(Obj), 1000)
- }
- };
- Obj.getProp();
同样可以填坑,但是看上去没有使用箭头函数来的优雅。
本文介绍了 JavaScript 中的 this 关键字在各种情况下的含义,虽然这只是 JavaScript 中一个很小的概念,但借此我们可以深入了解 JavaScript 中函数的执行环境,而这是理解闭包等其他概念的基础。掌握了这些概念,才能充分发挥 JavaScript 的特点,才会发现 JavaScript 语言特性的强大。
来源: http://www.css88.com/archives/8482