搜尋
首頁web前端js教程ES6中函數的擴充(程式碼範例)

ES6中函數的擴充(程式碼範例)

Nov 17, 2018 pm 03:28 PM
es6javascript

這篇文章帶給大家的內容是關於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);
});

在本例中,只有在未傳遞第二個參數、或明確將第二個參數值指定為undefined時,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"

會影響函數的length屬性

指定了預設值以後,函數的length屬性,將傳回沒有指定預設值的參數個數。也就是說,指定了預設值後,length屬性將會失真。

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

上面程式碼中,length屬性的回傳值,等於函數的參數個數減去指定了預設值的參數個數。例如,上面最後一個函數,定義了3個參數,其中有一個參數c指定了預設值,因此length屬性等於3減去1,最後得到2。 
這是因為length屬性的意義是,該函數預期傳入的參數個數。某個參數指定預設值以後,預期傳入的參數個數就不包含這個參數了。同理,後文的 rest 參數也不計入length屬性。

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

如果設定了預設值的參數不是尾參數,那麼length屬性也不再計入後面的參數了

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

參數預設值的作用域

一旦設定了參數的預設值,當函數進行宣告初始化時,參數會形成一個單獨的作用域(context)。等到初始化結束,這個作用域就會消失。這種語法行為,在不設定參數預設值時,是不會出現的。

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,而外層的全域變量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)事實上執行了下列程式碼來建立first與second的參數值:

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

改寫一下

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

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

本例中呼叫   add(1,    1)    與   add(undefined,    1)    對應以下的後台程式碼:

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

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

本例中呼叫add(undefined,1)拋出了錯誤案例被初始化時second尚未被初始化。這裡的second存在於暫時性死區內,對於second的引用就拋出了錯誤。 
函數參數擁有各自的作用域和暫時性死區,與函數體的作用域相分離,這意味著參數的預設值不允許存取函數體內部宣告的任意變數。

套用

利用參數預設值,可以指定某一個參數不得省略,如果省略就拋出一個錯誤。

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。如有侵權,請聯絡admin@php.cn刪除
超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安裝JavaScript?如何安裝JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。