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