请教一个关于箭头函数this的问题
发布于 4 年前 作者 nar142857 3101 次浏览 来自 问答

var x=11; var obj={ x:22, say:()=>{ console.log(this.x); } } obj.say(); // undefined

这个输出的为什么是undefined 而不是11? 打印出整个this的话 是个空对象 {}; 网上查了好多都说这里是输出11, 难道是我的运行环境的问题?

程序完整截图如下 image.png image.png

16 回复

1532007667(1).png 有时候命令行好用点儿

箭头函数中的this指向父级作用域的this

@WPBLRUN 在命令行直接打印代码运行确实用var的时候会输出x=11,this为全局对象,用let的时候全局没有x这个变量输出是undefined,这些表现和我的理解是符合的。但是在IDE或者在在命令行 运行node test.js 文件的时候, 不论用var 还是let this指向的都是一个空对象{}; 这就让我有点费解了。这些环境应该也是有全局对象才对的啊。 image.png image.png

@lucky-leaf 是应该指向父级的this, 但运行结果和预期不一致啊, 在IDE中运行 this指向了{};

@nar142857 image.png https://cnodejs.org/topic/52308842101e574521c16e06 node 的命令行里输入代码执行和直接 使用node xxx.js执行还是有区别的。可以看下上个链接,在node test.js 执行时候,箭头函数实际绑定的this 是 module.exports 也就是 {};

undefined是你say方法的返回值, 你仔细看看是先打印11 然后打印undefined。 因为你say方法没有返回值,所以是undefined。 你改成这样看看打印啥。 var x=11; var obj={ x:22, say:()=>{ console.log(this.x); return 1; } } obj.say();

undefined就变成11了。

@nar142857 image.png 我实在想不到怎么把代码放在一个对象里执行,也没折腾出等价的代码来,晚上实在想不出例子了。现在也只是通过打log的代码更明显地看到this空对象是什么怎么来的。 @Visiters 希望有深入理解模块化的能够解惑,谢谢了!

先说结论,在node.js中执行箭头函数,其中的this永远指向文件的全局对象module.exports。如果是function的写法的话指向的是最近的父级作用域 验证过程如下:

Snipaste_2018-07-20_09-52-14.png

萌新第一次发言,如果有错请指正

@Gitforxuyang 你单独建一个xx.js文件执行以下, 和return 没关系哦, 输出的还是undefined, 楼上应该是正解。

@WPBLRUN 这个应该是正解,辛苦了。 经我测试发现,箭头函数的this指向的是 module.exports指向的对象,而不是module.exports对象。image.png image.png 我改变了module.exports的指向,但this并没有改变。 这样就会有另外一个问题,好多框架在都会在文件开头写exports = module.exports ={} 这样的语句,但是如果在这样的文件里面使用箭头函数,感觉会导致this的指向和预期的不一致。

@Masterlu1998 感觉你的理解不太对, 普通function函数的this指向的应该是这个函数的调用者对象, 使用call和apply函数的时候可以明显的看出来, 箭头函数没有this所以使用this的时候会向定义生效时的父级查找。

@nar142857 这个例子“箭头函数的this指向的是 module.exports指向的对象,而不是module.exports对象。” 厉害,找到根本原因了。学习了

箭头函数没有this…

个人认为是let关键字建立了块级作用域,声明的全局变量不会被注册到 window 上; 而此时的this指向的是当前的全局对象;浏览器即window,node的话。。看是在模块内(module.exports)还是全局(global)

var x = 1; let y = 2; console.log(this.x); // 1 console.log(this.y); // undefined

然后抛出一个揣测: let声明的变量不具有声明提前和变量提升。

1、声明提前即为 在声明之前无法使用,编译器不会将let声明的变量在当前作用于提前(根据MDN提供的相关文档这一点应该是正确的)。

2、其实变量提升这里我是有点混乱的,

在{}块级作用于下let声明的变量不会像使用var关键字或者不使用关键字声明的变量一样会提升到{}的同级作用域(根据MDN提供的相关文档这一点应该是正确的),

{ let x = 1; var y = 2; } console.log(y); // 2 console.log(x); // x is not defined

但在没有{}的情况下 例如刚才的例子 let声明的变量是否为全局变量 如下看起来好像是这样的

let x = 1; console.log(x); // 1 console.log(this.x); // undefined

接下来我们看一下全局对象的定义(开始大段摘抄)

①JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。
② 全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。全局对象不是任何对象的属性,所以它没有名称。
③ 在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象。但通常不必用这种方式引用全局对象,因为全局对象是作用域链的头,这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。例如,当JavaScript 代码引用 parseInt() 函数时,它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头,还意味着在顶层 JavaScript 代码中声明的所有变量,都将成为全局对象的属性。
④ 全局对象只是一个对象,而不是类。既没有构造函数,也无法用new实例化一个新的全局对象。
⑤ 实际上,ECMAScript 标准没有规定全局对象的类型,而在客户端 JavaScript 中,全局对象就是 Window 对象,表示允许 JavaScript 代码的 Web 浏览器窗口。浏览器把全局对象作为window对象的一部分实现了,因此,所有的全局属性和函数都是window对象的属性和方法。 

综上所述,let声明的变量不是全局变量,只是因为这种情况下我们的代码都会被隐式的加上{},成为全局作用域内的块级作用域, 而{}内的var声明的变量会提升到全局作用域 并且在{}中我们可以使用let声明的变量 造成了彷佛我们用let声明了全局变量,其实并非如此

回答的有些晚了 会不会沉了? 欢迎指教

如果你在全局作用域内使用 let 或 const,那么绑定就会发生在全局作用域内,但是不会向全局对象内部添加任何属性。这就意味着你不能使用 let 或 const 重写全局变量,而仅能屏蔽掉它们。如下所示:

// 在浏览器中运行
let RegExp = "Hello!";
console.log(RegExp);                    // "Hello!"
console.log(window.RegExp === RegExp);  // false

const ncz = "Hi!";
console.log(ncz);                       // "Hi!"
console.log("ncz" in window);           // false

在这里,let 声明创建了新的 RegExp 绑定并屏蔽掉了全局对象的 RegExp 属性。也就是说 window.RegExp 和 RegExp 并不等同,所以全局作用域并没有被污染。同样 const 声明创建了新的绑定的同时也没有在全局对象内部添加任何属性。这个设定使得在全局作用域内使用 let 或 const 声明要比 var 安全得多,特别是在你不想在全局对象内部添加属性的时候。

如果你想让代码可以被全局对象访问,你仍然需要使用 var,特别是当你想要在多个 window 和 frame 之间共享代码的时候

回到顶部