>  기사  >  웹 프론트엔드  >  ES6의 기능 확장(코드 예)

ES6의 기능 확장(코드 예)

不言
不言앞으로
2018-11-17 15:28:081454검색

이 기사는 ES6의 기능 확장(코드 예제)에 대한 것입니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

함수 매개변수의 기본값

ES6에서는 매개변수 정의 바로 뒤에 작성되는 함수 매개변수의 기본값을 설정할 수 있습니다.

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

매개변수 변수는 기본적으로 선언되므로 let 또는 const를 사용하여 다시 선언할 수 없습니다.

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

매개변수 기본값을 사용하는 경우 함수에 동일한 이름의 매개변수가 포함될 수 없습니다.

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

매개변수의 기본값은 값으로 전달되지 않지만, 기본값 표현식의 값은 매번 다시 계산됩니다. 즉, 매개변수 기본값이 느리게 평가됩니다.

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

모든 매개변수의 기본값은 함수 선언에서 지정할 수 있습니다. 매개변수의 순위가 기본값을 지정하지 않는 매개변수보다 먼저 지정되는 경우에도 마찬가지입니다.

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

이 예시에서 timeout의 기본값은 두 번째 매개변수가 전달되지 않거나 두 번째 매개변수 값이 명시적으로 정의되지 않음으로 지정된 경우에만 사용됩니다.

구조 분해 할당의 기본값과 함께 사용됨

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

위 코드는 객체의 구조 분해 할당의 기본값만 사용하고, 함수 매개변수. foo 함수의 매개변수가 객체인 경우에만 구조해제 할당을 통해 변수 x, y가 생성됩니다. foo 함수가 호출될 때 매개변수가 제공되지 않으면 변수 x 및 y가 생성되지 않고 오류가 보고됩니다. 이러한 상황은 함수 매개변수에 기본값을 제공함으로써 피할 수 있습니다.

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"

함수의 길이 속성에 영향을 미칩니다

기본값이 지정된 후 함수의 길이 속성은 지정된 기본값 없이 매개변수의 수를 반환합니다. 즉, 기본값을 지정하면 길이 속성이 왜곡됩니다.

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

위 코드에서 길이 속성의 반환 값은 함수의 매개변수 개수에서 지정된 기본값을 갖는 매개변수 개수를 뺀 값과 같습니다. 예를 들어, 위의 마지막 함수는 3개의 매개변수를 정의하고 그 중 하나의 매개변수 c는 기본값을 지정하므로 길이 속성은 3에서 1을 뺀 것과 같고 마지막으로 2를 얻습니다.
길이 속성의 의미는 함수에 전달될 것으로 예상되는 매개변수의 개수이기 때문입니다. 매개변수에 기본값이 할당된 후 전달될 것으로 예상되는 매개변수 수에는 이 매개변수가 포함되지 않습니다. 같은 방식으로 나머지 매개변수는 나중에 길이 속성에 포함되지 않습니다.

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

기본값을 갖는 매개변수가 tail 매개변수가 아닌 경우 길이 속성은 더 이상 다음 매개변수에 포함되지 않습니다.

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

매개변수 기본값의 범위 value#🎜🎜 #

매개변수의 기본값이 설정되면 함수가 선언되고 초기화될 때 매개변수는 별도의 범위(컨텍스트)를 형성합니다. 초기화가 완료되면 이 범위는 사라집니다. 이 구문 동작은 매개변수 기본값이 설정되지 않은 경우 나타나지 않습니다.

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
매개변수 초기화는 매개변수에 값이 전달되거나 매개변수의 기본값이 사용되는지에 관계없이 함수가 호출될 때 수행됩니다.

위 코드에서 함수 f가 호출되면 매개변수 y = x가 별도의 범위를 형성합니다. 이 범위에서는 변수 x 자체가 정의되지 않으므로 외부 전역 변수 x를 가리킵니다. 함수가 호출될 때 함수 본문 내부의 로컬 변수 x는 기본값 변수 x에 영향을 주지 않습니다. 이때 전역 변수 x가 존재하지 않으면 오류가 보고됩니다.

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}
foo() // 3
x // 1
위 코드에서 foo 함수의 매개변수는 별도의 범위를 형성합니다. 이 범위에서는 변수 x가 먼저 선언된 다음 변수 y가 선언됩니다. y의 기본값은 익명 함수입니다. 이 익명 함수 내의 변수 x는 동일한 범위의 첫 번째 매개변수 x를 가리킵니다. foo 함수는 내부 변수 x를 선언합니다. 이 변수와 첫 번째 매개변수 x는 동일한 범위에 있지 않으므로 동일한 변수가 아닙니다. 따라서 y를 실행한 후에는 내부 변수 x와 외부 전역 변수의 값이 변경됩니다. 변수 x는 변경되지 않았습니다.

var x =3의 var가 제거되면 foo 함수의 내부 변수 x는 첫 번째 매개변수 x를 가리키며 이는 익명 함수 내부의 x와 일치하므로 최종 출력은 2이고 외부 변수는 2입니다. global 변수 x는 영향을 받지 않습니다.

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}
foo() // 2
x // 1
매개변수 기본값에 대한 임시 데드존

1et 선언과 유사하게 함수의 각 매개변수는 초기화에 액세스하기 전에는 허용되지 않는 새로운 식별자 바인딩을 생성합니다. 그렇지 않으면 오류가 발생합니다. 매개변수 초기화는 함수가 호출될 때 발생하며, 값이 매개변수에 전달되거나 매개변수의 기본값이 사용됩니다.

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
add(1, 1) 및 add(1)을 호출하면 실제로 다음 코드를 실행하여 첫 번째와 두 번째의 매개변수 값을 생성합니다.

//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));//    抛出错误

이 예에서 add(1, 1) 및 add(undefine, 1) 호출은 다음 배경 코드에 해당합니다.

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

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

이 예에서 add 호출 (정의되지 않음, 1) throws first가 초기화될 때 second가 아직 초기화되지 않았기 때문에 오류가 발생했습니다. 여기서 두 번째는 임시 데드존에 존재하며, 두 번째에 대한 참조는 오류를 발생시킵니다.

함수 매개변수에는 함수 본문의 범위와 분리된 고유한 범위와 임시 데드존이 있습니다. 즉, 매개변수의 기본값은 함수 본문 내에 선언된 모든 변수에 대한 액세스를 허용하지 않는다는 의미입니다.


Application

매개변수 기본값을 사용하면 특정 매개변수를 생략하지 않도록 지정할 수 있습니다. 생략하면 오류가 발생합니다.

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

위 내용은 ES6의 기능 확장(코드 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제