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