摘要:等價與注意如果構造函數有自己的返回,那么情況有所不同。,定義了的屬性,默認是聲明的函數名,匿名函數是。匿名函數表達式和函數聲明都不會創建匿名作用域。
ECMAScript規范中對Function的文檔描述,我認為是ECMAScript規范中最復雜也是最不好理解的一部分,它涉及到了各方面。光對Function就分了Function Definitions、Arrow Function Definitions、Method Definitions、Generator Function Definitions、Class Definitions、Async Function Definitions、Async Arrow Function Definitions這幾塊。我準備花三章來介紹Function。這篇文章主要是理解ArrowFunction和GeneratorFunction,當然還包括最基本最普通的Function Definitions。
Function在了解Function Definitions之前我們需要知道函數對象(Function Object)。我們都知道Function本質上也是一個對象,所以普通對象有的方法Function對象都有,此外Function對象還有自己的內部方法。所有Function對象都有一個[[Call]]的內部方法,有了這個方法Function對象才能被用作函數調用,即你***()時內部調用就是[[Call]]方法,當然不是所有有[[Call]]方法的Function對象都可以進行***()調用,常見的Map、Set等方法雖然有[[Call]]方法,但是你不能進行Map()和Set(),這時候就用到了Function對象的另一個內部方法[[Construct]],當Function作為構造函數調用時,就會使用[[Construct]]方法。
[[Call]]注意:不是所有Function對象都有[[Construct]]方法。只有當Function作為構造函數調用時,才會有[[Construct]]方法,比如ArrowFunction和GeneratorFunction只有[[Call]]方法沒有[[Construct]]方法。
先說[[Call]]方法,看到這個名字很容易讓人想起Function.prototype中的call方法,沒錯Function.prototype.call以及Function.prototype.apply都是顯示的調用了[[Call]]方法,之所以說顯示調用是相比于***()調用,call和apply要簡單直接的多。[[Call]]方法接受兩個參數,一個是thisArgument,另一個是argumentsList,thisArgument即表示了function中的this對象,argumentsList代表了function中的參數列表,看!和Function.prototype.apply的調用方式是如此的相似。
function foo(){} foo(1,2) //當執行foo()方法時,實際上內部是如下調用 foo.[[Call]](undefined,? 1, 2 ?) //? ?表示ECMAScript的List規范類型 //注意,如果你是隱式調用function,那么thisArgument是undefined,不是常說的全局對象window, //只是在[[Call]]內部執行時檢測到如果thisArgument是undefined或null, //且在非嚴格模式下才會變成全局對象,即foo(1,2)你可以認為等價與下面的: foo.call(null,1,2) foo.apply(undefined,[1,2]) //------------------- var a={ foo:function(){} } a.foo(1,2) //等價與==> foo.[[Call]](a,? 1, 2 ?) //等價與==> foo.call(a,1,2) //等價與==> foo.apply(a,[1,2])
[[Construct]]這里有個建議,以后你遇到this指向問題的時候,你把function轉成call或者apply模式,你就能清楚的明白this指向什么。
[[Construct]]內部方法主要有new關鍵字調用Function時才會執行[[Construct]]方法。[[Construct]]方法主要接受兩個參數一個是argumentsList, 還有一個是newTarget。newTarget正常調用下指向調用的function對象。比如foo(),newTarget就是foo,你可以在函數內部用new.target訪問到。構造函數中的this對象與newTarget有關,如果newTarget.prototype存在,且是Object對象,則this就是ObjectCreate(newTarget.prototype),ObjectCreate是Object.create內部調用的方法,如果newTarget.prototype不存在或者不是Object對象,this相當于ObjectCreate(Object.prototype)。
function Foo(){} var fooInstance = new Foo(1,2) //等價與==> var fooInstance = Foo.[[Construct]](? 1, 2 ?,Foo); fooInstance instanceof Foo //true Object.create(Foo.prototype) instanceof Foo //true //注意如果構造函數有自己的return返回,那么情況有所不同。 //返回的是Object,則構造函數的實例就是返回的對象 //返回的不是Object,相當于默認沒有返回 function Foo(){ return {a:1}} var fooInstance = new Foo(1,2) fooInstance instanceof Foo //false,注意不是true,fooInstance不是Foo的實例 Object.create(Foo.prototype) instanceof Foo //true //只要Foo.prototype存在且是對象,那么Object.create(Foo.prototype)永遠是Foo的一個實例Function Definitions
Function Definitions包含了FunctionDeclaration和FunctionExpression,有一些早期錯誤檢測添加到Function Definitions中,其中在function中的let、const和var聲明的變量規則參考上一篇文章var、let、const聲明的區別,另外有一些附加的早期錯誤:
function中的參數被認為是var聲明,因此:
function foo(a,b){ let a = 1; //SyntaxError,重復聲明a } foo();
如果函數體是嚴格模式而參數列表不是簡單參數列表,則語法錯誤:
//不是簡單參數指的是包含解構賦值 function foo(a=1,...c){ "use strict" //SyntaxError } //如果"use strict"在函數體外定義則沒有錯誤 "use strict" function foo(a=1,...c){} //ok
函數體以及函數參數不能直接出現super
function foo(super){} //SyntaxError function foo(){ super();} //SyntaxErrorFunctionDeclaration
FunctionDeclaration分為帶變量名的函數聲明以及匿名函數聲明,匿名函數聲明只能在export中可用,其它任何地方使用匿名函數聲明都報錯。
在進行評估腳本和函數的時候會對包含在其中的函數聲明進行InstantiateFunctionObject方法,即初始化函數對象。注:該方法是在執行腳本和函數代碼之前進行的。
InstantiateFunctionObject方法簡單來說做了三步:1.FunctionCreate 2.makeConstructor 3. SetFunctionName。分開說
FunctionCreate創建了一個Function對象F,包括初始化內部插槽的值,比如上面提到的[[Call]],[[Construct]]方法的定義,原型對象[[Prototype]]的值,這里指的是Function.prototype,F的length屬性,指function的參數個數。
makeConstructor(F),這句話不是指創建構造器,這里指定義了F的prototype屬性的值,以及prototype中constructor的值,普通函數prototype相當于object.create(Object.prototype),constructor===F。
SetFunctionName,定義了F的name屬性,默認是聲明的函數名,匿名函數是default。
function foo(a,b){}; foo.__proto__ === Function.prototype; foo.length === 2; foo.prototype.__proto__ === Object.prototype; foo.prototype.constructor === foo; foo.name === "foo";FunctionExpression
FunctionExpression也分為兩類,有變量名的函數表達式和匿名函數表達式。函數表達式在執行時也會創建Function對象,步驟和函數聲明相似。其中匿名函數表達式不會定義屬性name,即不會執行第三步中的SetFunctionName。有變量名的函數表達式與函數聲明以及匿名函數表達式的區別在于作用域鏈,我們都知道一旦函數表達式中定義了變量名,我們就可以在函數體內通過該變量名調用函數自身。可問題來了,該函數變量名是定義在哪里呢?函數外還是在函數內呢?
var func = function foo(){}; foo(); //Uncaught ReferenceError: foo is not defined //顯然沒有在函數外定義函數表達式的變量名,那么是定義在函數內的? //我提到過在全局作用域和函數作用域中,var、function聲明的變量,let和const不能重復聲明。 var func = function foo(){ let foo = 1; //ok,可見函數表達式的變量名也不是在函數內聲明的。 }; foo();
看到這可能有人會認為函數表達式的變量名可能允許let和const進行覆蓋。其實不是,有變量名的函數表達式在創建Function對象的時候,創建了一個匿名作用域,在該作用域中定義了函數表達式的變量名。按上面這個例子,foo函數的外部作用域并不是全局作用域,而是一個匿名作用域,匿名作用域的外部作用域才是真正的全局作用域。匿名函數表達式和函數聲明都不會創建匿名作用域。
ArrowFunctionArrowFunction(箭頭函數)是ES6新增的一種新語法,主要是用來簡化function的寫法,更準確的說是簡化匿名函數表達式的一種寫法。因此匿名函數表達式的規則也適用于ArrowFunction,不過兩者還是有區別的,ArrowFunction中沒有規定不能直接出現super,也就是說在ArrowFunction中可以用super方法,其次ArrowFunction內部沒有[[Construct]]方法,因此不能作為構造器調用,所以在創建Function對象時不執行makeConstructor方法。最重要一點就是ArrowFunction沒有本地的this對象。
我們上面提道所有Function對象都有[[Call]]內部方法,接受this對象和參數列表兩個字段。此外Function對象還有一個[[ThisMode]]內部屬性,用來判斷是ArrowFunction還是非ArrowFunction,如果是ArrowFunction,那么不管[[Call]]中傳來的this是什么都會被丟棄。此外arguments, super和new.target和this也是一樣的。我在以前的文章中稍微提到過ArrowFunction中的this對象,我在這重新講一下:
var name = "outer arrow"; var obj = { name:"inner arrow", arrow: () => { console.log(this.name) } } obj.arrow(); //outer arrow,不是inner arrow
我們在ArrowFunction遇到this對象時,你不要把this看成是ArrowFunction的一部分,你從ArrowFunction中拿出this放到ArrowFunction的外部,觀察外部的this對象是什么,外部的this對象就是ArrowFunction的this對象。此外還要清楚不管是call還是apply都是對ArrowFunction無效的,它們最終調用的都是[[Call]]內部方法,當然bind也是無效的。
我們看一下ArrowFunction中的super應用,還是改編了MDN中的例子:
var obj1 = { method() { console.log("method 1"); } } var obj2 = { method() { console.log("method 2"); return ()=>{super.method();} } } Object.setPrototypeOf(obj2, obj1); var arrow = obj2.method() //method 2 arrow(); //method 1
注意:method1和method2其實就是Method Definitions。
如果單看arrow這個函數,它本身是不可能有super的,因為沒有任何繼承關系,只是一個單一的ArrowFunction,但是你放在obj2中就有了意義,Object.setPrototypeOf(obj2, obj1);這句話把obj1變為obj2的原型對象,obj2繼承了obj1的屬性和方法,obj2的super對象就是obj1,因此ArrowFunction中的super參照this可知,該super是obj1。
總的一句話概括ArrowFunction中的this,arguments, super和new.target都是通過原型鏈來查找的,不是動態創建的。
GeneratorFunction關于GeneratorFunction我不準備講怎么用它,我只談一下它的工作原理。說實話GeneratorFunction用到的情況實在太少了,我自己在做項目的時候基本不會用到GeneratorFunction,但這不妨礙我們學習GeneratorFunction。
GeneratorFunction也是Function的一種,Function的規則也適用于GeneratorFunction,此外在GeneratorFunction的參數中不能出現yield表達式。
GeneratorFunction與普通的Function一樣,都會創建Function對象,但是區別也在這里,上面提到了Function的[[Prototype]]原型值是Function.prototype,但是GeneratorFunction不同,它的[[Prototype]]原型值是%Generator%,此外Function的prototype屬性是ObjectCreate(Object.prototype),但是GeneratorFunction的卻是ObjectCreate(%GeneratorPrototype%)而且prototype中沒有constructor屬性,不能作為構造器調用。
注:Function.prototype也寫作%FunctionPrototype%,Object.prototype也寫作%ObjectPrototype%。%Generator%和%GeneratorPrototype%沒有全局名稱,不能直接訪問。
執行函數時,內部調用了[[Call]]方法,但是和Function不同的是GeneratorFunction返回的不是函數的執行結果,而是一個對象,這個對象是GeneratorFunction的一個實例,這跟[[Construct]]方法很像。
function* gen(){} gen(); //返回的其實是Object.create(gen.prototype)對象。
你可以比較gen()和Object.create(gen.prototype)這兩個對象,你會發現它們很像,只是Object.create(gen.prototype)缺少了Generator對象的一些內部狀態。可以說雖然GeneratorFunction沒有[[Construct]]方法,不能作為構造器調用,但是你可以認為GeneratorFunction本身就是一個構造器。
此外在創建GeneratorFunction對象時,還做了一些其他操作,我們在以前的文章中提到了執行上下文,GeneratorFunction對象有個[[GeneratorContext]]內部插槽,當評估GeneratorFunction定義的代碼時,GeneratorFunction對象把當前正在運行的執行上下文存在了[[GeneratorContext]]中,并掛起了該正在運行的執行上下文,因此對GeneratorFunction中代碼的評估被暫停了,從而執行其它代碼,當你調用GeneratorFunction對象的next方法時,他把[[GeneratorContext]]中保存的執行上下文取出放到執行上下文棧頂部,成為正在運行的執行上下文,此時GeneratorFunction中暫定評估的代碼又重新開始執行,直到執行完畢或者遇到yield表達式。當遇到yield表達式時,它又把正在運行的執行上下文從棧中移除,暫停對GeneratorFunction代碼的執行,等待下次next方法調用之后繼續執行。
簡單來說GeneratorFunction的實現原理其實是運行的執行上下文之間不停來回切換。
GeneratorFunction基本就提到這里了,最后附上ECMAScript關于GeneratorFunction的一張關系圖片:
關于Function的簡單介紹就暫時告一段落,在下一篇文章中我會來簡單介紹Promise和AsyncFunction。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92062.html
摘要:指向的改變構造函數中的操作符會調用函數的內部的方法,創建對象,之后調用函數的方法,把新創建對象作為值。調用函數時與設置的值以及箭頭函數皆為動態的改變指針的方法。這一特性使得箭頭函數在中的函數中使用起來很方便。 原文地址 JavaScript中的this 原理 錯誤的this指向 通常所說的:如果是全局環境中,this指向全局對象,如果是對象的方法,這this指向這個對象。 例子1: ...
摘要:花點時間搞清楚中的分號規則吧不管你喜歡結尾帶分號或省略分號的模式分號允許的場景分號一般允許出現在大部分語句的末尾,比如等栗子僅有一個分號可以表示空語句在中合法,比如可解析為三個空語句空語句可用于輔助產生語法合法的解析結果,如如果沒有末尾的 花點時間搞清楚JS中的分號規則吧~~~不管你喜歡結尾帶分號或省略分號的模式 分號允許的場景 分號一般允許出現在大部分語句(statement)的末尾...
摘要:如下在第一個例子中,被點擊元素是通過,這個形式參數來代替的。它的作用和形式參數類似,其本質上是一個對象的引用,它的特殊性在于不需要手動傳值,所以使用起來會更加簡單和方便。 無論在 javascript 的日常使用中還是前端面試過程中,this 的出鏡率都極高。這無疑說明了,this 的重要性。但是 this 非常靈活,導致很多人覺得 this 的行為難以理解。本文從為什么要有 this...
閱讀 1129·2021-10-27 14:13
閱讀 2636·2021-10-09 09:54
閱讀 897·2021-09-30 09:46
閱讀 2424·2021-07-30 15:30
閱讀 2166·2019-08-30 15:55
閱讀 3409·2019-08-30 15:54
閱讀 2847·2019-08-29 14:14
閱讀 2771·2019-08-29 13:12