我什么时候应该在ECMAScript 6中使用箭头功能?

时间:2014-04-08 13:50:55

标签: javascript lambda ecmascript-6 ecmascript-harmony arrow-functions

问题针对的是那些在即将到来的ECMAScript 6(Harmony)背景下考虑过代码风格的人,以及那些已经使用过该语言的人。

使用() => {}function () {},我们得到两种非常类似的方法来编写ES6中的函数。在其他语言中,lambda函数通常通过匿名来区分自己,但在ECMAScript中,任何函数都可以是匿名的。这两种类型中的每一种都具有唯一的使用域(即当this需要明确绑定或明确地不绑定时)。在这些域之间存在大量的情况,其中任何一种符号都可以。

ES6中的箭头功能至少有两个限制:

  • 不使用new
  • 修正了this在初始化时绑定范围

除了这两个限制之外,箭头函数理论上几乎可以在任何地方替换常规函数。在实践中使用它们的正确方法是什么?应该使用箭头功能,例如:

  • “他们工作的任何地方”,即在任何地方,函数不必与this变量无关,我们也不会创建对象。
  • 仅“需要的任何地方”,即事件监听器,超时,需要绑定到某个范围
  • 具有'短'功能但不具有'长'功能
  • 仅适用于不包含其他箭头功能的功能

我正在寻找的是在ECMAScript的未来版本中选择适当的函数符号的指南。该指南需要明确,以便可以向团队中的开发人员讲授,并保持一致,这样就不需要从一个函数符号到另一个函数符号进行不断的重构。

9 个答案:

答案 0 :(得分:292)

不久前,我们的团队将其所有代码(一个中型AngularJS应用程序)迁移到使用 Traceur Babel编译的JavaScript。我现在使用以下经验法则来处理ES6及更高版本中的函数:

  • 在全局范围和function属性中使用Object.prototype
  • class用于对象构造函数。
  • 在其他地方使用=>

为什么几乎到处都使用箭头功能?

  1. 范围安全:当一致地使用箭头函数时,一切都保证使用与根相同的thisObject。如果即使单个标准函数回调与一堆箭头函数混合在一起,那么范围也会变得混乱。
  2. 紧凑性:箭头功能更易于读写。 (这看似自以为是,所以我将进一步举几个例子。)
  3. 清晰度:当几乎所有东西都是箭头功能时,任何常规function都会立即伸出来定义范围。开发人员始终可以查找次高function语句,以查看thisObject的内容。
  4. 为什么总是在全局范围或模块范围内使用常规函数?

    1. 表示不应访问thisObject
    2. 的功能
    3. window对象(全局范围)最好明确解决。
    4. 许多Object.prototype定义存在于全局范围内(请考虑String.prototype.truncate等),而且通常必须属于function类型。始终在全局范围内使用function有助于避免错误。
    5. 全局范围内的许多函数都是旧式类定义的对象构造函数。
    6. 函数可以命名为 1 。这有两个好处:(1)写function foo(){}const foo = () => {}更不尴尬 - 特别是在其他函数调用之外。 (2)函数名称显示在堆栈跟踪中。虽然命名每个内部回调都很繁琐,但命名所有公共函数可能是一个好主意。
    7. 函数声明是hoisted,(意味着它们可以在声明之前被访问),这是静态效用函数中的一个有用属性。

    8. 对象构造函数

      尝试实例化箭头函数会抛出异常:

      var x = () => {};
      new x(); // TypeError: x is not a constructor
      

      函数优于箭头函数的一个关键优势是函数兼作对象构造函数:

      function Person(name) {
          this.name = name;
      }
      

      然而,功能相同的 2 ES Harmony draft class definition几乎同样紧凑:

      class Person {
          constructor(name) {
              this.name = name;
          }
      }
      

      我希望最终不鼓励使用前一种符号。某些对象构造函数表示法仍然可以用于简单的匿名对象工厂,其中对象是以编程方式生成的,但不是很多。

      如果需要对象构造函数,则应考虑将函数转换为class,如上所示。语法也适用于匿名函数/类。


      箭头功能的可读性

      坚持常规功能的可能最好的理由 - 范围安全性被诅咒 - 箭头功能的可读性低于常规功能。如果您的代码首先不起作用,那么箭头函数可能似乎没有必要,并且当箭头函数不一致使用时,它们看起来很难看。

      ECMAScript已经发生了很大变化,因为ECMAScript 5.1为我们提供了功能Array.forEachArray.map以及所有这些功能编程功能,这些功能让我们使用以前使用过for循环的函数。异步JavaScript已经取得了相当大的进展。 ES6还将发布一个Promise对象,这意味着更多的匿名函数。功能编程没有回头路。在功能JavaScript中,箭头函数优于常规函数。

      比如说这个(特别令人困惑的)代码 3

      function CommentController(articles) {
          this.comments = [];
      
          articles.getList()
              .then(articles => Promise.all(articles.map(article => article.comments.getList())))
              .then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
              .then(comments => {
                  this.comments = comments;
              })
      }
      

      具有常规功能的同一段代码:

      function CommentController(articles) {
          this.comments = [];
      
          articles.getList()
              .then(function (articles) {
                  return Promise.all(articles.map(function (article) { 
                      return article.comments.getList();
                  }));
              })
              .then(function (commentLists) {
                  return commentLists.reduce(function (a, b) {
                      return a.concat(b); 
                  });
              })
              .then(function (comments) {
                  this.comments = comments;
              }.bind(this));
      }
      

      虽然任何一个箭头功能都可以用标准功能代替,但这样做几乎没有什么好处。哪个版本更具可读性?我会说第一个。

      我认为使用箭头功能或常规功能的问题随着时间的推移会变得不那么重要。大多数函数将成为类方法,它们会删除function关键字,或者它们将成为类。函数将继续用于通过Object.prototype修补类。与此同时,我建议为任何真正属于类方法或类的内容保留function关键字。


      备注

      1. 命名箭头功能已deferred in the ES6 spec。它们可能仍会添加未来版本。
      2. 根据规范草案"类声明/表达式创建构造函数/原型对与函数声明" 完全相同,只要类不使用{{1关键字。一个小的区别是类声明是常量,而函数声明不是。
      3. 关于单语句箭头函数中的块的注意事项:我喜欢在单独调用箭头函数的地方使用块(例如赋值)。这样很明显可以丢弃返回值。

答案 1 :(得分:78)

根据proposal,箭头旨在解决和解决传统Function Expression的几个常见痛点。"他们打算通过词法绑定this并提供简洁的语法来改善问题。

然而,

  • 不能始终如一地绑定this词汇
  • 箭头函数语法细腻且含糊不清

因此,箭头函数会产生混淆和错误的机会,应该从JavaScript程序员的词汇表中排除,仅用function替换。

关于词汇this

this存在问题:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

箭头函数旨在解决我们需要在回调中访问this属性的问题。有几种方法可以做到这一点:可以将this分配给变量,使用bind,或使用Array聚合方法上的第3个参数。然而箭头似乎是最简单的解决方法,因此该方法可以像这样重构:

this.pages.forEach(page => page.draw(this.settings));

但是,请考虑代码是否使用了像jQuery这样的库,其方法特别绑定this。现在,有两个this值要处理:

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

我们必须使用functioneach动态绑定this。我们不能在这里使用箭头功能。

处理多个this值也会让人感到困惑,因为很难知道作者正在谈论哪个this

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

作者是否真的打算致电Book.prototype.reformat?或者他忘记绑定this,打算打电话给Reader.prototype.reformat?如果我们将处理程序更改为箭头函数,我们同样会想知道作者是否需要动态this,但是选择了箭头,因为它适合于一行:

function Reader() {
    this.book.on('change', () => this.reformat());
}

有人可能会提出:"箭头有时可能是错误的使用功能,这是特殊情况吗?也许如果我们很少需要动态this值,那么在大多数情况下仍然可以使用箭头。"

但问问自己:"它是否值得它'调试代码并发现错误的结果是由边缘情况引起的?'"我不仅在大多数情况下避免麻烦,而且在100%的情况下都是这样。

有一种更好的方法:始终使用function(因此this始终可以动态绑定),并始终通过变量引用this。变量是词法并且有许多名称。将this分配给变量会使您的意图明确:

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

此外,始终this分配给变量(即使只有一个this或没有其他功能)也确保即使在代码已更改。

此外,动态this也不例外。 jQuery用于超过5000万个网站(截至2016年2月撰写)。以下是动态绑定this的其他API:

  • Mocha(昨天下载约120k)通过this公开了测试方法。
  • Grunt(昨天下载约63,000)通过this公开构建任务的方法。
  • Backbone(昨天下载约22k)定义了访问this
  • 的方法
  • 事件API(如DOM')引用带有EventTarget的{​​{1}}。
  • 修补或扩展的原型API引用this
  • 的实例

(统计信息来自http://trends.builtwith.com/javascript/jQueryhttps://www.npmjs.com。)

您可能已经需要动态this绑定。

有时期望词汇this,但有时不会;就像有时期望动态this一样,但有时不会。值得庆幸的是,有一种更好的方法,它总能生成并传达预期的绑定。

关于简洁语法

箭头功能成功地提供了一种更短的句法形式"用于功能。但这些较短的功能会让你更成功吗?

this"更容易阅读"比x => x * x?也许是这样,因为它更有可能产生一个短的代码行。根据Dyson的The influence of reading speed and line length on the effectiveness of reading from screen

  

中线长度(每行55个字符)似乎支持正常和快速的有效读数。这产生了最高水平的理解。 。

对条件(三元)运算符和单行function (x) { return x * x; }语句进行了类似的证明。

但是,你真的在​​写简单的数学函数advertised in the proposal吗?我的域名不是数学的,所以我的子程序很少如此优雅。相反,我通常会看到箭头函数打破列限制,并由于编辑器或样式指南而换行到另一行,这会使"可读性无效"根据戴森的定义。

有人可能会提出,"如果可能,只使用短版本的短版本怎么样?"但现在风格规则与语言约束相矛盾:"尽量使用最短的函数符号,记住有时只有最长的符号会按预期绑定if。"这种混淆使箭头特别容易被滥用。

箭头函数语法存在许多问题:

this

这两个函数在语法上都是有效的。但是const a = x => doSomething(x); const b = x => doSomething(x); doSomethingElse(x); 不在doSomethingElse(x);的正文中,它只是一个写得不好的顶级陈述。

当扩展到块形式时,不再存在隐式b,人们可能忘记恢复。但表达式可能旨在产生副作用,那么谁知道明天是否需要明确的return

return

可以将作为rest参数的内容解析为扩展运算符:

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

分配可能与默认参数混淆:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

块看起来像对象:

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens

这是什么意思?

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

作者是否打算创建一个无操作或返回空对象的函数? (考虑到这一点,我们是否应该在() => {} 之后放置{?我们是否应该仅限于表达式语法?这会进一步减少箭头的频率。)

=>看起来像=><=

>=

要立即调用箭头函数表达式,必须将x => 1 ? 2 : 3 x <= 1 ? 2 : 3 if (x => 1) {} if (x >= 1) {} 放在外面,但将()置于内部是有效的,可能是故意的。

()

虽然如果有人写(() => doSomething()()) // Creates function calling value of `doSomething()` (() => doSomething())() // Calls the arrow function 打算写一个立即调用的函数表达式,那么就不会发生任何事情。

很难说箭头功能更容易理解&#34;考虑到上述所有情况。一个可以学习使用此语法所需的所有特殊规则。这真的值得吗?

(() => doSomething()());的语法无异常概括。仅使用function意味着语言本身会阻止编写令人困惑的代码。要编写在所有情况下都应在语法上理解的过程,我选择function

关于指南

您要求的指南需要明确&#34;明确&#34;和#34;一致。&#34;使用箭头函数最终将导致语法有效,逻辑无效的代码,两种函数形式交织在一起,有意义且任意。因此,我提供以下内容:

ES6中的功能表示法指南:

  • 始终使用function创建程序。
  • 始终将function分配给变量。请勿使用this

答案 2 :(得分:32)

Arrow functions were created to simplify function scope and solving the this keyword by making it more simpler. They utilize the => syntax, which looks like an arrow.

Note: It does not replace the existing functions. If you replace every function syntax with arrow functions, its not going to work in all cases.

Let's have a look at the existing ES5 syntax, If the this keyword were inside an object’s method (a function that belongs to an object), what would it refer to?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

The above snippet would refer to an object and print out the name "RajiniKanth". Let's explore the below snippet and see what would this point out here.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Now what about if the this keyword were inside of method’s function?

Here this would refer to window object than the inner function as its fallen out of scope. Because this, always references the owner of the function it is in, for this case — since it is now out of scope — the window/global object.

When it is inside of an object’s method — the function’s owner is the object. Thus the this keyword is bound to the object. Yet when it is inside of a function, either stand alone or within another method, it will always refer to the window/global object.

var fn = function(){
  alert(this);
}

fn(); // [object Window]

There are ways to solve this problem in our ES5 itself, let us look into that before diving into ES6 arrow functions on how solve it.

Typically you would, create a variable outside of the method’s inner function. Now the ‘forEach’ method gains access to this and thus the object’s properties and their values.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

using bind to attach the this keyword that refers to the method to the method’s inner function.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   }).bind(this);
  }
};

Actor.showMovies();

Now with ES6 arrow function, we can deal with lexical scoping issue in a simpler way.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Arrow functions are more like function statements, except that they bind the this to parent scope. If the arrow function is in top scope, this argument will refer to window/global scope, while an arrow function inside a regular function will have its this argument the same as its outer function.

With arrow functions this is bound to the enclosing scope at creation time and cannot be changed. The new operator, bind, call, and apply have no effect on this.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

In the above example, we lost the control of this. We can solve the above example by using a variable reference of this or using bind. With ES6, it becomes easier in managing the this as its bound to lexical scoping.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

When not to Arrow functions

Inside an object literal.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getName is defined with an arrow function, but on invocation it alerts undefined because this.name is undefined as the context remains to window.

It happens because the arrow function binds the context lexically with the window object... i.e outer scope. Executing this.name is equivalent to window.name, which is undefined.

Object prototype

The same rule applies when defining methods on a prototype object. Instead of using an arrow function for defining sayCatName method, which brings an incorrect context window:

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

Invoking constructors

this in a construction invocation is the newly created object. When executing new Fn(), the context of the constructor Fn is a new object: this instanceof Fn === true.

this is setup from the enclosing context, i.e the outer scope which makes it not assigned to newly created object.

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

Callback with dynamic context

Arrow function binds the context statically on declaration and is not possible to make it dynamic. Attaching event listeners to DOM elements is a common task in client side programming. An event triggers the handler function with this as the target element.

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

this is window in an arrow function that is defined in the global context. When a click event happens, browser tries to invoke the handler function with button context, but arrow function does not change its pre-defined context. this.innerHTML is equivalent to window.innerHTML and has no sense.

You have to apply a function expression, which allows to change this depending on the target element:

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

When user clicks the button, this in the handler function is button. Thus this.innerHTML = 'Clicked button' modifies correctly the button text to reflect clicked status.

References: https://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/

答案 3 :(得分:13)

  

箭头功能 - 目前使用最广泛的ES6功能......

用法:除以下情况外,所有ES5功能都应替换为ES6箭头功能:

不应使用箭头功能:

  1. 在想要功能提升的时候
    • 因为箭头功能是匿名的。
  2. 当我们想在函数中使用this / arguments
    • 由于箭头函数没有自己的this / arguments,因此它们取决于它们的外部背景。
  3. 当我们想要使用命名函数时
    • 因为箭头功能是匿名的。
  4. 当我们想要将函数用作constructor
    • 由于箭头功能没有自己的this
  5. 当我们想要在对象文字中添加函数作为属性并在其中使用对象时
    • 因为我们无法访问this(应该是对象本身)。
  6. 让我们了解箭头函数的一些变体以便更好地理解:

    变体1 :当我们想要向函数传递多个参数并从中返回一些值时。

    ES5版

    var multiply = function (a,b) {
        return a*b;
    };
    console.log(multiply(5,6)); //30
    

    ES6版本

    var multiplyArrow = (a,b) => a*b;
    console.log(multiplyArrow(5,6)); //30
    

    注意: 不需要function个关键字。 =>是必需的。 {}是可选的,当我们不提供{} return由JavaScript隐式添加时,当我们提供{}时,如果需要,我们需要添加return

    变体2 :当我们想要将一个参数传递给一个函数并从中返回一些值时。

    ES5版

    var double = function(a) {
        return a*2;
    };
    console.log(double(2)); //4
    

    ES6版本

    var doubleArrow  = a => a*2;
    console.log(doubleArrow(2)); //4
    

    注意: 当只传递一个参数时,我们可以省略括号()

    变体3 :当我们不想将任何参数传递给函数并且不想返回任何值时。

    ES5版

    var sayHello = function() {
        console.log("Hello");
    };
    sayHello(); //Hello
    

    ES6版本

    var sayHelloArrow = () => {console.log("sayHelloArrow");}
    sayHelloArrow(); //sayHelloArrow
    

    变体4 :当我们想要明确地从箭头函数返回时。

    ES6版本

    var increment = x => {
      return x + 1;
    };
    console.log(increment(1)); //2
    

    变体5 :当我们想要从箭头函数返回一个对象时。

    ES6版本

    var returnObject = () => ({a:5});
    console.log(returnObject());
    

    注意: 我们需要将对象包装在括号()中,否则JavaScript无法区分块和对象。

    变体6 :箭头函数没有自己的arguments(像对象一样的数组),它们依赖于arguments的外部上下文。

    ES6版本

    function foo() {
      var abc = i => arguments[0];
      console.log(abc(1));
    };    
    foo(2); // 2
    

    注意: foo是一个ES5函数,其中arguments数组类似于object,传递给它的参数为2,因此arguments[0]的{​​{1}}为2。

    foo是一个ES6箭头函数,因为它没有自己的abc,因此它会打印arguments arguments[0]的外部上下文。

    变体7 :箭头函数没有自己的foo,它们依赖于this的外部上下文

    ES5版

    this

    注意: 传递给setTimeout的回调是一个ES5函数,它有自己的var obj5 = { greet: "Hi, Welcome ", greetUser : function(user) { setTimeout(function(){ console.log(this.greet + ": " + user); // "this" here is undefined. }); } }; obj5.greetUser("Katty"); //undefined: Katty ,它在this环境中是未定义的,因此我们得到输出:

    use-strict

    ES6版本

    undefined: Katty
    

    注意: 传递给var obj6 = { greet: "Hi, Welcome ", greetUser : function(user) { setTimeout(() => console.log(this.greet + ": " + user)); // this here refers to outer context } }; obj6.greetUser("Katty"); //Hi, Welcome: Katty 的回调是一个ES6箭头函数,它没有它自己的setTimeout所以它从它的外部上下文this获取它,greetUserthis obj6因此我们得到输出:

    Hi, Welcome: Katty
    

    <强>杂 我们不能将new与箭头功能一起使用。 箭头函数没有prototype属性。 通过thisapply调用箭头功能时,我们没有绑定call

答案 4 :(得分:6)

除了到目前为止的大答案之外,我还想提出一个非常不同的理由,为什么箭头函数在某种意义上比“普通”JavaScript函数更好。为了便于讨论,让我们暂时假设我们使用类型检查器,如TypeScript或Facebook的“Flow”。考虑以下玩具模块,它是有效的ECMAScript 6代码加上流类型注释:(我将包含无类型代码,这将在Babab的实际结果中,在此答案的最后,因此它实际上可以运行。)

export class C {
  n : number;
  f1: number => number; 
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

现在看看当我们使用来自不同模块的C类时会发生什么,例如:

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

正如你所看到的,类型检查器失败:f2应该返回一个数字,但它返回一个字符串!

更糟糕的是,似乎无法想象的类型检查器可以处理普通(非箭头)JavaScript函数,因为f2的“this”不会出现在f2的参数列表中,所以需要“this”的类型不能作为注释添加到f2。

此问题是否也会影响不使用类型检查程序的人?我是这么认为的,因为即使我们没有静态类型,我们也会认为它们就在那里。 (“第一个参数必须是一个数字,第二个参数必须是一个字符串”等。)隐藏的“这个” - 可能会或可能不会在函数体内使用的参数使我们的心理簿记变得更难。

这是可运行的无类型版本,由Babel生成:

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

答案 5 :(得分:3)

我更喜欢在不需要访问本地this的情况下使用箭头功能,因为箭头功能do not bind their own this, arguments, super, or new.target

答案 6 :(得分:1)

以简单的方式,

var a =20; function a(){this.a=10; console.log(a);} 
//20, since the context here is window.

另一个例子:

var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();

Ans:控制台会打印20.

无论何时执行函数都会创建自己的堆栈,在此示例中ex函数与new运算符一起执行,因此将创建上下文,并且inner时执行它JS会创建一个新的堆栈并执行inner函数a global context尽管有一个本地上下文。

因此,如果我们希望inner函数具有ex的本地上下文,那么我们需要将上下文绑定到内部函数。

箭头解决了这个问题,而不是取Global context它们取local context(如果存在)。在given example,new ex()this

因此,在所有绑定都是显式的情况下,Arrows会默认解决问题。

答案 7 :(得分:1)

箭头功能或Lambdas,是在ES 6中引入的。除了其优雅的语法外,最显着的功能差异在于范围 this 箭头功能

  

正则函数表达式中,this关键字根据调用它的 context 绑定到不同的值。

     

箭头功能中,this词法约束约束,这意味着它在定义箭头功能的范围内从this结束(父范围),并且无论在何处以及如何调用/调用它都不会改变。

限制箭头功能作为对象上的方法

// this = global Window
let objA = {
 id: 10,
 name: "Simar",
 print () { // same as print: function() 
  console.log(`[${this.id} -> ${this.name}]`);
 }
}
objA.print(); // logs: [10 -> Simar]
objA = {
 id: 10,
 name: "Simar",
 print: () => {
  // closes over this lexically (global Window)
  console.log(`[${this.id} -> ${this.name}]`);
 }
};
objA.print(); // logs: [undefined -> undefined]

对于objA.print(),当使用常规print()定义了function方法时,它通过将this正确解析为objA来进行方法调用而工作,但在定义为箭头=>函数。这是因为在常规函数中,this作为对象(objA)的方法被调用时,是对象本身。但是,在使用箭头功能的情况下,this被绑定到定义它的封闭范围的this(在我们的例子中是global / Window),并且在调用过程中保持不变objA上的方法。

仅在预期this在时间定义上是固定且受约束的情况下,对象BUT的方法中的箭头功能才优于常规功能。

/* this = global | Window (enclosing scope) */

let objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( function() {
    // invoked async, not bound to objB
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( () => {
    // closes over bind to this from objB.print()
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [20 -> Paul]

对于objB.print(),其中print()方法被定义为调用console.log( [$ {this.id}-> {this.name}] )的函数作为setTimeout上的回调异步执行,当将箭头函数用作回调时,this正确解析为objB,但是当将回调定义为常规函数时,=>失败。这是因为传递给setTimeout(()=>..)的箭头this函数在其父{ie调用定义它的objB.print()。换句话说,箭头=>函数传递给绑定到{{1}的setTimeout(()==>...的{​​{1}}的原因是objB {{1} }本身就是this

通过将其绑定到正确的objB.print(),我们可以轻松地使用this来使定义为常规函数的回调工作。

objB

但是,在异步回调的情况下,箭头函数会派上用场,并且出错的可能性也较小,在异步回调的情况下,我们知道Function.prototype.bind()并应该绑定到该函数时就知道了。

需要在每次调用中更改的箭头功能的局限性

任何时候,我们都需要在调用时可以更改其this的函数,而不能使用箭头函数。

const objB = {
 id: 20,
 name: "Singh",
 print () { // same as print: function() 
  setTimeout( (function() {
    console.log(`[${this.id} -> ${this.name}]`);
  }).bind(this), 1)
 }
}
objB.print() // logs: [20 -> Singh]

以上所有选项均无法与箭头功能this [$ {this.id}-> {this.name}] this一起使用,因为/* this = global | Window (enclosing scope) */ function print() { console.log(`[${this.id} -> {this.name}]`); } const obj1 = { id: 10, name: "Simar", print // same as print: print }; obj.print(); // logs: [10 -> Simar] const obj2 = { id: 20, name: "Paul", }; printObj2 = obj2.bind(obj2); printObj2(); // logs: [20 -> Paul] print.call(obj2); // logs: [20 -> Paul] 无法更改,并且将保持与定义范围(全局/窗口)的封闭范围的const print = () => { console.log(绑定。在所有这些示例中,我们一个接一个地调用了具有不同对象();}this)的同一函数,这两个对象都是在声明this函数之后创建的。

这些是人为的例子,但让我们考虑一些更现实的例子。如果必须编写类似于obj1的方法的obj2方法,我们又不能将其定义为lambda,因为它需要从调用上下文中推断出print(),即。调用它的数组

由于这个原因,reduce()函数绝不能定义为箭头函数,因为构造函数的arrays不能在声明时设置。每次使用this关键字调用构造函数时,都会创建一个新对象,然后将其绑定到该特定调用。

此外,当框架或系统接受稍后在动态上下文constructor中调用的回调函数时,我们将无法使用箭头函数,因为this可能需要在每次调用时进行更改。这种情况通常发生在DOM事件处理程序中

new

这也是为什么在 Angular 2 + Vue.js 之类的框架中,模板组件绑定方法是常规函数/方法的原因,{{1 }}的调用由绑定功能的框架管理。 (Angular使用Zone.js来管理异步上下文,以调用视图模板绑定函数。)

另一方面,在 React 中,当我们想将组件的方法作为事件处理程序传递给this时,我们应该将this定义为箭头函数,例如对于每次调用,我们都希望它是为呈现的DOM元素生成JSX的组件的相同实例。


在我的Medium出版物中也可以找到该文章。如果您喜欢炮兵,或者有任何意见和建议,请 拍手 ,或在{{3 }}。

答案 8 :(得分:1)

我仍然坚持在该线程中在my first answer中编写的所有内容。但是,从那时起,我对代码风格的看法逐渐形成,因此,对于这个问题,我有一个新的答案是基于我的上一个答案。

关于词汇this

在我的最后一个答案中,我故意避开了我对这种语言的基本信念,因为它与我所做的论点没有直接关系。尽管如此,如果没有明确说明这一点,我可以理解为什么许多人在发现箭头如此有用时只是拒绝我的建议而不使用箭头。

我的信念是:首先不应该使用this。因此,如果某人故意避免在其代码中使用this,则箭头的“词汇this”功能几乎没有价值。同样,在this是坏事的前提下,箭头对this的处理不是“好事”;相反,它更多地是对另一种不良语言功能的损害控制。 / p>

我认为某些人不会遇到这种情况,但是即使对于那些这样做的人,他们也必须始终发现自己正在代码库中工作,其中this每个文件出现一百次,而很少(或很多)损害控制是一个理智的人所希望的。因此,从某种意义上讲,当情况变得更好时,箭可能是好的。

即使使用带有箭头的this编写代码比没有使用箭头编写代码更容易,使用箭头的规则仍然非常复杂(请参阅:当前线程)。因此,您所要求的指导方针既不是“明确的”也不是“一致的”。即使程序员知道箭头的模糊性,我仍然认为它们耸了耸肩并接受了它们,因为词汇this的价值掩盖了它们。

所有这些都是以下实现的序言:如果不使用this,则箭头通常引起的关于this的歧义变得无关紧要。在这种情况下,箭头变得更加中立。

关于简洁语法

当我写第一个答案时,我认为,即使勉强遵守最佳实践也是值得付出的代价,如果这意味着我可以生产出更完美的代码。但是我最终意识到,简洁可以用作一种抽象形式,也可以提高代码质量-足以证明有时偏离最佳实践是合理的。

换句话说:该死,我也想要一线功能!

关于准则

由于this中性箭头功能的可能性,以及简洁性值得追求,我提供以下更为宽松的指导原则:

ES6中的功能表示法准则:

  • 请勿使用this
  • 对要按名称调用的函数使用函数声明(因为它们已悬挂)。
  • 使用箭头函数进行回调(因为它们往往更短)。