ES6中使用箭头符号和不使用箭头符号,向类添加方法有什么区别?

4

我最近发现了两种在JavaScript ES6中向类添加方法的方法。以下是它们的简要说明:

class SomeClass {

  someMethod(arg) {
    console.log(this.anotherMethod); 
      // This will produce an error because 'this' is undefined here.
  }

  anotherMethod = (arg) => {
    // does stuff
  }

}

请问有人能给我一个好的解释,解释这里到底发生了什么,以及语法的含义是什么?我从babel tutorial中理解到,在ES6中箭头=>表示函数。但我认为还有其他事情正在发生。
具体来说,如果我没有弄错的话,如果我尝试通过this关键字访问someMethod(),它将无法工作。我认为关键在于一种解释,我无法从Babelify ES6教程中理解:

与函数不同,箭头共享其周围代码的词法this。

这是否意味着箭头符号将把函数分配给其周围范围的this?因此,如果您在类的范围内使用箭头,则该函数被分配给指向该对象的this吗?
我通过Babelify的ES6 repl运行了上面的示例代码。我无法完全理解这段代码。在我的看法中,使用箭头语法创建的方法被添加到了新的Class=Object本身,而使用非箭头语法创建的方法被添加到了Class=Object的原型中。
有没有人能给出一个简洁明了的解释,说明二者之间的区别以及下面的代码在做什么?
"use strict";

var _createClass = (function() {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }
    return function(Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
})();

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

var SomeClass = (function () {
  function SomeClass() {
    _classCallCheck(this, SomeClass);

    this.anotherMethod = function (dog) {
      // do stuff
    };
  }

  _createClass(SomeClass, [{
    key: "someMethod",
    value: function someMethod(arg) {
      console.log(this.anotherMethod);
      // This will produce an error b/c this is undefined here.
    }
  }]);

  return SomeClass;
})();
3个回答

3

有人能给我一个关于这里正在发生的事情和语法含义的好解释吗?

你对箭头函数的理解和期望是正确的。

你正在使用一项备受争议的ES7提案:类属性。它基本上与在构造函数中声明该属性相同:

constructor() {
  this.anotherMethod = (arg) => {
    // does stuff
  };
}

因此,this将指向该实例。
如果您在Babel中禁用Babel的实验模式,您将看到此属性声明会产生语法错误。

1
区别在于一个是有效的,另一个是语法错误。
根据 类定义

ClassDeclaration[Yield, Default]
    class BindingIdentifier[?Yield] ClassTail[?Yield]
    [+Default] class ClassTail[?Yield]

ClassTail[Yield]
    ClassHeritage[?Yield]opt { ClassBody[?Yield]opt }

ClassBody[Yield]
    ClassElementList[?Yield]

ClassElementList[Yield]
    ClassElement[?Yield]
    ClassElementList[?Yield] ClassElement[?Yield]

ClassElement[Yield]
    MethodDefinition[?Yield]
    static MethodDefinition[?Yield]
    ;


ClassDeclaration[Yield, Default]:
    class BindingIdentifier[?Yield] ClassTail[?Yield]
    [+Default] class ClassTail[?Yield]
ClassTail[Yield]:
    ClassHeritage[?Yield]opt { ClassBody[?Yield]opt }
ClassBody[Yield]:
    ClassElementList[?Yield]
ClassElementList[Yield]:
    ClassElement[?Yield]
    ClassElementList[?Yield] ClassElement[?Yield]
ClassElement[Yield]:
    MethodDefinition[?Yield]
    static MethodDefinition[?Yield]
    ;

这意味着类体只能包含方法定义或静态方法定义。

它们在方法定义中定义:

MethodDefinition[Yield] :
    PropertyName[?Yield] ( StrictFormalParameters ) { FunctionBody }
    GeneratorMethod[?Yield]
    get PropertyName[?Yield] ( ) { FunctionBody }
    set PropertyName[?Yield] ( PropertySetParameterList ) { FunctionBody }

因此,以下代码不允许出现在类体内:
anotherMethod = (arg) => {
  // does stuff
}

如果您使用 Traceur 而不是 Babel,它会抱怨语法错误。

第二种方法是使用ES7提议的类属性。 - loganfsmyth
1
@loganfsmyth 很有趣,我不知道这一点。但问题只涉及ES6,而代码无效ES6。 - Oriol
不知道,根据“我最近发现了两种在JavaScript ES6中向类添加方法的方法”的说法,听起来他们只是认为类属性是ES6,并且想要更多关于如何使用箭头函数的信息,而您没有回答。 - loganfsmyth

1
这是否意味着箭头符号将函数分配给包围它的作用域的this? 是的,尽管我会说“当前执行上下文的this”。 如果我正确理解你的问题,那么例子可以更简单:
// When called with new, this is a new object
function Foo(name) {
  this.name = name;
}

// Get this of the current execution context
var currentThis = this;

// Using an arrow function means that when the method is called
// as a method of a Foo instance, this will be
// the current this
Foo.prototype.bar = (x)=> {
  console.log(x + ':' + (typeof this));
  return this;
};

var foo = new Foo('foo');

console.log(foo.name); // fo
console.log(foo.bar('bar') == currentThis); // bar:object
                                             // true

抱歉,错过了:

有没有人能简洁明了地解释一下下面的代码在做什么?

这是一种创建构造函数的迂回方式。 _createClass 函数使用 Object.definePropety 将属性从 protoProps 对象复制到构造函数的原型上,将 staticProps 对象复制到构造函数本身上。

构造函数(“class”)还有一个 _classCallCheck 方法,因此如果其被调用时 this 不是自己的实例(即未作为构造函数调用),则调用失败。这是为了检查它是否已使用 new 调用,但这不是万无一失的,因为可以以其他方式设置 this(虽然我不知道在这种情况下是否有任何意义)。

也许这只是简略的概括,但这是要点。


2
ES7提议的类属性,例如箭头函数示例,在构造过程中实际上是在其包装上下文设置为实例时执行的,因此它们不会像您的bar示例一样放置在原型上。 - loganfsmyth
@loganfsmyth——ECMAScript ed 6(或者如果你很时髦,可以称之为ECMAScript 2015)的箭头函数目前支持非常差,尽管它已经成为当前标准一个月了(天哪,时间都去哪儿了?)。进入下一个版本肯定是领先的。;-) - RobG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接