文档章节

JavaScript 函数中的外部变量——理解 this

傅易
 傅易
发布于 2016/12/05 00:53
字数 1127
阅读 271
收藏 5

js 中的 this 指向确实是个坑,网上有人轰轰烈烈地讨论它,讨论 js 闭包,其实并没有那么玄学,让我们一点点剥开它的面纱。

很多知识性内容来自 邱桐城《JavaScript 中的 this》 的启发,基于他的文章,我写下了我的总结。

在全局作用域下

在浏览器环境下:

console.log(this);
// Window { .. }
this === window;
// true

全局作用域下,this 指向 Window 对象,这很好理解,仍然是传统 js 的结果。

在 node 环境下:

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

全局作用域下,this 指向 global 对象。

严格模式,在 node 环境下:

'use strict';
console.log(this);
// {}

遵循严格模式的规范,this 不再指向全局对象。

函数对象作用域下

function foo() {
    console.log(this);
}
foo();
// global / Window

严格模式,在 node 环境下:

'use strict';
function foo() {
    console.log(this);
}
foo();
// undefined

经过我的测试,虽然满足了规范要求,但在 node 7.2.0 下仍然出现了如上所示的不一致结果。

对象方法作用域下

let obj = {
    foo: function() {
        console.log(this);
    }
};
obj.foo();
// { foo: [Function] }
// obj 的值实际上是个匿名类的对象,foo 的值实际上是个匿名函数

作为对象方法时,this 指向该对象。

function func() {
    console.log(this);
}
let obj = {
    foo: func
};
obj.foo();
// { foo: [Function func] }

let foo1 = obj.foo;
foo1();
// global

注意到:在函数体内使用的、在函数体外定义(声明)的变量,是 传引用 的。

你可能对这个例子印象深刻:

var foos = [];
for (var i = 0; i < 3; ++i) {
    foos.unshift(function () {
        console.log(i);
    });
}
console.log(i);
// 3
for (var j in foos) {
    ++i;
    console.log(i);
    // 4 5 6
    foos[j]();
    // 4 5 6
}

i 变量在函数内是外部变量的引用,所以当函数外的 i 值变化了,函数内的 i 值也一同变化。

避免这样的外部变量引用也很简单,使用 constlet 这样的新关键字是最简单的一种,它们比传统的 var 有着更严谨的定义域,使该变量无法被运行时上下文访问到而保证其值不被替换:

const foos = [];
for (let i = 0; i < 3; ++i) {
    foos.unshift(function () {
        console.log(i);
    });
}
for (let i in foos) {
    foos[i]();
}
// 0 1 2

并不是新的关键字解决了该问题,而是新的关键字拥有更严谨的作用域。如果该变量在运行时上下文中仍能访问,那问题依旧:

const foos = [];
let i;
for (i = 0; i < 3; ++i) {
    foos.unshift(function () {
        console.log(i);
    });
}
for (let j in foos) {
    foos[j]();
}
// 3 3 3

看过上面的例子,你应该明白,函数内的 this 也是这样一个外部变量的引用。在传统使用 function 关键字的语法中,确实如此;但在 ES6 引入的新式 => 语法中,事情不一样了,你可以看做在函数内插入了这么一行伪代码:

// 这不是真正的 js 代码
const this = outer.this;

试看一例:

'use strict';

const obj1 = {
    foo: function() {
        console.log(this);
        return () => {
            console.log(this);
        };
    }
};
const foo1 = obj1.foo();
// { foo: [Function: foo] }
foo1();
// { foo: [Function: foo] }

const obj2 = {
    foo: function() {
        console.log(this);
        return function () {
            console.log(this);
        };
    }
};
const foo2 = obj2.foo();
// { foo: [Function: foo] }
foo2();
// undefined

=> 语法定义的函数对象,其 (const) this 指向定义时的上下文的 this,而不像 function 关键字定义的函数对象,其 (var) this 会跟随外部 this 的变化而变化。

在构造函数对象作用域下(使用 new 关键字)

'use strict';

function A() {
    console.log(this);
}

var a = new A();
// A {}
console.log(a);
// A {}

var b = A();
// undefined
console.log(b);
// undefined

构造函数中 this 指向其构造出来的对象,但是一定不要忘记使用 new 关键字。

call / apply / bind

js 中的函数对象,其 prototype 中定义了如下三个函数:

func.call(thisArg[, arg1[, arg2[, ...]]]);

执行函数 func,使用第一个参数作为 this,其他参数作为 func 的实参,一一对应。

func.apply(thisArg[, [arg1, arg2, ...]]);

执行函数 func,使用第一个参数作为 this,第二个参数为数组,数组中的每个元素作为 func 的实参,一一对应。

var foo = func.bind(thisArg[, arg1[, arg2[, ...]]]);

绑定 func 的 this 和所有参数,返回一个新的函数,但不执行它。

bind 的 this 对 new 关键字无效,但其他实参有效:

function A(name) {
    console.log(this.name);
    this.name = name;
    console.log(this.name);
}
var obj = {
    name: "obj"
};
var B = A.bind(obj, "B");
var b = new B('b');
// undefined B
console.log(obj.name);
// obj

要注意,=> 语法下的 this 不受影响,该语法下 this 视为 const 变量,不接受修改。

© 著作权归作者所有

共有 人打赏支持
傅易
粉丝 28
博文 105
码字总数 64235
作品 0
海淀
后端工程师
【旧文新读】解释“闭包”需要几行代码?

新读(2017年10月19日) 本文写于 ,今天做了一点修改,所谓修改,其实只是删去了几句不影响技术内容的话。关于闭包,我最近写了一篇新的文章,提到了静态作用域,相比本文,是对闭包的更深一...

天方夜
07/04
0
0
我所认识的JavaScript的闭包特性

闭包是一个比较抽象的概念,尤其是对js新手来说。在这里,我就我个人的理解j简单谈一下: 闭包:官方解释是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量...

longteng2013
2013/08/25
0
0
JavaScript模块化开发一瞥

对于那些正在构建大型应用程序,而对JavaScript不甚了解的开发者而言,他们最初必须要面对的挑战之一就是如何着手组织代码。起初只要在标记之间嵌入几百行代码就能跑起来,不过很快代码就会变...

偶是小娃
2014/02/25
0
0
[译] 理解 JavaScript 中的执行上下文和执行栈

原文地址:Understanding Execution Context and Execution Stack in Javascript 原文作者:Sukhjinder Arora 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:CoolR...

子非
09/20
0
0
翻译 - JavaScript中的作用域与变量声明提升

本文地址:http://blog.163.com/jinluhz/blog/static/113830152201131132035178/ 原文地址:http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting 原文作者:ben cherry ......

蜗牛奔跑
2015/06/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

【大福利】极客时间专栏返现二维码大汇总

我已经购买了如下专栏,大家通过我的二维码你可以获得一定额度的返现! 然后,再给大家来个福利,只要你通过我的二维码购买,并且关注了【飞鱼说编程】公众号,可以加我微信或者私聊我,我再...

飞鱼说编程
今天
1
0
Spring5对比Spring3.2源码之容器的基本实现

最近看了《Spring源码深度解析》,该书是基于Spring3.2版本的,其中关于第二章容器的基本实现部分,目前spring5的实现方式已有较大改变。 Spring3.2的实现: public void testSimpleLoad(){...

Ilike_Java
今天
1
0
【王阳明心学语录】-001

1.“破山中贼易,破心中贼难。” 2.“夫万事万物之理不外于吾心。” 3.“心即理也。”“心外无理,心外无物,心外无事。” 4.“人心之得其正者即道心;道心之失其正者即人心。” 5.“无...

卯金刀GG
今天
2
0
OSChina 周三乱弹 —— 我们无法成为野兽

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @ _刚刚好: 霸王洗发水这波很骚 手机党少年们想听歌,请使劲儿戳(这里) hahahahahahh @嘻酱:居然忘了喝水。 让你喝可乐的话, 你准忘不了...

小小编辑
今天
9
0
vm GC 日志 配置及查看

-XX:+PrintGCDetails 打印 gc 日志 -XX:+PrintTenuringDistribution 监控晋升分布 -XX:+PrintGCTimeStamps 包含时间戳 -XX:+printGCDateStamps 包含时间 -Xloggc:<filename> 可以将数据保存为......

Canaan_
昨天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部