《Eloquent JavaScript 3rd》筆記

前言篇

計算機是人思想的一部分,邏輯、秩序、規則…… 不常見的任務須要額外的編程才能解決。git

機器很死板,無趣。程序員

忍受機器後能享受到便利。express

借鑑了天然界以及人類的模式到編程的設計上。編程

編程的規則原理語法很簡單,但創造出知足複雜世界的需求會很難。api

學習中的有些困難和痛苦勢必要的。這樣以後的學習就更容易了。數組

不要妄自菲薄,換換方式,或者歇一歇,而後堅持學習。瀏覽器

計算機結構是龐大的,找到圍繞目標的所需的知識,理解運行原理。安全

計算機很蠢,可是優勢是很快。bash

控制計算機解決問題,就是掌控複雜度的藝術,掌控很差就崩了。。服務器

新的問題每每用舊的眼光和方式解決很差,或者不夠好。

不斷的實踐出錯優化才能融會貫通各類狀況下的考慮,而簡單的閱讀卻沒辦法作到。

語言怎麼起做用的

從最底層到最接近問題,編程語言存在着不一樣層面的抽象,有時候細節須要忽略,有時候又要操縱細節,這方面作得最好的是C++,而JS是主要面向瀏覽器的。它的不少抽象層級都面向瀏覽器的應用而不是性能,底層,等等。

幹同一件事的代碼能夠寫成不少樣子,是更易閱讀,更省性能仍是寫代碼用的時間最少(這是人和公司最寶貴的東西,除此以外才是金錢花費),取決於你的需求進行平衡取捨。

什麼JS

JS最先是寫瀏覽器交互、特效之類的小腳本。

JS與Java有關,否則爲啥不起別名,但僅僅是爲了蹭市場名聲,技術實現上上大抵都無關。

JS在編程語言中很爛,但它面向新手,使用的人多了,一次次版本更新也變好起來了。曲線救國,人多才是王道。

JS歷史不談,2015年後ES6版本,每一年都有更新,因此快更新瀏覽器與時俱進吧!

JS還在MongoDB中能夠做爲腳本查詢語言、Node.js服務器語言。

編碼

不只要學,還要寫出來。這是一門工程學科,實現是根本,學是爲了實現的更好。 Don’t assume you understand them until you’ve actually written a working solution.

全局概覽

語言,瀏覽器,Node.js。也就是能幹什麼,在哪裏幹,還能夠在哪裏幹。

值,類型,操做符

程序的表面之下是龐大的代碼支撐着

計算機中只有數據,數據的信息能夠被解釋爲控制指令、文件內容等。

二進制的bit,binary digit,只有0和1的序列,表明着各類信息。

例如數字13的二進制表示。

0   0   0   0   1   1   0   1
 128  64  32  16   8   4   2   1
複製代碼

易失性內存:8g -> 687 1947 6736 bits,687億個比特(嚇了一跳吧~)

管理如此大量的比特們,須要進行分模塊處理,否則會迷失混亂。values, 編程語言中的各類值來劃分不一樣含義的比特信息,值多了抽象出類型,具備一樣的特色,細化下去就是值的不一樣,再向上抽象就是類型的不一樣。

計算機是複雜的,內存也是複雜的,但人的生命是有限的,沒辦法瞭解全部的細節。(這是全部矛盾的源頭)

  • 但在編程語言中只關心重要的抽象,省略掉不關心的部分。
  • 好比我須要一個變量來放今天早餐花費了多少錢
    • 關心的部分:
      • 一個是名字花費cost
      • 一個是金額6
      • 合起來就是let cost = 6;
    • 不關心的部分:
      • 上面那行命令怎麼從鍵盤到CPU傳遞
      • CPU怎麼理解6cost綁定的值,仍是這行命令執行6次。
      • CPU把6給我存在哪一個地方
      • 這個6能夠共享給其餘變量,仍是不能重複利用的的獨一無二的
      • cost怎麼和6進行的綁定……
    • 事實上是規定不能關心麼,不,你能夠無聊或者感興趣研究它到底在某一步幹了什麼,畢竟種族中隨機的變異致使人多了就會有小部分人這麼執着於每一個地方,關鍵是大部分人的時間都很寶貴,咱們在時時刻刻變老,人腦處理的信息是固定的,而計算機的發展是無數前輩智慧的結晶,咱們能用一我的的一輩子去了解這麼龐大的知識?不存在的,只能挑重點。

數字

number, 64bits

  • 不一樣信息的表示共有2^64種,每建立一個數字的變量,計算機會申請這麼大的空間,換成十進制有20位,通常是不用關心會超過申請空間(overflow)。
  • 3.14
    • 浮點數表示只會把小數的字面值精確到某位存儲起來,不會徹底復原小數,因此判斷時和標準比較差值,
    • 如我想要一個數是3.14偏差在0.01,那麼判斷if((x-3.14)<0.01), 而不是 if(x==3.14)
  • 314 能夠表示爲3.14e2, e(exponent)

數字通常還要分出一位用來表示正負,若是還要表示小數就要有更多位的損耗,有各類表示小數的方法,你看想要完成更多的功能,就確定要付出一些東西。

算術

數字的主要功能是用來算術,因此這些東西不是被憑空發明出來的,而是實實在在有用纔會出如今JavaScript中。

  • 二元操做符:+ - * / %

  • precedence: * / % > + -

  • %: remainder operator

    • 除餘,咔嚓咔嚓除完剩下的,多好理解,比死背強記簡單多了
  • 改變默認優先級:括號

3種特殊數字

  1. Infinity, -Infinity
  2. NaN: not a number

字符串

  • 好寬容,三種符號可表示字符串,沒有character類型
    1. backticks,反引號中的string,也叫template literal
    2. 換行符不用進行轉義就能夠保留
    3. half of 100 is ${100 / 2}
    4. 計算${}
    5. 轉換成字符
    6. 被包含
    7. ''
    8. ""
  • 轉義字符:backslash,\
    • 意味着這個字符以後的應該特殊對待,
  • 字符串中的每一個字母佔16bits,小於Unicode數量,因此有些字符用佔2個字母的空
  • 只有+操做符:concatenates

一元操做符

操做符除了用符號表示,再多了符號不夠用命名關鍵字表示.

操做符的操做數能夠是一個,兩個……

一元操做符:

  • typeof: return a string of type of the operand

符號的一詞多義:

  • 緣由:由於鍵盤的符號有限不夠用,有的符號又當爹又當媽
  • -(2-1):
    • 第一個-,unary operator,操做的對象是一個,即2-1的結果,語義是將一個數置負,
    • 第二個-,binary operator,須要兩個操做對象,語義是有兩個數,他們想減

布爾值

信息的存儲最理想狀態是e次方,即2.718,而現實離理想還有段距離,因此目前的計算機是基於二進制的,實際上三進制更理想。不過二進制是第二好的選擇,比十進制不知道高到哪裏去了。

既然是二進制,就免不了這個二的狀態由哪兩個表示,計算機說是0和1,實際上在不一樣器件裏面的表示,有用電平高低的,有用正弦不一樣的,程序利用truefalse,日常咱們說話用有和沒有,是或者不是,開或者關,若是是三進制那還包括一種狀況,那就是不知

比較

比較的結果是個布爾值

字符的比較是按照ASCII碼以及Unicode碼來的,而不是字典中從A到Z的順序,注意Unicode字符是ASCII的超集,而後ASCII中的編碼在Unicode中大小是相同的,所謂的兼容ASCII,無非是前面多8個0。

只有一種值不等於它本身: NaN,它的含義就是用來表示無心義的結果,因此他也不和其餘無心義結果相等。Oh,shit,難以理解。。

邏輯操做符

語義:注意它的操做對象是布爾值,不是用來算數的,儘管數字0和1會被變量提高轉換成true 和 false。

  • and: &&
  • or: ||
  • not: !

precedence: > == > && > ||

一個三元的邏輯操做符:true ? 1 : 2, ternary, conditional operator, question mark and a colon

三元操做符短路,意味着有些語句不執行。

Short-circuiting of logical operators

短路,左邊算完了若是返回左邊,右邊就不驗證了,稱short-circuit evaluation.

||左邊true就返回左邊,false就返回右邊。 &&左邊false就返回左邊,true就返回右邊。

我從未據說過布爾邏輯的處理是這樣的,什麼叫and運算?左邊false,就返回左邊對象?爲何不是返回false?我要你這個運算何用?這種神奇的功能我本身八輩子都用不到

垃圾語言,奇葩設定,毀我青春,****。

WTF爲何不和C++,Java同樣?

Examples of expressions that can be converted to false are:

null;
NaN;
0;
empty string ("" or '' or ``); 
undefined.
複製代碼
  1. ||先把左邊轉換成布爾值,是個數就返回左邊,不是個數返回右邊
  2. 轉化規則
-  `0`, `NaN`, `""`, `null`, `undifined`會被算做 `false`
- 其餘的全部算做`true`
複製代碼
  1. true 則返回左邊
- `console.log('bat||'ant')  // -> bat`
複製代碼
  1. false 則返回右邊
- `console.log(null||'hi')  // -> hi`
複製代碼
  • 利用這個特性,能夠將多是空值的變量加上||,使之成爲備胎。

空值

設計上的委曲求全,唉,但是Rust語言不火啊,人們並不須要正確,人們須要兼容穩定能用。

  1. null
  2. undefined

自動類型轉換

JS老是喜歡能處理你給的各類值,也就是對你很包容,但這對於精確的控制卻很差。語言的設計

type coercion

  • null*8 -> 0

  • "5" - 1 -> 4字符拼接優先於數值計算。

  • "5" + 1 -> 51

  • NaN若是一旦產生,那麼與此相關的結果仍是會NaN

  • 自動類型轉換和嚴格相等

    • ==!=
      • same type
        • 除了NaN
      • diff type
        • nullundefined之間爲true,和其餘爲false
        • 其餘類型自動轉換
    • ===!==
      • 不包括自動類型轉換

程序結構

表達式和語句 expressions and statements

磚塊在高樓中才能體現更大的價值。意思是良禽擇木而棲才能發揮更大價值?

可以產生一個值的代碼塊稱之爲表達式,expression。字面值,帶括號,運算式都是expression

expression之間互相組合、嵌套組成了語義更復雜的expression。

expression是某一小段子句,而語句,statement是一句完整的話。程序就是一列語句們的集合。

最簡單的statement是一個expression加上一個分號。(可忽略的分號。。。)

expression產生一個值,而後被周圍的代碼所利用。

而statement隻立足於他本身,除非指定和其餘東西有交集。當改變了屏幕上的文字或者改變了計算機內部的一些狀態,以至於影響後來的語句執行的結果,這就叫作影響,effects。像1;這種語句確實改變了了計算機內部的東西,但對於別的代碼沒有明顯的做用。

分號這個東西大部分時間可加可不加,可是你懂的總有意外。有時候不加分號會讓會讓兩行代碼變成互相影響的一句statement,爲了安全、不找麻煩,仍是加上吧,否則要認識不少哪些是必須加分號的複雜狀況。

綁定 bindings

表達式會產生一個值,好比1+2,可是產生的結果3,若是不馬上使用它,或找一個空間分給他,它立刻就不見了。

let caught = 5 * 5;

variable or binding

keyword: let, 聲明+賦值

  • 聲明瞭caught,就是宣佈要有光!,而後計算機內存裏面就找到一個地方對應着這個caught.
  • 賦值,右邊5*5的結果綁定到了caught上。
  • 只聲明沒賦值的狀況爲undifined
  • 以後再次出現caught,它就會被要麼做爲空間地址,要麼取它的值25
  • 一次聲名,屢次綁定。
  • 綁定更像觸手同樣能夠多個綁定指向同一個值。這句話很費解,是指變量只是個引用,這個值是共享的?仍是說綁定的值和引用的位置是分離的?

var是個歷史遺留問題,const一次聲明賦值爲常量,鞠躬盡瘁,死然後已。

Binding names

字母、數字、$_,不能以數字開頭,不能用關鍵字,不能用保留字。

其實就一個原則就行了,字母開頭,各類駝峯表明變量、函數、仍是類。也別起什麼各類奇葩名字,沒事找事。

Environment

程序啓動的時候,環境就激活了語言自己的一部分綁定,以及提供交互的一些綁定。

Functions

默認環境提供的一些變量類型爲function.

調用、請求函數的執行,稱invoking, calling, applying.

回想數學f(x)=y+1;函數要有輸入x,用括號來表示。稱arguments, 這個參數可能(x,y),也能夠是(x,3,4)

console.log function

console.log 不是一個綁定,它包含了一個句號,對吧,變量名是不容許的,那它是什麼呢? console是一個綁定,.log表明檢索這個綁定的一個名爲log的property。

Return values

side effect: 函數的主要做用是用來返回一個值的,那麼顯示一段文字,彈出對話框叫作反作用。(這是函數定義,不是寫代碼的目的。。)

因爲函數要return一個結果,因此它符合一個expression,也就是能夠和另外的expression組合嵌套在一句statement中。console.log(Math.min(1, 3)+1);

Control flow

straight-line

prompt中輸入的值爲string,用Number()轉換成number進行計算,其實不用人工轉化下一行計算的時候會自動轉換類型的。。

conditional execution

原先一條路走到黑,如今變成二選一,而後繼續走下面的語句。

if (1 + 1 == 2) console.log("It's true");

通常狀況下代碼要寫的讓人看着方便,大部分都要加大括號,除非一行簡單的if。

多重嵌套的時候每次當下判斷都是二選一,注意合併簡化

while & do loop

2^10 (2 to the 10th power)

do while,for 和 while(){}的區別就是語義上的

  • 最少幹一次用do while
  • 先判斷條件再作就用while
  • for和wihle大部分等價
    • 將三個東西做爲一個總體劃分比while強一點。哪三個東西呢?第一個初始化條件就是while以前的語句,第二個判斷條件就是while的判斷條件,最後一個執行語句能夠放在while執行語句的最後。
    • break時都是直接退出
    • 小部分的區別在於continue
      • while會直接跳事後面代碼從新判斷,
      • for會直接跳事後面的代碼,執行第三條語句後再去從新判斷。

縮進空格、換行

純粹爲了易讀性,一個仍是兩個空格、一個仍是多個換行都不影響它的邏輯。但換不換行是有區別的,參考加不加;的各類規則。

for循環

明明有while爲何還要發明for呢?由於經常使用啊,由於代碼邏輯驚人的類似重複的部分人就願意把它封裝成新東西,輕輕一揮,魔法就實現了,多好。

for和while的區別就是while常常須要一個循環計數器,而且每輪循環都必需要改動這個循環計數器,再加上原本的條件判斷,這不就是for啦?

break continue

for中不設循環終止判斷,就能夠把判斷挪到循環體中if(){*; *; break;}中,這樣的好處是判斷完以後能夠繼續在for的環境中執行一些語句。也就是說若是有if(){break;},考慮是否能夠放在for語句頭中?

break 若是用原來的機制實現,至關於在原代碼前加了個if,而且添加的一個分支只有一個改變循環條件、而且還要抵消for第三個語句(若是在for語句中)。你看現在你想要的,一個break就能夠解決了。

continue表明着跳過這句以後的循環體中的代碼,執行for第三部分(若是在for語句中)。而後繼續下一次循環

continue 若是用原來機制實現,至關於原代碼前添加if,而且添加的一個分支什麼都不作,而且還要抵消for第三個語句(若是在for語句中)。

這兩個一個是直接跳出全部循環,一個跳出當前這一輪循環。

updating bindings succinctly

當一個變量的變化是由原來的本身進行更新的話,能夠直接在賦值的時候指定什麼操做

counter = counter +1;

// 更簡便的自我更新

counter += 1;

// 對於自個人加一減一,還能夠縮寫

counter ++;

// 在Java中 a++ 和 ++a 實現不同,返回的對象是一份原a對象的拷貝對象,因此值和原a同樣,後者返回的是加完的a對象,因此相對於原a值加一。在JS中還不知道啥實現

複製代碼

switch

注意default:break:,雖然不少時候工具會自動猜想你的意圖,幫你補全一些不嚴謹的邏輯。

一個多狀態的值 --> 多重if的代碼的簡寫模式 --> 就是switch。。經常使用的東西纔會被髮明。

多對多的怎麼辦呢?用函數封裝一下,多個輸入值,每一個值有多個狀態,隨你所願的組合判斷,

Capitalization

  1. 全小寫不易讀
  2. 下劃線,打字多太累
  3. 全大寫是函數綁定的構造器
  4. 駝峯風格是慣例。。

一切都是有緣由的,可是全部的事咱們真的須要知道緣由?

註釋

有時候代碼並不能層次鮮明的由淺入深,由全局到定位局部信息的導航功能,這個時候須要註釋來快速定位一大坨只有一個名字的代碼究竟是幹什麼的。

VS Code中快捷鍵Ctrl /能夠快速判斷當前是HTML仍是CSS仍是JavaScript仍是JSX代碼進行插入相應的註釋。

注意多行嵌套沒有實現(不是不能實現)交錯的處理。以及 //右邊全算做註釋

練習

Looping a triangle

Q1:

#
##
###
####
#####
######
#######
複製代碼

A1:

for (let i=0, j=""; i<7; i++) {
  j += "#";
  console.log(j);
}
複製代碼
  • i僅表明從第1到第7行,而這每一行打印出幾個的井號跟i無關(至少在這題裏面,有的題打印的星星數量可能跟行號有數學關係),這7行每行打印出什麼跟j的自加有關。
  • 固然這兩行代碼由於每次迭代都執行了,能夠跟i++放在一塊兒,看我的喜愛了,放在那裏太擠太醜了不是麼。。
  • 僅從該題目的話,這些代碼是合格的,但若是考慮擴展性(好比用戶指定打多少行、每行的#的數量表達式),效率(是一次性打印,仍是逐行打印)等等,就會有不一樣的解法

Q2:

print number 1-100, 3的倍數用Fizz代替,5的倍數用Buzz代替,如果3和5的倍數,用15代替
複製代碼

A2:

for(let i=1; i<=100; i++) {
  let result = new Array();
  let string = "";
  let number = i;
  if(i%3===0) {
    string += "Fizz";
  }
  if(i%5===0) {
    string += "Buzz";
  }
  result.push(string||number);
  for(let i of result) {
    console.log(i);
  }
}
複製代碼
  • if 沒有else的時候,要注意else和if以後的語句不同,由於else是作過判斷的,跟判斷有關就加個else,跟判斷無關,就不加else,直接在後面寫。
  • i表明着迭代多少次,number其實能夠直接用i不用新建,由於正好題目是1到100,但若是是100到200呢?這就須要將打印的次數和打印的數字區分開,雖然有時候他們值相等不用新建變量,但他們語義是不同的!
  • 首先3和5的倍數是兩個原子不可拆的操做,而15不是,因此再添加分支判斷15就很浪費,而15實際上就是3和5字符的相加,因此每一步字符是加操做。其次根據JavaScrit||的奇葩設定正好幫助返回FizzBuzz或者備胎數字,納愛斯。
  • 將全部結果的數據存到了數組中,不只合乎打印出的結果,更容易往後其餘操做,雖然只是一道題,不須要啥擴展性 Q3:
Chessboard
 # # # #
# # # # 
 # # # #
# # # # 
 # # # #
# # # # 
 # # # #
# # # #
複製代碼

A3:

// 若將該功能封裝爲函數,下面就是輸入的參數變量,n階矩陣,兩種要打印的符號
  let size = 8;
  let symbol1 = " ";
  let symbol2 = "#";
// 橫向重複的最小單元,要進行O(logn) 替換掉 O(n)的重複字符的複製
  let symbolSum = symbol1 + symbol2;
  let contentOddAddon = size%2==1?symbol1:"";
  let contentOdd = repeatCharBinary(symbolSum, Math.floor(size/2)) + contentOddAddon;
// 求偶數行的內容,由奇數行內容轉換,踢掉第一個,再根據最後一個添加適當元素
  let contentEvenArray = contentOdd.split("");
  contentEvenArray.shift();
  contentEvenArray.push((contentEvenArray[contentEvenArray.length-1]==symbol1)?symbol2:symbol1);
  let contentEven = contentEvenArray.join("");
// 縱向重複的最小單元,要進行O(logn) 替換掉 O(n)的重複字符的複製
  let contentTwoLine = contentOdd + "\n" + contentEven + "\n";
  let contentOddLineAddon = size%2===1?contentOdd:"";
  let content = repeatCharBinary(contentTwoLine, Math.floor(size/2)) + contentOddLineAddon;
  console.log(content);
// 重複某個字符的nlog函數
  function repeatCharBinary(char, n) {
    // 這個地方須要進行重複計算字符,能夠將代碼封裝爲一個函數
    // 線性複製char更改成指數級複製
    // 將十進制n轉換爲二進制,除2取餘,對應一、二、四、八、16的權重
    // 餘1則存在,那麼將結果加上對應的權重數量的字符串,餘0則表示不存在,那就不加這位的字符串。
    let tmp="";
    while(n!=0) {
      if(n%2==1) {
        tmp += char;
      }
      n = parseInt(n/2); //結果從低位算起,餘1則存在,結果加之,繼續除2餘1,算更高一位
      // 以前算每一位的權重是否存在,這一行是算若是存在,對應的字符串長多少,每輪要變爲更低位字符串的兩倍。
      char += char; 
    }
    return tmp;
  }
複製代碼
  • 題目兩種不一樣的符號,交叉成字符串,寬n,高n,奇偶互異,思路是
  1. 計算出第一行的內容
  2. 模式就是兩個字符的屢次重複
  3. 偶數n/2次
  4. 基數n/2次+第一個字符
  5. 這麼屢次的重複採用8421的指數翻倍相加?
  6. 第一行內容隊列一進一出造成第二行內容
  7. 第一二行做爲一個重複的最小單位,而後屢次指數重複輸出
  8. 取整要用Math.floor(),默認的取整不知實現機制,不過老是少一個,很奇怪。

Function

搞計算機的是天才麼?不是的,你們只是站在巨人的肩膀上,用別人生產的磚塊壘起本身想要的一面牆。

函數是個很重要的內容。它將一大塊代碼濃縮成一個名字。這樣能夠分解一個大問題的龐大代碼,變成幾個小問題的代碼,只抽象出變化的部分,稱之爲輸入,而後輸入參數進入一樣邏輯的代碼中,產生不一樣的輸出。而僅僅只須要用一個函數名字就能夠關聯變量和邏輯。

優秀的文章常常經過不一樣的詞彙乃至精妙絕倫的句子來體現文章的藝術,而編程不一樣,它只在意能幹成什麼事,因此可能不那麼動聽引人入勝。

編程中的語言就像公式同樣枯燥,但若是懂了就明白它的精確和簡潔,不一樣於文章給人精神上的享受,相似的帳單也很枯燥,但它精確簡潔,而且你須要它的時候,他能知足你。

defining a function

let area = function(x,y) { return x*y;}
複製代碼

能夠經過綁定到一個變量來重複調用這個函數。

函數功能

  1. 輸入
  2. 能夠沒有
  3. 輸出
  4. 沒有return,執行完默認return 一個undefined
  5. return
  6. return; 則return 一個undefined
  7. side effect

bindings and scopes

做用域只在當前以及子做用域

  1. Global
  2. Local bindings
  3. 函數的每次調用都會新建一個實例
  4. ES2015前,只有函數能建立新做用域(好比if for 都默認是global)
  5. ES2015後,JS的做用域終於和C++Java等正常語言一致了。。

nested scope

多級嵌套,lexical scoping

Functions as values

let hi = function() {

};
複製代碼

新建變量的函數綁定,要加分號。

函數綁定的名字呢,還能夠被綁定爲別的東西,因此變量並非函數,他只是個指示器,表明着它可能指向一個數字、字符串、或者是函數等。

declaration notation

聲明式的函數標記, 僅僅聲明,不進行調用

function hello() {}
複製代碼
  1. 能夠不用加分號
  2. 這種函數在執行的時候會挪到當前做用域的最開始部分優先於其餘代碼

arrow function

let hey = (x, y) => {

};
複製代碼

省略了函數名,而後直接將輸入指向輸出。

const square = x => x*x;
複製代碼
  1. 只有1個變量的時候能夠省略括號
  2. 只有一行語句要return的話,能夠省略大括號。 涉及到知識點
  3. 遞歸
  • 要有終止條件
  • 爲了學習語法的demo並不表明最優解
  • 既然出現遞歸和循環均可以乾的事, 那麼說明他們有擅長的方面, 否則沒價值的東西不會出如今語言中,可是對於某個問題可能只有其中一種是最好的.
  1. 邊界條件, 特殊值的覆蓋
  • 關鍵要找到典型的例子進行覆蓋測試.
  • 若是例子不用典型表明, 測試的次數太多.
  • 若是例子選出來了但表明不典型, 覆蓋的範圍太少.
  1. 有問題先Google
  • 那裏是廣闊的海洋, 數不清的金銀混雜着狗屎, 但通常而言被篩選到第一頁搜索結果的都是上好的金子.
  1. 默認參數
  2. 箭頭函數
  3. for中的i,默認爲循環計數器
  • 若是用它作了別的含義,最好新建變量,使之修改的時候更容易
  1. 統一的函數結果的返回接口, 容易修改和閱讀
  • 但在屢次遞歸中直接無論進入到哪一個分支中都進行return, 是否是語義更清晰些?少繞一步賦值給結果,再return出去.
  • 可是若是是複雜的大量代碼有利於定位return值的變化
  1. 拼寫錯誤, WTF!
  2. 再小的代碼, 實現起來也是要費些周折, 不要忽略實際問題而想固然
  3. 語法是會慢慢進化幫助開發者的, 好比最後一個參數以後也能夠加,, 方便往後直接添加.
let power = (base, exponent=2) => {
  let result;
  if(exponent===0) {
    result = 1;
  } else {
    result = base * power(base, exponent-1);
  }
  return result;
};
console.log(
  power(0),
  power(1),
  power(0,3),
  power(2,1),
  power(2,2),
  power(3),
  power(100,3),
  power(100,100),
);
複製代碼

the call stack

函數執行完會把return的值返還到調用它的地方,而後繼續執行接下來的代碼。

call stack: 函數調用的棧

  1. 每調用一個新的就會push進一個新的函數執行完再pop出去
  2. 這個棧是佔內存空間的,若是調用、遞歸的函數太多,棧會溢出

optional arguments

給一個函數多個參數,而函數只要一個的時候,它就取一個而且繼續執行。

而若是給的函數不夠,那麼默認undefined

怎麼樣JS是否是很混蛋?若是程序員無心寫錯了,他都不報錯?

那若是有意寫成這樣,至關於C++ 中的函數重載。

function minus(a, b) {
  if (b === undefined) return -a;
  else return a - b;
}
複製代碼

哇,好牛逼。。這樣居然實現了減法。。一個數就是減本身,兩個就是互相減

默認參數, 另外指定了則按指定的,無指定的按默認的

funcition area(redius, PI=3.14) {

}
複製代碼

closure

ES2015後改成了正常編程語言的做用域規則。chain scope,子能夠看到父,父不可看到子的變量。 ES2015以前只有function有本身的做用域。

ES2015以前定義的var關鍵字最好用let, const代替。 若是沒有關鍵字直接hi="hi",默認是全局做用域

內部做用域能夠調用父做用域的變量,那麼若是父做用域想調用子做用域呢?

讓父函數新建一個子函數,並return這個子函數,子函數能夠訪問它爹,而後新建一個變量承接父函數,這個變量就綁定到了return回的子函數。

若是這個變量是全局的,那麼這個子函數不會回收,因此它爹也不會回收。

一樣不用var新建的變量若是綁定到了一個函數,也是全局變量,不會回收。

因此注意垃圾滿了。儘可能不須要不要用閉包。

這樣引用一個函數實例的一個子函數,稱之爲一個閉包。

recursion

函數調用本身叫作遞歸,遞歸要有終止條件return出去.

  1. 遞歸實現比for 更慢3倍。。由於調用函數須要的資源更多。
  2. 注意棧溢出

優雅和速度老是頗有趣的矛盾體。。。更快意味着可能只顧目的,吃相很差看。更好看,可能顧及的就多,妨礙了核心目標。因此以切都是權衡,哪些必需要快不顧長相,快到什麼程度?一樣相反。

一個程序的項目實現的地方有不少,市場、宣傳、技術、組織。而技術之中也有開發效率、機器效率、用戶友好、利於維護等一堆互相矛盾的點,要抓住主要的目的,勢必就要在其餘點上妥協。你不可能用最快的開發速度,寫出最省機器資源、最利於維護擴展、最對用戶友好的代碼。

機器的快速發展,因此程序語言愈來愈高級,庫愈來愈順手,你若是總是擔憂這個地方性能很差,那麼你想一想你有沒有由於自由的呼吸空氣而以爲浪費,有沒有以爲本身跑步呼吸了更多的空氣而浪費?爲何?由於空氣與你的呼吸相比很廉價,一樣在機器相對於人的時間很廉價。因此那段代碼利於你快速理解閱讀、而且最快的知足功能,爲何要去省下幾口空氣呢?除非你掉進了水裏(機器運行吃力),當空氣(性能)真的很重要的時候,再去考慮空氣(性能)。

甚至不少你覺的必定要優化的東西根本不值一提, 就像你多呼吸了兩口空氣同樣可有可無.你收貨的只有沾沾自喜覺得賺到了, 但你失去的是你最寶貴的生命的幾分鐘甚至幾個小時.

還有不少時候你所謂的優化在優化編譯器的專業大師前不值一提, 它甚至原本能夠大幅度自動優化的地方,被你用生澀的奇技淫巧僅僅優化了一點.不要自做聰明, 衡量你生命的付出和換來的產出而不只僅是沾沾自喜.

求從1開始, [+5]或者[*3]獲得指定數的方法

遞歸在多分支的時候實現起來比循環更好。

  1. 注意遞歸終結條件, 只能是成功或者找不到, 而不是最短路徑.
  2. 本質就是暴力嘗試...只不過用遞歸寫起來簡單.
  3. 只有反引號裏才能透明寫string模板, ${ variable }添加變量.
  4. 兩個參數一個做爲計算, 一個做爲輸出展現. 這是兩個信息, 畢竟沒辦法打印一個算式的過程? 目前孤陋寡聞...
  5. 運用遞歸要找到問題中能轉化成初始條件, 層層相扣重複的邏輯, 以及終結條件, 不要用過程來思考, 容易卡死..
let findIt = target => {
      function find(current, history) {
        let result;
        if (current === target) {
          result = history;
        } else if (current > target) {
          result = null;
        } else {
          result =  find(current + 5, `(${history} + 5)`) || 
                    find(current * 3, `(${history} * 3)`);
        }
        return result;
      }
      return find(1, "1");
    }
複製代碼

growing functions

個人函數, 進化吧! 通常對函數的需求體如今兩方面:

  1. 重複的代碼段,很大程度上邏輯類似.若是不用函數的隱患有:
  2. 最重要的不是性能, 而是屢次重複的代碼更大可能的出錯.
  3. 其次是代碼太多不利於人閱讀.
  4. 再而後纔會是機器性能的浪費.
  5. 你覺的那應該有這麼一個函數,儘管還沒寫,預先的感受.

農場初版

  1. 007 Cows
  2. 011 Chickens

即保證數字爲3, 真的是聞所未聞, 用字符串長度來作判斷讓其一次次的加0直到3位...

function printFarmInventory(cows, chickens) {
  let cowString = String(cows);
  while (cowString.length < 3) {
    cowString = "0" + cowString;
  }
  console.log(`${cowString} Cows`);
  let chickenString = String(chickens);
  while (chickenString.length < 3) {
    chickenString = "0" + chickenString;
  }
  console.log(`${chickenString} Chickens`);
}
printFarmInventory(7, 11);
複製代碼

農場第二版

當咱們在農場又加了豬以後, 重複的代碼塊被封裝再一個函數裏面.

function printZeroPaddedWithLabel(number, label) {
  let numberString = String(number);
  while (numberString.length < 3) {
    numberString = "0" + numberString;
  }
  console.log(`${numberString} ${label}`);
}

function printFarmInventory(cows, chickens, pigs) {
  printZeroPaddedWithLabel(cows, "Cows");
  printZeroPaddedWithLabel(chickens, "Chickens");
  printZeroPaddedWithLabel(pigs, "Pigs");
}

printFarmInventory(7, 11, 3);
複製代碼

函數名字又臭又長, 這裏面有幾個基本的信息

  • 數字補全到多少位(width), 須要補全(width-length)位, 可能農場變大以後就上千上萬須要更多位
  • 數字自己是多少(number), 它的範圍若是超過寬度呢呢?
  • 用什麼補全會變化麼? 不會..由於是用0, 這叫封裝不變的, 留出變化的接口(函數的輸入)

一個名字意思精煉小巧的函數, 不只能讓人一眼看出它的意義, 還可讓別的代碼複用這種基礎的函數, 想想語言自己帶的那些經常使用的函數, 越是通用越是功能單一, 越是能夠互相組合成爲更強大的功能. 想想鍵盤上的26個字符, 不只能打出成千上萬的英文文章, 還能打出漢字來.靠的就是抽象基本模式,使得記憶負擔小, 這叫討人喜歡, 而後互相組合能打出各類字, 這叫功能強大. 若是是無心義的編碼,你要記2k多種沒有規律的基本漢字的編碼, 不討人喜歡吧, 雖然功能也強大.

原則: 不要亂優化修改, 除非你明確的須要, 否則一切從簡,快,正確. 當你僅僅須要一朵花的時候, 那就不要又設置花盆又設置肥料. 要控制住你的衝動, 你優化的東西可能和你的目的沒多大關聯, 僅僅是來自基因的傾向性要讓你熟悉探索周圍環境, 由於你可能沒寫多少有意義的代碼, 卻浪費時間改來改去讓機器內存下降0.01%?那主要任務怎麼辦?

function zeroPad(number, width) {
  let string = String(number);
  while (string.length < width) {
    string = "0" + string;
  }
  return string;
}

function printFarmInventory(cows, chickens, pigs) {
  console.log(`${zeroPad(cows, 3)} Cows`);
  console.log(`${zeroPad(chickens, 3)} Chickens`);
  console.log(`${zeroPad(pigs, 3)} Pigs`);
}

printFarmInventory(7, 16, 3);
複製代碼

functions and side effects

函數能夠用來return一個有用的值, 也能夠side effect.

但主要做用在return的值上面的函數, 才能夠有用的和其餘函數組合.爲啥? 由於side effect就在函數內部, 輸出的信息無法傳給別的函數.

pure function: 不依賴其餘代碼的side effect, 也不輸出本身的side effect. 每次一樣的輸入, 都會產生一樣的輸出. 一個純函數若是在某一個地方正常, 那麼它在全部地方都正常.而其餘函數可能依賴的環境不一樣.

在機器朝向純數學發展的路上時, 純函數確定擁有更嚴謹有效的解決問題的能力, 但... 機器並非純粹的理想產物,它被現實一代代所創造優化, 因此用面向機器的代碼在目前每每更高效, 而純函數做爲高級抽象, 耗費更多的資源.

Exercise

min()函數

let min = (x,y) => x>y?y:x;
複製代碼

recusion isEven()函數

基本:

  1. 0是偶數
  2. 1是奇數 演繹: N的奇偶性和N-2是同樣的 終止條件: n=0或n=1;
let isEven = (x) => {
  let result;
  switch(x) {
    case 0:
      result = false;
      break;
    case 1:
      result = true;
      break;
    default:
      result = isEven(x>0?x-2:x+2);
      break; 
  }
  return result;
};
console.log(isEven(50));
// → true
console.log(isEven(75));
// → false
console.log(isEven(-1));
// → ??
// 這個屢次判斷正負的語句能夠提早置於外部, 講這個函數封閉爲閉包訪問靜態變量, 使判斷正負只運行一次.
複製代碼

B counting

let countChar = (string, char) => {
      let cnt = 0;
      for(let i=0; i<string.length; i++) {
        if(string[i] === char)cnt++;
      }
      return cnt;
    }
    console.log(countChar("HelHsdfklkjlkhloHi", "H"))
複製代碼
相關文章
相關標籤/搜索