Heim  >  Artikel  >  Web-Frontend  >  Erweiterung von Funktionen in ES6 (Codebeispiel)

Erweiterung von Funktionen in ES6 (Codebeispiel)

不言
不言nach vorne
2018-11-17 15:28:081463Durchsuche

Der Inhalt dieses Artikels befasst sich mit der Erweiterung von Funktionen (Codebeispiele) in ES6. Ich hoffe, dass er für Freunde hilfreich ist.

Standardwerte für Funktionsparameter

ES6 ermöglicht das Festlegen von Standardwerten für Funktionsparameter, die direkt nach der Parameterdefinition geschrieben werden.

Vor ES6:

function makeRequest(url,timeout,callback) {
    timeout=(typeof timeout!=="undefined")?timeout:2000;
    callback=(typeof callback!=="undefined")?callback:function(){};
                //    函数的剩余部分
}
//ES6 
function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

Parametervariablen werden standardmäßig deklariert, sodass sie nicht erneut mit let oder const deklariert werden können.

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

Bei Verwendung von Parameter-Standardwerten kann die Funktion keine Parameter mit demselben Namen haben.

// 不报错
function foo(x, x, y) {
  // ...
}
// 报错
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

Der Standardwert des Parameters wird nicht als Wert übergeben, sondern der Wert des Standardwertausdrucks wird jedes Mal neu berechnet. Das heißt, Parameter-Standardwerte werden träge ausgewertet.

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}
foo() // 100
x = 100;
foo() // 101

Sie können den Standardwert eines beliebigen Parameters in der Funktionsdeklaration angeben, auch wenn der Parameter vor dem Parameter steht, der keinen Standardwert angibt.

function makeRequest(ur1,timeout=2000,callback){
//函数的剩余部分
}
//    使用默认的    timeout
makeRequest("/foo",    undefined,    function(body)    {
    doSomething(body);
});
//    使用默认的    timeout
makeRequest("/foo");
//    不使用默认值
makeRequest("/foo",    null,    function(body)    {
    doSomething(body);
});

In diesem Beispiel wird der Standardwert von timeout nur verwendet, wenn der zweite Parameter nicht übergeben wird oder wenn der zweite Parameterwert explizit als undefiniert angegeben ist.

Wird in Kombination mit dem Standardwert der Destrukturierungszuweisung verwendet

function foo({x, y = 5}) {
  console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

Der obige Code verwendet nur den Standardwert der Destrukturierungszuweisung des Objekts und nicht den Standardwert des Funktionsparameters. Nur wenn der Parameter der Funktion foo ein Objekt ist, werden die Variablen x und y durch destrukturierende Zuweisung generiert. Wenn beim Aufruf der Funktion foo keine Parameter angegeben werden, werden die Variablen x und y nicht generiert und es wird ein Fehler gemeldet. Diese Situation kann vermieden werden, indem Standardwerte für Funktionsparameter bereitgestellt werden.

function foo({x, y = 5} = {}) {
  console.log(x, y);
}
foo() // undefined 5
function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
// 报错
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}
fetch('http://example.com')
// "GET"

wirkt sich auf das Längenattribut der Funktion aus.

Nachdem der Standardwert angegeben wurde, gibt das Längenattribut der Funktion die Anzahl der Parameter ohne angegebenen Standardwert zurück. Mit anderen Worten: Wenn ein Standardwert angegeben wird, wird das Längenattribut verzerrt.

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

Im obigen Code ist der Rückgabewert des Längenattributs gleich der Anzahl der Parameter der Funktion minus der Anzahl der Parameter mit angegebenen Standardwerten. Die letzte Funktion oben definiert beispielsweise drei Parameter, von denen ein Parameter c einen Standardwert angibt, sodass das Längenattribut gleich 3 minus 1 ist und wir schließlich 2 erhalten.
Dies liegt daran, dass die Bedeutung des Längenattributs die Anzahl der Parameter ist, die voraussichtlich an die Funktion übergeben werden. Nachdem einem Parameter ein Standardwert zugewiesen wurde, umfasst die Anzahl der erwarteten Parameter, die übergeben werden, diesen Parameter nicht. Ebenso wird der Restparameter später nicht in das Längenattribut aufgenommen.

(function(...args) {}).length // 0

Wenn der Parameter mit dem eingestellten Standardwert nicht der Tail-Parameter ist, wird das Längenattribut in den folgenden Parametern nicht mehr gezählt

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

Der Umfang des Parameter-Standardwerts

Sobald der Standardwert des Parameters festgelegt ist, bildet der Parameter einen separaten Bereich (Kontext), wenn die Funktion deklariert und initialisiert wird. Wenn die Initialisierung abgeschlossen ist, verschwindet dieser Bereich. Dieses Syntaxverhalten tritt nicht auf, wenn der Parameter-Standardwert nicht festgelegt ist.

var x = 1;
function f(x, y = x) {
  console.log(y);
}
f(2) // 2
let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

Die Parameterinitialisierung erfolgt, wenn die Funktion aufgerufen wird, unabhängig davon, ob ein Wert an den Parameter übergeben wird oder der Standardwert des Parameters verwendet wird.
Wenn im obigen Code die Funktion f aufgerufen wird, bildet der Parameter y = x einen separaten Bereich. In diesem Bereich ist die Variable x selbst nicht definiert und zeigt daher auf die äußere globale Variable x. Wenn die Funktion aufgerufen wird, hat die lokale Variable x im Funktionskörper keinen Einfluss auf die Standardwertvariable x. Wenn die globale Variable x zu diesem Zeitpunkt nicht existiert, wird ein Fehler gemeldet.

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}
foo() // 3
x // 1

Im obigen Code bilden die Parameter der Funktion foo einen separaten Bereich. In diesem Bereich wird zuerst die Variable x und dann die Variable y deklariert. Der Standardwert von y ist eine anonyme Funktion. Die Variable x innerhalb dieser anonymen Funktion zeigt auf den ersten Parameter x im selben Bereich. Die Funktion foo deklariert eine interne Variable x im Inneren. Diese Variable und der erste Parameter x liegen nicht im selben Bereich, daher sind sie nicht dieselbe Variable. Daher werden nach der Ausführung von y die Werte der internen Variablen x und der externen globalen Variable angezeigt Variable x hat sich nicht geändert.
Wenn die Variable var x =3 entfernt wird, zeigt die interne Variable x der Funktion foo auf den ersten Parameter x, der mit dem x innerhalb der anonymen Funktion übereinstimmt, sodass die endgültige Ausgabe 2 und die äußere globale Variable ist Variable x bleibt davon unberührt.

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}
foo() // 2
x // 1

Temporäre Totzone für Parameter-Standardwerte

Ähnlich wie bei der 1et-Deklaration erstellt jeder Parameter der Funktion eine neue Bezeichnerbindung, auf die vor der Initialisierung nicht zugegriffen werden darf, andernfalls wird sie ausgelöst ein Fehler. Die Parameterinitialisierung erfolgt beim Aufruf der Funktion, unabhängig davon, ob ein Wert an den Parameter übergeben oder der Standardwert des Parameters verwendet wird.

function    getValue(value)    {
    return    value    +    5;
}
function    add(first,    second    =    getValue(first))    {
    return    first    +    second;
}
console.log(add(1,    1));//    2
console.log(add(1));//    7

Der Aufruf von add(1, 1) und add(1) führt tatsächlich den folgenden Code aus, um die Parameterwerte von first und second zu erstellen:

//JS调用add(1,1)可表示为
let    first    =    1;
let    second    =    1;
//JS调用add(1)可表示为
let    first    =    1;
let    second    =    getValue(first);

Rewrite it

function    add(first    =    second,    second)    {
    return    first    +    second;
}

console.log(add(1,    1));//    2
console.log(add(undefined,    1));//    抛出错误

In diesem Beispiel entspricht der Aufruf von add(1, 1) und add(undefiniert, 1) dem folgenden Hintergrundcode:

//    JS    调用    add(1,    1)    可表示为
let    first    =    1;
let    second    =    1;

//    JS    调用    add(1)    可表示为
let    first    =    second;
let    second    =    1;

In diesem Beispiel löst der Aufruf von add(undefiniert, 1) einen Fehler aus weil im ersten Bei der Initialisierung wurde der zweite noch nicht initialisiert. Die Sekunde existiert hier in der temporären Totzone und der Verweis auf die Sekunde löst einen Fehler aus.
Funktionsparameter verfügen über einen eigenen Bereich und eine temporäre Totzone, die vom Bereich des Funktionskörpers getrennt sind. Dies bedeutet, dass der Standardwert des Parameters keinen Zugriff auf Variablen zulässt, die im Funktionskörper deklariert sind.

Anwendung

Mithilfe von Parameter-Standardwerten können Sie festlegen, dass ein bestimmter Parameter nicht weggelassen werden darf. Wenn er weggelassen wird, wird ein Fehler ausgegeben.

function throwIfMissing() {
  throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}
foo()
// Error: Missing parameter

rest 参数

ES6 引入 rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。rest参数就是一个真正的数组,数组特有的方法都可以使用。

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}
add(2, 5, 3) // 10
// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

rest 参数的限制条件

一是函数只能有一个剩余参数,并且它必须被放在最后。

// 报错
function f(a, ...b, c) {
  // ...
}

第二个限制是剩余参数不能在对象字面量的    setter    属性中使用,这意味着如下代码同样会导致语法错误:

1et object={
//语法错误:不能在setter中使用剩余参数
set name(...value){
//一些操作
};

存在此限制的原因是:对象字面量的setter被限定只能使用单个参数;而剩余参数按照定义是不限制参数数量的,因此它在此处不被许可。    
前面有讲函数的length属性,不包括 rest 参数。

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

严格模式

从 ES5 开始,函数内部可以设定为严格模式。

function doSomething(a, b) {
  'use strict';
  // code
}

ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

// 报错
function doSomething(a, b = a) {
  'use strict';
  // code
}
// 报错
const doSomething = function ({a, b}) {
  'use strict';
  // code
};
// 报错
const doSomething = (...a) => {
  'use strict';
  // code
};
const obj = {
  // 报错
  doSomething({a, b}) {
    'use strict';
    // code
  }
};

这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。  
把函数包在一个无参数的立即执行函数里面可以规避这种限制。

const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());

在代码块中声明函数

在ES3或更早版本中,在代码块中声明函数(即块级函数)严格来说应当是一个语法错误,但所有的浏览器却都支持该语法。可惜的是,每个支持该语法的浏览器都有轻微的行为差异,所以最佳实践就是不要在代码块中声明函数(更好的选择是使用函数表达式)。

严格模式下

为了控制这种不兼容行为,ES5的严格模式为代码块内部的函数声明引入了一个错误。  
然而ES6会将    doSomething()函数视为块级声明,并允许它在定义所在的代码块内部被访问。块级函数与    let    函数表达式相似,在执行流跳出定义所在的代码块之后,函数定义就会被移除。关键区别在于:块级函数会被提升到所在代码块的顶部;而使用let的函数表达式则不会 。

"use    strict";
if    (true)    {

    console.log(typeof doSomething);//"function"
    function    doSomething()    {
        //    ...
    }
    doSomething();
}
console.log(typeof    doSomething);//    "undefined

非严格模式下

ES6    在非严格模式下同样允许使用块级函数,但行为有细微不同。块级函数的作用域会被提升到所在函数或全局环境的顶部,而不是代码块的顶部。

//    ES6    behavior
if    (true)    {
    console.log(typeof    doSomething);//"function"
    function    doSomething()    {
        //    ...
    }
    doSomething();
}
console.log(typeof    doSomething);//    "function"

name 属性

函数的name属性,返回该函数的函数名。

function foo() {}
foo.name // "foo"

getter函数,因此它的名称是    "get    firstName"    ,以标明它的特征;同样,setter函数也会带有"set"的前缀(getter与setter函数都必须用Object.getOwnPropertyDescriptor()来检索)。另外,使用bind()创建的函数会在名称属性值之前带有"bound”前缀;而使用Function构造器创建的函数,其名称属性则会有“anonymous”前缀,如果将一个匿名函数赋值给一个变量,ES5的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

new.target元属性

JS为函数提供了两个不同的内部方法:[[Call]]与[[Construct]]。当函数未使用new进行调用时,[[Call]]方法会被执行,运行的是代码中显示的函数体。而当函数使用new进行调用时,[[Construct]]方法则会被执行,负责创建一个被称为新目标的新的对象,并且使用该新目标作为this去执行函数体。拥有[[Construct]]    方法的函数被称为构造器。记住并不是所有函数都拥有[[Construct]]方法,因此不是所有函数都可以用new来调用。

在ES5中判断函数如何被调用

在ES5中判断函数是不是使用了new来调用(即作为构造器),最流行的方式是使用instanceof,例如:

function    Person(name)    {
    if    (this    instanceof    Person)    {
            this.name    =    name;//    使用new
    }    else    {
            throw    new    Error("You    must    use    new    with    Person
    }
}

var    person    =    new    Person("Nicholas");
var    notAPerson    =    Person("Nicholas");//抛出错误

可惜的是,该方法并不绝对可靠,因为在不使用    new    的情况下this仍然可能是    Person    的实例。

var    notAPerson    =    Person.call(person,    "Michael");    //    奏效了!

在ES6中判断函数如何被调用

为了解决这个问题,ES6引入了new.target 元属性。元属性指的是“非对象”(例如new)上的一个属性,并提供关联到它的目标的附加信息。当函数的[[Construct]]方法被调用时,new.target 会被填入new运算符的作用目标,该目标通常是新创建的对象实例的构造器,并且会成为函数体内部的this值。而若[[Call]]被执行,new.target的值则会是undefined。    
通过检查new.target是否被定义,这个新的元属性就让你能安全地判断函数是否被使用new 进行了调用。

function    Person(name)    {
    if    (typeof new.target    !==    "undefined")    {
            this.name    =    name;//    使用new
    }    else    {
            throw    new    Error("You    must    use    new    with    Person
    }
}
var    person    =    new    Person("Nicholas");
var    notAPerson    =    Person("Nicholas");//抛出错误

警告:在函数之外使用new.target会有语法错误。

箭头函数

ES6 允许使用“箭头”(=>)定义函数。但它的行为在很多重要方面与传统的JS函数不同:

区别

  • 没有this、super、arguments,也没有new.target 绑定:this、super、arguments、以及函数内部的new.target的值由所在的、最靠近的非箭头函数来决定。

  • 不能被使用new调用:箭头函数没有[[Construct]]方法,因此不能被用为构造函数,使用new调用箭头函数会抛出错误。

  • 没有原型:既然不能对箭头函数使用new,那么它也不需要原型,也就是没有prototype 属性。

  • 不能更改this:this的值在函数内部不能被修改,在函数的整个生命周期内其值会保持不变。

  • 没有arguments对象:既然箭头函数没有arguments绑定,你必须依赖于具名参数或剩余参数来访问函数的参数。

  • 不允许重复的具名参数:箭头函数不允许拥有重复的具名参数,无论是否在严格模式下;而相对来说,传统函数只有在严格模式下才禁止这种重复。

  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

下面是一种特殊情况,虽然可以运行,但会得到错误的结果。

let foo = () => { a: 1 };
foo() // undefined

上面代码中,原始意图是返回一个对象{a:1},但是由于引擎认为大括号是代码块,所以执行了一行语句a: 1。这时,a可以被解释为语句的标签,因此实际执行的语句是1;,然后函数就结束了,没有返回值。  
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

let fn = () => void doesNotReturn();

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。  
所以,箭头函数转成 ES5 的代码如下。

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

箭头函数的this看外层的是否有函数

如果有,外层函数的this就是内部箭头函数的this。

 function Person() {
     this.obj = {
         showThis : () => {
             console.log(this);//person
         }
     }
 }
    let fun5 = new Person();
    fun5.obj.showThis();

如果没有,this值就会是全局对象(在浏览器中是window,在nodejs中是global)。

    let obj = {
        name : 'kobe',
        age : 39,
        getName : () => {
            btn2.onclick = () => {
                console.log(this);//window
            };
        }
    };
    obj.getName();

不适用的场合

由于箭头函数使得this从“动态”变成“静态”,下面两个场合不应该使用箭头函数。  
第一个场合是定义函数的方法,且该方法内部包括this。

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

第二个场合是需要动态this的时候,也不应使用箭头函数。

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。  
另外,如果函数体很复杂,有许多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。

双冒号运算符

箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。  
函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

let log = ::console.log;
// 等同于
var log = console.log.bind(console);

Das obige ist der detaillierte Inhalt vonErweiterung von Funktionen in ES6 (Codebeispiel). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen