什么时候应该在ECMAScript 6中使用Arrow函数?

2020/09/28 13:21 · javascript ·  · 0评论

该问题针对的是那些在即将发布的ECMAScript 6(Harmony)中考虑过代码风格的人,并且已经使用了该语言。

通过() => {}和,function () {}我们得到了两种非常相似的方法来在ES6中编写函数。在其他语言中,lambda函数通常通过匿名来区分自己,但是在ECMAScript中,任何函数都可以是匿名的。两种类型中的每一种都有唯一的使用域(即,当this需要显式绑定或显式不绑定时)。在这些域之间,有很多情况下两种表示法都会起作用。

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

  • 不要用工作new创建时,不能使用prototype
  • 固定this绑定到初始化范围

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

  • “无论它们在哪里工作”,即在每个地方函数不必与this变量无关,我们也没有创建对象。
  • 仅需要绑定到特定范围的“任何需要它们的地方”,即事件侦听器,超时
  • 具有“短”功能但不具有“长”功能
  • 仅对于不包含另一个箭头功能的功能

我正在寻找的是在将来的ECMAScript版本中选择适当的功能符号的指南。指南必须明确,以便可以向团队中的开发人员讲授,并且要保持一致,以便不需要从一个功能符号到另一个功能符号之间不断地来回重构。

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

  • 使用function在全球范围和Object.prototype性质。
  • 使用class的对象构造。
  • =>在其他地方使用

为什么几乎在所有地方都使用箭头功能?

  1. 示波器安全性:始终使用箭头功能时,保证所有内容都使用与thisObject相同的内容。如果什至将单个标准函数回调与一堆箭头函数混在一起,则范围可能会混乱。
  2. 紧凑性:箭头功能更易于读写。(这似乎是自以为是的,所以我将进一步举例说明)。
  3. 清晰度:当几乎所有功能都是箭头功能时,任何常规都会function立即伸出来定义范围。开发人员始终可以查找下一个较高的function语句,以查看其含义thisObject

为什么总是在全局作用域或模块作用域上使用常规函数?

  1. 指示不应访问的功能thisObject
  2. window对象(全局范围)是最好的明确处理。
  3. 许多Object.prototype定义都存在于全局范围内(例如,思考String.prototype.truncate等),并且function无论如何通常都必须是类型定义function在全局范围内一致使用有助于避免错误。
  4. 全局范围内的许多函数都是旧式类定义的对象构造函数。
  5. 函数可以命名为1这有两个好处:(1)编写起来function foo(){}const foo = () => {}—尤其是在其他函数调用之外— 笨拙(2)函数名称显示在堆栈跟踪中。虽然命名每个内部回调很麻烦,但是命名所有公共函数可能是一个好主意。
  6. 函数声明是悬挂式的(意味着可以在声明它们之前对其进行访问),这是静态实用程序函数中的一个有用属性。

对象构造函数

尝试实例化箭头函数会引发异常:

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

因此,与箭头函数相比,函数的一个关键优势是该函数还可以兼用作对象构造函数:

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

但是,功能相同的2 ES Harmony 草案类定义几乎一样紧凑:

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

我希望最终不鼓励使用前一种表示法。一些对象仍可以将对象构造函数表示法用于简单的匿名对象工厂,在这些对象中以编程方式生成对象,而其他方面则不多。

在需要对象构造函数的地方,应该考虑将函数转换为a,class如上所示。该语法也可用于匿名函数/类。

箭头功能的可读性

坚持使用常规功能的最佳论据(该死的示波器安全性)将是,箭头功能不如常规功能易读。如果您的代码最初没有功能,则箭头功能可能似乎没有必要,并且如果未始终使用箭头功能,它们看起来很丑陋。

自ECMAScript 5.1为我们提供了function以来,ECMAScript发生了很大变化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. ES6规范中推迟了命名箭头功能他们可能仍会添加将来的版本。
  2. 根据规范草案只要类不使用extend关键字,“类声明/表达式将创建与函数声明完全相同的构造函数/原型”一个小的区别是类声明是常量,而函数声明不是常量。
  3. 关于单条语句箭头函数中的块的注意事项:我希望在仅因副作用而调用箭头函数的地方(例如赋值)使用块。这样,很明显可以丢弃返回值。

根据提议,箭的目的是“解决和解决传统的几个常见痛点” 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给变量,use 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);
    });
};

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

处理多个this值也可能会造成混淆,因为很难知道哪个this作者在谈论:

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

作者实际上打算打电话Book.prototype.reformat吗?还是他忘记绑定this并打算打电话Reader.prototype.reformat如果将处理程序更改为箭头函数,我们将同样想知道作者是否想要dynamic this,但选择了箭头,因为它适合一行:

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

一个人可能会提出:“有时箭头可能是使用错误的功能,这是不是很特殊?也许如果我们只很少需要动态this值,那么大多数时候使用箭头仍然可以。

但请问问自己:“调试代码并发现错误的结果是由'edge case'带来的'值得'吗?”我希望不仅在大多数时候避免麻烦,而且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也不是例外。截至2017年2月,已有5,000万个网站使用jQuery。以下是其他this动态绑定的API

  • Mocha(昨天下载了约120k)通过公开了用于测试的方法this
  • Grunt(昨天下载量约为63k)通过公开了构建任务的方法this
  • 骨干网(昨天下载约22k)定义了访问方法this
  • 事件API(如DOM的)引用EventTargetwith this
  • 修补或扩展的原型API引用带有的实例this

(通过http://trends.builtwith.com/javascript/jQueryhttps://www.npmjs.com进行的统计。)

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

this有时会期望词汇,但有时不会;this有时会产生动态变化,但有时却不会。值得庆幸的是,有一种更好的方法,它总是产生并传达期望的绑定。

关于简洁语法

箭头功能成功地为功能提供了“较短的语法形式”。但是,这些较短的功能会使您更成功吗?

x => x * x“容易阅读”比function (x) { return x * x; }也许是这样,因为它更有可能产生单个短代码行。根据戴森(Dyson)的观点,读取速度和行长对从屏幕读取效果的影响

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

条件(三元)运算符和单行if语句也有类似的理由

但是,您是否真的在编写提案中宣传的简单数学函数我的领域不是数学领域,所以我的子程序很少那么优雅。相反,由于编辑器或样式指南,我通常看到箭头函数突破了列限制,并换行到另一行,这使戴森的定义使“可读性”无效。

一个人可能会提出:“如果可能的话,仅将简短版本用于简短功能?” 但是现在,一条样式规则与语言约束相矛盾:“尝试使用可能的最短函数表示法,请记住,有时只有最长的表示法会this按预期绑定。” 这种混合使箭头特别容易被滥用。

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

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

这两个函数在语法上均有效。doSomethingElse(x);它不在的内在b,它只是一个缩进的顶级语句。

当扩展为块形式时,不再有隐式的return,可以忘记恢复。但是该表达可能只是为了产生副作用,所以谁知道以后是否return有必要明确表达

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

虽然,如果有人(() => doSomething()());打算写一个立即调用的函数表达式来写,那么什么也不会发生。

考虑到以上所有情况,很难说箭头功能“更易理解”。一个可以学会利用这种语法要求所有的特殊规则。是不是真的值得吗?

的语法function毫无例外地被概括。function排他性使用意味着该语言本身可以防止编写令人困惑的代码。为了编写在所有情况下都应从语法上理解的过程,我选择function

关于指南

您要求的准则必须“清晰”和“一致”。使用箭头函数最终将导致语法上有效,逻辑上无效的代码,而两种函数形式却是有意义且任意地交织在一起的。因此,我提供以下内容:

ES6中的功能符号指南:

  • 始终使用创建程序function
  • 始终分配this给变量。不要使用() => {}

创建箭头功能是为了简化功能scopethis通过使其更简单来解决关键字。他们利用=>语法,看起来像箭头。

注意:它不会替代现有功能。如果将所有函数语法替换为箭头函数,则在所有情况下均不能正常使用。

让我们看一下现有的ES5语法,如果this关键字位于对象的方法(属于对象的函数)内,则它指的是什么?

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

上面的代码段引用了,object并打印出了名称"RajiniKanth"让我们探索下面的代码片段,看看这里指出了什么。

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

Actor.showMovies();

现在该this关键字是否在其中method’s function呢?

在这里,这window objectinner function它掉线的要多scope因为this,总是引用函数所在的所有者,在这种情况下(因为它现在不在范围内),它是窗口/全局对象。

当它位于object的方法中时- function的所有者是对象。因此,this关键字绑定到对象。但是,当它位于函数内部时,无论是独立存在还是在另一种方法中,它将始终引用该window/global对象。

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

fn(); // [object Window]

有多种方法可以解决我们ES5自己的问题,让我们在深入探讨如何解决它的ES6箭头功能之前先进行研究。

通常,您将在方法的内部函数外部创建一个变量。现在,该‘forEach’方法可以访问属性,this从而可以访问object’s属性及其值。

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();

使用bind附加的this引用该方法的关键字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();

现在有了ES6箭头功能,我们可以lexical scoping以更简单的方式处理问题。

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更像是函数语句,除了它们bind的this parent scope如果arrow function is in top scopethis参数引用window/global scope,则常规函数内部的箭头函数的this参数与其外部函数相同。

With arrow函数在创建时this绑定到附件scope,无法更改。新的运算符,绑定,调用和应用对此没有影响。

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

在上面的示例中,我们失去了对此的控制权。我们可以通过使用this或使用变量引用来解决上述示例bind使用ES6,可以更轻松地管理与this绑定的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

什么时候不使用箭头功能

在对象文字内部。

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

Actor.getName();

Actor.getName与箭头函数来定义,但在调用它提示未定义,因为this.nameundefined作为上下文仍然window

发生这种情况是因为arrow函数将上下文与window object...即外部作用域按词法绑定执行this.name等效于window.name,未定义。

对象原型

在上定义方法时,同样的规则适用prototype object而不是使用箭头函数来定义sayCatName方法,这会带来错误的结果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

调用构造函数

this在构造调用中是新创建的对象。执行new Fn()时,的上下文constructor Fn是一个新对象:this instanceof Fn === true

this 是从封闭上下文(即,使其不分配给新创建的对象的外部范围)中设置的。

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

具有动态上下文的回调

箭头函数context在声明时静态绑定,无法使其动态化。将事件侦听器附加到DOM元素是客户端编程中的常见任务。事件触发以此为目标元素的处理函数。

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

this是在全局上下文中定义的箭头函数中的窗口。当发生click事件时,浏览器尝试使用按钮上下文调用处理程序函数,但是arrow函数不会更改其预定义上下文。this.innerHTML等同于window.innerHTML并且没有意义。

您必须应用一个函数表达式,该表达式允许根据目标元素进行更改:

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

当用户单击按钮时,处理程序功能中的该按钮为按钮。从而this.innerHTML = 'Clicked button'正确修改按钮文本以反映单击状态。

参考:https :
//dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/

箭头功能-迄今为止使用最广泛的ES6功能...

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

不应使用箭头功能:

  1. 当我们想要功能提升时

    • 因为箭头函数是匿名的。
  2. 当我们想在功能中

    使用this/arguments

    • 由于箭头函数本身不具有this/ arguments,因此它们取决于其外部上下文。
  3. 当我们想使用命名函数时

    • 因为箭头函数是匿名的。
  4. 当我们想使用功能作为 constructor
    • 因为箭头功能没有自己的功能this
  5. 当我们想在对象文字中添加函数作为属性并在其中使用对象时

    • 因为我们无法访问this(应该是对象本身)。

让我们了解箭头函数的一些变体,以便更好地理解:

变体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对象数组和传递给它的参数,2因此arguments[0]foo2。

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

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

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

注意:传递给setTimeout的回调是ES5函数,它具有它自己的this,在use-strict环境中未定义,因此我们得到输出:

undefined: Katty

ES6版本

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

注:传递给回调setTimeout是一个ES6箭头功能,它不具有它自己的this,因此它需要从它的外部背景下,是greetUser具有thisobj6因此我们得到的输出:

Hi, Welcome: Katty

杂项:
我们不能
new与箭头功能一起使用箭头函数没有prototype属性。this通过apply调用箭头功能时,我们没有绑定call

除了到目前为止的好答案之外,我还要提出一个非常不同的原因,为什么箭头函数在某种意义上要比“普通” JavaScript函数更好。为了便于讨论,我们暂时假设我们使用类型检查器,例如TypeScript或Facebook的“ Flow”。考虑以下玩具模块,该模块是有效的ECMAScript 6代码以及Flow类型注释:(我将在此答案的末尾包含未键入的代码,该代码实际上是Babel产生的,因此可以实际运行。)

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的注释。

这个问题还会影响不使用类型检查器的人吗?我想是这样,因为即使我们没有静态类型,我们也会认为它们在那里。(“第一个参数必须是数字,第二个参数必须是字符串”等。)在函数的主体中可能使用或可能不使用的隐藏的“ this”参数使我们的心理记录更加困难。

这是可运行的无类型版本,由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!

我仍然支持在该主题的第一个答案写的所有内容但是,从那时起,我对代码风格的看法有所发展,因此我对这个问题的新答案以我的上一个为基础。

关于词汇 this

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

我的信念是:我们不应该首先使用this它。因此,如果一个人故意避免this在其代码中使用,那么this箭头的“词汇”功能几乎没有价值。而且,在this以坏事为前提的情况下,箭的对待不算this是“好事”;相反,它更像是另一种不良语言功能的损害控制形式。

我认为有些人不会遇到这种情况,但是即使对于那些这样做的人,他们也必须始终发现自己正在代码库中工作,this每个文件出现一百次,并且很少(或很多)破坏控制就可以了。一个合理的人可以希望的。因此,箭在某种程度上可以改善情况,在某种程度上可以是好的。

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

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

关于简洁语法

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

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

关于指南

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

ES6中的功能符号指南:

  • 不要使用this
  • 对要通过名称调用的函数使用函数声明(因为它们已被吊起)。
  • 使用箭头函数进行回调(因为它们倾向于更短)。

我更喜欢在不需要访问local的所有时间使用箭头函数this,因为箭头函数不会绑定自己的this,arguments,super或new.target

简单来说,

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();

回答:控制台将显示20。

原因是,每当执行一个函数时,都会创建其自己的堆栈,在此示例中,该ex函数由new操作员执行,因此将创建上下文,并且在inner执行时,JS将创建一个新的堆栈并执行该inner函数,global context尽管存在当地情况。

因此,如果我们希望inner函数具有局部上下文,ex则需要将上下文绑定到内部函数。

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

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

箭头功能或lambda表达式,在ES 6.除了其在最少的语法优雅进行了介绍,最显着的功能性差范围界定的 this 箭头函数内

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

arrow函数中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]

对于使用normal定义objA.print()when print()方法function ,它可以通过this正确解析objA为方法调用工作,但是当定义为箭头=>函数时会失败这是因为this在常规函数中,作为对象(objA上的方法调用时,对象本身就是对象。但是,在使用箭头函数的情况下,this将其按词法绑定到this定义它的封闭范围(在本例中为global / Window)的范围,并在调用过程中保持不变,与on上的方法相同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当被用作回叫的箭头功能,但未能当回叫被定义为常规函数时。这是因为arrow =>函数从其父类(即)按词法传递到了setTimeout(()=>..)关闭状态this调用objB.print()定义了它。在其他词,箭头=>功能传递给setTimeout(()==>...必将objBthis,因为在调用objB.print() thisobjB它本身。

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可以在调用时更改的函数,我们不能使用箭头函数。

/* 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.id}-> {this.name}]配合使用,);}因为this它们无法更改,并且将绑定到this定义它的封闭范围(全局/窗口)。在所有这些示例中,我们依次调用了具有不同对象(obj1obj2的相同函数,这两个对象都是在print()声明函数之后创建的

这些是人为的例子,但让我们考虑一些更现实的例子。如果我们必须编写reduce()类似于在上使用的方法arrays ,则我们再也不能将其定义为lambda,因为它需要this从调用上下文(即)进行推断调用它的数组

因此,constructor永远不能将函数定义为箭头函数,因为this构造函数在声明时不能设置。每次使用new关键字调用构造函数时,都会创建一个新对象,然后将其绑定到该特定调用。

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

'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

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

另一方面,在React中,例如,当我们希望将组件的方法作为事件处理程序传递时,<input onChange={this.handleOnchange} />我们应handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}为每次调用将其定义为箭头函数,我们希望它与生成用于渲染的JSX的组件相同DOM元素。


我的中号出版物上也有此文章如果您喜欢机枪,或者有任何意见和建议,请Medium鼓掌发表评论

本文地址:http://javascript.askforanswer.com/shenmeshihouyinggaizaiecmascript-6zhongshiyongarrowhanshu.html
文章标签: ,   ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!