¿Cuándo debo usar las funciones de flecha en ECMAScript 6?

407

La pregunta está dirigida a personas que han pensado en el estilo de código en el contexto del próximo ECMAScript 6 (Harmony) y que ya han trabajado con el lenguaje.

Con () => {}y function () {}estamos obteniendo dos formas muy similares de escribir funciones en ES6. En otros idiomas, las funciones lambda a menudo se distinguen por ser anónimas, pero en ECMAScript cualquier función puede ser anónima. Cada uno de los dos tipos tiene dominios de uso únicos (es decir, cuándo thisdebe vincularse explícitamente o no explícitamente). Entre esos dominios hay una gran cantidad de casos en los que cualquier notación servirá.

Las funciones de flecha en ES6 tienen al menos dos limitaciones:

  • No funciona newy no se puede usar al crearprototype
  • Fijo thisvinculado al alcance en la inicialización

Dejando a un lado estas dos limitaciones, las funciones de flecha teóricamente podrían reemplazar las funciones regulares en casi cualquier lugar. ¿Cuál es el enfoque correcto usándolos en la práctica? ¿Deben usarse las funciones de flecha, por ejemplo:

  • "en todas partes funcionan", es decir, en todas partes una función no tiene que ser independiente de la thisvariable y no estamos creando un objeto.
  • solo "en todas partes donde se necesitan", es decir, oyentes de eventos, tiempos de espera, que deben estar sujetos a un determinado alcance
  • con funciones 'cortas' pero no con funciones 'largas'
  • solo con funciones que no contienen otra función de flecha

Lo que estoy buscando es una guía para seleccionar la notación de función apropiada en la versión futura de ECMAScript. La directriz deberá ser clara, de modo que pueda enseñarse a los desarrolladores de un equipo, y ser coherente para que no requiera una refactorización constante de una notación de función a otra.

lisando
fuente
33
¿Consideras Fixed this bound to scope at initialisationcomo una limitación?
thefourtheye
12
Es una ventaja, pero también puede ser una limitación si planea reutilizar la función fuera del contexto original. Por ejemplo, al agregar una función a una clase dinámicamente a través de Object.prototype. Lo que quiero decir con "limitación" es que cambiar el valor de thises algo que puede hacer con funciones regulares pero no con funciones de flecha.
lyschoening
1
Honestamente, creo que las pautas de estilo de codificación son bastante obstinadas. No me malinterpreten, creo que son importantes, pero no hay una sola guía que sea adecuada para todos.
Felix Kling
No creo que Fixed this bound to scope at initialisationsea ​​una limitación. :) Echa un vistazo a este artículo: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy
3
@thefourtheye, "limitación" aquí significa "limitación porque un traductor de código automático tonto no podría reemplazar a ciegas uno por otro y asumir que todo funcionará como se esperaba".
Pacerier

Respuestas:

322

Hace un tiempo, nuestro equipo migró todo su código (una aplicación AngularJS de tamaño medio) a JavaScript compilado usando Traceur Babel . Ahora estoy usando la siguiente regla general para las funciones en ES6 y más allá:

  • Uso functionen el ámbito global y para Object.prototypepropiedades.
  • Uso classpara constructores de objetos.
  • Úselo en =>cualquier otro lugar.

¿Por qué usar las funciones de flecha en casi todas partes?

  1. Seguridad del alcance: cuando las funciones de flecha se usan de manera consistente, se garantiza que todo usará igual thisObjectque la raíz. Si incluso una única devolución de llamada de función estándar se combina con un montón de funciones de flecha, existe la posibilidad de que el alcance se confunda.
  2. Compacidad: las funciones de flecha son más fáciles de leer y escribir. (Esto puede parecer obstinado, por lo que daré algunos ejemplos más adelante).
  3. Claridad: cuando casi todo es una función de flecha, cualquier regular functionsobresale inmediatamente para definir el alcance. Un desarrollador siempre puede buscar la siguiente functiondeclaración más alta para ver cuál thisObjectes.

¿Por qué usar siempre funciones regulares en el alcance global o alcance del módulo?

  1. Para indicar una función que no debe acceder a thisObject.
  2. El windowobjeto (alcance global) se aborda mejor explícitamente.
  3. Muchas Object.prototypedefiniciones viven en el ámbito global (pensar, String.prototype.truncateetc.) y, en general, esas tienen que ser del tipo function. El uso constante functionen el ámbito global ayuda a evitar errores.
  4. Muchas funciones en el ámbito global son constructores de objetos para definiciones de clase de estilo antiguo.
  5. Las funciones se pueden nombrar 1 . Esto tiene dos ventajas: (1) Es menos incómodo escribir function foo(){}que const foo = () => {}, en particular, fuera de otras llamadas a funciones. (2) El nombre de la función se muestra en las trazas de la pila. Si bien sería tedioso nombrar cada devolución de llamada interna, nombrar todas las funciones públicas es probablemente una buena idea.
  6. Las declaraciones de funciones se izan (lo que significa que se puede acceder a ellas antes de declararlas), que es un atributo útil en una función de utilidad estática.


Constructores de objetos

Intentar instanciar una función de flecha produce una excepción:

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

Por lo tanto, una ventaja clave de las funciones sobre las funciones de flecha es que las funciones funcionan como constructores de objetos:

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

Sin embargo, la definición de clase de borrador 2 ES Harmony funcionalmente idéntica es casi tan compacta:

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

Espero que el uso de la notación anterior eventualmente se desaliente. Algunos todavía pueden usar la notación de constructor de objetos para fábricas de objetos anónimos simples donde los objetos se generan mediante programación, pero no para mucho más.

Cuando se necesita un constructor de objetos, se debe considerar convertir la función a a classcomo se muestra arriba. La sintaxis también funciona con funciones / clases anónimas.


Legibilidad de las funciones de flecha

Probablemente el mejor argumento para apegarse a las funciones regulares (maldita sea la seguridad del alcance) sería que las funciones de flecha son menos legibles que las funciones regulares. Si su código no es funcional en primer lugar, entonces las funciones de flecha pueden no parecer necesarias, y cuando las funciones de flecha no se usan consistentemente, se ven feas.

ECMAScript ha cambiado bastante desde que ECMAScript 5.1 nos dio el funcional Array.forEach, Array.mapy todas estas características de programación funcional que nos hacen usar funciones donde antes se habrían usado bucles for. JavaScript asíncrono ha despegado bastante. ES6 también enviará un Promiseobjeto, lo que significa aún más funciones anónimas. No hay vuelta atrás para la programación funcional. En JavaScript funcional, las funciones de flecha son preferibles a las funciones regulares.

Tomemos, por ejemplo, este (particularmente confuso) fragmento de código 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;
        })
}

El mismo código con funciones regulares:

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

Si bien cualquiera de las funciones de flecha se puede reemplazar por una función estándar, sería muy poco lo que ganaría al hacerlo. ¿Qué versión es más legible? Yo diría el primero.

Creo que la pregunta de si usar funciones de flecha o funciones regulares será menos relevante con el tiempo. La mayoría de las funciones se convertirán en métodos de clase, que eliminan la functionpalabra clave, o se convertirán en clases. Las funciones permanecerán en uso para parchear clases a través de Object.prototype. Mientras tanto, sugiero reservar la functionpalabra clave para cualquier cosa que realmente debería ser un método de clase o una clase.


Notas

  1. Las funciones de flecha con nombre se han diferido en la especificación ES6 . Todavía podrían agregarse una versión futura.
  2. De acuerdo con el borrador de la especificación "Las declaraciones / expresiones de clase crean una función constructora / par prototipo exactamente como para las declaraciones de función" siempre que una clase no use la extendpalabra clave. Una diferencia menor es que las declaraciones de clase son constantes, mientras que las declaraciones de funciones no lo son.
  3. Nota sobre bloques en funciones de flecha de enunciado simple: me gusta usar un bloque donde se llama a una función de flecha solo para el efecto secundario (por ejemplo, asignación). De esta manera, está claro que el valor de retorno puede descartarse.
lisando
fuente
44
La otra vez que te gustaría usar functiones cuando no quieres thisestar atado, ¿verdad? Mi escenario más común para esto son los eventos, donde es posible que desee thishacer referencia al objeto (generalmente nodo DOM) que activó el evento.
Brett
13
De hecho, creo que en el ejemplo 3, las funciones regulares son más legibles. Incluso los no programadores podrían adivinar lo que está sucediendo. Con las flechas, necesita saber exactamente cómo funcionan para comprender ese ejemplo. Quizás más nuevas líneas ayudarían al ejemplo de la flecha, pero no lo sé. Solo mis 2 centavos, pero las flechas me dan escalofríos (pero aún no los he usado, así que puedo convertirme pronto)
Spencer
3
@Spencer ese es un punto justo. Desde mi propia experiencia, =>termina viéndose mejor con el tiempo. Dudo que los no programadores sientan de manera muy diferente los dos ejemplos. Si está escribiendo el código ES2016, normalmente tampoco terminará usando tantas funciones de flecha. En este ejemplo, usando async / await y una comprensión de matriz, terminaría con solo una función de flecha en la reduce()llamada.
lyschoening
3
Estoy completamente de acuerdo con Spencer en que las funciones regulares son mucho más legibles en ese ejemplo.
jonschlinkert
2
Buena respuesta, gracias! personalmente también uso flechas en el alcance global tanto como sea posible. Esto me deja casi sin 'función'. Para mí, una 'función' en el código significa un caso especial que debe sobresalir y ser considerado cuidadosamente.
kofifus
81

Según la propuesta , las flechas apuntaban "a abordar y resolver varios puntos de dolor comunes de los tradicionales Function Expression". Tenían la intención de mejorar las cosas mediante la vinculación thisléxica y ofreciendo una sintaxis concisa.

Sin embargo,

  • Uno no puede unirse consistentemente thisléxico
  • La sintaxis de la función de flecha es delicada y ambigua

Por lo tanto, las funciones de flecha crean oportunidades para la confusión y los errores, y deben excluirse del vocabulario de un programador de JavaScript, reemplazarse functionexclusivamente.

En cuanto a léxico this

this es problemático:

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

Las funciones de flecha intentan solucionar el problema donde necesitamos acceder a una propiedad thisdentro de una devolución de llamada. Ya hay varias formas de hacerlo: se podría asignar thisa una variable, usar bindo usar el tercer argumento disponible en los Arraymétodos agregados. Sin embargo, las flechas parecen ser la solución más simple, por lo que el método podría refactorizarse así:

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

Sin embargo, considere si el código usó una biblioteca como jQuery, cuyos métodos se unen thisespecialmente. Ahora, hay dos thisvalores con los que lidiar:

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

Debemos usar functionpara eachunir thisdinámicamente. No podemos usar una función de flecha aquí.

Tratar con múltiples thisvalores también puede ser confuso, porque es difícil saber de qué thisestaba hablando un autor:

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

¿El autor realmente tenía la intención de llamar Book.prototype.reformat? ¿O se olvidó de atar thisy tenía la intención de llamar Reader.prototype.reformat? Si cambiamos el controlador a una función de flecha, nos preguntaremos de manera similar si el autor quería la dinámica this, pero elegimos una flecha porque cabe en una línea:

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

Uno puede plantearse: "¿Es excepcional que las flechas a veces sean una función incorrecta? Tal vez si raramente necesitamos thisvalores dinámicos , entonces estaría bien usar flechas la mayor parte del tiempo".

Pero pregúntese esto: "¿Valdría la pena 'depurar el código y descubrir que el resultado de un error fue provocado por un' caso límite '?" Preferiría evitar problemas no solo la mayor parte del tiempo, sino 100% del tiempo

Hay una mejor manera: usar siempre function(por thislo que siempre se puede vincular dinámicamente) y siempre hacer referencia a thistravés de una variable. Las variables son léxicas y asumen muchos nombres. Asignar thisa una variable aclarará sus intenciones:

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

Además, siempre asignar thisa una variable (incluso cuando hay una sola thiso ninguna otra función) asegura que las intenciones de uno permanezcan claras incluso después de cambiar el código.

Además, la dinámica thisno es excepcional. jQuery se usa en más de 50 millones de sitios web (a partir de este escrito en febrero de 2016). Aquí hay otras API que se vinculan thisdinámicamente:

  • Mocha (~ 120k descargas ayer) expone métodos para sus pruebas a través de this.
  • Grunt (~ 63k descargas ayer) expone métodos para crear tareas a través de this.
  • Backbone (~ 22k descargas ayer) define los métodos de acceso this.
  • Las API de eventos (como los DOM) se refieren a un EventTargetcon this.
  • Las API de Prototypal que están parcheadas o extendidas se refieren a instancias con this.

(Estadísticas a través de http://trends.builtwith.com/javascript/jQuery y https://www.npmjs.com ).

Es probable que ya requiera thisenlaces dinámicos .

A thisveces se espera un léxico , pero a veces no; así como a thisveces se espera una dinámica , pero a veces no. Afortunadamente, hay una mejor manera, que siempre produce y comunica el enlace esperado.

En cuanto a la sintaxis concisa

Las funciones de flecha lograron proporcionar una "forma sintáctica más corta" para las funciones. ¿Pero estas funciones más cortas te harán más exitoso?

¿Es x => x * x"más fácil de leer" que function (x) { return x * x; }? Tal vez lo sea, porque es más probable que produzca una única línea de código corta. De acuerdo con la influencia de Dyson de la velocidad de lectura y la longitud de la línea en la efectividad de la lectura desde la pantalla ,

Una longitud de línea media (55 caracteres por línea) parece apoyar la lectura efectiva a velocidades normales y rápidas. Esto produjo el más alto nivel de comprensión. . .

Se hacen justificaciones similares para el operador condicional (ternario) y para ifdeclaraciones de una sola línea .

Sin embargo, ¿ realmente está escribiendo las funciones matemáticas simples anunciadas en la propuesta ? Mis dominios no son matemáticos, por lo que mis subrutinas rara vez son tan elegantes. Por el contrario, comúnmente veo que las funciones de flecha rompen el límite de una columna y se ajustan a otra línea debido al editor o la guía de estilo, que anula la "legibilidad" según la definición de Dyson.

Uno podría plantearse: "¿Qué tal si usamos la versión corta para funciones cortas, cuando sea posible?" Pero ahora una regla estilística contradice una restricción del lenguaje: "Trate de usar la notación de función más corta posible, teniendo en cuenta que a veces solo la notación más larga se unirá thiscomo se esperaba". Tal combinación hace que las flechas sean particularmente propensas al mal uso.

Existen numerosos problemas con la sintaxis de la función de flecha:

const a = x =>
    doSomething(x);

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

Ambas funciones son sintácticamente válidas. Pero doSomethingElse(x);no está en el cuerpo de b, es solo una declaración de alto nivel mal sangrada.

Al expandirse a la forma de bloque, ya no hay un implícito return, que uno podría olvidar restaurar. Pero la expresión solo puede haber tenido la intención de producir un efecto secundario, entonces, ¿quién sabe si returnserá necesario un explícito en el futuro?

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

Lo que se puede considerar como un parámetro de descanso se puede analizar como el operador de propagación:

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

La asignación se puede confundir con los argumentos predeterminados:

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

Los bloques parecen objetos:

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

¿Qué significa esto?

() => {}

¿Pretendía el autor crear un no-op, o una función que devuelva un objeto vacío? (Con esto en mente, ¿deberíamos ubicarnos {después =>? ¿Deberíamos restringirnos solo a la sintaxis de la expresión? Eso reduciría aún más la frecuencia de las flechas).

=>se parece <=y >=:

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

Para invocar una expresión de función de flecha inmediatamente, se debe colocar ()en el exterior, pero colocarlo ()en el interior es válido y podría ser intencional.

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

Sin embargo, si uno escribe (() => doSomething()());con la intención de escribir una expresión de función invocada inmediatamente, simplemente no sucederá nada.

Es difícil argumentar que las funciones de flecha son "más comprensibles" con todos los casos anteriores en mente. Uno podría aprender todas las reglas especiales requeridas para utilizar esta sintaxis. ¿Realmente vale la pena?

La sintaxis de functiones excepcionalmente generalizada. Usar functionexclusivamente significa que el lenguaje mismo evita que uno escriba código confuso. Para escribir procedimientos que deberían entenderse sintácticamente en todos los casos, elijo function.

Sobre una directriz

Solicita una directriz que debe ser "clara" y "coherente". El uso de las funciones de flecha eventualmente dará como resultado un código sintácticamente válido, lógicamente inválido, con ambas formas de función entrelazadas, de manera significativa y arbitraria. Por lo tanto, ofrezco lo siguiente:

Pauta para la notación de funciones en ES6:

  • Siempre cree procedimientos con function.
  • Asignar siempre thisa una variable. No utilice () => {}.
Jackson
fuente
55
Interesante escribir sobre la vista de un programador funcional en JavaScript. No estoy seguro de estar de acuerdo con el argumento de las variables privadas. OMI pocas personas realmente los necesitan; aquellos que lo necesiten probablemente también necesitarán otras características contractuales e irán a una extensión de lenguaje como TypeScript de todos modos. Ciertamente puedo ver el atractivo de un en selflugar de un esto. Las trampas de la función de flecha indicadas también son todas válidas, y los mismos estándares que en otras declaraciones que pueden ir sin llaves definitivamente se aplican aquí también; de lo contrario, creo que con su argumento uno podría defender las funciones de flecha en todas partes.
lyschoening
77
"Tener múltiples formas de hacer las cosas crea vectores innecesarios para argumentos y disensiones en el lugar de trabajo y la comunidad lingüística. Sería mejor si la gramática del lenguaje no nos permitiera tomar malas decisiones". Estoy de acuerdo mucho Buen relato! Creo que las funciones de flecha son en realidad un paso atrás. En un tema diferente, desearía que mis compañeros de trabajo dejaran de intentar convertir JavaScript en C # con una serie de definiciones .prototype. Es asqueroso. Debo enlazar anónimamente tu publicación :)
Spencer
11
Muy bien escrito! Aunque no estoy de acuerdo con la mayoría de sus puntos, es importante considerar el punto de vista opuesto.
minexew
44
No funcionan las flechas, pero el comportamiento extraño thises el problema de Javascript. En lugar de estar implícitamente vinculado, thisdebe pasarse como un argumento explícito.
bob
55
" Utilice siempre la función (por lo que siempre se puede vincular dinámicamente) y siempre haga referencia a esto a través de una variable ". ¡No podría estar más en desacuerdo!
48

Las funciones de flecha se crearon para simplificar la función scopey resolver la thispalabra clave haciéndola más simple. Utilizan la =>sintaxis, que se parece a una flecha.

Nota: No reemplaza las funciones existentes. Si reemplaza cada sintaxis de función con funciones de flecha, no funcionará en todos los casos.

Echemos un vistazo a la sintaxis ES5 existente. Si la thispalabra clave estuviera dentro del método de un objeto (una función que pertenece a un objeto), ¿a qué se referiría?

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

El fragmento anterior se referirá a un objecte imprimirá el nombre "RajiniKanth". Exploremos el fragmento a continuación y veamos qué señala esto aquí.

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

Actor.showMovies();

¿Y si la thispalabra clave estuviera dentro de method’s function?

Aquí esto se referiría a window objectque se ha inner functioncaído scope. Porque this, siempre hace referencia al propietario de la función en la que se encuentra, para este caso, ya que ahora está fuera del alcance, la ventana / objeto global.

Cuando está dentro del objectmétodo de un - el functionpropietario del objeto es el objeto. Por lo tanto, la palabra clave this está vinculada al objeto. Sin embargo, cuando está dentro de una función, ya sea solo o dentro de otro método, siempre se referirá al window/globalobjeto.

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

fn(); // [object Window]

Hay formas de resolver este problema en nosotros ES5mismos, analicemos eso antes de sumergirnos en las funciones de flecha ES6 sobre cómo resolverlo.

Por lo general, crearía una variable fuera de la función interna del método. Ahora el ‘forEach’método obtiene acceso thisy, por lo tanto, las object’spropiedades y sus valores.

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

usando bindpara adjuntar la thispalabra clave que se refiere al método al 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();

Ahora con la ES6función de flecha, podemos tratar el lexical scopingproblema de una manera más simple.

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

Actor.showMovies();

Arrow functionsson más como declaraciones de funciones, excepto que hacen bindesto parent scope. Si el arrow function is in top scope, thisel argumento se referirá a window/global scope, mientras que una función de flecha dentro de una función regular tendrá su este argumento lo mismo que su función externa.

Con arrowfunciones thisestá vinculado al cierre scopeen el momento de la creación y no se puede cambiar. El nuevo operador, vincular, llamar y aplicar no tiene ningún efecto sobre esto.

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

En el ejemplo anterior, perdimos el control de esto. Podemos resolver el ejemplo anterior usando una referencia variable de thiso usando bind. Con ES6, se vuelve más fácil administrar el thiscomo está vinculado 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

Cuando no a las funciones de flecha

Dentro de un objeto literal.

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

Actor.getName();

Actor.getNamese define con una función de flecha, pero en la invocación alerta sin definir porque this.namees undefinedcomo queda el contexto window.

Ocurre porque la función de flecha une el contexto léxico con el window object... es decir, el alcance externo. La ejecución this.namees equivalente a window.name, que no está definida.

Prototipo de objeto

La misma regla se aplica al definir métodos en a prototype object. En lugar de usar una función de flecha para definir el método sayCatName, que trae un incorrecto 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

Invocando constructores

thisen una invocación de construcción es el objeto recién creado. Al ejecutar nuevo Fn (), el contexto de la constructor Fnes un objeto nuevo: this instanceof Fn === true.

this se configura desde el contexto envolvente, es decir, el ámbito externo que hace que no se asigne al objeto recién creado.

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

Devolución de llamada con contexto dinámico

La función de flecha vincula la contextdeclaración estática en y no es posible hacerla dinámica. Adjuntar oyentes de eventos a elementos DOM es una tarea común en la programación del lado del cliente. Un evento activa la función del controlador con esto como elemento de destino.

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

thises una ventana en una función de flecha que se define en el contexto global. Cuando ocurre un evento de clic, el navegador intenta invocar la función del controlador con el contexto del botón, pero la función de flecha no cambia su contexto predefinido. this.innerHTMLes equivalente window.innerHTMLy no tiene sentido.

Debe aplicar una expresión de función, que permite cambiar esto dependiendo del elemento de destino:

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

Cuando el usuario hace clic en el botón, esto en la función del controlador es el botón. Por lo tanto, this.innerHTML = 'Clicked button'modifica correctamente el texto del botón para reflejar el estado en el que se hizo clic.

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

Thalaivar
fuente
Bueno, debo admitir que "lo mejor está en el medio" . Votaron a favor de una declaración, que las funciones de flecha no cubrirán los posibles casos de uso de funciones. Realmente están diseñados para resolver solo una parte de los problemas comunes. Simplemente cambiar a ellos por completo será una exageración.
BlitZ
@DmitriPavlutin: Comprueba mi publicación actualizada, es una colección de muchas cosas ... puede que deba publicar una referencia.
Thalaivar
2
Su código después de la línea 'usando bind para adjuntar esta palabra clave que se refiere al método a la función interna del método'. tiene errores ¿Has probado el resto de tus ejemplos?
Isaac Pak
El uno using bind to attach the this keyword that refers to the method to the method’s inner function.tiene errores de sintaxis.
Coda Chang
Debería servar 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();
Coda Chang
14

Funciones de flecha: la función ES6 más utilizada hasta ahora ...

Uso: Todas las funciones de ES5 deben reemplazarse con las funciones de flecha de ES6, excepto en los siguientes escenarios:

Las funciones de flecha NO deben usarse:

  1. Cuando queremos elevar funciones
    • como funciones de flecha son anónimas.
  2. Cuando queremos usar this/ argumentsen una función
    • Como las funciones de flecha no tienen this/ argumentspor sí mismas, dependen de su contexto externo.
  3. Cuando queremos usar funciones con nombre
    • como funciones de flecha son anónimas.
  4. Cuando queremos usar la función como constructor
    • como las funciones de flecha no tienen las suyas this.
  5. Cuando queremos agregar la función como una propiedad en el objeto literal y usar el objeto en él
    • ya que no podemos acceder this(que debería ser el objeto mismo).

Comprendamos algunas de las variantes de las funciones de flecha para comprender mejor:

Variante 1 : cuando queremos pasar más de un argumento a una función y devolverle algún valor.

Versión ES5 :

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

Versión ES6 :

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

Nota: la functionpalabra clave NO es necesaria. =>es requerido. {}son opcionales, cuando no proporcionamos {} returnestá implícitamente agregado por JavaScript y cuando lo hacemos {}necesitamos agregarlo returnsi lo necesitamos.

Variante 2 : cuando queremos pasar SOLO un argumento a una función y devolverle algún valor.

Versión ES5 :

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

Versión ES6 :

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

Nota: Al pasar un solo argumento podemos omitir paréntesis ().

Variante 3 : cuando NO queremos pasar ningún argumento a una función y NO queremos devolver ningún valor.

Versión ES5 :

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

Versión ES6 :

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

Variante 4 : cuando queremos regresar explícitamente de las funciones de flecha.

Versión ES6 :

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

Variante 5 : cuando queremos devolver un objeto de las funciones de flecha.

Versión ES6 :

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

Nota: Necesitamos envolver el objeto entre paréntesis, de lo ()contrario, JavaScript no puede diferenciar entre un bloque y un objeto.

Variante 6 : Las funciones de flecha NO tienen arguments(un objeto similar a una matriz) propio, dependen del contexto externo arguments.

Versión ES6 :

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

Nota: fooes una función ES5, con una argumentsgran variedad de objetos y como un argumento pasado a ella es 2por lo que arguments[0]para el fooes 2.

abces una función ES6 flecha, ya que no tiene su propio argumentspor lo tanto, se imprime arguments[0]de foosu contexto externo en su lugar.

Variante 7 : las funciones de flecha NO tienen thissus propias características, dependen del contexto externo parathis

Versión 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

Nota: La devolución de llamada pasada a setTimeout es una función ES5 y tiene su propia función thisque no está definida en el use-strictentorno, por lo tanto, obtenemos resultados:

undefined: Katty

Versión 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

Nota: La devolución de llamada se pasa a setTimeoutuna función ES6 flecha y que no tiene su propio thisasí que lo toma de su contexto exterior que es greetUserla que tiene thisque es obj6de ahí obtenemos la salida:

Hi, Welcome: Katty

Varios: No podemos usar newcon funciones de flecha. Las funciones de flecha no tienen prototypepropiedad. NO tenemos enlace de thiscuando la función de flecha se invoca a través de applyo call.

Manishz90
fuente
6

Además de las excelentes respuestas hasta ahora, me gustaría presentar una razón muy diferente por la cual las funciones de flecha son, en cierto sentido, fundamentalmente mejores que las funciones de JavaScript "ordinarias". En aras de la discusión, supongamos temporalmente que usamos un corrector de tipo como TypeScript o "Flow" de Facebook. Considere el siguiente módulo de juguete, que es un código ECMAScript 6 válido más anotaciones de tipo Flow: (incluiré el código sin tipo, que sería el resultado real de Babel, al final de esta respuesta, para que realmente se pueda ejecutar).

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;};
  }
}

Ahora vea qué sucede cuando usamos la clase C de un módulo diferente, como este:

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!

Como puede ver, el verificador de tipos falló aquí: se suponía que f2 devolvería un número, ¡pero devolvió una cadena!

Peor aún, parece que ningún verificador de tipos concebible puede manejar funciones JavaScript normales (que no sean flechas), porque el "esto" de f2 no ​​aparece en la lista de argumentos de f2, por lo que el tipo requerido para "esto" no podría agregarse. como una anotación a f2.

¿Este problema también afecta a las personas que no usan verificadores de tipo? Creo que sí, porque incluso cuando no tenemos tipos estáticos, pensamos como si estuvieran allí. ("Los primeros parámetros deben ser un número, el segundo una cadena", etc.) Un argumento oculto "este" que puede o no usarse en el cuerpo de la función hace que nuestra contabilidad mental sea más difícil.

Aquí está la versión sin tipo ejecutable, que sería producida por 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!

Carsten Führmann
fuente
4

Todavía mantengo todo lo que escribí en mi primera respuesta en este hilo. Sin embargo, mi opinión sobre el estilo de código se ha desarrollado desde entonces, por lo que tengo una nueva respuesta a esta pregunta que se basa en la última.

En cuanto a léxico this

En mi última respuesta, evité deliberadamente una creencia subyacente que sostenía sobre este lenguaje, ya que no estaba directamente relacionado con el argumento que estaba haciendo. Sin embargo, sin que esto se indique explícitamente, puedo entender por qué muchas personas simplemente se oponen a mi recomendación de no usar flechas, cuando las encuentran tan útiles.

Mi creencia es esta: no deberíamos estar usando thisen primer lugar. Por lo tanto, si una persona deliberadamente evita usar thisen su código, entonces el "léxicothis " de las flechas tiene poco o ningún valor. Además, bajo la premisa de que thises algo malo, el tratamiento de Arrow dethis es menos que "algo bueno"; en cambio, es más una forma de control de daños para otra característica de lenguaje incorrecto.

Me imagino que esto no se le ocurre a algunas personas, pero incluso a aquellos a quienes les ocurre, siempre deben encontrarse trabajando dentro de bases de código donde this aparece cientos de veces por archivo, y un poco (o mucho) control de daños es todo una persona razonable podría esperar. Entonces, las flechas pueden ser buenas, en cierto modo, cuando mejoran una mala situación.

Incluso si es más fácil escribir código con this flechas que sin ellas, las reglas para usar flechas siguen siendo muy complejas (ver: hilo actual). Por lo tanto, las pautas no son "claras" ni "consistentes", como lo solicitó. Incluso si los programadores conocen las ambigüedades de las flechas, creo que se encogen de hombros y las aceptan de todos modos, porque el valor del léxico las thiseclipsa.

Todo esto es un prefacio a la siguiente realización: si uno no usa this, entonces la ambigüedad acerca de las thisflechas normalmente se vuelve irrelevante. Las flechas se vuelven más neutrales en este contexto.

En cuanto a la sintaxis concisa

Cuando escribí mi primera respuesta, era de la opinión de que incluso una adhesión servil a las mejores prácticas era un precio que valía la pena pagar si significaba que podía producir un código más perfecto. Pero finalmente me di cuenta de que la brevedad puede servir como una forma de abstracción que también puede mejorar la calidad del código, lo suficiente como para justificar el alejamiento de las mejores prácticas a veces.

En otras palabras: maldita sea, ¡también quiero funciones de una línea!

Sobre una directriz

Con la posibilidad de que thislas funciones de flecha neutral y la brevedad valgan la pena, ofrezco la siguiente guía más indulgente:

Pauta para la notación de funciones en ES6:

  • No utilice this.
  • Use declaraciones de funciones para las funciones que llamaría por nombre (porque están izadas).
  • Utilice las funciones de flecha para las devoluciones de llamada (porque tienden a ser menos estrictas).
Jackson
fuente
Acuerde al 100% con la sección "Directriz para la notación de funciones en ES6" en la parte inferior, especialmente con las funciones de elevación y devolución de llamada en línea. ¡buena respuesta!
Jeff McCloud
1

De una manera simple,

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

Otra instancia:

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

Respuesta: La consola imprimiría 20.

La razón es que cada vez que se ejecuta una función se crea su propia pila, en este ejemplo, la exfunción se ejecuta con elnew operador para que se cree un contexto, y cuando innerse ejecuta, JS crearía una nueva pila y ejecutaría la innerfunciónglobal context aunque haya un contexto local

Entonces, si queremos inner función tenga un contexto local que seaex entonces debemos vincular el contexto a la función interna.

Las flechas resuelven este problema, en lugar de tomar el Global context, toman el local contextsi existe. En elgiven example, tomará new ex()comothis .

Entonces, en todos los casos donde el enlace es explícito, las flechas resuelven el problema por defecto.

Rajendra kumar Vankadari
fuente
1

Las funciones de flecha o Lambdas, se introdujeron en ES 6. Además de su elegancia en sintaxis mínima, la diferencia funcional más notable es el alcance this dentro de una función de flecha

En las expresiones de función regular , la thispalabra clave está vinculada a diferentes valores en función del contexto en el que se llama.

En las funciones de flecha , thisestá ligado léxicamente , lo que significa que se cierra thisdel ámbito en el que se definió la función de flecha (ámbito principal) y no cambia sin importar dónde y cómo se invoca / llama.

Limitaciones Funciones de flecha como métodos en un objeto

// 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]

En el caso de objA.print()cuando el print()método se define usando regular function , funcionó resolviendo thisadecuadamente la objAinvocación del método, pero falló cuando se definió como una =>función de flecha . Es porque thisen una función regular cuando se invoca como método en un objeto ( objA), es el objeto mismo. Sin embargo, en el caso de una función de flecha, thisse une léxicamente a la del thisalcance adjunto donde se definió (global / Ventana en nuestro caso) y permanece igual durante su invocación como método activado objA.

Ventajas de las funciones de una flecha sobre las funciones regulares en los métodos de un objeto PERO solo cuando thisse espera que esté fijo y vinculado en la definición de tiempo.

/* 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]

En el caso de objB.print()que el print()método se defina como una función que invoca console.log([$ {this.id} -> {this.name}] de )forma asíncrona como una devolución de llamada activada setTimeout , thisresuelta correctamente objBcuando una función de flecha se utilizó como devolución de llamada pero falló cuando la devolución de llamada se definió como una función regular. Es porque la =>función de flecha pasó a setTimeout(()=>..)cerrada sobrethis léxicamente desde su padre, es decir. invocación de lo objB.print()que lo definió. En otras palabras, la =>función de flecha pasó a setTimeout(()==>...atada objBcomo thisporque la invocación de objB.print() thiseraobjB en sí.

Podríamos usar fácilmente Function.prototype.bind()para hacer que la devolución de llamada definida como una función regular funcione, vinculándola a la correcta 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]

Sin embargo, las funciones de flecha son útiles y menos propensas a errores en el caso de las devoluciones de llamada asíncronas, donde sabemos thisen el momento de la definición de las funciones a las que llega y debe vincularse.

Limitación de las funciones de flecha donde esto necesita cambiar a través de invocaciones

En cualquier momento, necesitamos una función cuyo this se pueda cambiar en el momento de la invocación, no podemos usar funciones de flecha.

/* 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]

Ninguno de los anteriores funcionará con la función de flecha const print = () => { console.log([$ {this.id} -> {this.name}] );}ya thisque no se puede cambiar y permanecerá vinculado a la funciónthis alcance del área adjunta donde se definió (global / Ventana). En todos estos ejemplos, invocamos la misma función con diferentes objetos ( obj1y obj2) uno tras otro, los cuales fueron creados después de que la print()función fue declarada.

Estos fueron ejemplos artificiales, pero pensemos en algunos ejemplos más de la vida real. Si tuviéramos que escribir nuestroreduce() método similar a uno que funcione arrays , nuevamente no podemos definirlo como lambda, porque necesita inferir thisdel contexto de invocación, es decir. la matriz en la que se invocó

Por esta razón, constructor funciones nunca se pueden definir como funciones de flecha, ya que thispara una función constructora no se puede establecer en el momento de su declaración. Cada vez que se invoca una función constructora con una newpalabra clave, se crea un nuevo objeto que luego se vincula a esa invocación particular.

Además, cuando los marcos o sistemas aceptan una función o funciones de devolución de llamada que se invocarán más tarde con un contexto dinámico this , no podemos usar las funciones de flecha como de nuevothis posible que tenga que cambiar con cada invocación. Esta situación comúnmente llega con los controladores de eventos 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');
});

Esta es también la razón por la cual en frameworks como Angular 2+ y Vue.js esperan que los métodos de enlace de componentes de plantilla sean funciones / métodos regulares, ya thisque su invocación es administrada por los marcos para las funciones de enlace. (Angular usa Zone.js para administrar el contexto asíncrono para invocaciones de funciones de enlace de plantilla de vista).

Por otro lado, en React , cuando queremos pasar el método de un componente como un controlador de eventos, por ejemplo<input onChange={this.handleOnchange} /> , debemos definir handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}como una función de flecha para cada invocación, queremos que sea la misma instancia del componente que produjo el JSX para renderizado Elemento DOM.


Este artículo también está disponible en mi publicación Medium . Si le gusta el artile, o tiene algún comentario y sugerencia, aplauda o deje comentarios en Medium .

Simar Singh
fuente