欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

分析ES5和ES6的apply區(qū)別

 更新時(shí)間:2021年05月06日 10:06:11   作者:淺笑·  
這篇文章主要介紹了分析ES5和ES6的apply區(qū)別,對(duì)ES6感興趣的同學(xué),可以參考下

概述

眾所周知, ES6 新增了一個(gè)全局、內(nèi)建、不可構(gòu)造的Reflect對(duì)象,并提供了其下一系列可被攔截的操作方法。其中一個(gè)便是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;
}

它們都接受一個(gè)提供給被調(diào)用函數(shù)的 this 參數(shù)和一個(gè)參數(shù)數(shù)組(或一個(gè)類數(shù)組對(duì)象, array-like object )。

可選參數(shù)

可以最直觀看到的是,function.apply()給函數(shù)的第二個(gè)傳參「參數(shù)數(shù)組」是可選的,當(dāng)不需要傳遞參數(shù)給被調(diào)用的函數(shù)時(shí),可以不傳或傳遞null、undefined值。而由于function.apply()只有兩個(gè)參數(shù),所以實(shí)踐中連第一個(gè)參數(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ù),則必須填一個(gè)空數(shù)組或者空的類數(shù)組對(duì)象(純JavaScript下空對(duì)象也可以,若是 TypeScript 則需帶上length: 0的鍵值對(duì)以通過類型檢查)。

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)會(huì)有所不同,若它的值是null或undefined,則會(huì)被自動(dòng)替換為全局對(duì)象(瀏覽器下為window),而基本數(shù)據(jù)類型值則會(huì)被自動(dòng)包裝(如字面量1的包裝值等價(jià)于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)過測(cè)試,發(fā)現(xiàn)上述該非嚴(yán)格模式下的行為對(duì)于Reflect.apply()也是有效的,只是 MDN 文檔沒有同樣寫明這一點(diǎn)。

異常處理

Reflect.apply可視作對(duì)Function.prototype.apply的封裝,一些異常判斷是一樣的。如傳遞的目標(biāo)函數(shù)target實(shí)際上不可調(diào)用、不是一個(gè)函數(shù)等等,都會(huì)觸發(fā)異常。但異常的表現(xiàn)卻可能是不一樣的。

如我們向target參數(shù)傳遞一個(gè)對(duì)象而非函數(shù),應(yīng)當(dāng)觸發(fā)異常。

而Function.prototype.apply()拋出的異常語義不明,直譯是.call不是一個(gè)函數(shù),但如果我們傳遞一個(gè)正確可調(diào)用的函數(shù)對(duì)象,則不會(huì)報(bào)錯(cuò),讓人迷惑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)用的對(duì)象,如果補(bǔ)齊了第二、第三個(gè)參數(shù),則拋出的異常描述與上述完全不同:

不過Reflect.apply()對(duì)于只傳遞一個(gè)不可調(diào)用對(duì)象的異常,是與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ù),才會(huì)去校驗(yàn)第三個(gè)參數(shù)數(shù)組的參數(shù);這也說明Reflect.apply()的參數(shù)校驗(yàn)是有順序的:

Reflect.apply(console.log)
// Thrown:
// TypeError: CreateListFromArrayLike called on non-object

實(shí)際使用

雖然目前沒有在Proxy以外的場(chǎng)景看到更多的使用案例,但相信在兼容性問題逐漸變得不是問題的時(shí)候,使用率會(huì)得到逐漸上升。

我們可以發(fā)現(xiàn) ES6Reflect.apply()的形式相較于傳統(tǒng) ES5 的用法,會(huì)顯得更直觀、易讀了,讓人更容易看出,一行代碼希望使用哪個(gè)函數(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]'

可能有人會(huì)不同意,這不是寫得更長(zhǎng)、更麻煩了嗎?關(guān)于這點(diǎn),見仁見智,對(duì)于單一函數(shù)的重復(fù)調(diào)用,確實(shí)是打的代碼更多了;對(duì)于需要靈活使用的場(chǎng)景,會(huì)更符合函數(shù)式的風(fēng)格,只需指定函數(shù)對(duì)象、傳遞參數(shù),即可獲得預(yù)期的結(jié)果。

但是對(duì)于這個(gè)案例來說,可能還會(huì)有一點(diǎn)小問題:每次調(diào)用都需要?jiǎng)?chuàng)建一個(gè)新的空數(shù)組!盡管現(xiàn)在多數(shù)設(shè)備性能足夠好,程序員不需額外考慮這點(diǎn)損耗,但是對(duì)于高性能、引擎又沒有優(yōu)化的場(chǎng)景,先創(chuàng)建一個(gè)可重復(fù)使用的空數(shù)組可能會(huì)更好:

const EmptyArgs = []

function getType(obj) {
    return Reflect.apply(
        Object.prototype.toString,
        obj,
        EmptyArgs
    )
}

另一個(gè)調(diào)用String.fromCharCode()的場(chǎng)景可以做代碼中字符串的混淆:

Reflect.apply(
    String.fromCharCode,
    undefined,
    [104, 101, 108, 108,
     111,  32, 119, 111,
     114, 108, 100,  33]
)
// 'hello world!'

對(duì)于可傳多個(gè)參數(shù)的函數(shù)如Math.max()等可能會(huì)更有用,如:

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ù)個(gè)數(shù),如果傳入太大的數(shù)組的話也可能報(bào)超過棧大小的錯(cuò)誤。這個(gè)大小因平臺(tái)和引擎而異,如 PC 端 node.js可以達(dá)到很大的大小,而手機(jī)端的jsC 可能就會(huì)限制到 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ī)整易用,它有如下特點(diǎn):

1.直觀易讀,將被調(diào)用函數(shù)放在參數(shù)中,貼近函數(shù)式風(fēng)格;

2.異常處理具有一致性,無歧義;

3.所有參數(shù)必傳,編譯期錯(cuò)誤檢查和類型推斷更友好。

如今vue.js 3 也在其響應(yīng)式系統(tǒng)中大量使用 Proxy 和 Reflect 了,期待不久的將來 Reflect 會(huì)在前端世界中大放異彩!

以上就是分析ES5和ES6的apply區(qū)別的詳細(xì)內(nèi)容,更多關(guān)于ES5和ES6區(qū)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論