您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關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屬性將失真。
(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)拋出了錯誤,是因為在first被初始化時second尚未被初始化。此處的second存在于暫時性死區內,對于second的引用就拋出了錯誤。
函數參數擁有各自的作用域和暫時性死區,與函數體的作用域相分離,這意味著參數的默認值不允許訪問在函數體內部聲明的任意變量。
利用參數默認值,可以指定某一個參數不得省略,如果省略就拋出一個錯誤。
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter
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();
一是函數只能有一個剩余參數,并且它必須被放在最后。
// 報錯 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屬性,返回該函數的函數名。
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"
JS為函數提供了兩個不同的內部方法:[[Call]]與[[Construct]]。當函數未使用new進行調用時,[[Call]]方法會被執行,運行的是代碼中顯示的函數體。而當函數使用new進行調用時,[[Construct]]方法則會被執行,負責創建一個被稱為新目標的新的對象,并且使用該新目標作為this去執行函數體。擁有[[Construct]] 方法的函數被稱為構造器。記住并不是所有函數都擁有[[Construct]]方法,因此不是所有函數都可以用new來調用。
在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引入了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。
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中函數擴展的示例就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。