Maison >interface Web >js tutoriel >Explication détaillée de la fonction de flèche JS

Explication détaillée de la fonction de flèche JS

青灯夜游
青灯夜游avant
2019-11-26 16:18:573503parcourir

En JS, les fonctions fléchées peuvent être utilisées de plusieurs manières, tout comme les fonctions normales. Cependant, ils sont généralement utilisés lorsque des expressions de fonctions anonymes sont requises, telles que les fonctions de rappel.

Explication détaillée de la fonction de flèche JS

L'exemple suivant montre des exemples de fonctions fléchées comme fonctions de rappel, en particulier pour les méthodes de tableau telles que map(), filter(), reduce(), sort().

const scores = [ 1, 28, 66, 666];
const maxScore = Math.max(...scores);

scores.map(score => +(score / maxScore).toFixed(2));

À première vue, les fonctions fléchées semblent être définies et utilisées comme des fonctions régulières, mais ce n'est pas le cas. En raison de la simplicité des fonctions fléchées, elles sont différentes des fonctions normales. D'un autre point de vue, les fonctions fléchées peuvent être considérées comme des fonctions JS anormales.

[Recommandations de cours associées : Tutoriel vidéo JavaScript]

Bien que la syntaxe des fonctions fléchées soit très simple, ce n'est pas l'objet de cet article. Cet article parle principalement des différences entre le comportement des fonctions fléchées et des fonctions régulières, et de la manière dont nous pouvons utiliser ces différences pour mieux utiliser les fonctions fléchées.

● Que ce soit en mode strict ou en mode non strict, les fonctions fléchées ne peuvent pas avoir de paramètres nommés en double.

● Les fonctions fléchées n'ont pas de liaison arguments. Cependant, ils peuvent accéder à l'objet arguments de la fonction parent non-flèche la plus proche.

● Les fonctions fléchées ne peuvent jamais être utilisées comme constructeurs, et naturellement elles ne peuvent pas être appelées à l'aide du mot-clé new. Par conséquent, il n'y a pas d'attribut prototype pour les fonctions fléchées.

● La valeur à l'intérieur d'une fonction fléchée reste inchangée tout au long de la durée de vie de la fonction et est toujours liée à la valeur de la fonction parent non-flèche la plus proche.

Paramètres de fonction nommés

Les fonctions en JS sont généralement définies avec des paramètres nommés. Les paramètres nommés sont utilisés pour mapper les paramètres positionnellement aux variables locales dans la portée de la fonction.

Regardons la fonction suivante :

function logParams (first, second, third) {
  console.log(first, second, third);
}

// first => 'Hello'
// second => 'World'
// third => '!!!'
logParams('Hello', 'World', '!!!'); // "Hello"  "World"  "!!!"

// first => { o: 3 }
// second => [ 1, 2, 3 ]
// third => undefined
logParams({ o: 3 }, [ 1, 2, 3 ]); // {o: 3}  [1, 2, 3]

logParams()La fonction est définie par trois paramètres nommés : first, second et third. S'il y a plus d'arguments nommés que ceux transmis à la fonction, les arguments restants sont undefined.

Les fonctions JS présentent un comportement étrange en mode non strict avec des paramètres nommés. En mode non strict, les fonctions JS autorisent des paramètres nommés répétés. Regardons l'exemple :

function logParams (first, second, first) {
  console.log(first, second);
}

// first => 'Hello'
// second => 'World'
// first => '!!!'
logParams('Hello', 'World', '!!!'); // "!!!"  "World"

// first => { o: 3 }
// second => [ 1, 2, 3 ]
// first => undefined
logParams({ o: 3 }, [ 1, 2, 3 ]); // undefined  [1, 2, 3]

Nous pouvons voir que le paramètre first est répété, il est donc mappé au premier paramètre passé à. l'appel de fonction. Les valeurs des trois paramètres écrasent le premier paramètre, ce qui n'est pas un comportement souhaitable.

// 由于参数重复,严格模式会报错
function logParams (first, second, first) {
  "use strict";
  console.log(first, second);
}

Comment les fonctions fléchées gèrent les arguments en double

À propos des fonctions fléchées :

vs. Différentes fonctions, que ce soit en mode strict ou en mode non strict, les fonctions fléchées n'autorisent pas la répétition des paramètres. Les paramètres répétés provoqueront une erreur de syntaxe.

// 只要你敢写成重复的参数,我就敢死给你看
const logParams = (first, second, first) => {
  console.log(first, second);
}

Surcharge de fonctions

La surcharge de fonctions est la possibilité de définir des fonctions afin que la fonction correspondante puisse être appelée en fonction du nombre de paramètres. , La liaison peut être utilisée dans JS pour réaliser cette fonction.

Regardez une simple fonction surchargée qui calcule la moyenne des arguments transmis :

function average() {
  const length = arguments.length;

  if (length == 0) return 0;

  // 将参数转换为数组
  const numbers = Array.prototype.slice.call(arguments);

  const sumReduceFn = function (a, b) { return a + Number(b) };
  // 返回数组元素的总和除以数组的长度
  return numbers.reduce(sumReduceFn, 0) / length;
}

De cette façon, la fonction peut être appelée avec n'importe quel nombre d'arguments, de 0 au nombre maximum d'arguments la fonction peut accepter. Cela devrait être 255.

average(); // 0
average('3o', 4, 5); // NaN
average('1', 2, '3', 4, '5', 6, 7, 8, 9, 10); // 5.5
average(1.75, 2.25, 3.5, 4.125, 5.875); // 3.5

Essayez maintenant d'utiliser la syntaxe de la fonction head-cut pour copier la fonction average() Généralement, nous penserons que ce n'est pas difficile, et nous ne pouvons pas le faire comme ça :

.
const average = () => {
  const length = arguments.length;

  if (length == 0) return 0;

  const numbers = Array.prototype.slice.call(arguments);
  const sumReduceFn = function (a, b) { return a + Number(b) };

  return numbers.reduce(sumReduceFn, 0) / length;
}

Maintenant, lors du test de cette fonction, nous constaterons qu'elle génère une erreur de référence, arguments n'est pas défini.

Que faisons-nous de mal

Pour les fonctions fléchées :

Contrairement aux fonctions normales, arguments n'existe pas dans dans les fonctions fléchées. Cependant, les objets arguments des fonctions parents non fléchées sont accessibles.

Sur la base de cette compréhension, la fonction average() peut être modifiée en une fonction régulière, qui renverra le résultat de la fonction flèche imbriquée immédiatement appelée. La fonction flèche imbriquée pourra accéder au arguments. fonction de la fonction parent 🎜>.

function average() {
  return (() => {
    const length = arguments.length;

    if (length == 0) return 0;

    const numbers = Array.prototype.slice.call(arguments);
    const sumReduceFn = function (a, b) { return a + Number(b) };

    return numbers.reduce(sumReduceFn, 0) / length;
  })();
}

Cela peut résoudre le problème de arguments l'objet n'est pas défini, mais cette approche à la con est évidemment redondante.

Faire quelque chose de différent

Existe-t-il une alternative au problème ci-dessus Vous pouvez utiliser le paramètre rest d'es6.

En utilisant le paramètre ES6 rest, nous pouvons obtenir un tableau qui enregistre tous les paramètres passés à la fonction. restLa syntaxe fonctionne pour tous les types de fonctions, qu'il s'agisse de fonctions régulières ou fléchées.

const average = (...args) => {
  if (args.length == 0) return 0;
  const sumReduceFn = function (a, b) { return a + Number(b) };

  return args.reduce(sumReduceFn, 0) / args.length;
}

Il y a certaines choses à noter lors de l'utilisation du paramètre rest :

 Quantity Le paramètre rest est différent de l'objet arguments à l'intérieur de la fonction. Le paramètre rest est un paramètre de fonction réel, tandis que l'objet arguments est un objet interne lié à la portée de la fonction.

 ● 一个函数只能有一个rest参数,而且它必须位于最后一个参数。这意味着函数可以包含命名参数和rest参数的组合。

 ● rest 参数与命名参数一起使用时,它不包含所有传入的参数。但是,当它是惟一的函数参数时,表示函数参数。另一方面,函数的arguments对象总是捕获所有函数的参数。

 ● rest参数指向包含所有捕获函数参数的数组对象,而arguments对象指向包含所有函数参数的类数组对象。

接着考虑另一个简单的重载函数,该函数将数字根据传入的进制转换为另一个类的进制数。 可以使用一到三个参数调用该函数。 但是,当使用两个或更少的参数调用它时,它会交换第二个和第三个函数参数。如下所示:

function baseConvert (num, fromRadix = 10, toRadix = 10) {
  if (arguments.length < 3) {
    // swap variables using array destructuring
    [toRadix, fromRadix] = [fromRadix, toRadix];
  }
  return parseInt(num, fromRadix).toString(toRadix);
}

调用 baseConvert 方法:

// num => 123, fromRadix => 10, toRadix => 10
console.log(baseConvert(123)); // "123"

// num => 255, fromRadix => 10, toRadix => 2
console.log(baseConvert(255, 2)); // "11111111"

// num => 'ff', fromRadix => 16, toRadix => 8
console.log(baseConvert('ff', 16, 8)); // "377"

使用箭头函数来重写上面的方法:

const baseConvert = (num, ...args) => {
  // 解构`args`数组和
  // 设置`fromRadix`和`toRadix`局部变量
  let [fromRadix = 10, toRadix = 10] = args;

  if (args.length < 2) {
    // 使用数组解构交换变量
    [toRadix, fromRadix] = [fromRadix, toRadix];
  }

  return parseInt(num, fromRadix).toString(toRadix);
}

构造函数

可以使用new关键字调用常规JS函数,该函数作为类构造函数用于创建新的实例对象。

function Square (length = 10) {
  this.length = parseInt(length) || 10;

  this.getArea = function() {
    return Math.pow(this.length, 2);
  }

  this.getPerimeter = function() {
    return 4 * this.length;
  }
}

const square = new Square();

console.log(square.length); // 10
console.log(square.getArea()); // 100
console.log(square.getPerimeter()); // 40

console.log(typeof square); // "object"
console.log(square instanceof Square); // true

当使用new关键字调用常规JS函数时,将调用函数内部[[Construct]]方法来创建一个新的实例对象并分配内存。之后,函数体将正常执行,并将this映射到新创建的实例对象。最后,函数隐式地返回 this(新创建的实例对象),只是在函数定义中指定了一个不同的返回值。

此外,所有常规JS函数都有一个prototype属性。函数的prototype属性是一个对象,它包含函数创建的所有实例对象在用作构造函数时共享的属性和方法。

以下是对前面的Square函数的一个小修改,这次它从函数的原型上的方法,而不是构造函数本身。

function Square (length = 10) {
  this.length = parseInt(length) || 10;
}

Square.prototype.getArea = function() {
  return Math.pow(this.length, 2);
}

Square.prototype.getPerimeter = function() {
  return 4 * this.length;
}

const square = new Square();

console.log(square.length); // 10
console.log(square.getArea()); // 100
console.log(square.getPerimeter()); // 40

console.log(typeof square); // "object"
console.log(square instanceof Square); // true

如下所知,一切仍然按预期工作。 事实上,这里有一个小秘密:ES6 类在后台执行类似于上面代码片段的操作 - 类(class)只是个语法糖。

那么箭头函数呢

它们是否也与常规JS函数共享此行为?答案是否定的。关于箭头函数:

与常规函数不同,箭头函数永远不能使用new关键字调用,因为它们没有[[Construct]]方法。 因此,箭头函数也不存在prototype属性。

箭头函数不能用作构造函数,无法使用new关键字调用它们,如果这样做了会抛出一个错误,表明该函数不是构造函数。

因此,对于箭头函数,不存在可以作为构造函数调用的函数内部的new.target等绑定,相反,它们使用最接近的非箭头父函数的new.target值。

此外,由于无法使用new关键字调用箭头函数,因此实际上不需要它们具有原型。 因此,箭头函数不存在prototype属性。

由于箭头函数的prototypeundefined,尝试使用属性和方法来扩充它,或者访问它上面的属性,都会引发错误。

const Square = (length = 10) => {
  this.length = parseInt(length) || 10;
}

// throws an error
const square = new Square(5);

// throws an error
Square.prototype.getArea = function() {
  return Math.pow(this.length, 2);
}

console.log(Square.prototype); // undefined

this 是啥

JS函数的每次调用都与调用上下文相关联,这取决于函数是如何调用的,或者在哪里调用的。

函数内部this值依赖于函数在调用时的调用上下文,这通常会让开发人员不得不问自己一个问题:this值是啥。

下面是对不同类型的函数调用this指向一些总结:

 ● 使用new关键字调用:this指向由函数的内部[[Construct]]方法创建的新实例对象。this(新创建的实例对象)通常在默认情况下返回,除了在函数定义中显式指定了不同的返回值。

 ● 不使用new关键字直接调用:在非严格模式下,this指向window对象(浏览器中)。然而,在严格模式下,this值为undefined;因此,试图访问或设置此属性将引发错误。

 ● 间接使用绑定对象调用Function.prototype对象提供了三种方法,可以在调用函数时将函数绑定到任意对象,即:call()apply()bind()。 使用这些方法调用函数时,this指向指定的绑定对象。

 ● 作为对象方法调用this指向调用函数(方法)的对象,无论该方法是被定义为对象的自己的属性还是从对象的原型链中解析。

 ● 作为事件处理程序调用:对于用作DOM事件侦听器的常规函数,this指向触发事件的目标对象、DOM元素、documentwindow

再来看个函数,该函数将用作单击事件侦听器,例如,表单提交按钮:

function processFormData (evt) {
  evt.preventDefault();

  const form = this.closest('form');

  const data = new FormData(form);
  const { action: url, method } = form;
}

button.addEventListener('click', processFormData, false);

与前面看到的一样,事件侦听器函数中的 this值是触发单击事件的DOM元素,在本例中是button

因此,可以使用以下命令指向submit按钮的父表单

this.closest('form');

如果将函数更改为箭头函数语法,会发生什么?

const processFormData = (evt) => {
  evt.preventDefault();

  const form = this.closest('form');
  const data = new FormData(form);
  const { action: url, method } = form;
}

button.addEventListener('click', processFormData, false);

如果现在尝试此操作,咱们就得到一个错误。从表面上看,this 的值并不是各位想要的。由于某种原因,它不再指向button元素,而是指向window对象。

如何修复this指向

利用上面提到的 Function.prototype.bind() 强制将this值绑定到button元素:

button.addEventListener('click', processFormData.bind(button), false);

但这似乎不是各位想要的解决办法。this仍然指向window对象。这是箭头函数特有的问题吗?这是否意味着箭头函数不能用于依赖于this的事件处理?

为什么会搞错

关于箭头函数的最后一件事:

与常规函数不同,箭头函数没有this的绑定。 this的值将解析为最接近的非箭头父函数或全局对象的值。

这解释了为什么事件侦听器箭头函数中的this值指向window 对象(全局对象)。 由于它没有嵌套在父函数中,因此它使用来自最近的父作用域的this值,该作用域是全局作用域。

但是,这并不能解释为什么不能使用bind()将事件侦听器箭头函数绑定到button元素。对此有一个解释:

与常规函数不同,内部箭头函数的this值保持不变,并且无论调用上下文如何,都不能在其整个生命周期中更改。

箭头函数的这种行为使得JS引擎可以优化它们,因为可以事先确定函数绑定。

考虑一个稍微不同的场景,其中事件处理程序是使用对象方法中的常规函数定义的,并且还取决于同一对象的另一个方法:

({
  _sortByFileSize: function (filelist) {
    const files = Array.from(filelist).sort(function (a, b) {
      return a.size - b.size;
    });

    return files.map(function (file) {
      return file.name;
    });
  },

  init: function (input) {
    input.addEventListener('change', function (evt) {
      const files = evt.target.files;
      console.log(this._sortByFileSize(files));
    }, false);
  }

}).init(document.getElementById('file-input'));

上面是一个一次性的对象,该对象带有_sortByFileSize()方法和init()方法,并立即调init方法。init()方法接受一个input元素,并为input元素设置一个更改事件处理程序,该事件处理程序按文件大小对上传的文件进行排序,并打印在浏览器的控制台。

如果测试这段代码,会发现,当选择要上载的文件时,文件列表不会被排序并打印到控制台;相反,会控制台上抛出一个错误,问题就出在这一行:

console.log(this._sortByFileSize(files));

在事件监听器函数内部,this 指向 input 元素 因此this._sortByFileSizeundefined

要解决此问题,需要将事件侦听器中的this绑定到包含方法的外部对象,以便可以调用this._sortByFileSize()。 在这里,可以使用bind(),如下所示:

init: function (input) {
  input.addEventListener('change', (function (evt) {
    const files = evt.target.files;
    console.log(this._sortByFileSize(files));
  }).bind(this), false);
}

现在一切正常。这里不使用bind(),可以简单地用一个箭头函数替换事件侦听器函数。箭头函数将使用父init()方法中的this的值:

init: function (input) {
  input.addEventListener('change', (function (evt) {
    const files = evt.target.files;
    console.log(this._sortByFileSize(files));
  }).bind(this), false);
}

再考虑一个场景,假设有一个简单的计时器函数,可以将其作为构造函数调用来创建以秒为单位的倒计时计时器。使用setInterval()进行倒计时,直到持续时间过期或间隔被清除为止,如下所示:

function Timer (seconds = 60) {
  this.seconds = parseInt(seconds) || 60;
  console.log(this.seconds);

  this.interval = setInterval(function () {
    console.log(--this.seconds);

    if (this.seconds == 0) {
      this.interval && clearInterval(this.interval);
    }
  }, 1000);
}

const timer = new Timer(30);

如果运行这段代码,会看到倒计时计时器似乎被打破了,在控制台上一直打印 NaN

这里的问题是,在传递给setInterval()的回调函数中,this指向全局window对象,而不是Timer()函数作用域内新创建的实例对象。因此,this.secondsthis.interval  都是undefined的。

与之前一样,要修复这个问题,可以使用bind()setInterval()回调函数中的this值绑定到新创建的实例对象,如下所示

function Timer (seconds = 60) {
  this.seconds = parseInt(seconds) || 60;
  console.log(this.seconds);

  this.interval = setInterval((function () {
    console.log(--this.seconds);

    if (this.seconds == 0) {
      this.interval && clearInterval(this.interval);
    }
  }).bind(this), 1000);
}

或者,更好的方法是,可以用一个箭头函数替换setInterval()回调函数,这样它就可以使用最近的非箭头父函数的this值:

function Timer (seconds = 60) {
  this.seconds = parseInt(seconds) || 60;
  console.log(this.seconds);

  this.interval = setInterval(() => {
    console.log(--this.seconds);

    if (this.seconds == 0) {
      this.interval && clearInterval(this.interval);
    }
  }, 1000);
}

现在理解了箭头函数如何处理this关键字,还需要注意箭头函数对于需要保留this值的情况并不理想 - 例如,在定义需要引用的对象方法时 使用需要引用目标对象的方法来扩展对象或扩充函数的原型。

不存在的绑定

在本文中,已经看到了一些绑定,这些绑定可以在常规JS函数中使用,但是不存在用于箭头函数的绑定。相反,箭头函数从最近的非箭头父函数派生此类绑定的值。

总之,下面是箭头函数中不存在绑定的列表:

 ● arguments:调用时传递给函数的参数列表

 ● new.target:使用new关键字作为构造函数调用的函数的引用

 ● super:对函数所属对象原型的引用,前提是该对象被定义为一个简洁的对象方法

 ● this:对函数的调用上下文对象的引用

原文:https://s0dev0to.icopy.site/bnevilleoneill/anomalies-in-javascript-arrow-functions-2afh

为了保证的可读性,本文采用意译而非直译。

本文来自 js教程 栏目,欢迎学习!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer