摘要:函數是對象理解函數是對象,是準確理解函數的第一步。在中,函數對象和其他對象一樣,均被視為一等公民。當函數執行完畢,其執行環境從棧中彈出并銷毀。此時的函數充當構造器的角色。調用函數對象的方法并將結果賦給。
函數是javascript中最重要的內容,也是其相對其他語言來說在設計上比較有意思的地方。javascript許多高級特性也或多或少和函數相關。本文將以函數為中心,對函數的各個關鍵知識點做簡要介紹。
函數是對象理解函數是對象,是準確理解函數的第一步。下面的代碼就創建了一個函數對象。
var sum = new Function("num1", "num2", "return num1 + num2;");
每個函數都是Function類型的實例。Function構造函數可以接受多個參數,最后一個參數是函數體,其他參數均為函數的形參。由于其書寫的不優雅和兩次解析導致的性能問題,這種方式不經常被采用,但是這種寫法對于理解函數就是對象是非常有幫助的。一般地,我們都用字面的方式來創建函數。
var sum = function(num1, num2){ return num1 + num2; } //或者 function sum(num1, num2){ return num1 + num2; }
以上兩種定義函數的方法分別叫做函數表達式和函數聲明,兩者的效果是等價的,區別在于解析器向執行環境加載數據時對兩者的處理不一樣。解析器會率先讀取函數聲明來創建函數對象,保證其在任何代碼執行之前可用;對于函數表達式,則必須等到解析器執行到對應的代碼行,函數對象才被創建。
在javascript中,函數對象和其他對象一樣,均被視為一等公民。所以函數可以被引用、可以作為參數被傳遞或作為返回值返回,這使得函數的使用非常的靈活。
函數的執行函數對象代表了一個過程,和大多數語言一樣通過函數調用表達式可以調用這個過程。但是javascript的函數對象還提供了另外兩種調用方式,call和apply方法。call和apply方法的第一個參數用于指定執行環境中this的綁定,后面的參數用于指定函數的實際參數。call和apply的唯一區別是實參的形式不一樣,call是用逗號分割,apply則是以數組傳遞。例如:
//函數調用表達式 sum(1, 2); //call方法 sum.call(this, 1, 2); //apply方法 sum.apply(this, [1, 2]);
不管用哪種調用方式,最終都是通過函數對象的[[Call]]方法實際調用這個過程。[[Call]]方法是javascript引擎內部使用的一個方法,程序不能直接訪問它。[[Call]]方法接受兩個參數,第一個參數指定this的綁定值,第二個參數指定函數的參數列表。為了表達方便,后面我們將[[Call]]方法的第一個參數稱作thisArg。函數對象的call方法和apply方法可以顯示指定thisArg,函數表達式則是隱式指定這個參數的。例如:
var foo = function(){ console.log(this); }; var obj = {name:"object"}; foo(); obj.foo = foo; obj.foo();
代碼在瀏覽器的執行結果如下:
Window {top: Window, window: Window, location: Location...} Object {name: "object", foo: function}
從執行結果可以看出,obj.foo()這種調用方法,隱式將調用它的對象obj作為了thisArg。但是為什么foo()這種調用方式this的綁定值是window這個全局對象?難道foo()這種調用方式將全局對象默認指定為thisArg?其實不是這樣的。thisArg并不是和this關鍵字的綁定一一對應的,其中有一個轉換過程。如下:
1.如果thisArg為undefined或者null,則this的綁定為全局對象。
2.如果thisArg不是Object類型,則將thisArg強制轉型為Object類型并綁定到this。
3.否則this的綁定就為thisArg。
其實foo()這種調用方式thisArg的值為undefined,通過以上的轉換過程將this綁定為全局對象。
前面提到過執行環境(Execution Context)這個概念,簡單來說執行環境就是函數在執行時所依賴的一個數據環境,它決定了函數的行為。程序執行流每次進入函數代碼時都會創建一個新的執行環境。活動的執行環境在邏輯上形成了一個棧的結構。當函數執行完畢,其執行環境從棧中彈出并銷毀。
每個執行環境都包含一個重要的組件:詞法環境(Lexical Environment)。詞法環境定義了javascript程序標識符到變量或函數的關聯關系。詞法環境包含了環境記錄(Environment Record)和一個到外層詞法環境的引用(如果有的話,否則為null)。環境記錄記錄了當前作用域下的變量或函數的綁定情況。有兩種類型的環境記錄,聲明式環境記錄(Declarative Environment Records)和對象環境記錄(Object Environment Records)。聲明式環境記錄包含了當前作用域下標識符到變量聲明和函數聲明的綁定。對象環境記錄是一個和特定對象綁定的環境記錄,用于臨時改變標識符的解析情況,比如在with子句中。
函數對象都有一個[[Scope]]屬性,函數對象在創建時會將當前執行環境的詞法環境的值賦予給[[Scope]]屬性。這個屬性是引擎的內部屬性,程序無法訪問到它。當程序流進入到函數時,javascript引擎會創建新的執行環境,同時也創建對應的詞法環境。引擎會將當前作用域聲明的變量和函數綁定到詞法環境,同時將[[Scope]]屬性的引用也添加到詞法環境。程序在進行標識符解析的時候,會優先從當前的詞法環境中搜索,搜索失敗則向外層詞法環境搜索,如果到最外層的全局環境還沒搜索到則會拋出異常。
嵌套定義的函數會形成javascript中一個有趣的特性:閉包。閉包的形成是由于內層函數引用了外層函數在創建它時的詞法環境。即使外層函數已經返回,執行環境已經銷毀,但是內層函數依然能夠通過詞法環境的引用訪問外層函數中定義的變量或函數。
with和catch子句with子句和catch子句都能臨時改變當前的詞法環境。他們的方式是有些區別的。先看with子句。
function foo(){ var background = "#ccc"; with(document){ body.style.background = background; } }
當執行流進入foo時,這時會創建一個聲明式詞法環境。執行流進入with子句的時候,引擎會創建一個對象環境記錄。此時with子句中的標識符解析都會先從document這個對象中查找。當with子句執行完之后,對象環境記錄銷毀。
try{ //do something }catch(e){ //handel error }
catch子句也能臨時改變當前的詞法環境。和with子句不一樣的是,它會創建一個聲明式詞法環境,將catch子句中的參數綁定到這個詞法環境。
構造器與原型繼承函數對象還有個非常重要的內部方法[[Construct]],當我們將new操作符應用到函數對象時就調用了[[Construct]]方法。此時的函數充當構造器的角色。下面的代碼就通過[[Construct]]創建了一個對象。
var Dog = function(){ } var dog = new Dog();
[[Construct]]方法的執行過程如下。
1.創建一個空對象obj。
2.設置obj的內部屬性[[Class]]為Object。
3.設置obj的內部屬性[[Extensible]]為true。
4.設置obj的[[Prototype]]屬性:如果函數對象prototype的值為對象則直接賦給obj,否則賦予Object的prototype值。
5.調用函數對象的[[Call]]方法并將結果賦給result。
6.如果result為對象則返回result,否則返回obj。
每個javascript對象都有一個[[Prototype]]的內部屬性,[[Prototype]]的值為一個對象,叫做原型對象。當程序在訪問javascript對象的某個屬性時,首先會在當前對象中搜索,搜索失敗則到原型鏈中搜索,直到搜索到相應值,否則就為undefined。javascript的這種特性叫做原型繼承。[[Construct]]方法的第四步是實現原型繼承的關鍵,它指定了javascript對象的[[Prototype]]屬性。
var Dog = function(){ } var animal = {}; Dog.prototype = animal; var dog = new Dog();
上面代碼創建出來的dog對象的原型就為animal,它“繼承”了animal對象的屬性。原型繼承是另外一種面向對象的模型,相對于“類”的繼承模型來說,原型繼承更加符合我們的現實世界的模型。原型繼承在javascript也是有非常廣的用途。
結語函數這條線將javascript許多核心內容串起來了,個人覺得這也是javascript最有意思的地方。本文主要是根據Ecma-262第五版規范中相關內容進行的總結和整理,由于能力有限,如有理解上的錯誤,望批評指出。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78932.html
摘要:深入之繼承的多種方式和優缺點深入系列第十五篇,講解各種繼承方式和優缺點。對于解釋型語言例如來說,通過詞法分析語法分析語法樹,就可以開始解釋執行了。 JavaScript深入之繼承的多種方式和優缺點 JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優缺點。 寫在前面 本文講解JavaScript各種繼承方式和優缺點。 但是注意: 這篇文章更像是筆記,哎,再讓我...
摘要:要理解立即執行函數,需要先理解一些函數的基本概念。函數表達式使用關鍵字聲明一個函數,但未給函數命名,最后將匿名函數賦予一個變量,叫函數表達式,這是最常見的函數表達式語法形式。 javascript和其他編程語言相比比較隨意,所以javascript代碼中充滿各種奇葩的寫法,有時霧里看花,當然,能理解各型各色的寫法也是對javascript語言特性更進一步的深入理解。 ( functio...
摘要:使用上一篇文章的例子來說明下自由變量進階期深入淺出圖解作用域鏈和閉包訪問外部的今天是今天是其中既不是參數,也不是局部變量,所以是自由變量。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第7天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:理解的函數基礎要搞好深入淺出原型使用原型模型,雖然這經常被當作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統的類繼承還要強大。中文指南基本操作指南二繼續熟悉的幾對方法,包括,,。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家帶來幫助....(據說是阿里的前端妹子寫的) this 的值到底...
摘要:理解作用域高級程序設計中有說到對象是在運行時基于函數的執行環境綁定的在全局函數中,等于,而當函數被作為某個對象調用時,等于那個對象。指向與匿名函數沒有關系如果函數獨立調用,那么該函數內部的,則指向。 理解this作用域 《javascript高級程序設計》中有說到: this對象是在運行時基于函數的執行環境綁定的:在全局函數中,this等于window,而當函數被作為某個對象調用時,t...
閱讀 854·2021-11-19 11:29
閱讀 3349·2021-09-26 10:15
閱讀 2855·2021-09-22 10:02
閱讀 2433·2021-09-02 15:15
閱讀 1970·2019-08-30 15:56
閱讀 2408·2019-08-30 15:54
閱讀 2903·2019-08-29 16:59
閱讀 635·2019-08-29 16:20