分析ES5和ES6的apply區(qū)別
概述
眾所周知, ES6 新增了一個全局、內(nèi)建、不可構(gòu)造的Reflect對象,并提供了其下一系列可被攔截的操作方法。其中一個便是Reflect.apply()了。下面探究下它與傳統(tǒng) ES5 的Function.prototype.apply()之間有什么異同。
函數(shù)簽名
MDN 上兩者的函數(shù)簽名分別如下:
Reflect.apply(target, thisArgument, argumentsList) function.apply(thisArg, [argsArray])
而 TypeScript 定義的函數(shù)簽名則分別如下:
declare namespace Reflect { function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any; } interface Function { apply(this: Function, thisArg: any, argArray?: any): any; }
它們都接受一個提供給被調(diào)用函數(shù)的 this 參數(shù)和一個參數(shù)數(shù)組(或一個類數(shù)組對象, array-like object )。
可選參數(shù)
可以最直觀看到的是,function.apply()給函數(shù)的第二個傳參「參數(shù)數(shù)組」是可選的,當(dāng)不需要傳遞參數(shù)給被調(diào)用的函數(shù)時,可以不傳或傳遞null、undefined值。而由于function.apply()只有兩個參數(shù),所以實踐中連第一個參數(shù)也可以一起不傳,原理上可以在實現(xiàn)中獲得undefined值。
(function () { console.log('test1') }).apply() // test1 (function () { console.log('test2') }).apply(undefined, []) // test2 (function () { console.log('test3') }).apply(undefined, {}) // test3 (function (text) { console.log(text) }).apply(undefined, ['test4']) // test4
而Reflect.apply()則要求所有參數(shù)都必傳,如果希望不傳參數(shù)給被調(diào)用的函數(shù),則必須填一個空數(shù)組或者空的類數(shù)組對象(純JavaScript下空對象也可以,若是 TypeScript 則需帶上length: 0的鍵值對以通過類型檢查)。
Reflect.apply(function () { console.log('test1') }, undefined) // Thrown: // TypeError: CreateListFromArrayLike called on non-object Reflect.apply(function () { console.log('test2') }, undefined, []) // test2 Reflect.apply(function () { console.log('test3') }, undefined, {}) // test3 Reflect.apply(function (text) { console.log(text) }, undefined, ['test4']) // test4
非嚴(yán)格模式
由文檔可知,function.apply()在非嚴(yán)格模式下thisArg參數(shù)變現(xiàn)會有所不同,若它的值是null或undefined,則會被自動替換為全局對象(瀏覽器下為window),而基本數(shù)據(jù)類型值則會被自動包裝(如字面量1的包裝值等價于Number(1))。
(function () { console.log(this) }).apply(null) // Window {...} (function () { console.log(this) }).apply(1) // Number { [[PrimitiveValue]]: 1 } (function () { console.log(this) }).apply(true) // Boolean { [[PrimitiveValue]]: true } 'use strict'; (function () { console.log(this) }).apply(null) // null (function () { console.log(this) }).apply(1) // 1 (function () { console.log(this) }).apply(true) // true
但經(jīng)過測試,發(fā)現(xiàn)上述該非嚴(yán)格模式下的行為對于Reflect.apply()也是有效的,只是 MDN 文檔沒有同樣寫明這一點。
異常處理
Reflect.apply可視作對Function.prototype.apply的封裝,一些異常判斷是一樣的。如傳遞的目標(biāo)函數(shù)target實際上不可調(diào)用、不是一個函數(shù)等等,都會觸發(fā)異常。但異常的表現(xiàn)卻可能是不一樣的。
如我們向target參數(shù)傳遞一個對象而非函數(shù),應(yīng)當(dāng)觸發(fā)異常。
而Function.prototype.apply()拋出的異常語義不明,直譯是.call不是一個函數(shù),但如果我們傳遞一個正確可調(diào)用的函數(shù)對象,則不會報錯,讓人迷惑Function.prototype.apply下到底有沒有call屬性?
Function.prototype.apply.call() // Thrown: // TypeError: Function.prototype.apply.call is not a function Function.prototype.apply.call(console) // Thrown: // TypeError: Function.prototype.apply.call is not a function Function.prototype.apply.call(console.log) ///- 輸出為空,符合預(yù)期
Function.prototype.apply()拋出的異常具有歧義,同樣是給target參數(shù)傳遞不可調(diào)用的對象,如果補齊了第二、第三個參數(shù),則拋出的異常描述與上述完全不同:
不過Reflect.apply()對于只傳遞一個不可調(diào)用對象的異常,是與Function.prototype.apply()全參數(shù)的異常是一樣的:
Reflect.apply(console) // Thrown: // TypeError: Function.prototype.apply was called on #<Object>, which is a object and not a function
而如果傳遞了正確可調(diào)用的函數(shù),才會去校驗第三個參數(shù)數(shù)組的參數(shù);這也說明Reflect.apply()的參數(shù)校驗是有順序的:
Reflect.apply(console.log) // Thrown: // TypeError: CreateListFromArrayLike called on non-object
實際使用
雖然目前沒有在Proxy以外的場景看到更多的使用案例,但相信在兼容性問題逐漸變得不是問題的時候,使用率會得到逐漸上升。
我們可以發(fā)現(xiàn) ES6Reflect.apply()的形式相較于傳統(tǒng) ES5 的用法,會顯得更直觀、易讀了,讓人更容易看出,一行代碼希望使用哪個函數(shù),執(zhí)行預(yù)期的行為。
// ES5 Function.prototype.apply.call(<Function>, undefined, [...]) <Function>.apply(undefined, [...]) // ES6 Reflect.apply(<Function>, undefined, [...])
我們選擇常用的Object.prototype.toString比較看看:
Object.prototype.toString.apply(/ /) // '[object RegExp]' Reflect.apply(Object.prototype.toString, / /, []) // '[object RegExp]'
可能有人會不同意,這不是寫得更長、更麻煩了嗎?關(guān)于這點,見仁見智,對于單一函數(shù)的重復(fù)調(diào)用,確實是打的代碼更多了;對于需要靈活使用的場景,會更符合函數(shù)式的風(fēng)格,只需指定函數(shù)對象、傳遞參數(shù),即可獲得預(yù)期的結(jié)果。
但是對于這個案例來說,可能還會有一點小問題:每次調(diào)用都需要創(chuàng)建一個新的空數(shù)組!盡管現(xiàn)在多數(shù)設(shè)備性能足夠好,程序員不需額外考慮這點損耗,但是對于高性能、引擎又沒有優(yōu)化的場景,先創(chuàng)建一個可重復(fù)使用的空數(shù)組可能會更好:
const EmptyArgs = [] function getType(obj) { return Reflect.apply( Object.prototype.toString, obj, EmptyArgs ) }
另一個調(diào)用String.fromCharCode()的場景可以做代碼中字符串的混淆:
Reflect.apply( String.fromCharCode, undefined, [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] ) // 'hello world!'
對于可傳多個參數(shù)的函數(shù)如Math.max()等可能會更有用,如:
const arr = [1, 1, 2, 3, 5, 8] Reflect.apply(Math.max, undefined, arr) // 8 Function.prototype.apply.call(Math.max, undefined, arr) // 8 Math.max.apply(undefined, arr) // 8
但由于語言標(biāo)準(zhǔn)規(guī)范沒有指定最大參數(shù)個數(shù),如果傳入太大的數(shù)組的話也可能報超過棧大小的錯誤。這個大小因平臺和引擎而異,如 PC 端 node.js可以達到很大的大小,而手機端的jsC 可能就會限制到 65536 等。
const arr = new Array(Math.floor(2**18)).fill(0) // [ // 0, 0, 0, 0, // ... 262140 more items // ] Reflect.apply(Math.max, null, arr) // Thrown: // RangeError: Maximum call stack size exceeded
總結(jié)
ES6 新標(biāo)準(zhǔn)提供的Reflect.apply()更規(guī)整易用,它有如下特點:
1.直觀易讀,將被調(diào)用函數(shù)放在參數(shù)中,貼近函數(shù)式風(fēng)格;
2.異常處理具有一致性,無歧義;
3.所有參數(shù)必傳,編譯期錯誤檢查和類型推斷更友好。
如今vue.js 3 也在其響應(yīng)式系統(tǒng)中大量使用 Proxy 和 Reflect 了,期待不久的將來 Reflect 會在前端世界中大放異彩!
以上就是分析ES5和ES6的apply區(qū)別的詳細內(nèi)容,更多關(guān)于ES5和ES6區(qū)別的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript iframe數(shù)據(jù)共享接口實現(xiàn)方法
在iframe與父窗口或者與子窗口傳遞數(shù)據(jù)是一個麻煩的事情,如果我們能夠?qū)懸粋€一勞永逸的接口那就再方便不過了,下面就來簡答介紹一下如何實現(xiàn)此功能,對js iframe相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-01-01使用json來定義函數(shù),在里面可以定義多個函數(shù)的實現(xiàn)方法
下面小編就為大家?guī)硪黄褂胘son來定義函數(shù),在里面可以定義多個函數(shù)的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10Extjs4中tree的拖拽功能(可以兩棵樹之間拖拽) 簡單實例
這篇文章主要介紹了Extjs4中tree的拖拽功能簡單實例,有需要的朋友可以參考一下2013-12-12