摘要:可以使用函數(shù)表達式的形式將結(jié)果賦值給變量即使把函數(shù)賦值給了另一個變量函數(shù)的名字依然有效所以遞歸依然可以正常運行。
最近一直在復習鞏固知識,以前寫的筆記現(xiàn)在也在看,為了勘誤之前的理解,加深印象,把markdown上所寫的又拿下來再敘述一遍,章節(jié)順序都是按照當時看《高程》的順序整理的,如有不對的地方還請拍磚指教,感謝!
========================================================================
函數(shù)表達式遞歸、閉包、模仿塊級作用域、私有變量
定義函數(shù)有兩種方式,函數(shù)聲明和函數(shù)表達式,函數(shù)聲明的語法是:
function functionName(arguments){ //函數(shù)體; }
函數(shù)聲明的一個重要特征就是函數(shù)聲明提升,在執(zhí)行代碼之前,都會讀取函數(shù)聲明,以下的例子不會出現(xiàn)錯誤:
sayHi(); function sayHi(){ alert("hi"); }
函數(shù)表達式的語法是:
var functionName = function(arguments){ //函數(shù)體; }
這種情況下創(chuàng)建的函數(shù)是匿名函數(shù),匿名函數(shù)的name屬性是空字符串,如果以這樣的方式調(diào)用會出現(xiàn)錯誤:
sayHi();//錯誤,函數(shù)還不存在; var sayHi = function(){ alert("hi"); }
如果使用if...else語句判斷一個條件,執(zhí)行同一個functionName的函數(shù)聲明,JavaScript的引擎會嘗試修正錯誤,將其轉(zhuǎn)換為合理的狀態(tài),而修正的機制不一樣,大多數(shù)會在condition為true時返回第一個聲明,少部分會為第二個聲明,因此在if...else中最好使用函數(shù)表達式,它會根據(jù)condition賦值給變量返回正確的函數(shù)結(jié)果。通過condition之后,函數(shù)賦值給變量,再選擇執(zhí)行函數(shù)。
var sayHi; if(condition){ sayHi = function(){ alert("Hi"); } }else{ sayHi = function(){ alert("Yo!"); } } sayHi();
能夠創(chuàng)建函數(shù)賦值給變量,當然也可以把函數(shù)作為其它函數(shù)的值返回。以下:
function compare(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } } }
遞歸
遞歸函數(shù)即自己調(diào)用自己,一個階乘函數(shù):
function sum(num){ if(num<=1){ return 1; }else{ return num * sum(num-1); } }
這樣一看好像沒有問題,但是如果把函數(shù)存在變量中,然后改變函數(shù)的引用為null,那么在執(zhí)行函數(shù)就會出錯。
var other = sum; sum = null; other(4);//sum isn"t a function;
使用callee()方法可以解耦,callee保存的是擁有這個arguments參數(shù)的函數(shù)。
function sum(num){ if(num<=1){ return 1; }else{ return num * arguments.callee(num-1); } }
而在strict模式下,callee是不可用的,并且考慮到代碼安全的因素callee和caller已經(jīng)被棄用了,所以盡量不要使用這個方法??梢允褂煤瘮?shù)表達式的形式,將結(jié)果賦值給變量,即使把函數(shù)賦值給了另一個變量,函數(shù)的名字依然有效,所以遞歸依然可以正常運行。
var factorial = function sum(num){ if(num<=1){ return 1; }else{ return num * sum(num-1); } } var other = factorial; factorial = null; other(7);//
ES6中關(guān)于尾遞歸的優(yōu)化
函數(shù)調(diào)用會在內(nèi)存形成一個"調(diào)用記錄",又稱"調(diào)用幀"(call frame),保存調(diào)用位置和內(nèi)部變量等信息。如果在函數(shù)A的內(nèi)部調(diào)用函數(shù)B,那么在A的調(diào)用記錄上方,還會形成一個B的調(diào)用記錄。等到B運行結(jié)束,將結(jié)果返回到A,B的調(diào)用記錄才會消失。如果函數(shù)B內(nèi)部還調(diào)用函數(shù)C,那就還有一個C的調(diào)用記錄棧,以此類推。所有的調(diào)用記錄,就形成一個"調(diào)用棧"-------摘自阮一峰老師的博客
function fac(n,total=1){ if(n == 1){ return total; } return fac(n - 1,n * total); }
2.閉包
執(zhí)行環(huán)境定義了所有變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。每個執(zhí)行環(huán)境都有與之關(guān)聯(lián)的變量對象,環(huán)境中定義的所以變量和函數(shù)都保存在這個對象中。當代碼在執(zhí)行環(huán)境中運行時,會創(chuàng)建變量對象的一個作用域鏈(保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問)。 當某個函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行環(huán)境及相應的作用域鏈,然后arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象,但在作用域鏈中,外部函數(shù)的活動對象處于第二位,在它之外的函數(shù)處于第三位,...直至作為作用域終點的全局執(zhí)行環(huán)境。
function compare(value1,value2){ if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } } var result = compare(5,10);
在這段代碼中,當調(diào)用compare()函數(shù)時,會創(chuàng)建一個包含arguments、value1、value2的活動對象,而全局執(zhí)行環(huán)境的變量對象result、compare則在這個作用域鏈的第二位。 每個執(zhí)行環(huán)境都有一個表示變量的對象-----變量對象,全局環(huán)境的對象會一直存在,而compare函數(shù)里的局部環(huán)境的變量對象,只在函數(shù)執(zhí)行時存在,創(chuàng)建compare函數(shù)時,會創(chuàng)建一個預先包含了全局變量對象compare、result的作用域鏈,此后又有一個活動對象被創(chuàng)建并被推入作用域鏈的前端。compare()的活動對象是arguments[5,10],value1 : 5,value2 : 10。所以此時的作用域鏈包含了本地活動對象和全局變量對象。作用域鏈是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。
一般來說,當函數(shù)執(zhí)行完畢后,局部活動對象就會銷毀,內(nèi)存中僅保存全局作用域,但是閉包的情況有所不同。
在一個函數(shù)內(nèi)部定義的函數(shù),會將外部函數(shù)的活動對象添加到它的作用域鏈中。
function createCompare(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } } } var compare = createCompare("name"); var result = compare({name : "Nicholas"},{name : "Greg"});
匿名函數(shù)在被返回之后,它的作用域鏈被初始化為外部函數(shù)的活動對象以及全局變量對象,因此它可以訪問外部函數(shù)的所有變量,并且在外部函數(shù)執(zhí)行完畢后,外部函數(shù)的作用域鏈會被銷毀,但是它的活動對象仍然保存著,因為此時匿名函數(shù)還在引用這個活動對象arguments:"name",propertyName : name。如果:
compare = null;
那么這時就會解除對匿名函數(shù)的引用,以便釋放內(nèi)存。
1)閉包與變量
閉包的一個副作用就是只能取得外部函數(shù)中任何變量的最后一個值,以下例子可以說明:
function create(){ var result = new Array(); for(var i = 0;i<10;i++){ result[i] = function(){ return i; } } return result; } console.log(result);
在這個函數(shù)內(nèi)部返回result時,它可以引用外部函數(shù)的活動對象以及全局變量對象,而當create()執(zhí)行完之后,這個活動對象仍然在被引用,此時i已經(jīng)變?yōu)榱?0,所以result只會返回同一個i值,即i=10;可以修改為:
function create(){ var result = new Array(); for(var i = 0;i<10;i++){ result[i] = function(num){ return function(){ return num; } }(i); } return result; } console.log(create());
修改后的函數(shù)閉包得到的值并不直接賦值給result,而是通過閉包,使這個匿名函數(shù)的內(nèi)部再返回一個匿名函數(shù),這個匿名函數(shù)可以訪問外部的活動對象num,再通過給內(nèi)部的函數(shù)傳遞變量i,賦值給num,所以最后可以得到function(1)、function(2)...function(10),并且他們的內(nèi)部屬性[[scope]]還會包含對應的i值。
2)關(guān)于this對象
匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此它的this指向一般指向window
var name = "The Window"; var object = { name : "My object", getName : function(){ return function(){ return this.name; }; } } alert(object.getName()());//The Window
以上例子中,匿名函數(shù)的的this指向的是window,所以得到的是"The Window",
因為this對象其實是函數(shù)執(zhí)行時的上下文,與如何定義函數(shù)并沒有關(guān)系,Object.getName(),指向的是Object對象,但是繼續(xù)調(diào)用匿名函數(shù),顯然this指向的不是這個對象,此時是全局環(huán)境下調(diào)用的這個函數(shù),因此this.name為"The Window".如果在定義匿名函數(shù)之前把this對象賦值給一個變量,那么調(diào)用匿名函數(shù)時會改變this的指向。
var name = "The Window"; var object = { name : "My object", getName : function(){ var _this = this; return function(){ return _this.name; }; } } alert(object.getName()());//My object
3)內(nèi)存泄漏
在IE9之前的瀏覽器中,IE中有一部分對象并不是原生對象,其DOM、BOM中的對象就是以COM對象的形式實現(xiàn)的,而針對COM對象垃圾回收機制是引用計數(shù),在閉包中很容易出現(xiàn)循環(huán)引用的問題,因此可能會出現(xiàn)內(nèi)存泄漏。
function Handle(){ var elment = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); } }
只要匿名函數(shù)存在,對element的引用數(shù)也至少為1,因此它占用的內(nèi)存永遠都不會回收,可以做以下的修改:
function Handle(){ var elment = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); } element = null; }
3)模仿塊級作用域
JS沒有塊級作用域的概念,也就是說在塊語句中定義的變量其實是在包含函數(shù)中創(chuàng)建的。以下例子可以說明:
function outNumbers(){ for(var i = 0;i<10;i++){ alert(i); } var i; alert(i);//10 }
在for循環(huán)中i從0-9,i=10會直接給后一個聲明賦值,所以會再次計數(shù),當然在ES6中,如果使用let或const聲明,for語句就會形成塊級作用域。這一節(jié)主要討論的是利用匿名函數(shù)自執(zhí)行形成塊級作用域。
如果是匿名函數(shù)自執(zhí)行,那么在它的內(nèi)部自然就會形成塊級作用域,例如:
(function(){ //塊級作用域; })();
如果采用函數(shù)聲明的形式創(chuàng)建函數(shù)表達式:
var someFunction = function(){ //塊級作用域: }; someFunction();
像第一個例子中創(chuàng)建的函數(shù)可以利用匿名函數(shù)自執(zhí)行修改為:
function outNumbers(court){ (function(){ for(var i = 0;i匿名函數(shù)自執(zhí)行并不影響閉包的特性,court仍然可以作為外部函數(shù)的活動對象被引用。通過模仿塊級作用域可以避免全局變量被污染以及函數(shù)的命名沖突,而且可以減少閉包占用的內(nèi)存問題,因為沒有指向匿名函數(shù)的引用,只要函數(shù)執(zhí)行完畢,就可以立即銷毀作用域鏈了。
4)私有變量
任何在函數(shù)中定義的變量都是私有變量,外部函數(shù)不能訪問到這些變量,如果在函數(shù)內(nèi)部創(chuàng)建一個閉包,那么閉包可以利用外部函數(shù)的活動對象,即外部函數(shù)的私有變量,利用這一點可以創(chuàng)建用于訪問私有變量的特權(quán)方法。以下兩種方式都可以為自定義類型創(chuàng)建私有變量和特權(quán)方法。 第一種是在構(gòu)造函數(shù)中定義特權(quán)方法,方法如下:function MyObject(){ var private = 10; function SomeFun = { return false; } this.public = function(){ alert(private++); return someFun(); }; } var person = new MyObject(); console.log(person.public());在這個方法中必須要創(chuàng)建構(gòu)造函數(shù),在實例上才可以調(diào)用這個特權(quán)方法從而訪問私有變量,但是這樣做會帶來構(gòu)造函數(shù)實例標識符重解析以及不同的作用域鏈,這一點和構(gòu)造函數(shù)模式相同。①.靜態(tài)私有變量
第二種方法為通過私有作用域定于私有變量和函數(shù),依靠原型模式,如下:(function(){ var private = 10; function PrivateFun(){ return false; } MyObject = function(){ } MyObject.prototype.public = function(){ private++; return PrivateFun(); } })();使用函數(shù)表達式是因為如果使用函數(shù)聲明,那么function內(nèi)部為塊級作用域,無法實現(xiàn)實例化對象的目的,從而也就無法達到訪問函數(shù)內(nèi)部私有變量和私有函數(shù)的目的,并且在匿名函數(shù)內(nèi)部的構(gòu)造函數(shù)也不能聲明,這樣以來變量就成為了全局變量,能夠在這個塊級作用域之外被使用到。示例:(function(){ var name = ""; Person = function(value){ name = value; }; Person.prototype.setName = function(value){ name = value; }; Person.prototype.getName = function(){ return name; }; })(); var person1 = new Person("Nicholas"); var person2 = new Person("Michael"); alert(person1.getName());//Michael alert(person2.getName());//MichaelPerson構(gòu)造函數(shù)與setName()和getName()一樣,都可以訪問匿名函數(shù)的私有變量name,變量name就成了靜態(tài)的、由所有實例共享的屬性。但是由于原型鏈的特性,一旦更改了name屬性,那么所有實例都會受到影響。
閉包和私有變量方法的一個明顯不足之處就是,他們都會多查找作用域鏈的一個層次,這顯然會在一定程度上影響查找速度。②.模塊模式
如果是只有一個實例的對象訪問私有變量和函數(shù),在必須以對象的形式創(chuàng)建的前提下,而且需要以某些數(shù)據(jù)初始化,同時還要公開一些能夠訪問這些私有數(shù)據(jù)的方法,可以采用這種方式:var singleton = function(){ var private = 10; function privateFun(){ return false; } return{ public : true, publicMethod : function(){ private++; return privateFun(); } } }();這一個()相當于 singleton(); //當使用公有辦法時,singleton.publicMethod();為什么使用函數(shù)聲明?
這僅僅是一個單例,所以不能將它實例化,僅將函數(shù)的返回值以對象的形式返回給變量就可以使用公有辦法訪問私有變量、函數(shù)。為什么在內(nèi)部以對象形式返回?
如果采用this對象形式訪問,這樣相當于構(gòu)造函數(shù)結(jié)構(gòu),并且在函數(shù)聲明的前提下,this對象為window(嚴格模式下為undefined)。下面的這個例子說明模塊模式可以應用的場景:
var application = function(){ //私有變量和函數(shù) var components = new Array(); //初始化 components.push(new BaseComponent()); //公共 return { getComponent : function(){ return components.length; }, registerComponent : function(component){ if(typeof component == "object"){ components.push(component); } } } }();在這個單例的公共接口中,前者可以返回已注冊的組件數(shù)目,后者用于注冊新組件。
③.增強的模塊模式
在返回對象之前加入對其增強的代碼,單例是自定義類型的實例,同時還必須添加某些屬性或方法對其加強的情況下,可以使用增強模塊模式。application的例子可以改寫為:var application = function(){ var components = new Array(); components.push(new BaseComponent()); //創(chuàng)建一個自定義類型實例,作為application的局部副本使用 var app = new BaseComponent(); app.getComponent = function(){ return components.length; }; app.registerComponent = function(component){ if(typeof component == "object"){ components.push(component); } }; //返回副本 return app; }();
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/82471.html
摘要:第一種方法是上面已經(jīng)說到的匿名函數(shù)表達式,即將匿名函數(shù)賦值給一個變量,然后通過變量名去調(diào)用。因為函數(shù)聲明是不能被執(zhí)行符號執(zhí)行的,除非通過函數(shù)名調(diào)用,只有表達式才能被執(zhí)行符號執(zhí)行匿名函數(shù)又沒有函數(shù)名,另外的辦法就是把它變成表達式。 函數(shù)聲明 function funcName(){ }; console.log(funcName); // 打印結(jié)果是funcName這個函數(shù)體。 聲明一...
摘要:關(guān)鍵詞必須是小寫的,并且必須以與函數(shù)名稱相同的大小寫來調(diào)用函數(shù)。當調(diào)用函數(shù)時,這些標識符則指代傳入函數(shù)的實參。函數(shù)表達式其實是忽略函數(shù)名稱的,并且不可以使用函數(shù)名這種形式調(diào)用函數(shù)。注意構(gòu)造函數(shù)無法指定函數(shù)名稱,它創(chuàng)建的是一個匿名函數(shù)。 一、關(guān)于函數(shù) JavaScript函數(shù)是指一個特定代碼塊,可能包含多條語句,可以通過名字來供其他語句調(diào)用以執(zhí)行函數(shù)包含的代碼語句。 比如我們有一個特定的...
摘要:函數(shù)表達式的值是在運行時確定,并且在表達式賦值完成后,該函數(shù)才能調(diào)用 1.定義 在javascript中我們定義函數(shù)有以下兩種方式: 函數(shù)聲明 function say(){ console.log(函數(shù)聲明); } 函數(shù)表達式 var say = function(){ console.log(函數(shù)表達式); } 2.實例解析 在平時開發(fā)中,...
摘要:標識符有效性正是導致函數(shù)語句與函數(shù)表達式不同的關(guān)鍵所在下一小節(jié)我們將會展示命名函數(shù)表達式的具體行為。歸根結(jié)底,只有給函數(shù)表達式取個名字,才是最穩(wěn)妥的辦法,也就是使用命名函數(shù)表達式。 前言 網(wǎng)上還沒用發(fā)現(xiàn)有人對命名函數(shù)表達式進去重復深入的討論,正因為如此,網(wǎng)上出現(xiàn)了各種各樣的誤解,本文將從原理和實踐兩個方面來探討JavaScript關(guān)于命名函數(shù)表達式的優(yōu)缺點。簡單的說,命名函數(shù)表達式只有...
摘要:關(guān)于構(gòu)造函數(shù)有幾點需要特別注意構(gòu)造函數(shù)允許在運行時動態(tài)的創(chuàng)建并編譯函數(shù)。而函數(shù)本身的表示該函數(shù)的形參。每一個函數(shù)都包含不同的原型對象,當將函數(shù)用作構(gòu)造函數(shù)的時候,新創(chuàng)建的對象會從原型對象上繼承屬性。 該文章以收錄: 《JavaScript深入探索之路》 前言 函數(shù)是這樣的一段JavaScript代碼,它只定義一次,但是可能被執(zhí)行或調(diào)用任意次。你可能已經(jīng)從諸如子例程或者過程這些名字里...
摘要:函數(shù)聲明和函數(shù)表達式的區(qū)別函數(shù)聲明只能出現(xiàn)在程序或函數(shù)體內(nèi)。所以,在等語義為語句的代碼塊中存在函數(shù)聲明,由于函數(shù)提升特性,會破壞掉原本的語義。 這篇談一下JS函數(shù)聲明與函數(shù)表達式的區(qū)別及要注意的地方: 函數(shù)聲明主要有兩種類型: 函數(shù)聲明 function fn() {}; 函數(shù)表達式 var fn = function () {}; 這兩種函數(shù)創(chuàng)建方式...
閱讀 5076·2021-11-25 09:43
閱讀 1431·2021-11-24 09:38
閱讀 2083·2021-09-30 09:54
閱讀 2955·2021-09-23 11:21
閱讀 2448·2021-09-10 10:51
閱讀 2464·2021-09-03 10:45
閱讀 1282·2019-08-30 15:52
閱讀 1885·2019-08-30 14:13