Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于箭头函数this的理解几乎完全是错误的 #150

Closed
darkrainChn opened this issue Mar 2, 2016 · 19 comments
Closed

关于箭头函数this的理解几乎完全是错误的 #150

darkrainChn opened this issue Mar 2, 2016 · 19 comments

Comments

@darkrainChn
Copy link

我原来的观点有误,修改掉吧。

我在发此文之前,曾经考虑过到底是我对原文理解有偏差,还是原文有误。最后判断是原文有误,现在看来是我错误判断了。
前面我也写到过以下文字——

这里说的lambda表达式的“定义时”,指的是代码运行过程中“遇到”lambda表达式的时候,它和代码的上下文没有关系,而是和运行时的上下文有关。
我们可以把lambda表达式看成一个变量,在代码运行“遇到”(看见)它之前,这个变量是未定义的;遇到的时候,就会把运行时的上下文绑定在展开函数的this上。此时lambda表达式内部的操作并没有被执行,它作为一个已经被定义并绑定了this的函数实体存在,等待被实际调用。

这里强调我也强调了函数定义是一个动态的概念,是外层代码执行时才产生了箭头函数的定义。之所以会觉得原文有问题,是因为“定义”这个词配合上“外层”的概念,很容易让人理解为:原文提到的“定义”是一个代码层面的概念,并且是与代码书写位置有关的,而不是一个“运行时”的概念。所以我会认为原文是错的。
现在再看这个问题,其实在这方面我们的观点是一致的,即箭头函数的this是它真正被定义时候的“运行时”上下文,而不是箭头函数实际使用(即内部语句被执行)时的上下文。结果对于原文理解不同,造成对原文错误的判断评价。

上面说的是一个理解问题,但接下来说的就是我本身的错误了。
对于“所有的箭头函数都没有自己的this,都指向外层”这句话,经过其他人的解释,我已经知道这是正确的表述了。而我对于箭头函数的展开:
var f = () => 5;
var f = function(){return 5;}.bind(this);
并不符合真正的实现机制。我这种展开方式,只是看上去效果与箭头函数差不多,除了这种展开后的函数可以被用作对象构造函数这一点差异外,不能再手动指定this、不能重新绑定其他的this,这个特性则是相同的。这种展开只能说是一种近似方式,它并不等价于原箭头函数,只是可能可以作为将代码改写为低版本时的一种实现方式。

我前面还提到扩展运算符所谓的“逆运算”问题,现在看来也只是理解方式不同造成的困惑,并不能说原书有错。
至于提到的另外2个小问题,目前看来确实是问题,但也只是小问题而已。回头找个时间再把它们写出来。

@hstarorg
Copy link

hstarorg commented Mar 3, 2016

首先,这种写法在JS中公认的名称就是箭头函数。
另外,不建议使用call或者apply这种用法来说this指向的问题。当然,原文可能是有偏颇之处。

BTW:我自己用的时候,如果需要用到this,我尽量避免使用箭头函数。

@ruanyf
Copy link
Owner

ruanyf commented Mar 3, 2016

@darkrainChn,首先,感谢你阅读了《ES6 标准入门》,并写了这么详细和深入的反馈。

“箭头函数”(arrow function)是规格里面的用语。事实上,整个规格里面一次也没有提到lambda

“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。

这句话是对的,并没有写错。

function foo(){
  setTimeout(() => {
    console.log("id:", this.id)
  }, 100);
}

foo.call({id:42});

请问,上面代码的{id: 42},到底是箭头函数定义时所在的对象,还是运行时所在的对象?

你认为,答案是后者。这是不对的。

因为,这个例子中,箭头函数位于foo函数内部。只有foo函数运行后,它才会按照定义生成,所以**foo运行时所在的对象,恰好是箭头函数定义时所在的对象**。

我对上面这个例子,做了一下修改。

function foo() {
  setTimeout( () => {
    console.log("id:", this.id);
  },100);
}

var id = 21; // 加入这行

foo.call( { id: 42 } );

请问,上面两个id,哪个是箭头函数定义时所在的对象,哪个是箭头函数运行时所在的对象?

你举的其他例子,都有这个问题。

你把箭头函数所在的函数与箭头函数本身混淆了。

最后,我再举一个例子。

请问,下面的代码运行结果是什么?

function foo() {
  return () => {
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()();
var t2 = f().call({id: 3})();
var t3 = f()().call({id: 4});

@ruanyf ruanyf closed this as completed Mar 3, 2016
@skyline0705
Copy link

原描述是比较有歧义性,一句话形容就是this指向当前所在function scope(不包括arrow function)…

@chaosforfun
Copy link

之前看过一个文章说箭头函数是没有this的,你在箭头函数里使用this就像使用普通变量一样,在箭头函数的scope内找不都会一直向父scope寻找。

@wizardforcel
Copy link

第一段代码。调用处的thiswindow,定义处的thisfoo中的this

你调用foo()this产生了默认绑定,foo中的this也是window,根本没法分辨。这样测试是没有意义的。所以只能通过显式绑定把foo中的this变成别的值来辨别。


第二段代码。调用处的this是产生事件的节点对象,定义处的thishandler.init中的this

谁告诉你handler.init中的this永远是handler了?你通过handler.init.call来调用,就不再是隐式绑定,而是显式绑定。所以箭头函数中的this就应该是你指定的那个值。


bind(this)是对的,建议原书改一改,这样理解起来就简单了。

@JonasGao
Copy link

JonasGao commented Mar 3, 2016

当我看到排版乱的一塌糊涂的时候,我就认为他提出的异议一定是错的了。。。而且并不想知道为什么。

@dxyqqs
Copy link

dxyqqs commented Mar 3, 2016

所有的箭头函数都没有自己的this,都指向外层

这句话就是箭头函数的精髓

“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。

这一句说的太模糊了,最好改成,总是指向所在函数运行时的this

@mqli
Copy link

mqli commented Mar 3, 2016

@darkrainChn 同学确实把箭头函数和箭头函数的定义函数弄混淆了,不过 @ruanyf

var f = v => v;

上面的箭头函数等同于:

var f = function(v) {
  return v;
};

这样的说法确实不准确,不过也不是是等同于

var f = function(){
  return v;
}.bind(this);

在ecma262规范中,箭头函数初始化到时候,会设置[ThisMode]为lexical

http://www.ecma-international.org/ecma-262/6.0/index.html#sec-functioninitialize

If kind is Arrow, set the [[ThisMode]] internal slot of F to lexical.

明确的规定,箭头函数根本没有自身的this绑定
http://www.ecma-international.org/ecma-262/6.0/index.html#sec-function-environment-records

If the value is "lexical", this is an ArrowFunction and does not have a local this value.

在函数执行前绑定this的时候,传入的thisArgument会被直接忽略
http://www.ecma-international.org/ecma-262/6.0/index.html#sec-ordinarycallbindthis

If thisMode is lexical, return NormalCompletion(undefined).

也就是说箭头函数本身没法修改this,所以对this访问永远是它继承外部上下的this,按照babel的实现来说,在箭头函数内部没有this引用的时候,默认编译成这样

var f = function(v) {
  return v;
};

但是如果箭头函数内部使用了this,就成了

function test() {
  var _this = this;

  var f = function f(v) {
    return _this.a;
  };
}

这个实现是符合标准的,全程没有绑定这回事。

“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。

这句话完全正确,而且语言是通过忽略对箭头函数的所有绑定操作来实现的,而不是简单的返回一个绑定this的闭包。

@lyyourc
Copy link

lyyourc commented Mar 3, 2016

箭头函数的 this 的值是他的 lexical scope 的 this 的值。然而很多人并不明白什么是 lexical scope。。。

@hemashushu
Copy link

我也补充一下,箭头函数里不但没有 this,也没有 arguments, super ……

参考资料:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/

我挺喜欢在 MDN 查阅 web 开发技术资料

@JonasGao
Copy link

JonasGao commented Mar 4, 2016

在 Chrome 48 里测试了一下,虽然内部实现不是简单的闭包,也不尽等同于 function 的 bind。但是最终在使用时,箭头函数内的 this 就像是闭包传入的一样。

不知道这样理解有没有问题。

@hemashushu
Copy link

@JonasGao 上面 MDN 的那个网址有个示例很好理解:

function Person() {
  var self = this; // Some choose `that` instead of `self`. 
  self.age = 0;

  setInterval(function growUp() {
    self.age++;
  }, 1000);
}

在使用感觉上可以说是等同于之前经常写的 var self = this; 这句。即:书写箭头函数时的那块代码外面的 this 。

@cbbfcd
Copy link

cbbfcd commented Sep 5, 2018

词法作用域

@plh97
Copy link

plh97 commented Sep 4, 2019

箭头函数就不是函数,他也就没有了自己的this,想要完全理解this,就得从汇编说起了。高级语言到汇编代码
我再次强调,this其实在汇编层面就存在的,他指向上一个调用栈,函数中的this是运行中的产物,当本函数执行完成就会推出调用栈,并将函数上下文恢复成上一个调用栈
请从汇编层面来理解this

@plh97
Copy link

plh97 commented Sep 4, 2019

从js的角度来理解this根本就是不合理的

@chico-malo
Copy link

@darkrainChn,首先,感谢你阅读了《ES6 标准入门》,并写了这么详细和深入的反馈。

“箭头函数”(arrow function)是规格里面的用语。事实上,整个规格里面一次也没有提到lambda

“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。

这句话是对的,并没有写错。

function foo(){
  setTimeout(() => {
    console.log("id:", this.id)
  }, 100);
}

foo.call({id:42});

请问,上面代码的{id: 42},到底是箭头函数定义时所在的对象,还是运行时所在的对象?

你认为,答案是后者。这是不对的。

因为,这个例子中,箭头函数位于foo函数内部。只有foo函数运行后,它才会按照定义生成,所以**foo运行时所在的对象,恰好是箭头函数定义时所在的对象**。

我对上面这个例子,做了一下修改。

function foo() {
  setTimeout( () => {
    console.log("id:", this.id);
  },100);
}

var id = 21; // 加入这行

foo.call( { id: 42 } );

请问,上面两个id,哪个是箭头函数定义时所在的对象,哪个是箭头函数运行时所在的对象?

你举的其他例子,都有这个问题。

你把箭头函数所在的函数与箭头函数本身混淆了。

最后,我再举一个例子。

请问,下面的代码运行结果是什么?

function foo() {
  return () => {
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()();
var t2 = f().call({id: 3})();
var t3 = f()().call({id: 4});

哈?我想问下,这个call方法是不是写错了?这个call第一个参数不是传入this的嘛, foo.call( { id: 42 } ) 改成 foo.call(null, { id: 42 } ); ?

@JesonSirius
Copy link

JesonSirius commented Sep 27, 2020

image
这个地方 ,当setTimeout里面是普通函数的时候,输出的不应该是 undefined吗 @ruanyf

我是在node环境下 打印出了undefined, 浏览器环境下是 21 , 是因为node环境下this指向 当前模块上下文 也就是setTimeout里面,这样理解对吗

ruanyf added a commit that referenced this issue Oct 2, 2020
roojay520 added a commit to roojay520/es6tutorial that referenced this issue Oct 10, 2020
docs(function/arrow fucntion): edit ruanyf#150
roojay520 pushed a commit to roojay520/es6tutorial that referenced this issue Oct 10, 2020
@Wolfeather
Copy link

@darkrainChn,首先,感谢你阅读了《ES6 标准入门》,并写了这么详细和深入的反馈。
“箭头函数”(arrow function)是规格里面的用语。事实上,整个规格里面一次也没有提到lambda

“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。

这句话是对的,并没有写错。

function foo(){
  setTimeout(() => {
    console.log("id:", this.id)
  }, 100);
}

foo.call({id:42});

请问,上面代码的{id: 42},到底是箭头函数定义时所在的对象,还是运行时所在的对象?
你认为,答案是后者。这是不对的。
因为,这个例子中,箭头函数位于foo函数内部。只有foo函数运行后,它才会按照定义生成,所以**foo运行时所在的对象,恰好是箭头函数定义时所在的对象**。
我对上面这个例子,做了一下修改。

function foo() {
  setTimeout( () => {
    console.log("id:", this.id);
  },100);
}

var id = 21; // 加入这行

foo.call( { id: 42 } );

请问,上面两个id,哪个是箭头函数定义时所在的对象,哪个是箭头函数运行时所在的对象?
你举的其他例子,都有这个问题。
你把箭头函数所在的函数与箭头函数本身混淆了。
最后,我再举一个例子。
请问,下面的代码运行结果是什么?

function foo() {
  return () => {
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()();
var t2 = f().call({id: 3})();
var t3 = f()().call({id: 4});

哈?我想问下,这个call方法是不是写错了?这个call第一个参数不是传入this的嘛, foo.call( { id: 42 } ) 改成 foo.call(null, { id: 42 } ); ?

没写错,就是将对象{id:42}当做函数的this,来调用了foo。call方法的后续参数是作为函数参数传入的 函数本身没有接收参数

@Miaowang-keep
Copy link

图片

这个地方,当setTimeout里面是普通函数的时候,输出的不应该是未定义的吗?@ruanyf
我是在节点环境下的打印器未定义,浏览环境下是 21 ,是因为节点环境下这个当前位置的时间也是setTimeout里面,这样理解对吗

this在node的全局下分两种情况:1、仅仅只是打印this(console.log(this))输出的是这个模块module.exports的对象
2、如果是在一个函数中打印。function foo (){console.log(this)} foo() 输出的是global对象!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests