1. 程式人生 > >全方位徹底讀懂<你不知道的JavaScript(上)>--一篇六萬多字的讀書筆記

全方位徹底讀懂<你不知道的JavaScript(上)>--一篇六萬多字的讀書筆記

前言

Q&A

  • 1.問:為什麼要寫這麼長,有必要嗎?是不是腦子秀逗了?
    答:我想這是大部分人看到這個標題都會問的問題.因為作為一個男人,我喜歡長一點,也不喜歡分割成幾個部分.一家人就要在一起,整整齊齊.好吧,正經點,其實整篇前言可以說都是在回答這個問題.你可以選擇先看完前言,再決定要不要和書本搭配起來閱讀. 這裡先簡單捋一下:1,內容多:首先這篇讀書筆記本來內容就很多,是對書本的全方位詳解.2,針對新人:針對那種紅寶書草草讀過一遍,對js只浮於介面呼叫的新手.3,留給讀者自己提煉:讀這種社科類書籍一般是先讀厚,再讀薄.這篇筆記就屬於最開始'讀厚'的階段.在讀者徹底讀懂後,再自己進一步提煉.關於怎麼讀書,我後面會詳細介紹.

  • 2.問:這麼長,那到底包含了些什麼內容?
    答:筆記的目錄結構和書本的完全一致.對每一節的內容進行更通俗的解讀(針對新人),對示例進行更深的說明,有的會輔以流程圖,並提供對應的mdn連線;對內容進行歸納,小節脈絡更清晰;添加了大量實際工作時的注意事項,增加了更加清晰和易懂的示例及註釋,並在原文基礎上進行了拓展和總結;對書中的錯誤和說了後面會進行介紹,而沒有介紹的填坑,翻譯或者容易引起誤會的稱呼的說明;添加了個人讀書時的感受和吐槽.

  • 3.問:書已經夠多了,還要看你這麼長的筆記?
    答:首先你要知道讀這種技術類書籍,不是讀小說!讀完並不意味著你讀懂了.而是需要將書中的知識轉換成你自己的.這篇筆記就是,幫助新手更方便地理解知識點,更流暢地進行閱讀.也可以在讀完一節後,通過對比,發現自己有什麼知識點是不懂或者遺漏,理解有誤的. 並且一些注意事項,容易被誤導的,關於書中觀點的吐槽等等,其實想說的都已經寫在筆記裡了.

  • 4.問:這本書到底怎麼樣,有沒有其他人說的那麼好?
    答:這是一個先揚後抑的回答.首先毫無疑問這是一本非常不錯的書!它系統地全面地對JavaScript進行解讀,優點缺點全都有.當你徹底讀懂這本書後,你對JavaScript的幾乎所有疑問都會得到解答(我對作用域是不是"物件"的疑問?也得到了解答).但它也是有一定門檻的,如果你對JS不熟,常用介面都不熟,很多名詞的表層意思都不太理解.這本書並不適合你,你花在問谷歌孃的時間可能比你讀書的都長,讀起來也是一知半解;不同於其他書,這本書很多時候沒有給出明確的概念定義,需要你自己反覆閱讀理解他的話.每一小節的脈絡結構也不是那麼清晰,有時候需要自己去梳理;不知道是不是翻譯的鍋,很多東西解釋得有點迷,本來很簡單,但卻說一堆並不常用的術語(可能國內不是這麼叫的),看得你一臉懵逼!有時候同一個概念,前後會出現三四個不同的名詞進行指代,沒有任何說明;整本書,具有很強的作者主觀情感在裡面.前半段,把JS捧得很高,說它引擎的各種優化好!但到後半段關於JavaScript中模擬類和繼承"的批評,說它們具有很大誤導性!更是嗤之以鼻!就差爆粗口了,好像JavaScript就是一個異教徒,應該綁在十字架上被燒死!但是他這樣的觀點,都是站在其他類語言的角度來看待,產生的.我想更多的讀者可能是隻接觸過JavaScript這一種語言,對他們來說,其實是根本沒有這些"疑惑"的!

讀書建議:

  • 1.不要抱任何功利和浮躁的心來讀書!
    這種以理論,概念為主的書,其實大家都是不那麼願意讀的.一是讀起來很費勁,抽象.二是實際工作,幾乎不會用到,在現在浮躁的前端圈這是吃力不討好.那這本書最大的用處是什麼?沒錯,就是被很多人用來應付面試!? 這本身沒什麼問題,你讀懂系列三本書,所有涉及JS的面試都能輕鬆應對.但是當抱著功利心時,你更多的則是敷衍.對書中的概念進行機械的複製,再貼上上自己膚淺的理解.OK,應付那些也是跟風的面試官足夠了.一般你回答了,他們也不會繼續往下問,問深了自己也不清楚,也不好否定你.如果你夠自信,'瞎扯'也可以唬住.如果你答不上,臉皮厚的會讓你回去自己查.真正知道的面試官,其實都是會給你解釋的,他們也不會忙到差這點時間.其實他們心裡也是很樂意展示自己學識豐富的一面.
    這種功利讀書方式,即使你讀完了(更多人是半途而廢),對你的技術也不會有任何幫助.因為讀完,你其實是一知半解的.這樣反而更糟,甚至可能會對你之前JavaScript正確的理解產生混淆.

  • 2.認認真真讀完一本書好過收藏一百篇相關文章(其實你壓根連一半都不會看)!

我一直認為想系統弄懂一門知識,書本才是最好的選擇,它絕對比你東拼西湊找來的一堆文章要好得多!現在前端圈隨便看看,一大堆全是原型鏈,閉包,this...這些內容.裡面的內容大同小異,很多理解也是比較淺顯,考慮的也比較片面.但浮躁的人就是喜歡這種文章,覺得自己收藏了,看了就徹底理解了(!?).其實這些文章裡有很多都是借鑑了本書.

首先,你必須知道知識都是有體系的,不是完全獨立的.例如想要徹底理解,原型鏈,閉包,this.就必須先弄清作用域和函式.知識都是環環相扣,相互關聯的.如果你想徹底弄懂,還是選擇讀書吧,由淺入深,全面理清所有知識點的關聯.記住 "一知半解"永遠比"無知"更糟!(當然不懂裝懂,還振振有詞的人另當別論).

  • 3.如何讀書:先讀厚,再讀薄!
    首先先把書讀厚: 將每一節裡的所有知識點弄懂,不留遺漏.記下所有提到的知識點,並將重要的知識點高亮標識(電子書的話).然後在自己本地的MD筆記裡,按照一定的邏輯順序,儘量用自己的話語進行闡述總結這些知識點.如果有讀幾遍也不理解的地方,可以查詢MDN,結合自己的實際工作經驗,或者先圈起來,繼續往下讀,隨著後面理解的深入,前面不懂的地方自然也就明瞭了.這篇讀書筆記就是帶你怎麼把書讀厚.
    然後把書讀薄: 這部分需讀者你自己在徹底理解的基礎上,並站在全域性的角度進行歸納去總結.先是按章進行思維導圖式的總結.然後章與章之間進行規律總結,並記住特例.例如:作用域與原型鏈都有一個類似的"就近原則",由於就近原則所以就產生了"遮蔽".這些都是需要自己站在全域性融會貫通的角度去總結.雖然網上有別人總結好的,但我們不應該養成什麼都依賴別人,自己直接複製的習慣(如果你想一直做一個'複製貼上'程式設計師的話).

第一部分 作用域和閉包

第一章 作用域是什麼

1.1 編譯原理

傳統編譯的三個步驟

  • 1,分詞/詞法分析(Tokenizing/Lexing) : 這個過程會將由字元組成的字串分解成(對程式語言來說)有意義的程式碼塊,這些程式碼塊被稱為詞法單元(token)。例如,考慮程式var a = 2;。這段程式通常會被分解成 為下面這些詞法單元:var、a、=、2、;。空格是否會被當作詞法單元,取決於空格在 這門語言中是否具有意義。
  • 2,解析/語法分析(Parsing): 這個過程是將詞法單元流(陣列)轉換成一個由元素逐級巢狀所組成的代表了程式語法結構的樹。這個樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)。var a = 2; 的抽象語法樹中可能會有一個叫作 VariableDeclaration 的頂級節點,接下來是一個叫作Identifier(它的值是a)的子節點,以及一個叫作 AssignmentExpression 的子節點。AssignmentExpression 節點有一個叫作 NumericLiteral(它的值是 2)的子節點。
  • 3,程式碼生成: 將 AST 轉換為可執行程式碼的過程稱被稱為程式碼生成。這個過程與語言、目標平臺等息息相關。拋開具體細節,簡單來說就是有某種方法可以將 var a = 2; 的 AST 轉化為一組機器指令,用來建立一個叫作 a 的變數(包括分配記憶體等),並將一個值儲存在 a 中。

說明: 此處只需記住第一步:分詞/詞法分析.第二步:解析/語法分析,得到抽象語法樹(AST).第三步:程式碼生成,將抽象語法樹轉換為機器指令.

JavaScript與傳統編譯的不同點:

  • 1,JavaScript 引擎不會有大量的(像其他語言編譯器那麼多的)時間用來進行優化.
  • 2,JavaScript與傳統的編譯語言不同,它不是在構建之前提前編譯的,大部分情況下,它是在程式碼執行前的幾微秒(甚至更短)進行編譯.
  • 3,JavaScript 引擎用盡了各種辦法(比如 JIT,可以延 遲編譯甚至實施重編譯)來保證效能最佳。
  • 4,JavaScript的編譯結果不能在分散式系統中進行移植。

1.2 理解作用域

1.2.1 演員表(程式碼編譯到執行的參與者)

首先介紹將要參與到對程式 var a = 2; 進行處理的過程中的演員們,這樣才能理解接下來將要聽到的對話。

  • 引擎 從頭到尾負責整個 JavaScript 程式的編譯及執行過程。
  • 編譯器 引擎的好朋友之一,負責語法分析及程式碼生成等髒活累活(詳見前一節的內容)。
  • 作用域 引擎的另一位好朋友,負責收集並維護由所有宣告的識別符號(變數)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前執行的程式碼對這些識別符號的訪問許可權。

1.2.2 對話(程式碼編譯執行過程)

JavaScript對var a =2;的處理過程

1.2.3 作用域的LHS查詢和RHS查詢

由上圖可知,引擎在獲得編譯器給的程式碼後,還會對作用域進行詢問變數.

現在將例子改為var a = b;此時引擎會對變數a和變數b都向作用域進行查詢.查詢分為兩種:LHS和RHS.其中L代表左.R代表右.即對變數a進行LHS查詢.對變數b進行RHS查詢.

單單從表象上看.LHS就是作用域對=左邊變數的查詢.RHS就是作用域對=右邊變數的查詢.但實際上並不是這麼簡單,首先LHS和RHS都是對變數進行查詢,這也是我為什麼要將例子從var a=2;改為var a=b;兩者的區別是兩者最終要查詢到的東西並不一致.LHS是要查詢到變數的宣告(而不是變數的值),從而後面可以為其賦值.RHS是要查詢到變數最終的值.還有一點,LHS 和 RHS 的含義是“賦值操作的左側或右側”並不一定意味著就是“= 賦值操作符的左側或右側”。賦值操作還有其他幾種形式,因此在概念上最 好將其理解為“賦值操作的目標是誰(LHS)”以及“誰是賦值操作的源頭(RHS)”.或者這樣理解如果這段程式碼需要得到該變數的'源值',則會進行RHS查詢.

1.2.4 引擎和作用域的對話

這部分比較簡單就是通過擬人方式比喻引擎和作用域的合作過程.一句話概括就是,引擎進行LHS和RHS查詢時都會找作用域要.

function foo(a) { 
  console.log( a ); // 2
}
foo( 2 );
複製程式碼

讓我們把上面這段程式碼的處理過程想象成一段對話,這段對話可能是下面這樣的。

引擎:我說作用域,我需要為 foo 進行 RHS 引用。你見過它嗎?
作用域:別說,我還真見過,編譯器那小子剛剛聲明瞭它。它是一個函式,給你。
引擎:哥們太夠意思了!好吧,我來執行一下 foo。
引擎:作用域,還有個事兒。我需要為 a 進行 LHS 引用,這個你見過嗎?
作用域:這個也見過,編譯器最近把它聲名為 foo 的一個形式引數了,拿去吧。
引擎:大恩不言謝,你總是這麼棒。現在我要把 2 賦值給 a。
引擎:哥們,不好意思又來打擾你。我要為 console 進行 RHS 引用,你見過它嗎?
作用域:咱倆誰跟誰啊,再說我就是幹這個。這個我也有,console 是個內建物件。 給你。
引擎:麼麼噠。我得看看這裡面是不是有 log(..)。太好了,找到了,是一個函式。
引擎:哥們,能幫我再找一下對 a 的 RHS 引用嗎?雖然我記得它,但想再確認一次。
作用域:放心吧,這個變數沒有變動過,拿走,不謝。
引擎:真棒。我來把 a 的值,也就是 2,傳遞進 log(..)。

1.3作用域巢狀

當一個塊或函式巢狀在另一個塊或函式中時,就發生了作用域的巢狀。進而形成了一條作用域鏈.因此,在當前作用 域中無法找到某個變數時,引擎就會在外層巢狀的作用域中繼續查詢,直到找到該變數, 或抵達最外層的作用域(也就是全域性作用域)為止。

當引擎需要對作用域進行查詢時.引擎會從當前的執行作用域開始查詢變數,如果找不到, 就向上一級繼續查詢。當抵達最外層的全域性作用域時,無論找到還是沒找到,查詢過程都 會停止。

1.4 異常

例子:

function foo(a) { 
  console.log( a + b ); 
  b = a;
}
foo( 2 );
複製程式碼
  • 如果 RHS 查詢在所有巢狀的作用域中遍尋不到所需的變數,引擎就會丟擲 ReferenceError 異常。例如上面例子中console.log(a+b)由於RHS此時是找不到b的值.故會丟擲ReferenceError.
  • 如果 RHS 查詢找到了一個變數,但是你嘗試對這個變數的值進行不合理的操作, 比如試圖對一個非函式型別的值進行函式呼叫,或著引用 null 或 undefined 型別的值中的 屬性,那麼引擎會丟擲另外一種型別的異常,叫作 TypeError
  • 當引擎執行 LHS 查詢時,如果在頂層(全域性作用域)中也無法找到目標變數,全域性作用域中就會建立一個具有該名稱的變數,並將其返還給引擎,前提是程式執行在非 “嚴格模式”下。例如上面例子中的b=a;.
  • 在嚴格模式中 LHS 查詢失敗時,並不會建立並返回一個全域性變數,引擎會丟擲同 RHS 查詢 失敗時類似的 ReferenceError 異常。

1.5 LHS與RHS小結

  • LHS和RHS查詢都是引擎對作用域的查詢
  • LHS和RHS查詢都是隻對變數進行查詢
  • LHS和RHS都會沿著作用域鏈進行查詢,直到最上層的全域性作用域.如果沒找到的話,在非嚴格模式下,LHS則會在全域性建立一個相同名稱的變數.RHS則會丟擲ReferenceError的異常.
  • 如果查詢的目的是對變數進行賦值,那麼就會使用 LHS 查詢;如果目的是獲取變數的值,就會使用 RHS 查詢。
  • LHS只是找到變數的容器而已,方便進行賦值
  • =操作符或呼叫函式時傳入引數的操作都會導致關聯作用域的賦值操作。此時都會進行LHS查詢
  • RHS查詢則需要找到變數的值.

第二章 詞法作用域

作用域分為兩種工作模式:

  • 1,詞法作用域.是目前最為普遍的,被大多數程式語言所採用的模式.當然JavaScript也是使用的詞法作用域.
  • 2,動態作用域.使用較少,比如 Bash 指令碼、Perl 中的一些模式等.

2.1 詞法階段

詞法階段: 大部分標準語言編譯器的第一個工作階段叫作詞法化(也叫單詞化)。詞法化的過程會對原始碼中的字元進行檢查,如果是有狀態的解析過程,還會賦予單詞語義。

詞法作用域: 詞法作用域就是定義在詞法階段的作用域也被稱為靜態作用域。即在JavaScript裡作用域的產生是在編譯器出來的第一階段詞法階段產生的,並且是你在書寫完程式碼時就已經確定了的.

詞法作用域位置: 詞法作用域位置範圍完全由寫程式碼期間函式所宣告的位置來決定.

理解詞法作用域及巢狀: 看下例子:

function foo(a) { 
  var b = a * 2;
  
  function bar(c) { 
    console.log( a, b, c );
  }

  bar( b * 3 ); 
}
foo( 2 ); // 2, 4, 12

複製程式碼

在這個例子中有三個逐級巢狀的作用域。為了幫助理解,可以將它們分成3個逐級包含的"氣泡作用域"。

  • 1:包含著整個全域性作用域,其中只有一個識別符號:foo。
  • 2:包含著 foo 所建立的作用域,其中有三個識別符號:a、bar 和 b。
  • 3:包含著 bar 所建立的作用域,其中只有一個識別符號:c。

注意: 沒有任何函式的氣泡可以(部分地)同時出現在兩個外部作用域的氣泡中,就如同沒有任何函式可以部分地同時出現在兩個父級函式中一樣。

引擎對作用域的查詢:
這一部分在上一節中已經說過,就是從當前作用域逐級向上,直到最上層的全域性作用域.這裡再進一步進行講解.作用域查詢會在找到第一個匹配的識別符號時停止。在多層的巢狀作用域中可以定義同名的識別符號,這叫作“遮蔽效應”(內部的識別符號“遮蔽”了外部的識別符號)。拋開遮蔽效應, 作用域查詢始終從執行時所處的最內部作用域開始,逐級向外或者說向上進行,直到遇見第一個匹配的識別符號為止。

注意:

  • 全域性變數會自動成為全域性物件(比如瀏覽器中的 window物件)的屬性,因此可以不直接通過全域性物件的詞法名稱,而是間接地通過對全域性物件屬性的引 用來對其進行訪問。例如:window.a 通過這種技術可以訪問那些被同名變數所遮蔽的全域性變數。但非全域性的變數如果被遮蔽了,無論如何都無法被訪問到。
  • 詞法作用域查詢只會查詢一級識別符號,比如 a、b 和 c。如果程式碼中引用了 foo.bar.baz,詞法作用域查詢只會試圖查詢 foo 識別符號,找到這個變數後,物件屬性訪問規則會分別接管對 bar 和 baz 屬性的訪問。

2.2 欺騙詞法

欺騙詞法: 引擎在執行時來“修改”(也可以說欺騙)詞法作用域.或者說就是在引擎執行時動態地修改詞法作用域(本來在編譯詞法化就已經確定的).

欺騙詞法的兩種機制:(下面這兩種機制理解了解即可,不推薦實際開發使用)

2.2.1 eval

JavaScript 中的 eval(..) 函式可以接受一個字串為引數,並將其中的內容視為好像在書寫時就存在於程式中這個位置的程式碼。即將eval放在該詞法作用域,然後eval攜帶的程式碼就會動態加入到該詞法作用域.

通過下面的例子加深理解:

function foo(str, a) { 
  eval( str ); // 欺騙! 
  console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

複製程式碼

eval(..) 呼叫中的 "var b = 3;" 這段程式碼會被當作本來就在那裡一樣來處理。由於那段程式碼聲明瞭一個新的變數 b,因此它對已經存在的 foo(..) 的詞法作用域進行了修改。當 console.log(..) 被執行時,會在 foo(..) 的內部同時找到 a 和 b,但是永遠也無法找到外部的 b。因此會輸出“1, 3”而不是正常情況下會輸出的“1, 2”。

注意:

  • eval(..) 通常被用來執行動態建立的程式碼.可以據程式邏輯動態地將變數和函式以字元形式拼接在一起之後傳遞進去。
  • 在嚴格模式下,eval(...)無法修改所在的作用域。
  • 與eval(...)類似,setTimeout(..)和 setInterval(..) 的第一個引數可以是字串,字串的內容可以被解釋為一段動態生成的函式程式碼。
  • new Function(..) 函式的行為也很類似,最後一個引數可以接受程式碼字串,並將其轉化為動態生成的函式(前面的引數是這個新生成的函式的形參)。這種構建函式的語法比 eval(..) 略微安全一些,但也要儘量避免使用。
var sum = new Function("a", "b", "return a + b;");
console.log(sum(1, 1111));  //1112

複製程式碼

2.2.2 with(不推薦實際使用)

例子:

function foo(obj) { 
  with (obj) {
    a = 2; 
  }
}

var o1 = {
  a: 3
};

var o2 = { 
  b: 3
};
foo( o1 );
console.log( o1.a ); // 2

foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被洩漏到全域性作用域上了!
複製程式碼

起初你會覺得o1的a屬性被with裡的a進行了詞法引用被遮蔽了成為了2.而o2沒有a屬性,此時with不能進行詞法引用,所以此時o2.a就會變成undefined.但是,為什麼最後console.log(a)會為2?因為在執行foo(o2)時,with會對其中的a=2進行LHS查詢,但它在o2作用域,foo()作用域,全域性作用域都沒找到,因此就建立了一個全域性變數a並隨後賦值2.

總的來說,with就是將一個沒有或有多個屬性的物件處理為一個完全隔離的詞法作用域,因此這個物件的屬性也會被處理為定義在這個作用域中的詞法識別符號。

注意: 使用 eval(..) 和 with 的原因是會被嚴格模式所影響(限制)。with 被完全禁止,而在保留核心功能的前提下,間接或非安全地使用 eval(..) 也被禁止了。

2.2.3 效能

JavaScript 引擎會在編譯階段進行數項的效能優化。其中有些優化依賴於能夠根據程式碼的詞法進行靜態分析,並預先確定所有變數和函式的定義位置,才能在執行過程中快速找到識別符號。但是eval(..) 和 with會在執行時修改或建立新的作用域,以此來欺騙其他在書寫時定義的詞法作用域。這麼做就會導致引擎無法知道eval和with它們對詞法作用域進行什麼樣的改動.只能對部分不進行處理和優化!因此如果程式碼中大量使用 eval(..) 或 with,那麼執行起來一定會變得非常慢!。

2.3 小結

  • 詞法作用域是在你書寫程式碼時就已經決定了的.在編譯的第一階段詞法分析階段產生詞法作用域.此時詞法作用域基本能夠知道全部識別符號在哪裡以及是如何宣告的,從而能夠預測在執行過程中如何對它 們進行查詢。
  • eval(..) 和 with。前者可以對一段包含一個或多個宣告的“程式碼”字串進行演算,並藉此來修改已經存在的詞法作用域(在執行時)。後者本質上是通過將一個物件的引用當作作用域來處理,將物件的屬性當作作用域中的識別符號來處理,從而建立了一個新的詞法作用域(同樣是在執行時)。
  • 一般不要在實際程式碼中使用eval(...)和with,因為不僅危險,而且會造成效能問題!

第三章 函式作用域和塊作用域

3.1 函式中的作用域

  • JavaScript 具有基於函式的作用域,一般情況下每宣告 一個函式都會建立一個函式作用域.
  • 函式作用域的含義是指,屬於這個函式的全部變數都可以在整個函式的範圍內使用及複用(事實上在巢狀的作用域中也可以使用)。這樣的好處是JavaScript 變數可以根據需要改變值型別。

3.2 隱藏內部實現

因為

  • 子級函式作用域可以直接訪問父級函式作用域裡的識別符號;
  • 父級函式作用域不能直接訪問子級函式作用域裡的識別符號.

所以用函式宣告對程式碼進行包裝,實際上就是把這些程式碼“隱藏”起來了。

為什麼要將程式碼進行"隱藏"? 因為最小授權或最小暴露原則。這個原則是指在軟體設計中,應該最小限度地暴露必 要內容,而將其他內容都“隱藏”起來,比如某個模組或物件的 API 設計。 隱藏的好處:

  • 實現程式碼私有化,減少外部對內部程式碼的干擾,保持其穩定性.
  • 規避衝突: 可以避免同名識別符號之間的衝突, 兩個識別符號可能具有相同的名字但用途卻不一樣,無意間可能造成命名衝突。衝突會導致 變數的值被意外覆蓋。那麼一般規避衝突的手段有哪些?
      1. 全域性名稱空間: 變數衝突的一個典型例子存在於全域性作用域中。當程式中載入了多個第三方庫時,如果它們沒有妥善地將內部私有的函式或變數隱藏起來,就會很容易引發衝突。這些庫通常會在全域性作用域中宣告一個名字足夠獨特的變數,通常是一個物件。這個物件被用作庫的名稱空間,所有需要暴露給外界的功能都會成為這個物件(名稱空間)的屬性,而不是將自己的識別符號暴漏在頂級的詞法作用域中。
    • 2.模組管理: 另外一種避免衝突的辦法和現代的模組機制很接近,就是從眾多模組管理器中挑選一個來 使用。實際上就是我們常用的amd,commonjs,import模組機制.

3.3 函式作用域

函式宣告與函式表示式:

function foo() {
	...
}
複製程式碼

我們知道函式foo內的變數和函式被隱藏起來了,是不會對全域性作用域造成汙染.但是變數名foo仍然存在於全域性作用域中,會造成汙染.那有什麼方法能避免函式名的汙染呢?那就是作為函式表示式,而不是一個標準的函式宣告.這樣函式名只存在於它自己的函式作用域內,而不會存在於其父作用域,這樣就沒有了汙染.舉個函式宣告的例子:

var a = 2;
(function foo(){ 
  var a = 3;
  console.log( a ); // 3 
})(); 
  console.log( a ); // 2
複製程式碼

當我們用()包裹一個函式,並立即執行.此時這個包裝函式宣告是從(function開始的而不是從function關鍵字開始.這樣foo就會被當做一個函式表示式,而不是一個函式宣告(即foo不會存在於父級作用域中).回到上面的例子中,全域性作用域是訪問不到foo的,foo只存在於它自己的函式作用域中.

補充: 什麼是函式宣告和函式表示式 首先我們得了解JS宣告函式的三種方式:

  • 函式表示式(Function Expression): 將函式定義為表示式語句(通常是變數賦值,也可以是自呼叫形式)的一部分。通過函式表示式定義的函式可以是命名的,也可以是匿名的。因為它可以沒有函式名,因此常被用作匿名函式.如果有,其函式名也只存在自身的函式作用域.並且函式表示式不能以“function”開頭.函式表示式可以儲存在變數或者物件屬性裡. (在函式宣告前加上運算子是可以將其轉化為函式表示式的.例如!,+,-,().舉個例子:!function(){console.log(1)}()的結果是1,並不會報錯)
  • 函式宣告(Function Declaration):  函式宣告是一種獨立的結構,它會宣告一個具名函式,並必須以function開頭. 且函式宣告會進行函式提升.使它能在其所在作用域的任意位置被呼叫,即後面的程式碼中可以將此函式通過函式名賦值給變數或者物件屬性.
  • Function()構造器: 即使用Function構造器建立函式.不推薦這種用法, 容易出問題
//Function()構造器
var f =new Function()

// 函式表示式
var f = function() {
      console.log(1);  
}

// 函式宣告
function f (){
     console.log(2);
}

console.log(f())
//思考一下,這裡會打印出什麼
複製程式碼

怎麼區分函式宣告和函式表示式: 看 function 關鍵字出現在宣告中的位置(不僅僅是一行程式碼,而是整個宣告中的位置)。如果 function 是宣告中的第一個詞,那麼就是一個函式宣告,否則就是一個函式表示式。例如上例中,是從(開始而不是function.

補充: 上面這段是原書的解釋,我覺得這個解釋並不完全,這裡給出我自己的解釋.

  • 表象區別:和它說的一樣,只要是以function開頭進行宣告,並且含有函式名的就一定是函式宣告.
  • 內在區別:其實我在上面補充兩者的定義時已經說得很清楚了,我再對比總結下.
    • 函式提升:函式宣告,會將整個函式進行提升.而函式表示式則不會提升,它是在引擎執行時進行賦值,且要等到表示式賦值完成後才能呼叫。
    • 函式表示式是可以沒有函式名的,如果有,它的函式名也只存在於自身的作用域,var f = function fun(){console.log(fun)}其他地方是沒有的.這也避免了全域性汙染,也方便遞迴.

3.3.1 匿名和具名

函式表示式可以是匿名的,而函式宣告則不可以省略函式名.有函式名的就是具名函式,沒有函式名的就是匿名函式.

匿名函式的缺點:

    1. 匿名函式在棧追蹤中不會顯示出有意義的函式名,使得除錯很困難。
    1. 如果沒有函式名,當函式需要引用自身時只能使用已經過期的arguments.callee引用,比如在遞迴中。另一個函式需要引用自身的例子,是在事件觸發後事件監聽器需要解綁自身。
    1. 匿名函式省略了對於程式碼可讀性/可理解性很重要的函式名。一個描述性的名稱可以讓程式碼不言自明。

所以給函式表示式指定一個函式名可以有效解決以上問題。始終給函式表示式命名是一個最佳實踐.

PS: 個人意見是如果函式表示式有賦值給變數或屬性名或者就是一次性呼叫的.其實是沒必要加上函式名.因為程式碼裡取名本來就很難,取不好反而會造成誤解.

3.3.2 立即執行函式表示式

比如 (function foo(){ .. })()。第一個 ( ) 將函式變成表示式,第二個 ( ) 執行了這個函式。這就是立即執行函式表示式,也被稱為IIFE,代表立即執行函式表示式 (Immediately Invoked Function Expression);

IIFE可以具名也可以匿名.好處和上面提到的一樣.IIFE還可以是這種形式(function(){ .. }()).這兩種形式在功能上是一致的。

3.4 塊作用域

函式作用域是JavaScript最常見的作用域單元,有時我們僅會將var賦值變數在if或for的{...}內使用,而不會在其他地方使用.但它仍然會對外層的函式作用域造成汙染.這個時候就會希望能有一個作用域能將其外部的函式作用域隔開,宣告的變數僅在此作用域有效.塊作用域(通常就是{...}包裹的內部)就可以幫我們做到這點.

從 ES3 釋出以來,JavaScript 中就有了塊作用域,而 with 和 catch 分句就是塊作用域的兩個小例子。

3.4.1 with

我們在第 2 章討論過 with 關鍵字。它不僅是一個難於理解的結構,同時也是塊作用域的一個例子(塊作用域的一種形式),用 with 從物件中創建出的作用域僅在 with 宣告中而非外部作用域中有效。

3.4.2 try/catch

try/catch 的 catch 分句會建立一個塊作用域,其中宣告的變數僅在 catch 內部有效。

try {
  undefined(); // 執行一個非法操作來強制製造一個異常
}
catch (err) {
  console.log( err ); // 能夠正常執行! 
}
console.log( err ); // ReferenceError: err not found
複製程式碼

err 僅存在 catch 分句內部,當試圖從別處引用它時會丟擲錯誤。 那麼如果我們想用catch建立一個不是僅僅接收err的塊作用域,該怎麼做呢?

try{throw 2;}catch(a){ 
  console.log( a ); // 2
}
console.log( a ); // ReferenceError
複製程式碼

這樣就建立了一個塊作用域,且a=2,僅在catch分句中存在.在ES6之前我們可以使用這種方法來使用塊作用域.

3.4.3 let

ES6 引入了新的 let 關鍵字,提供了除 var 以外的另一種變數宣告方式。let 關鍵字可以將變數繫結到所在的任意作用域中(通常是 { .. } 內部)。

用 let 將變數附加在一個已經存在的塊作用域上的行為是隱式的。例如在if的{...}內用let宣告一個變數.那什麼是顯式地建立塊作用域呢?就是單獨建立{}來作為let的塊作用域.而不是借用if或者for提供的{}.例如{let a=2;console.log(a)}
注意: 使用 let 進行的宣告不會在塊作用域中進行提升.
塊作用域的好處:

  • 1,垃圾收集
function process(data){
        // 在這裡做點有趣的事情
     }
     var someReallyBigData=function(){
         //dosomeing
     }
     process(someReallyBigData);

     var btn=document.getElementById("my_button");
     btn.addEventListener("click",function click(evt){
        alert("button click");
		//假如我們在這裡繼續呼叫someReallyBigData就會形成閉包,導致不能垃圾回收(這段是書裡沒有,我加上方便理解的)
     },false);
複製程式碼

click 函式的點選回撥並不需要 someReallyBigData 變數。理論上這意味著當 process(..) 執行後,在記憶體中佔用大量空間的資料結構就可以被垃圾回收了。但是,由於 click 函式形成了一個覆蓋整個作用域的閉包,JavaScript 引擎極有可能依然儲存著這個結構(取決於具體實現)。 但顯式使用塊作用域可以讓引擎清楚地知道沒有必要繼續儲存 someReallyBigData 了:

function process(data){
       // 在這裡做點有趣的事情
    }
    // 在這個塊中定義的內容可以銷燬了! 
    {
      let someReallyBigData = { .. }; 
      process( someReallyBigData );
    }
    var btn=document.getElementById("my_button");
    btn.addEventListener("click",function click(evt){
       alert("button click");
    },false);
複製程式碼
    1. let迴圈
for (let i=0; i<10; i++) { 
	  console.log( i );
     }
console.log( i ); // ReferenceError
複製程式碼

for 迴圈頭部的 let 不僅將 i 繫結到了 for 迴圈的塊中,事實上它將其重新繫結到了迴圈的每一個迭代中,確保使用上一個迴圈迭代結束時的值重新進行賦值。這樣就避免了i對外部函式作用域的汙染.

3.4.4 const

除了 let 以外,ES6 還引入了 const,同樣可以用來建立塊作用域變數,但其值是固定的(常量)。之後任何試圖修改值的操作都會引起錯誤。

var foo = true;
if (foo) {
  var a = 2;
  const b = 3; // 包含在 if 中的塊作用域常量
  a = 3; // 正常!
  b = 4; // 錯誤! 
}
console.log( a ); // 3
console.log( b ); // ReferenceError!
複製程式碼

3.5 小結

函式是 JavaScript 中最常見的作用域單元。本質上,宣告在一個函式內部的變數或函式會在所處的作用域中“隱藏”起來,可以有效地與外部作用域隔開.

但函式不是唯一的作用域單元。塊作用域指的是變數和函式不僅可以屬於所處的作用域,也可以屬於某個程式碼塊(通常指 { .. } 內部)即塊作用域。ES6中就提供了let和const來幫助建立塊作用域.

第四章 提升

4.1 先有雞(賦值)還是先有蛋(宣告)

考慮第一段程式碼

a = 2;
var a; 
console.log( a );
複製程式碼

輸出結果是2,而不是undefined

考慮第二段程式碼

console.log( a ); 
var a = 2;
複製程式碼

輸出結果是undefined,而不是ReferenceError 考慮完以上程式碼,你應該會考慮這個問題.到底是宣告(蛋)在前,還是賦值(雞)在前?

4.2 編譯器再度來襲

編譯器的內容,回憶一下,引擎會在解釋 JavaScript 程式碼之前首先對其進行編譯。編譯階段中的一部分工作就是找到所有的宣告,並用合適的作用域將它們關聯起來。 之後引擎會詢問作用域,對宣告進行賦值操作.

那麼,在編譯階段找到所有的聲明後,編譯器又做了什麼?答案就是提升 以上節的第一段程式碼為例,當你看到 var a = 2; 時,可能會認為這是一個宣告。但 JavaScript 實際上會將其看成兩個宣告:var a;和a = 2;。 第一個定義宣告是在編譯階段進行的。第二個賦值宣告會被留在原地等待執行階段。在第一個宣告在編譯階段時,編譯器會對var a;宣告進行提升(即把var a;置於所在作用域的最上面).而a = 2;則會保持所在位置不動.此時程式碼會變成

var a; 
a = 2;
console.log( a );
複製程式碼

由此可知,在編譯階段,編譯器會對宣告進行提升.即先有蛋(宣告)後有雞(賦值)。 哪些宣告會被進行提升?

  • 變數宣告:例如上例中的var a;.不包括後面的a = 2;不包含有賦值操作的宣告.
  • 函式宣告:注意是函式宣告,而不是函式表示式!(不清楚可以看前面的3.3節,我有詳細說明).函式宣告提升,是將整個函式進行提升,而不是僅僅函式名的提升.

4.3 函式優先

函式宣告和變數宣告都會被提升。但是一個值得注意的細節(這個細節可以出現在有多個“重複”宣告的程式碼中)是函式會首先被提升,然後才是變數。 考慮以下程式碼:

foo(); // 1
var foo;
function foo() { 
  console.log( 1 );
}
foo = function() { 
  console.log( 2 );
};
複製程式碼

會輸出 1 而不是 2 !這個程式碼片段會被引擎理解為如下形式:

function foo() { 
  console.log( 1 );
}
foo(); // 1
foo = function() { 
  console.log( 2 );
};
複製程式碼

注意,var foo 儘管出現在 function foo()... 的宣告之前,但它是重複的宣告(因此被忽略了),因為函式宣告會被提升到普通變數之前。 注意: js會忽略前面已經宣告的宣告(不管是變數宣告還是函式宣告,只要其名稱相同,則後續不會再進行重複宣告).但是對該變數新的賦值,會覆蓋之前的值.
一句話概括:函式宣告的優先順序高於變數宣告,會排在它前面.

4.4 小結

  • 對於var a = 2 JavaScript引擎會將var a和 a = 2當作兩個單獨的宣告,第一個是編譯階段的任務,而第二個則是執行階段的任務。
  • 論作用域中的宣告出現在什麼地方,都將在程式碼本身被執行前首先進行處理。 可以將這個過程形象地想象成所有的宣告(變數和函式)都會被“移動”到各自作用域的最頂端,這個過程被稱為提升。
  • 宣告本身會被提升,而包括函式表示式的賦值在內的賦值操作並不會提升(即賦值操作都不會提升)。
  • 注意:,當普通的 var 宣告和函式宣告混合在一起的時候,並且宣告相同時(var的變數名和函式名相同時,會引發js對重複宣告的忽略)!一定要注意避免重複宣告!

第五章 作用域閉包

5.1 啟示

  • JavaScript中閉包無處不在,你只需要能夠識別並擁抱它。
  • 閉包是基於詞法作用域書寫程式碼時所產生的自然結果,你甚至不需要為了利用它們而有意識地建立閉包。

5.2 實質問題 && 5.3 現在我懂了

因為這兩小節理解透了其實發現書裡也沒講什麼,這裡就進行合併,並補充拓展我自己的理解和總結.
什麼是閉包?(廣義版)
書中解釋: 當函式可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函式是在當前詞法作用域之外執行。
MDN的解釋: 閉包是函式和宣告該函式的詞法環境的組合。
我的解釋(詳細版): 必須包含兩點:

  • 1,有函式.由於函式自身的特性,它能訪問所在的詞法作用域.並能儲存外部詞法作用域的變數和函式到自己的函式作用域.
  • 2,有該函式所在的詞法環境.其實在JavaScript中任何函式都會處在一個詞法環境中.不管是全域性作用域還是函式作用域.

綜上簡單版就是:MDN的解釋閉包是函式和宣告該函式的詞法環境的組合。
還可以繼續延伸成極簡版:JavaScript中的函式就會形成閉包
Tips: 注意到上面對詞法作用域詞法環境兩詞的分開使用了嗎?1,裡此時函式還沒被執行,所以使用的是詞法作用域即靜態作用域.2,裡,此時函式被執行,此時詞法作用域就會變成詞法環境(包含靜態作用域與動態作用域).所以其實MDN的解釋其實更準確一點,

我們日常使用時所說的閉包(狹義版,嚴格意義上的):
為了便於對閉包作用域的觀察和使用.我們實際使用時會將閉包的函式作用域暴露給當前詞法作用域之外.也就是本書一直強調的閉包函式需要在它本身的詞法作用域以外執行.作者認為符合這個條件才稱得上是真正的閉包(也就是我們日常使用常說的'使用閉包',並且使用任何回撥函式其實也是閉包).
所以狹義版就是:閉包是函式和宣告該函式的詞法環境的組合,並且將閉包的函式作用域暴露給當前詞法作用域之外.

閉包暴露函式作用域的三種方式:
下面部分是書中沒有的,是自己實際使用時的總結,並且符合這三種形式之一的就是我們日常使用時所說的閉包(狹義版)

  • 1,通過外部函式的引數進行暴露.
function foo() { 
  var a = 2;
  function bar() { 
   baz(a) //通過外部函式的引數進行暴露
  }
  bar(); 
};
function baz(val) { 
   console.log( val ); // 2 
}
foo();
複製程式碼
  • 2,通過外部作用域的變數進行暴露
var val;
function foo() { 
  var a = 2;
  function bar() { 
   val=a //通過外部作用域的變數進行暴露
  }
  bar(); 
};
foo();
console.log(val)  //2
複製程式碼
  • 3,通過return直接將整個函式進行暴露
function foo() { 
   var a = 2;
   function bar() { 
    console.log(a)
   }
   return bar //通過return直接將整個函式進行暴露
};
var val=foo();
val()  //2
複製程式碼

關於閉包的記憶體洩露問題:
首先必須宣告一點:使用閉包並不一定會造成記憶體洩露,只有使用閉包不當才可能會造成記憶體洩露.(吐槽:面試很多新人時,張口就說閉包會造成記憶體洩露)
為什麼閉包可能會造成記憶體洩露呢?原因就是上面提到的,因為它一般會暴露自身的作用域給外部使用.如果使用不當,就可能導致該記憶體一直被佔用,無法被JS的垃圾回收機制回收.就造成了記憶體洩露.
注意: 即使閉包裡面什麼都沒有,閉包仍然會隱式地引用它所在作用域裡的所用變數. 正因為這個隱藏的特點,閉包經常會發生不易發現的記憶體洩漏問題.
常見哪些情況使用閉包會造成記憶體洩露:

  • 1,使用定時器未及時清除.因為計時器只有先停止才會被回收.所以決辦法很簡單,將定時器及時清除,並將造成記憶體的變數賦值為null(變成空指標)
  • 2,相互迴圈引用.這是經常容易犯的錯誤,並且也不容易發現.舉個栗子:
function foo() { 
  var a = {}; 
  function bar() { 
    console.log(a); 
  }; 
  a.fn = bar; 
  return bar; 
};
複製程式碼

這裡建立了一個a 的物件,該物件被內部函式bar引用。然後,a建立了一個屬性fn指向了bar,最後返回了innerFn()。這樣就形成了bar和a的相互迴圈引用.可能有人說bar裡不使用console.log(a)不就沒有引用了嗎就不會造成記憶體洩露了.NONONO,bar作為一個閉包,即使它內部什麼都沒有,foo中的所有變數都還是隱使地被 bar所引用。這個知識點是我前面忘記提到的,也是書中沒有提到的.算了我現在加到前面去吧.所以即使bar內什麼都沒有還是造成了迴圈引用,那真正的解決辦法就是,不要將a.fn = bar.

  • 3,將閉包引用到全域性變數上.因為全域性變數是隻有當頁面被關閉的時候才會被回收.
  • 4,在閉包中對DOM進行不當的引用.這個常見於老IE瀏覽器,現代瀏覽器已經長大了,已經學會了自己處理這種情況了.這裡就不贅述了.想知道的可以自行問谷娘和度娘.

總而言之,解決辦法就是使閉包的能正常引用,能被正常回收.如果實在不行,就是在使用完後,手動將變數賦值null,強行進行垃圾回收.

5.4 迴圈和閉包

看如下例子:

for (var i=1; i<=5; i++) { 
  setTimeout( function timer() {
    console.log( i );
  }, i*1000 );
}
複製程式碼

我們期望的結果是分別輸出數字 1~5,每秒一次,每次一個。
但實際結果是,這段程式碼在執行時會以每秒一次的頻率輸出五次 6。
(關於書裡的解釋,我覺得有點說複雜了,沒說到點子上,下面是我的解釋.)
為什麼會是這樣的結果?
timer毫無疑問是一個閉包,它是可以訪問到外部的變數i.在進行for迴圈時,timer()會被重複執行5次,也就是它會 console.log( i )5次.(關鍵部分來了!)這5次i其實是同一個i.它是來自於外部作用域,即for裡面宣告的i.在詞法作用域中變數i只可能對應一個唯一的值,即變數和它的值是一一對應的.不會變化的.那這個值到底是多少呢?這個值就是最終值! i的最終值就是6即for迴圈完後i的值.當引擎執行console.log( i )時,它會詢問i所對應的作用域,問它i的值是多少.這個時候作用域進行RHS查詢得到的結果就是最終值6.

為什麼我們會以為分別輸出1~5?
因為在for迴圈中,我們錯以為每一次迴圈時,函式所輸出的i是根據迴圈動態變化的.即是1~5累加變化的.但實際上它所訪問的i是同一個固定不變的值,即最終值6.可能你會有這樣的疑惑,那我迴圈還有意義嗎?i其實一開始就確定是6了.沒有變化過!錯!i變化過,它的確是從1逐步增加到6的.只是外部作用域的i值只可能是迴圈完後的最終值,並且函式timer()並沒有儲存每次i變化的值.它只是訪問了外部作用域的i值即最終的值6. OK我們知道了出錯的地方,就是我們沒有把每次i的值儲存在一個獨立的作用域中. 接下來,看下這個改進的例子結果是多少.

for (var i=1; i<=5; i++) { 
  (function() {
    setTimeout( function timer() { 
	  console.log( i );
    }, i*1000 );
  })();
}
複製程式碼

它的最終值仍然是5個6.為什麼?我們來分析下,上例中,它用了一個匿名函式包裹了定時器,並立即執行.在進行for迴圈時,會創造5個獨立的函式作用域(由匿名函式建立的,因為它是閉包函式).但是這5個獨立的函式作用域裡的i也全都是對外部作用域的引用.即它們訪問的都是i的最終值6.這並不是我們想要的,我們要的是5個獨立的作用域,並且每個作用域都儲存一個"當時"i的值.

解決辦法: 那我們這樣改寫.

for (var i=1; i<=5; i++) { 
  (function () {
    var j =i;
    setTimeout( function timer() { 
	  console.log( j );
    }, j*1000 );
  })();
}
//這次終於結果是分別輸出數字 1~5,每秒一次,每次一個。	

            
           

相關推薦

全方位徹底&lt;知道JavaScript()&gt;--讀書筆記

前言 Q&A 1.問:為什麼要寫這麼長,有必要嗎?是不是腦子秀逗了? 答:我想這是大部分人看到這個標題都會問的問題.因為作為一個男人,我喜歡長一點,也不喜歡分割成幾個部分.一家人就要在一起,整整齊齊.好吧,正經點,其實整篇前言可以說都是在回答這個問題.你可以選擇先看完前言

c++知道的用法之foreach

// arraytest.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #include <iostream> #include<vect

知道的Canvas(

Canvas基礎 一、Canvas是什麼 Canvas是一個可以使用指令碼(通常為JavaScript來繪製圖形的HTML) 元素.例如,它可以用於繪製圖表、製作圖片構圖或者製作簡單的動畫,主要用來繪製2D圖形。 Canvas的預設高度為300px*150px,可以使用HTML的高度和寬度屬性來自定義Canv

&lt;轉&gt;about持續集成,知道的事

哪些 克服 簡單的 避免 不同類 faq 令行 git 簡單 從別處看到了一篇關於持續集成的文章,個人感覺蠻不錯的,分享給大家。。。 原文鏈接:對於持續集成實踐的常見問題解答 1、什麽是持續集成? 集成,就是一些孤立的事物或元素通過某種方式集中在一起,產生聯系,從而構成

UI設計教程分享:讓徹底字型

一份普普通通、規規矩矩的設計 一份讓人印象深刻、新穎有趣的設計   差在哪?其實就差在三個字上! “優秀的設計不是每一個細節都有亮點,而是弱化其他元素,讓某一個亮點最大化。”   今天“驫叔的設計心得”就來總結一下關於“字”的心得。 在設計和選擇字型前,我們要先想

知道的Node.js效能優化,了之後水平直線上升

本文由雲+社群發表 “當我第一次知道要這篇文章的時候,其實我是拒絕的,因為我覺得,你不能叫我寫馬上就寫,我要有乾貨才行,寫一些老生常談的然後加上好多特技,那個 Node.js 效能啊好像 Duang~ 的一下就上去了,那讀者一定會罵我,Node.js 根本沒有這樣搞效能優化的,都是假的。” ------

知道的Node.js性能優化,了之後水平直線上升

fail 組類型 frame 繼續 同時 all dstream 引擎 perf 本文由雲+社區發表 “當我第一次知道要這篇文章的時候,其實我是拒絕的,因為我覺得,你不能叫我寫馬上就寫,我要有幹貨才行,寫一些老生常談的然後加上好多特技,那個 Node.js 性能啊好像 Du

知道的二維碼掃描模組、二維碼頭行業應用?

終端 熱門行業 roc 系統 哪些 mark 門禁 cto ext 隨著二維碼識別技術的發展,近些年以二維碼掃描模組為核心掃碼硬件無論是生活還是工作,都給我們帶來了前所未有的改變。設備掃描讀取乘車碼乘坐公交地鐵、在自助機上刷支付寶微信付款碼實現二維碼支付等一系列O2O智能設

深入JDK源碼,這裏總有知道的知識點!

方法 int com 運行時異常 form 成對 adl 拷貝 般的 Java的基礎知識有很多,但是我認為最基礎的知識應該要屬jdk的基礎代碼,jdk的基礎代碼裏面,有分了很多基礎模塊,其中又屬jdk包下面的lang包最為基礎。 我們下面將總結和分析一下lang包下面最為基

1.男子在路邊根接著根地抽煙。一個女士走過來對他說:“嘿,你不知道你是在慢性自殺嗎?註意看看煙盒的警告信息。”“沒關系”, 男子悠然自得地又吸了口:“我是個程序員。”“嗯?這和是程序員有什麽關系?...

我不知道 不知道 對他 上網 是我 .com 一個 但是 err 1.一男子在路邊一根接著一根地抽煙。一個女士走過來對他說:“嘿,你不知道你是在慢性自殺嗎?註意看看煙盒上的警告信息。”“沒關系”,男子悠然自得地又吸了一口:“我是個程序員。”“嗯?這和你是程序員有什麽關系?”

【轉載】史最全:TensorFlow 好玩的技術、應用和知道的黑科技

tube map 高性能 知識 seq 出現 執行時間 mes lex 【導讀】TensorFlow 在 2015 年年底一出現就受到了極大的關註,經過一年多的發展,已經成為了在機器學習、深度學習項目中最受歡迎的框架之一。自發布以來,TensorFlow 不斷在完善並增加新

知道的 flex 技巧

hacker https ems add init 實踐 事情 pwa 需要 一、使用 Auto Margins 對齊 不需要給圖片使用任何的 flex,也不需要給父容器設置 space-between,只需要給 ‘ BUY-BUY-BUY‘ 按鈕設置

通過一個案例徹底10046 trace--節級深入破解

統計 merge tin 版本 單位 byte bbed 恢復 技術 轉載請註明出處:http://blog.csdn.net/guoyjoe/article/details/378405832014.7.23晚20:30 Oracle support組貓大師分享《通過一

知道javaScript筆記(2)

是否 foreach 函數 嚴格模式 console spa new 簡單的 否則 this和對象原型 this是一個很特別的關鍵字,被自動定義在所有函數的作用域中 // foo.count 是0,字面理解是錯誤的 function foo(num) {

微信聊天記錄要怎麽恢復刪除的記錄?這一小妙招知道

說到男人出軌,女人就有話要說了。相信絕大數女人聽到自己男人在外面有小三,一定是非常的暴跳如雷,電話不停的轟擊男人的手機,直到男人接聽為止。這事第一件事,獲得男人目前所在的具體位置。 那麽第二件事就是等男人回到家後拷問男人以及關於小三的事

知道javaScript筆記(4)

作用域 能夠 max rip 指數 upper 是否 進制 spa 類型: JavaScript 有7種內置類型 空值 (null) 未定義(undefined) 布爾值(boolean) 數字(number) 字符串(string) 對象(object)

或許知道的10條SQL技巧

提高效率 經驗 查詢 中國 nbsp 結果集 復雜 移動 前綴 這幾天在寫索引,想到一些有意思的TIPS,希望大家有收獲。 一、一些常見的SQL實踐 (1)負向條件查詢不能使用索引 select * from order where status!=0 and st

知道JavaScript學習筆記1——作用域

模式 引用 語法分析 訪問 要素 並不會 參數 嵌套 ron 處理程序三要素: 引擎:編譯與執行過程。 編譯器:語法分析與代碼生成等。 作用域:收集並維護由所有聲明的標識符(變量)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限。 示

[肯定知道]PeopleSoft中PSADMIN知道的秘密

相同 菜單 gty 隱藏 選項 更新 nds log hrn PeopleSoft psadmin工具是用於管理PS App server,process scheduler 和 web server節點的。可以使用一些設置菜單選項來管理或配置上面提到的任何組件。要是用任何

知道的Google Search

data- 手工 title swe 第一次 tro quest .exe 列表 0.導讀 這篇文章講了這三個事兒: 如何訪問Google?----------什麽?不是直接輸入地址麽?Google的地址是什麽?------ 你在逗我?難道不是w