javascript是目前web領(lǐng)域中實(shí)用最為廣泛的語(yǔ)言,不管是在前端還是在后端都能看到它的影子,可以說(shuō)web從業(yè)者不論怎樣都繞不開(kāi)它。在前端領(lǐng)域,各種框架層出不窮,最火的時(shí)候幾乎每個(gè)月都有新的框架誕生,如angularjs,vuejs等。在后端領(lǐng)域,nodejs可謂如火如荼,打破了人們對(duì)javascript只能作為前端語(yǔ)言的認(rèn)知。按照此勢(shì)頭下去,javascript會(huì)越來(lái)越流行,會(huì)隨著web的發(fā)展越來(lái)越重要。現(xiàn)在基本沒(méi)有第二種語(yǔ)言可以挑戰(zhàn)js在web前端中的地位,至少10年以內(nèi)不可能。
所以不論你是想學(xué)各種前端框架還是nodejs,都需要深入理解javascript的工作原理以及特性,只有這樣才能以不變應(yīng)萬(wàn)變。
特別是,現(xiàn)在ES6語(yǔ)法的面世(ES7未來(lái)幾年可能面試),原生語(yǔ)法的支持,極大的便利了前端語(yǔ)言的編寫(xiě),至少,相對(duì)ES5來(lái)說(shuō),可以像服務(wù)端一樣,編寫(xiě)出優(yōu)雅的代碼來(lái)。
javascript的實(shí)現(xiàn)原理
Javascript誕生與1995年,是Netscape(網(wǎng)景)公司推出的瀏覽器端語(yǔ)言。雖然含有Java關(guān)鍵字,卻與java沒(méi)有半點(diǎn)關(guān)系。隨后Javascript越來(lái)越火,網(wǎng)景公司想將其標(biāo)準(zhǔn)化,這樣更加利于網(wǎng)絡(luò)的發(fā)展,遂將其提交給了ECMA(歐洲計(jì)算機(jī)制造商協(xié)會(huì))管理,負(fù)責(zé)對(duì)其的標(biāo)準(zhǔn)化。ECMA機(jī)構(gòu)以JS為原型,推出了一個(gè)ECMAScript的腳步語(yǔ)言,規(guī)定各大瀏覽器廠商都必須依照ECMAScript標(biāo)準(zhǔn)實(shí)現(xiàn)各種的JS,保證JS具有良好的跨平臺(tái)性。所以可以將ECMAScript看成是標(biāo)準(zhǔn)化的JS,一個(gè)意思。
ECMAScript本質(zhì)上是一種語(yǔ)言規(guī)范,其與平臺(tái)沒(méi)有關(guān)系,比如瀏覽器等。web瀏覽器只是ES的宿主環(huán)境之一,負(fù)責(zé)實(shí)現(xiàn)ES以及提供ES與環(huán)境交互的手段。宿主環(huán)境還有Node以及Flash等。
JS的實(shí)現(xiàn)要比ES規(guī)定的要復(fù)雜的多,ES只規(guī)定了基本語(yǔ)言特性。瀏覽器下的JS實(shí)現(xiàn)可以由下面三部分組成:
- 語(yǔ)言部分(ES)
- 文檔對(duì)象模型(DOM)
-
瀏覽器對(duì)象模型(BOM)
DOM是負(fù)責(zé)操作由XML編寫(xiě)的應(yīng)用程序的API,負(fù)責(zé)將整個(gè)頁(yè)面映射成多層節(jié)點(diǎn)結(jié)構(gòu)。如下所示:
image
DOM可以提供JS對(duì)節(jié)點(diǎn)結(jié)構(gòu)的任何操作,增刪改查等。根據(jù)DOM提供的功能多樣性,將DOM分為DOM1,DOM2,DOM3這幾個(gè)級(jí)別。DOM1由DOM Core與DOM HTML組成。DOM2在DOM1的基礎(chǔ)上提供了更多的操作與功能。DOM3則更進(jìn)一步的擴(kuò)展了DOM。
BOM是瀏覽器對(duì)象模型,負(fù)責(zé)提供瀏覽器與JS的交互接口,提供JS操作瀏覽器的窗口與框架等。這個(gè)沒(méi)啥好說(shuō)的!
總之要實(shí)現(xiàn)一個(gè)完整的瀏覽器端JS,這個(gè)三個(gè)部分缺一不可。
javascript與java,C/C++的區(qū)別和聯(lián)系。
javascript是一門(mén)動(dòng)態(tài)語(yǔ)言,即在編寫(xiě)好代碼后不用編譯,由js解釋器解釋執(zhí)行,同時(shí)變量不用顯式的寫(xiě)出類(lèi)型,統(tǒng)一用var類(lèi)型表示,具體的變量類(lèi)型由JS解釋器推測(cè),與python和ruby一樣。說(shuō)到j(luò)s,大家經(jīng)常聽(tīng)到面向函數(shù)式編程,這是js的一大設(shè)計(jì)特性。強(qiáng)大的function。其實(shí)在js中,函數(shù)本質(zhì)上也是對(duì)象,也繼承自O(shè)bject類(lèi),也有屬性等。js中也很多地方需要我們注意,它與java和C++很不一樣。
- js中沒(méi)有類(lèi)繼承關(guān)鍵字,和java與C++不一樣。js的類(lèi)繼承需要自己動(dòng)手實(shí)現(xiàn),這也衍生出了多種類(lèi)繼承的編寫(xiě)范式。
- 同時(shí)js中沒(méi)有函數(shù)重載特性,這個(gè)需要特別注意。因?yàn)樵趈s中函數(shù)只是普通對(duì)象,沒(méi)有函數(shù)簽名(函數(shù)名+參數(shù))。而在java和C++中,用函數(shù)簽名唯一標(biāo)示一個(gè)函數(shù)。不過(guò)在js中我們也可以有多種方式模擬出函數(shù)重載的效果。
- js中的作用域與java也不一樣,js中有作用域鏈,在函數(shù)執(zhí)行中,解釋器會(huì)根據(jù)執(zhí)行函數(shù)的作用域鏈一層層的往上尋找變量,一直找到位于末端的window作用域中。
- js中有原型的概念,每個(gè)類(lèi)都有對(duì)于的原型,包括函數(shù)等。類(lèi)對(duì)象中有引用指向原型對(duì)象,所以同一類(lèi)的原型對(duì)象被所有類(lèi)對(duì)象共享。由此衍生出很多有意思的特性。
- js中有閉包,這個(gè)閉包特性是由作用域鏈的設(shè)計(jì)衍生出來(lái)的,特別值得注意。根據(jù)閉包特性,結(jié)合匿名函數(shù),我們可以模擬塊級(jí)作用域效果,甚至可以模擬出單例模式以及私有變量等。
- js中的繼承與多態(tài),需要程序員自己實(shí)現(xiàn),與java和C++不一樣。利用js的原型鏈,可以寫(xiě)出很多不同的繼承效果,各有特點(diǎn)。寫(xiě)js中的繼承遠(yuǎn)比java中有技術(shù)含量,哈哈!
- js有垃圾回收機(jī)制,但是比較簡(jiǎn)單,沒(méi)有jvm中的有意思。
以下僅為個(gè)人學(xué)習(xí)心得體會(huì)乃本人覺(jué)得Javascrpt較為基礎(chǔ)、必要部分內(nèi)容分享
一、Javascript垃圾回收方式
Javascript具有自動(dòng)垃圾回收機(jī)制,開(kāi)發(fā)人員不需要關(guān)心內(nèi)存的使用與釋放。垃圾回收方式主要有下述兩種
- 1、標(biāo)記清除法
Javascript中最常用的就是標(biāo)記清除。當(dāng)變量進(jìn)入環(huán)境(例如,在函數(shù)中聲明的變量)時(shí),就將這個(gè)變量標(biāo)記為“進(jìn)入環(huán)境”;當(dāng)變量離開(kāi)環(huán)境時(shí),標(biāo)記為“離開(kāi)環(huán)境”。最后垃圾收集器會(huì)清除帶有“離開(kāi)環(huán)境”的對(duì)象,釋放內(nèi)存。 - 2、引用計(jì)數(shù)法
另一種不太常見(jiàn)的垃圾回收方法叫引用計(jì)數(shù)法。引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值的引用次數(shù)。當(dāng)聲明一個(gè)變量并將一個(gè)引用類(lèi)型賦給該變量時(shí)候,則這個(gè)值的引用次數(shù)就+1,如果同一個(gè)值又被賦給另一個(gè)變量時(shí)候,該值的引用次數(shù)再+1;反之,引用該對(duì)象的變量取得了另外的值,則引用變量的引用次數(shù)-1。當(dāng)引用的次數(shù)為0時(shí)候,則對(duì)應(yīng)的內(nèi)存將在垃圾回收時(shí)候被釋放。
問(wèn)題:在循環(huán)引用中內(nèi)存得不到釋放。如下述demo
function problem() {
var obj1 = new Object()
var obj2 = new Object()
obj1.a = obj2
obj2.b = obj1
demo中,obj1,obj2通過(guò)屬性各自相互引用,也就是這兩個(gè)對(duì)應(yīng)的引用次數(shù)都是2,在引用計(jì)數(shù)法下永不會(huì)被垃圾回收;但在標(biāo)記清除策略中,該方法執(zhí)行后就離開(kāi)了環(huán)境不再使用,可以被垃圾回收。在ie中,Javascript訪問(wèn)COM對(duì)象是基于這個(gè)引用計(jì)數(shù)策略的,也就是會(huì)存在這種垃圾無(wú)法回收的情況。為了避免這種情況,在對(duì)應(yīng)不再使用時(shí)候,最好手動(dòng)賦值null釋放對(duì)象。
二、理解對(duì)象
創(chuàng)建對(duì)象的最簡(jiǎn)單方式就是創(chuàng)建Object的實(shí)例,然后再為它添加屬性、方法,如下所示:
var person = new Object()
person.name = 'Jack'
person.age = 29
person.jbo = 'Software Engineer'
person.sayName = function () {
console.log(this.name)
}
上面的例子創(chuàng)建了一個(gè)名為person的對(duì)象,并為它添加了三個(gè)屬性(name、age、job)和一個(gè)sayName的方法。這些屬性、方法在創(chuàng)建時(shí)候帶有一些特征值,Javascript通過(guò)這些特征值來(lái)定義它們的行為。
- 屬性類(lèi)型:js中有兩種屬性,數(shù)據(jù)屬性 和** 訪問(wèn)器屬性**
1、數(shù)據(jù)屬性
數(shù)據(jù)屬性包括一個(gè)數(shù)據(jù)值的位置。在這個(gè)位置可以讀取和寫(xiě)入值。它包含有4個(gè)描述其行為的特性:configurable、Enumerable、writable、value。
var person = {
name = 'Jack'
}
// 該對(duì)象創(chuàng)建了一個(gè)name的數(shù)據(jù),value值為‘Jack’。其他configurable、Enumerable、writable特征值為默認(rèn)值true。
要修改它的特征值,需要使用ES5中的Object.defineProperty()方法。 如下:
var person()
Object.property(person,'name',{
value: 'Jack',
writabe: false
})
// 將name屬性的可寫(xiě)特性設(shè)置為false
person.name = 'tom'
person.name // 'Jack' 上述賦值語(yǔ)句并不能修改它的值
2、訪問(wèn)器屬性
訪問(wèn)器屬性不包含數(shù)據(jù)值,它們包含一堆getter和setter函數(shù)。在讀取訪問(wèn)器屬性時(shí)候,會(huì)調(diào)用getter函數(shù),這個(gè)函數(shù)負(fù)責(zé)返回有效的值;在寫(xiě)入訪問(wèn)器屬性時(shí)候,會(huì)調(diào)用setter函數(shù)并傳入新的值,這個(gè)函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù)。
訪問(wèn)器屬性值不能直接定義,必須通過(guò)**Object.defineProperty()方法,如下:
var book = {
_year: 2014,
edition: 1
}
Object.defineProperty(book, '_year', {
get: function() {
return this._year
},
set: function(newVal) {
this._year = newVal
}
})
創(chuàng)建對(duì)象的方式:
- 工廠模式
function createPerson(name, age, job){
var person = new Object()
person.name = name
person.age = age
person.job = job
return person
}
var person1 = createPerson('Jack', 29, 'teacher')
- 構(gòu)造函數(shù)模式
function createPerson(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
console.log( this.name)
}
}
var person1 = new createPerson('Jack', 29, 'teacher')
相比于工廠模式,沒(méi)有顯示的創(chuàng)建對(duì)象,沒(méi)有retrun語(yǔ)句
- 原型模式
我們創(chuàng)建的每一個(gè)函數(shù)都有一個(gè)propertype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,而這個(gè)對(duì)象的用途是包含可以由特定類(lèi)型的所有實(shí)例共享的屬性與方法。
function Person = {}
person.propertype.name = 'Jack'
person.propertype.age = 29
var person1 = new Person()
person.name // 'Jack'
person.age // 29
三、原型鏈
原型鏈?zhǔn)荍S中實(shí)現(xiàn)繼承的主要方式。每一個(gè)JS類(lèi)中都有一個(gè)指向該類(lèi)原型對(duì)象的引用。該原型對(duì)象有一個(gè)constructor屬性,指向構(gòu)造函數(shù),如圖所示:
由該類(lèi)生成的對(duì)象中,也有個(gè)隱含的prototype屬性,指向該類(lèi)的原型對(duì)象!設(shè)想一下,我們?nèi)绾螌⒘硪粋€(gè)類(lèi)的對(duì)象實(shí)例作為某個(gè)類(lèi)的原型對(duì)象會(huì)怎么樣呢?如下所示:
可以看到對(duì)于instance對(duì)象而言,prototype指向的是SubType類(lèi)的原型,而SubType類(lèi)的原型是SuperType的類(lèi)實(shí)例,其中的prototype指向SuperType類(lèi)的原型對(duì)象,這樣就形成了一個(gè)3層的原型鏈(包含Object原型)。當(dāng)解析器在instance中尋找變量時(shí),它會(huì)先在實(shí)例對(duì)象中尋找,然后沿著原型鏈一直向上尋找,直到找到為止!最終如果在Object類(lèi)的原型中都沒(méi)有找到,那么會(huì)產(chǎn)生錯(cuò)誤!
如圖所示:
根據(jù)原型鏈,我們可以寫(xiě)出JS中的繼承代碼,一般推薦用混合方式實(shí)現(xiàn),即構(gòu)造函數(shù)與原型鏈混合方式:
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name, age) {
this.age = age;
SuperType.call(this, name);
}
//繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.Age);
}
SuperType A = new SuperType("hh");
SubType B = new SubType("MM", 26);
這樣就完成了繼承的編寫(xiě),混合方式的好處,可以避免只用原型鏈方式的一些缺點(diǎn),比如不能向構(gòu)造函數(shù)中傳遞參數(shù),或者對(duì)于引用類(lèi)型的值,不能做到對(duì)象獨(dú)有一份!但是上式方式依然有缺點(diǎn),可以發(fā)現(xiàn),每次創(chuàng)建SubType類(lèi)時(shí),會(huì)調(diào)用SuperType的構(gòu)造函數(shù),創(chuàng)建兩個(gè)變量,name與colors。但是我們會(huì)發(fā)現(xiàn)這兩個(gè)變量其實(shí)在SubType的原型中已經(jīng)存在了,只是SubType的對(duì)象實(shí)例中的變量屏蔽了其原型對(duì)象中的兩個(gè)變量!這樣一來(lái),造成空間浪費(fèi),同時(shí)也耗費(fèi)了在形成SubType原型對(duì)象中調(diào)用SuperType的構(gòu)造函數(shù)的時(shí)間。
那么如何解決上述問(wèn)題呢,我們可以用寄生混合式繼承方法。代碼如下:
function inheritPrototype(subType, superType) {
var prototype = Object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
}
function SubType(name, age) {
this.age = age;
SuperType.call(this, name);
}
SubType.prototype = inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
alert(this.Age);
}
SuperType A = new SuperType("hh");
SubType B = new SubType("MM", 26);
采用這種寄生混合式的繼承方法,使用寄生式繼承父類(lèi)的prototype對(duì)象,將結(jié)果做子類(lèi)的prototype,這樣就可以避免調(diào)用父類(lèi)的構(gòu)造函數(shù),同時(shí)需要將prototype對(duì)象的constructor屬性指向子類(lèi)構(gòu)造函數(shù)即可。
這種方法是目前公認(rèn)的最理想的繼承范式,能正常使用instanceof和isPrototypeOf()。
思考:這種方式就沒(méi)有缺點(diǎn)嗎?如果后續(xù)我們?cè)赟uperType的原型對(duì)象中增加一個(gè)方法,但是SubType的原型是復(fù)制品,所以后續(xù)的SubType對(duì)象實(shí)例中不能得到該方法。但是如果采用原型式繼承+混合式繼承呢?能不能得到更好的效果呢?思考下面這段代碼:
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name, age) {
this.age = age;
SuperType.call(this, name);
}
//原型繼承SuperType的原型對(duì)象
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.sayAge = function() {
console.log(this.Age);
}
SuperType A = new SuperType("hh");
SubType B = new SubType("MM", 26);
//增加這段代碼
SuperType.prototype.sayColors = function() {
console.log(this.colors);
}
B.sayColors();
四、閉包
閉包是JS中的一個(gè)非常重要的概念,不少開(kāi)發(fā)人員搞不清匿名函數(shù)跟閉包兩個(gè)概念,經(jīng)?;熘?。閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包常見(jiàn)的方式是在一個(gè)函數(shù)內(nèi)部建立另一個(gè)函數(shù),例如:
function createCompare(propertyName) {
return function(object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName];
return value2 - value1;
}
}
var compare = createCompare("name");
var result = compare({name: "haha"}, {name: "hehe"});
該函數(shù)返回一個(gè)匿名函數(shù),在該匿名函數(shù)中可以訪問(wèn)外面函數(shù)的活動(dòng)變量propertyName。該原理是:在匿名函數(shù)返回以后,匿名函數(shù)的作用域鏈被初始化為包含createCompare()函數(shù)的活動(dòng)對(duì)象以及全局變量對(duì)象。這樣,在匿名函數(shù)執(zhí)行時(shí)就可以訪問(wèn)createCompare函數(shù)的活動(dòng)變量了。值得注意的是,createCompare函數(shù)在執(zhí)行完畢后,它的變量對(duì)象并沒(méi)有被銷(xiāo)毀,因?yàn)橛心涿瘮?shù)的作用域鏈依然在引用這個(gè)活動(dòng)對(duì)象。換句話說(shuō),createCompare函數(shù)執(zhí)行完后,它的作用域鏈會(huì)被銷(xiāo)毀,但是它的活動(dòng)變量卻保留在了內(nèi)存中,直到匿名函數(shù)被銷(xiāo)毀后,它的活動(dòng)對(duì)象才會(huì)被銷(xiāo)毀。用下圖表示:
java中有匿名對(duì)象,js中有匿名函數(shù),其實(shí)本質(zhì)都差不多。js中函數(shù)對(duì)象都有一個(gè)name屬性。對(duì)于name屬性,其實(shí)是指向函數(shù)聲明時(shí)跟在function后面的名字。但是在匿名函數(shù)中name為空字符串。這里需要正確理解函數(shù)聲明與函數(shù)表達(dá)式。
函數(shù)聲明:
functionName(a, b); //由于函數(shù)聲明提升,所以可以執(zhí)行
function functionName(arg0, arg1) {
}
函數(shù)表達(dá)式:
a(); //函數(shù)表示式,,沒(méi)有函數(shù)聲明提升,所以不能執(zhí)行,報(bào)錯(cuò)!
var a = function(arg0, arg1) {
};
在遞歸情況下,可以用匿名函數(shù)很好的書(shū)寫(xiě),即使在嚴(yán)格模式下,依然可以使用:
var factorial = (function f(num) {
if (num <= 1)
return 1;
else
return num * f(num-1);
});
用命名函數(shù)表達(dá)式,可以將f()函數(shù)賦值給factorial變量,但是函數(shù)的名字依然是f,可以測(cè)試factorial.name依然是f。
有了匿名函數(shù),可以模擬塊級(jí)作用域:
(function(){
//模擬的塊級(jí)作用域
})();
在括號(hào)內(nèi)用函數(shù)聲明,表示這個(gè)是函數(shù)表達(dá)式,后面緊接括號(hào),表示立刻調(diào)用這個(gè)匿名函數(shù)。
匿名函數(shù)配合閉包特性,可以實(shí)現(xiàn)單例模式:
var singleton = (function() {
//設(shè)置私有變量與私有函數(shù)
var privateVariable = 10;
function privateFunction(){
alert("hello world");
}
//創(chuàng)建對(duì)象,可以是任意類(lèi)型的對(duì)象
var object = new Object();
//添加特權(quán)/公有屬性與方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
};
return object;
})();
五、關(guān)于this對(duì)象
在閉包中使用this對(duì)可能導(dǎo)致一些問(wèn)題。我們知道this對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定的:在全局中,this等于window;而當(dāng)函數(shù)被作為某個(gè)對(duì)象的方法調(diào)用時(shí)候,this等于調(diào)用的那個(gè)對(duì)象。不過(guò),匿名函數(shù)的執(zhí)行環(huán)境具有局部性,因此其對(duì)象通常指向window。但有時(shí)候由于編寫(xiě)閉包的的方式不同,這一點(diǎn)可能不那么明顯。如下demo:
var name = 'This window'
var Obj = {
name: 'My Object',
getName: function () {
return function () {
return this.name
}
}
}
console.log(Obj.getName()()) // 'This window'
上訴代碼先創(chuàng)建了一個(gè)全局變量name,又創(chuàng)建了包含name屬性的對(duì)象。這個(gè)對(duì)象還包含方法getName,它返回一個(gè)匿名函數(shù)而匿名函數(shù)又返回this.name。由于getName()返回的是一個(gè)函數(shù),因此調(diào)用Obj.getName()會(huì)立即調(diào)用它返回的函數(shù),返回結(jié)果就是一個(gè)字符串‘This window’,即全局變量name的值。為什么匿名函數(shù)沒(méi)有取得其閉包作用域的this對(duì)象呢?
事實(shí)上,每個(gè)函數(shù)在被調(diào)用時(shí),其活動(dòng)對(duì)象會(huì)都會(huì)自動(dòng)取得兩個(gè)特殊變量:this和arguments。內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí)候,只會(huì)搜索到其活動(dòng)對(duì)象為止因此永遠(yuǎn)訪問(wèn)不肯直接訪問(wèn)外部環(huán)境中的這兩個(gè)變量。不過(guò),如果外部作用域中的this對(duì)象保存在一個(gè)閉包能改訪問(wèn)到的變量里面,就可以讓閉包訪問(wèn)到該對(duì)象了,如下所述:
var name = 'this window'
var Object = {
name: 'my Object',
getName: function () {
var self = this
return function () {
return self.name
}
}
}
console.log(Object.getName()()) // 'my Object'
this對(duì)應(yīng)在函數(shù)中指向的對(duì)象是否就是固定的不能被改變呢?
答: 不是的。可通過(guò)下述幾個(gè)方法改變this的指向:bind,apply,call,至于這幾個(gè)方法的區(qū)別,有興趣的再自己去研究一番。
六、作用域鏈
執(zhí)行環(huán)境是js中一個(gè)重要的概念。執(zhí)行環(huán)境定義了對(duì)象或函數(shù)可以訪問(wèn)到的數(shù)據(jù)。每一個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象,環(huán)境定義的所以變量和函數(shù)都保存在這個(gè)對(duì)象中。程序編寫(xiě)者無(wú)法正常訪問(wèn)該對(duì)象,但是后臺(tái)的解析器會(huì)訪問(wèn)到它。
全局執(zhí)行環(huán)境是最外圍的一個(gè)執(zhí)行環(huán)境,在web瀏覽器中是window對(duì)象。當(dāng)某個(gè)執(zhí)行環(huán)境執(zhí)行完后,環(huán)境會(huì)被銷(xiāo)毀,與之相關(guān)聯(lián)的變量對(duì)象中的所有變量與函數(shù)也可能會(huì)被銷(xiāo)毀。為什么用可能呢?有一個(gè)值得注意的地方,該變量對(duì)象銷(xiāo)不銷(xiāo)毀最本質(zhì)的是看有沒(méi)有其他引用指向它,如果有別的引用指向該變量對(duì)象,那么該變量對(duì)象不會(huì)被銷(xiāo)毀,比如在閉包中(函數(shù)中的函數(shù)情況)。
當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),解析器會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈。作用域鏈的最頂端始終是該執(zhí)行環(huán)境的變量對(duì)象。對(duì)于函數(shù)而言,變量對(duì)象是其活動(dòng)對(duì)象!當(dāng)在執(zhí)行環(huán)境中遇到一個(gè)變量時(shí),解析器會(huì)從作用域鏈的最頂端變量對(duì)象中找相應(yīng)的變量,沒(méi)有找到,則會(huì)順著作用域鏈一直找下去,最后找到全局執(zhí)行環(huán)境的變量對(duì)象!最后還是沒(méi)有,則會(huì)報(bào)錯(cuò)!
看下來(lái)一個(gè)函數(shù):
function compare(value1, value2) {
return value2 - value1;
}
上面的函數(shù)執(zhí)行在全局環(huán)境中,調(diào)用compare()時(shí),會(huì)創(chuàng)建一個(gè)包含arguments、value1以及value2的對(duì)象,this特殊的變量不能在變量對(duì)象中找到。所以,全局執(zhí)行環(huán)境中的變量對(duì)象則處在compare函數(shù)的作用域鏈中的第二位。如下圖所示:
七、高級(jí)技巧
- 定時(shí)器
setTimeOut、setInterval
如何編寫(xiě)一個(gè)精準(zhǔn)的定時(shí)器?
// 偽代碼 參考
let delay = 0
setTimeout(doSomething, delay)
doSomething=()=>{
const start = Date.now().getTime()
do()
const end = Date.now().getTime()
delay = 1000 - (end - start)
}
do = ()=>{
// 重復(fù)執(zhí)行邏輯
}
節(jié)流防抖
參考之前寫(xiě)的節(jié)流防抖文章http://www.itdecent.cn/p/990d2a40e81d防止對(duì)象被篡改
// 不可擴(kuò)展對(duì)象
var person = { name: 'jack'}
person.age = 29
即使第一行代碼已經(jīng)完整的定義了person對(duì)象,但第二行代碼仍然可以給它添加屬性,?,F(xiàn)在用Object.preventExtenions()方法可以改變這個(gè)行為讓你不能再給對(duì)象添加任何屬性和方法。
var person = { name: 'jack'}
Object.preventExtenions(person)
person.age = 29
concole.log(person.age) //undefined
以上為所有的心得體會(huì)分享。
by 劉榮杰