文档章节

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

傅易
 傅易
发布于 2016/12/05 00:53
字数 1127
阅读 284
收藏 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
博文 110
码字总数 69025
作品 0
海淀
后端工程师
私信 提问
【译】理解JavaScript闭包——新手指南

闭包是JavaScript中一个基本的概念,每个JavaScript开发者都应该知道和理解的。然而,很多新手JavaScript开发者对这个概念还是很困惑的。 正确理解闭包可以帮助你写出更好、更高效、简洁的代...

LINJIAJUN
11/28
0
0
【旧文新读】解释“闭包”需要几行代码?

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

天方夜
10/30
0
0
理解 Javascript 的闭包

前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将以例子入手来介绍...

虫虫
2012/03/07
12.7K
30
我所认识的JavaScript的闭包特性

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

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

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

偶是小娃
2014/02/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周二乱弹 —— 其实我在地板也睡不着

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @witt-z :分享歌词: 阴天 在不开灯的房间,当所有思绪都一点一点沉淀。 分享莫文蔚的单曲《阴天》: 《阴天》- 莫文蔚 手机党少年们想听歌,...

小小编辑
10分钟前
11
3
微服务分布式事务实现

https://www.processon.com/view/link/5b2144d7e4b001a14d3d2d30

WALK_MAN
今天
3
0
《大漠烟尘》读书笔记及读后感文章3700字

《大漠烟尘》读书笔记及读后感文章3700字: 在这个浮躁的社会里,你有多久没有好好读完一本书了? 我们总觉得自己和别人不一样,所以当看到别人身上的问题时,很少有“反求诸己”,反思自己。...

原创小博客
今天
4
0
大数据教程(9.5)用MR实现sql中的jion逻辑

上一篇博客讲解了使用jar -jar的方式来运行提交MR程序,以及通过修改YarnRunner的源码来实现MR的windows开发环境提交到集群的方式。本篇博主将分享sql中常见的join操作。 一、需求 订单数据表...

em_aaron
今天
3
0
十万个为什么之什么是resultful规范

起源 越来越多的人开始意识到,网站即软件,而且是一种新型的软件。这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点...

尾生
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部