摘要:復習基礎到底是什么的應用場合極其廣泛。常量不可以通過重新賦值改變其值,也不可以在代碼運行時重新聲明。布爾對象是原始布爾數據類型的一個包裝器整數整數可以用十進制基數為十六進制基數為八進制基數為以及二進制基數為表示。
復習js day1 js基礎 JavaScript 到底是什么
JavaScript 的應用場合極其廣泛。簡單到幻燈片、照片庫、浮動布局和響應按鈕點擊。復雜到游戲、2D 和 3D 動畫、大型數據庫驅動程序,等等。
JavaScript 相當簡潔,卻非常靈活。開發者們基于 JavaScript 核心編寫了大量實用工具,可以使 開發工作事半功倍。其中包括:
瀏覽器應用程序接口(API)—— 瀏覽器內置的 API 提供了豐富的功能,比如:動態創建 HTML 和設置 CSS 樣式、從用戶的攝像頭采集處理視頻流、生成3D 圖像與音頻樣本,等等。
第三方 API —— 讓開發者可以在自己的站點中整合其它內容提供者(Twitter、Facebook 等)提供的功能。
第三方框架和庫 —— 用來快速構建網站和應用。
變量 變量的定義和數據類型在應用程序中,使用變量來作為值的符號名。變量的名字又叫做標識符,其需要遵守一定的規則。
一個 JavaScript 標識符必須以字母、下劃線(_)或者美元符號($)開頭;后續的字符也可以是數字(0-9)。因為 JavaScript 語言是區分大小寫的,所以字母可以是從“A”到“Z”的大寫字母和從“a”到“z”的小寫字母。
變量 | 解釋 |
---|---|
string | 字符串(一串文本)。字符串的值必須將用引號(單雙均可,必須成對)擴起來。 |
Number | 數字。無需引號。 |
Boolean | 布爾值(真 / 假)。 true/false 是 JS 里的特殊關鍵字,無需引號。 |
Array | 數組,用于在單一引用中存儲多個值的結構 |
Object | 對象,JavaScript 里一切皆對象,一切皆可儲存在變量里。這一點要牢記于心。 |
字符串轉換為數字
有一些方法可以將內存中表示一個數字的字符串轉換為對應的數字。parseInt()和parseFloat()
parseInt 方法只能返回整數,所以使用它會丟失小數部分。另外,調用 parseInt 時最好總是帶上進制(radix) 參數,這個參數用于指定使用哪一種進制。
將字符串轉換為數字的另一種方法是使用一元加法運算符。
"1.1" + "1.1" = "1.11.1" (+"1.1") + (+"1.1") = 2.2 // 注意:加入括號為清楚起見,不是必需的。聲明變量
var 聲明全局變量和局部變量
let 聲明塊作用域的局部變量
const 聲明一個常量
使用var和let聲明的變量時沒有賦予初始值的,其值為undefined,
var a; console.log("The value of a is " + a); // a 的值是 undefined console.log("The value of b is " + b);// b 的值是 undefined var b; console.log("The value of c is " + c); // 未捕獲的引用錯誤: c 未被定義 let x; console.log("The value of x is " + x); // x 的值是 undefined console.log("The value of y is " + y);// 未捕獲的引用錯誤: y 未被定義 let y;
你可以使用 undefined 來判斷一個變量是否已賦值。在以下的代碼中,變量input未被賦值,因此 if 條件語句的求值結果是 true
var input; if(input === undefined){ doThis(); } else { doThat(); }
undefined 值在布爾類型環境中會被當作 false 。
數值類型環境中 undefined 值會被轉換為 NaN。
當你對一個 null 變量求值時,空值 null 在數值類型環境中會被當作0來對待,而布爾類型環境中會被當作 false
var n = null; console.log(n * 32); // 在控制臺中會顯示 0變量的作用域
在函數之外聲明的變量,叫做全局變量,因為它可被當前文檔中的任何其他代碼所訪問。在函數內部聲明的變量,叫做局部變量,因為它只能在當前函數的內部訪問。
ECMAScript 6 之前的 JavaScript 沒有 語句塊 作用域;相反,語句塊中聲明的變量將成為語句塊所在函數(或全局作用域)的局部變量。例如,如下的代碼將在控制臺輸出 5,因為 x 的作用域是聲明了 x 的那個函數(或全局范圍),而不是 if 語句塊。
if (true) { var x = 5; } console.log(x); // 5
如果使用 ECMAScript 6 中的 let 聲明,上述行為將發生變化。
if (true) { let y = 5; } console.log(y); // ReferenceError: y 沒有被聲明變量提升
JavaScript 變量的另一個不同尋常的地方是,你可以先使用變量稍后再聲明變量而不會引發異常。這一概念稱為變量提升;JavaScript 變量感覺上是被“提升”或移到了函數或語句的最前面。但是,提升后的變量將返回 undefined 值。因此在使用或引用某個變量之后進行聲明和初始化操作,這個被提升的變量仍將返回 undefined 值。
/** * 例子1 */ console.log(x === undefined); // true var x = 3; /** * 例子2 */ // will return a value of undefined var myvar = "my value"; (function() { console.log(myvar); // undefined var myvar = "local value"; })(); //詳細解釋就是 /** * 例子1 */ var x; console.log(x === undefined); // true x = 3; /** * 例子2 */ var myvar = "my value"; (function() { var myvar; console.log(myvar); // undefined myvar = "local value"; })();
由于存在變量提升,一個函數中所有的var語句應盡可能地放在接近函數頂部的地方。這個習慣將大大提升代碼的清晰度。
在 ECMAScript 6 中,let(const)將不會提升變量到代碼塊的頂部。因此,在變量聲明之前引用這個變量,將拋出引用錯誤(ReferenceError)。這個變量將從代碼塊一開始的時候就處在一個“暫時性死區”,直到這個變量被聲明為止。
console.log(x); // ReferenceError let x = 3;函數提升
對于函數來說,只有函數聲明會被提升到頂部,而函數表達式不會被提升。
/* 函數聲明 */ foo(); // "bar" function foo() { console.log("bar"); } /* 函數表達式 */ baz(); // 類型錯誤:baz 不是一個函數 var baz = function() { console.log("bar2"); };常量
你可以用關鍵字 const 創建一個只讀的常量。常量標識符的命名規則和變量相同:必須以字母、下劃線(_)或美元符號($)開頭并可以包含有字母、數字或下劃線。
const PI = 3.14;
常量不可以通過重新賦值改變其值,也不可以在代碼運行時重新聲明。它必須被初始化為某個值。
常量的作用域規則與 let 塊級作用域變量相同。若省略const關鍵字,則該標識符將被視為變量。
在同一作用域中,不能使用與變量名或函數名相同的名字來命名常量。
然而,對象屬性被賦值為常量是不受保護的,所以下面的語句執行時不會產生錯誤。
const MY_OBJECT = {"key": "value"}; MY_OBJECT.key = "otherValue";
同樣的,數組的被定義為常量也是不受保護的,所以下面的語句執行時也不會產生錯誤。
const MY_ARRAY = ["HTML","CSS"]; MY_ARRAY.push("JAVASCRIPT"); console.log(MY_ARRAY); //logs ["HTML","CSS","JAVASCRIPT"];字面量 (Literals)
譯注:字面量是由語法表達式定義的常量;或,通過由一定字詞組成的語詞表達式定義的常量
在JavaScript中,你可以使用各種字面量。這些字面量是腳本中按字面意思給出的固定的值,而不是變量。(譯注:字面量是常量,其值是固定的,而且在程序腳本運行中不可更改
數組字面量 (Array literals)數組字面值是一個封閉在方括號對([])中的包含有零個或多個表達式的列表,其中每個表達式代表數組的一個元素。當你使用數組字面值創建一個數組時,該數組將會以指定的值作為其元素進行初始化,而其長度被設定為元素的個數。
下面的示例用3個元素生成數組coffees,它的長度是3。
var coffees = ["French Roast", "Colombian", "Kona"]; var a=[3]; console.log(a.length); // 1 console.log(a[0]); // 3 //注意 這里的數組字面值也是一種對象初始化器。
若在頂層(全局)腳本里用字面值創建數組,JavaScript語言將會在每次對包含該數組字面值的表達式求值時解釋該數組。另一方面,在函數中使用的數組,將在每次調用函數時都會被創建一次。
數組字面值同時也是數組對象。有關數組對象的詳情請參見數組對象一文。
數組字面值中的多余逗號
(譯注:聲明時)你不必列舉數組字面值中的所有元素。若你在同一行中連寫兩個逗號(,),數組中就會產生一個沒有被指定的元素,其初始值是undefined。以下示例創建了一個名為fish的數組:
var fish = ["Lion", , "Angel"];
在這個數組中,有兩個已被賦值的元素,和一個空元素(fish[0]是"Lion",fish[1]是undefined,而fish[2]是"Angel";譯注:此時數組的長度屬性fish.length是3)。
如果你在元素列表的尾部添加了一個逗號,它將會被忽略。在下面的例子中,數組的長度是3,并不存在myList[3]這個元素(譯注:這是指數組的第4個元素噢,作者是在幫大家復習數組元素的排序命名方法)。元素列表中其它所有的逗號都表示一個新元素(的開始)。
注意:尾部的逗號在早期版本的瀏覽器中會產生錯誤,因而編程時的最佳實踐方式就是移除它們。
(譯注:而“現代”的瀏覽器似乎鼓勵這種方式,這也很好解釋原因。尾部逗號可以減少向數組的最后添加元素時,因為忘記為這最后一個元素加逗號 所造成的錯誤。)
var myList = ["home", , "school", ];
在下面的例子中,數組的長度是4,元素myList[0]和myList[2]缺失(譯注:沒被賦值,因而是undefined)。
var myList = [ , "home", , "school"];
再看一個例子。在這里,該數組的長度是4,元素myList[1]和myList[3]被漏掉了。(但是)只有最后的那個逗號被忽略。
var myList = ["home", , "school", , ];
理解多余的逗號(在腳本運行時會被如何處理)的含義,對于從語言層面理解JavaScript是十分重要的。但是,在你自己寫代碼時:顯式地將缺失的元素聲明為undefined,將大大提高你的代碼的清晰度和可維護性。
布爾字面量 (Boolean literals)布爾類型有兩種字面量:true和false。
不要混淆作為布爾對象的真和假與布爾類型的原始值true和false。布爾對象是原始布爾數據類型的一個包裝器
整數 (Integers)整數可以用十進制(基數為10)、十六進制(基數為16)、八進制(基數為8)以及二進制(基數為2)表示。
十進制整數字面量由一串數字序列組成,且沒有前綴0。
八進制的整數以 0(或0O、0o)開頭,只能包括數字0-7。
十六進制整數以0x(或0X)開頭,可以包含數字(0-9)和字母 a~f 或 A~F。
二進制整數以0b(或0B)開頭,只能包含數字0和1。
嚴格模式下,八進制整數字面量必須以0o或0O開頭,而不能以0開頭。
整數字面量舉例:
0, 117 and -345 (十進制, 基數為10)對象字面量 (Object literals)
015, 0001 and -0o77 (八進制, 基數為8)
0x1123, 0x00111 and -0xF1A7 (十六進制, 基數為16或"hex")
0b11, 0b0011 and -0b11 (二進制, 基數為2)
對象字面值是封閉在花括號對({})中的一個對象的零個或多個"屬性名-值"對的(元素)列表。你不能在一條語句的開頭就使用對象字面值,這將導致錯誤或產生超出預料的行為, 因為此時左花括號({)會被認為是一個語句塊的起始符號。(譯者:這 里需要對語句statement、塊block等基本名詞的解釋)
以下是一個對象字面值的例子。對象car的第一個元素(譯注:即一個屬性/值對)定義了屬性myCar;第二個元素,屬性getCar,引用了一個函數(即CarTypes("Honda"));第三個元素,屬性special,使用了一個已有的變量(即Sales)。
var Sales = "Toyota"; function CarTypes(name) { return (name === "Honda") ? name : "Sorry, we don"t sell " + name + "." ; } var car = { myCar: "Saturn", getCar: CarTypes("Honda"), special: Sales }; console.log(car.myCar); // Saturn console.log(car.getCar); // Honda console.log(car.special); // Toyota
更進一步的,你可以使用數字或字符串字面值作為屬性的名字,或者在另一個字面值內嵌套上一個字面值。如下的示例中使用了這些可選項。
var car = { manyCars: {a: "Saab", "b": "Jeep"}, 7: "Mazda" }; console.log(car.manyCars.b); // Jeep console.log(car[7]); // Mazda
對象屬性名字可以是任意字符串,包括空串。如果對象屬性名字不是合法的javascript標識符,它必須用""包裹。屬性的名字不合法,那么便不能用.訪問屬性值,而是通過類數組標記("[]")訪問和賦值。
var unusualPropertyNames = { "": "An empty string", "!": "Bang!" } console.log(unusualPropertyNames.""); // 語法錯誤: Unexpected string console.log(unusualPropertyNames[""]); // An empty string console.log(unusualPropertyNames.!); // 語法錯誤: Unexpected token ! console.log(unusualPropertyNames["!"]); // Bang!字符串字面量 (String literals)
字符串字面量是由雙引號(")對或單引號(")括起來的零個或多個字符。字符串被限定在同種引號之間;也即,必須是成對單引號或成對雙引號。下面的例子都是字符串字面值:
"foo" "bar" "1234" "one line another line" "John"s cat"
你可以在字符串字面值上使用字符串對象的所有方法——JavaScript會自動將字符串字面值轉換為一個臨時字符串對象,調用該方法,然后廢棄掉那個臨時的字符串對象。你也能用對字符串字面值使用類似String.length的屬性:
console.log("John"s cat".length) // 將打印字符串中的字符個數(包括空格) // 結果為:10
在ES2015中,還提供了一種模板字符串(template literals),模板字符串提供了一些語法糖來幫你構造字符串。這與Perl、Python還有其他語言中的字符串插值(string interpolation)的特性非常相似。除此之外,你可以在通過模板字符串前添加一個tag來自定義模板字符串的解析過程,這可以用來防止注入攻擊,或者用來建立基于字符串的高級數據抽象。
// Basic literal string creation `In JavaScript " " is a line-feed.` // Multiline strings `In JavaScript this is not legal.` // String interpolation var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?` // Construct an HTTP request prefix is used to interpret the replacements and construction POST`http://foo.org/bar?a=${a}&b=${b} Content-Type: application/json X-Credentials: ${credentials} { "foo": ${foo}, "bar": ${bar}}`(myOnReadyStateChangeHandler);
除非有特別需要使用字符串對象,否則,你應當始終使用字符串字面值。
js流程控制與錯誤處理 條件判斷語句條件判斷語句指的是根據指定的條件所返回的結果(真或假或其它預定義的),來執行特定的語句。JavaScript 支持兩種條件判斷語句:if...else和switch。
let iceCream = "chocolate"; if (iceCream === "chocolate") { alert("我最喜歡巧克力冰激淋了。"); } else { alert("但是巧克力才是我的最愛呀……"); }異常處理語句
你可以用 throw 語句拋出一個異常并且用 try...catch 語句捕獲處理它。
throw 語句
try...catch 語句
Promises從 ECMAScript 6 開始,JavaScript 增加了對 Promise 對象的支持,它允許你對延時和異步操作流進行控制。
Promise 對象有以下幾種狀態:
pending:初始的狀態,即正在執行,不處于 fulfilled 或 rejected 狀態。
fulfilled:成功的完成了操作。
rejected:失敗,沒有完成操作。
settled:Promise 處于 fulfilled 或 rejected 二者中的任意一個狀態, 不會是 pending。
展現了 Promise 的工作流
function imgLoad(url) { return new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); request.open("GET", url); request.responseType = "blob"; request.onload = function() { if (request.status === 200) { resolve(request.response); } else { reject(Error("Image didn"t load successfully; error code:" + request.statusText)); } }; request.onerror = function() { reject(Error("There was a network error.")); }; request.send(); }); }循環和迭代
JavaScript中提供了這些循環語句:
for 語句
do...while 語句
while 語句
labeled 語句
break 語句
continue 語句
for...in 語句
for...of 語句
for 語句一個for循環會一直重復執行,直到指定的循環條件為fasle。 JavaScript的for循環和Java與C的for循環是很相似的。
for ([initialExpression]; [condition]; [incrementExpression])
statement
當一個for循環執行的時候,會發生以下事件:
如果有初始化表達式initialExpression,它將被執行。這個表達式通常會初始化一個或多個循環計數器,但語法上是允許一個任意復雜度的表達式的。這個表達式也可以聲明變量。
計算condition表達式的值。如果condition的值是true,循環中的statement會被執行。如果condition的值是false,for循環終止。如果condition表達式整個都被省略掉了,condition的值會被認為是true。
循環中的 statement被執行。如果需要執行多條語句,可以使用塊 ({ ... })來包裹這些語句。
如果有更新表達式incrementExpression,執行它.
然后流程回到步驟2。
do...whiledo...while 語句一直重復直到指定的條件求值得到假(false)。 一個 do...while 語句看起來像這樣:
do statement while (condition);
statement 在檢查條件之前會執行一次。要執行多條語句(語句塊),要使用塊語句 ({ ... }) 包括起來。 如果 condition 為真(true),statement 將再次執行。 在每個執行的結尾會進行條件的檢查。當 condition 為假(false),執行會停止并且把控制權交回給 do...while 后面的語句。
在下面的例子中, 這個 do 循環將至少重復一次并且一直重復直到 i 不再小于 5。
do { i += 1; console.log(i); } while (i < 5);while 語句
一個 while 語句只要指定的條件求值為真(true)就會一直執行它的語句塊。一個 while 語句看起來像這樣:
while (condition)statement
如果這個條件變為假,循環里的 statement 將會停止執行并把控制權交回給 while 語句后面的代碼。
條件檢測會在每次 statement 執行之前發生。如果條件返回為真, statement 會被執行并緊接著再次測試條件。如果條件返回為假,執行將停止并把控制權交回給 while 后面的語句。
要執行多條語句(語句塊),要使用塊語句 ({ ... }) 包括起來。
例子 1
下面的 while 循環只要 n 小于 3就會一直執行:
var n = 0; var x = 0; while (n < 3) { n++; x += n; }
在每次循環里, n 會增加1并被加到 x 上。所以, x 和 n 的變化是:
第一次完成后: n = 1 和 x = 1
第二次完成后: n = 2 和 x = 3
第三次完成后: n = 3 和 x = 6
在三次完成后, 條件 n < 3 結果不再為真,所以循環終止了。
例子 2
避免無窮循環(無限循環)。保證循環的條件結果最終會變成假;否則,循環永遠不會停止。下面這個 while 循環會永遠執行因為條件永遠不會變成假:
while (true) { console.log("Hello, world"); }函數
函數 用來封裝可復用的功能。如果沒有函數,一段特定的操作過程用幾次就要重復寫幾次,而使用函數則只需寫下函數名和一些簡短的信息
瀏覽器內置函數和用戶定義的函數
定義函數 函數聲明一個函數定義(也稱為函數聲明,或函數語句)由一系列的function關鍵字組成,依次為:
函數的名稱。
函數參數列表,包圍在括號中并由逗號分隔。
定義函數的 JavaScript 語句,用大括號{}括起來。
例如,以下的代碼定義了一個簡單的square函數:
function square(number) { return number * number; }
函數square使用了一個參數,叫作number。這個函數只有一個語句,它說明該函數將函數的參數(即number)自乘后返回。函數的return語句確定了函數的返回值:
return number * number;
原始參數(比如一個具體的數字)被作為值傳遞給函數;值被傳遞給函數,如果被調用函數改變了這個參數的值,這樣的改變不會影響到全局或調用函數。
如果你傳遞一個對象(即一個非原始值,例如Array或用戶自定義的對象)作為參數,而函數改變了這個對象的屬性,這樣的改變對函數外部是可見的,如下面的例子所示:
function myFunc(theObject) { theObject.make = "Toyota"; } var mycar = {make: "Honda", model: "Accord", year: 1998}; var x, y; x = mycar.make; // x獲取的值為 "Honda" myFunc(mycar); y = mycar.make; // y獲取的值為 "Toyota" // (make屬性被函數改變了)函數表達式
雖然上面的函數聲明在語法上是一個語句,但函數也可以由函數表達式創建。這樣的函數可以是匿名的;它不必有一個名稱。例如,函數square也可這樣來定義:
var square = function(number) { return number * number; }; var x = square(4); // x gets the value 16
然而,函數表達式也可以提供函數名,并且可以用于在函數內部代指其本身,或者在調試器堆棧跟蹤中識別該函數:
var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)}; console.log(factorial(3));
當將函數作為參數傳遞給另一個函數時,函數表達式很方便。下面的例子演示了一個叫map的函數如何被定義,而后使用一個表達式函數作為其第一個參數進行調用:
function map(f,a) { var result = [],i; //創建一個新數組 for (i = 0; i != a.length; i++) result[i] = f(a[i]); return result; }
下面的代碼:
function map(f, a) { var result = []; // 創建一個數組 var i; // 聲明一個值,用來循環 for (i = 0; i != a.length; i++) result[i] = f(a[i]); return result; } var f = function(x) { return x * x * x; } var numbers = [0,1, 2, 5,10]; var cube = map(f,numbers); console.log(cube);
返回 [0, 1, 8, 125, 1000]。
在 JavaScript 中,可以根據條件來定義一個函數。比如下面的代碼,當num 等于 0 的時候才會定義 myFunc :
var myFunc; if (num == 0){ myFunc = function(theObject) { theObject.make = "Toyota" } }
除了上述的定義函數方法外,你也可以在運行時用 Function 構造器由一個字符串來創建一個函數 ,很像 eval() 函數。
當一個函數是一個對象的屬性時,稱之為方法。了解更多關于對象和方法的知識 使用對象。
調用函數調用函數節
定義一個函數并不會自動的執行它。定義了函數僅僅是賦予函數以名稱并明確函數被調用時該做些什么。調用函數才會以給定的參數真正執行這些動作。例如,一旦你定義了函數square,你可以如下這樣調用它:
square(5);
上述語句通過提供參數 5 來調用函數。函數執行完它的語句會返回值25。
函數一定要處于調用它們的域中,但是函數的聲明可以被提升(出現在調用語句之后),如下例:
console.log(square(5)); /* ... */ function square(n) { return n*n }
函數域是指函數聲明時的所在的地方,或者函數在頂層被聲明時指整個程序。
提示:注意只有使用如上的語法形式(即 function funcName(){})才可以。而下面的代碼是無效的。就是說,函數提升僅適用于函數聲明,而不適用于函數表達式。
console.log(square); // square is hoisted with an initial value undefined. console.log(square(5)); // TypeError: square is not a function var square = function (n) { return n * n; }
函數的參數并不局限于字符串或數字。你也可以將整個對象傳遞給函數。函數 show_props(其定義參見 用對象編程)就是一個將對象作為參數的例子。
函數可以被遞歸,就是說函數可以調用其本身。例如,下面這個函數就是用遞歸計算階乘:
function factorial(n){ if ((n == 0) || (n == 1)) return 1; else return (n * factorial(n - 1)); }
你可以計算1-5的階乘如下:
var a, b, c, d, e; a = factorial(1); // 1賦值給a b = factorial(2); // 2賦值給b c = factorial(3); // 6賦值給c d = factorial(4); // 24賦值給d e = factorial(5); // 120賦值給e
還有其它的方式來調用函數。常見的一些情形是某些地方需要動態調用函數,或者函數的實參數量是變化的,或者調用函數的上下文需要指定為在運行時確定的特定對象。顯然,函數本身就是對象,因此這些對象也有方法(參考Function )。作為此中情形之一,apply()方法可以實現這些目的。
函數作用域在函數內定義的變量不能在函數之外的任何地方訪問,因為變量僅僅在該函數的域的內部有定義。相對應的,一個函數可以訪問定義在其范圍內的任何變量和函數。換言之,定義在全局域中的函數可以訪問所有定義在全局域中的變量。在另一個函數中定義的函數也可以訪問在其父函數中定義的所有變量和父函數有權訪問的任何其他變量。
// 下面的變量定義在全局作用域(global scope)中 var num1 = 20, num2 = 3, name = "Chamahk"; // 本函數定義在全局作用域 function multiply() { return num1 * num2; } multiply(); // 返回 60 // 嵌套函數的例子 function getScore() { var num1 = 2, num2 = 3; function add() { return name + " scored " + (num1 + num2); } return add(); } getScore(); // 返回 "Chamahk scored 5"作用域和函數堆棧 遞歸
一個函數可以指向并調用自身。有三種方法可以達到這個目的:
函數名
arguments.callee
作用域下的一個指向該函數的變量名
例如,思考一下如下的函數定義:
var foo = function bar() { // statements go here };
在這個函數體內,以下的語句是等價的:
bar()
arguments.callee()
foo()
調用自身的函數我們稱之為遞歸函數。在某種意義上說,遞歸近似于循環。兩者都重復執行相同的代碼,并且兩者都需要一個終止條件(避免無限循環或者無限遞歸)。例如以下的循環:
var x = 0; while (x < 10) { // "x < 10" 是循環條件 // do stuff x++; }
可以被轉化成一個遞歸函數和對其的調用:
function loop(x) { if (x >= 10) // "x >= 10" 是退出條件(等同于 "!(x < 10)") return; // 做些什么 loop(x + 1); // 遞歸調用 } loop(0);
不過,有些算法并不能簡單的用迭代來實現。例如,獲取樹結構中所有的節點時,使用遞歸實現要容易得多:
function walkTree(node) { if (node == null) // return; // do something with node for (var i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }
跟loop函數相比,這里每個遞歸調用都產生了更多的遞歸。
將遞歸算法轉換為非遞歸算法是可能的,不過邏輯上通常會更加復雜,而且需要使用堆棧。事實上,遞歸函數就使用了堆棧:函數堆棧。
這種類似堆棧的行為可以在下例中看到:
function foo(i) { if (i < 0) return; console.log("begin:" + i); foo(i - 1); console.log("end:" + i); } foo(3); // 輸出: // begin:3 // begin:2 // begin:1 // begin:0 // end:0 // end:1 // end:2 // end:3嵌套函數和閉包
你可以在一個函數里面嵌套另外一個函數。嵌套(內部)函數對其容器(外部)函數是私有的。它自身也形成了一個閉包。一個閉包是一個可以自己擁有獨立的環境與變量的的表達式(通常是函數)。
既然嵌套函數是一個閉包,就意味著一個嵌套函數可以”繼承“容器函數的參數和變量。換句話說,內部函數包含外部函數的作用域。
可以總結如下:
內部函數只可以在外部函數中訪問。
內部函數形成了一個閉包:它可以訪問外部函數的參數和變量,但是外部函數卻不能使用它的參數和變量。
下面的例子展示了嵌套函數:
function addSquares(a, b) { function square(x) { return x * x; } return square(a) + square(b); } a = addSquares(2, 3); // returns 13 b = addSquares(3, 4); // returns 25 c = addSquares(4, 5); // returns 41
由于內部函數形成了閉包,因此你可以調用外部函數并為外部函數和內部函數指定參數:
function outside(x) { function inside(y) { return x + y; } return inside; } fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it result = fn_inside(5); // returns 8 result1 = outside(3)(5); // returns 8多層嵌套函數
函數可以被多層嵌套。例如,函數A可以包含函數B,函數B可以再包含函數C。B和C都形成了閉包,所以B可以訪問A,C可以訪問B和A。因此,閉包可以包含多個作用域;他們遞歸式的包含了所有包含它的函數作用域。這個稱之為作用域鏈。(稍后會詳細解釋)
思考一下下面的例子:
function A(x) { function B(y) { function C(z) { console.log(x + y + z); } C(3); } B(2); } A(1); // logs 6 (1 + 2 + 3)
在這個例子里面,C可以訪問B的y和A的x。這是因為:
B形成了一個包含A的閉包,B可以訪問A的參數和變量
C形成了一個包含B的閉包
B包含A,所以C也包含A,C可以訪問B和A的參數和變量。換言之,C用這個順序鏈接了B和A的作用域
反過來卻不是這樣。A不能訪問C,因為A看不到B中的參數和變量,C是B中的一個變量,所以C是B私有的。
當同一個閉包作用域下兩個參數或者變量同名時,就會產生命名沖突。更近的作用域有更高的優先權,所以最近的優先級最高,最遠的優先級最低。這就是作用域鏈。鏈的第一個元素就是最里面的作用域,最后一個元素便是最外層的作用域。
看以下的例子:
function outside() { var x = 5; function inside(x) { return x * 2; } return inside; }
outside()(10); // returns 20 instead of 10
命名沖突發生在return x上,inside的參數x和outside變量x發生了沖突。這里的作用鏈域是{inside, outside, 全局對象}。因此inside的x具有最高優先權,返回了20(inside的x)而不是10(outside的x)。
閉包是 JavaScript 中最強大的特性之一。JavaScript 允許函數嵌套,并且內部函數可以訪問定義在外部函數中的所有變量和函數,以及外部函數能訪問的所有變量和函數。但是,外部函數卻不能夠訪問定義在內部函數中的變量和函數。這給內部函數的變量提供了一定的安全性。此外,由于內部函數可以訪問外部函數的作用域,因此當內部函數生存周期大于外部函數時,外部函數中定義的變量和函數將的生存周期比內部函數執行時間長。當內部函數以某一種方式被任何一個外部函數作用域訪問時,一個閉包就產生了。
var pet = function(name) { //外部函數定義了一個變量"name" var getName = function() { //內部函數可以訪問 外部函數定義的"name" return name; } //返回這個內部函數,從而將其暴露在外部函數作用域 return getName; }; myPet = pet("Vivie"); myPet(); // 返回結果 "Vivie"
實際上可能會比上面的代碼復雜的多。在下面這種情形中,返回了一個包含可以操作外部函數的內部變量方法的對象。
var createPet = function(name) { var sex; return { setName: function(newName) { name = newName; }, getName: function() { return name; }, getSex: function() { return sex; }, setSex: function(newSex) { if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) { sex = newSex; } } } } var pet = createPet("Vivie"); pet.getName(); // Vivie pet.setName("Oliver"); pet.setSex("male"); pet.getSex(); // male pet.getName(); // Oliver
在上面的代碼中,外部函數的name變量對內嵌函數來說是可取得的,而除了通過內嵌函數本身,沒有其它任何方法可以取得內嵌的變量。內嵌函數的內嵌變量就像內嵌函數的保險柜。它們會為內嵌函數保留“穩定”——而又安全——的數據參與運行。而這些內嵌函數甚至不會被分配給一個變量,或者不必一定要有名字。
var getCode = (function(){ var secureCode = "0]Eal(eh&2"; // A code we do not want outsiders to be able to modify... return function () { return secureCode; }; })(); getCode(); // Returns the secret code
盡管有上述優點,使用閉包時仍然要小心避免一些陷阱。如果一個閉包的函數用外部函數的變量名定義了同樣的變量,那在外部函數域將再也無法指向該變量。
var createPet = function(name) { // Outer function defines a variable called "name" return { setName: function(name) { // Enclosed function also defines a variable called "name" name = name; // ??? How do we access the "name" defined by the outer function ??? } } }箭頭函數 this 的詞法
在箭頭函數出現之前,每一個新函數都重新定義了自己的 this 值(在嚴格模式下,一個新的對象在構造函數里是未定義的,以“對象方法”的方式調用的函數是上下文對象等)。以面向對象的編程風格,這樣著實有點惱人。
function Person() { // The Person() constructor defines `this` as itself. this.age = 0; setInterval(function growUp() { // In nonstrict mode, the growUp() function defines `this` // as the global object, which is different from the `this` // defined by the Person() constructor. this.age++; }, 1000); } var p = new Person();
在ECMAScript 3/5里,通過把this的值賦值給一個變量可以修復這個問題。
function Person() { var self = this; // Some choose `that` instead of `self`. // Choose one and be consistent. self.age = 0; setInterval(function growUp() { // The callback refers to the `self` variable of which // the value is the expected object. self.age++; }, 1000); }
另外,創建一個約束函數可以使得 this值被正確傳遞給 growUp() 函數。
箭頭函數捕捉閉包上下文的this值,所以下面的代碼工作正常。
function Person(){ this.age = 0; setInterval(() => { this.age++; // |this| properly refers to the person object }, 1000); } var p = new Person();預定義函數
JavaScript語言有好些個頂級的內建函數:
eval()
eval()方法會對一串字符串形式的JavaScript代碼字符求值。
uneval()
uneval()方法創建的一個Object的源代碼的字符串表示。
isFinite()
isFinite()函數判斷傳入的值是否是有限的數值。 如果需要的話,其參數首先被轉換為一個數值。
isNaN()
isNaN()函數判斷一個值是否是NaN。注意:isNaN函數內部的強制轉換規則十分有趣; 另一個可供選擇的是ECMAScript 6 中定義Number.isNaN() , 或者使用 typeof來判斷數值類型。
parseFloat()
parseFloat() 函數解析字符串參數,并返回一個浮點數。
parseInt()
parseInt() 函數解析字符串參數,并返回指定的基數(基礎數學中的數制)的整數。
decodeURI()
decodeURI() 函數對先前經過encodeURI函數或者其他類似方法編碼過的字符串進行解碼。
decodeURIComponent()
decodeURIComponent()方法對先前經過encodeURIComponent函數或者其他類似方法編碼過的字符串進行解碼。
encodeURI()
encodeURI()方法通過用以一個,兩個,三個或四個轉義序列表示字符的UTF-8編碼替換統一資源標識符(URI)的某些字符來進行編碼(每個字符對應四個轉義序列,這四個序列組了兩個”替代“字符)。
encodeURIComponent()
encodeURIComponent() 方法通過用以一個,兩個,三個或四個轉義序列表示字符的UTF-8編碼替換統一資源標識符(URI)的每個字符來進行編碼(每個字符對應四個轉義序列,這四個序列組了兩個”替代“字符)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/110263.html
摘要:一個閉包就是當一個函數返回時,一個沒有釋放資源的棧區所以參數和變量不會被垃圾回收機制回收。使用不當會很容易造成內存泄露。最后,垃圾回收器完成內存清除工作,銷毀那些帶標記的值并回收它們所占用的內存空間。 1.什么是閉包?閉包有啥特性以及存在什么問題? 概念:閉包是指有權訪問另一個函數作用域中的變量的函數。下面的outer就形成了一個閉包: function outer(){ co...
摘要:語法和數據類型正文開始本章節復習的是中的基本語法,變量聲明,數據類型和字面量。聲明一個塊作用域的局部變量,可賦一個初始值。變量聲明有三種方式如,聲明局部變量和全局變量。 最近開始把精力放在重新復習JavaScript的基礎知識上面,不再太追求各種花枝招展的前端框架,框架再多,適合實際項目才是最重要。 上星期在掘金發布了幾篇文章,其中最大塊算是 【復習資料】ES6/ES7/ES8/ES...
摘要:前情提要本人是一個學渣非科班入行年了吧前端東西真的好多啊又不斷更新需要不斷的學學學在去年年底開始我就開始不斷的尋找學習的方法如何更加高效的學習如何才能學的又快又好在這半年來不斷的總結慢慢找到了一些方法和訣竅此文章不是網格布局的教學文章只前情提要 ??本人是一個學渣,非科班入行2年了吧,前端東西真的好多啊,又不斷更新.需要不斷的學學學, showImg(https://user-gold-c...
閱讀 1094·2021-11-22 14:56
閱讀 1533·2019-08-30 15:55
閱讀 3375·2019-08-30 15:45
閱讀 1667·2019-08-30 13:03
閱讀 2879·2019-08-29 18:47
閱讀 3343·2019-08-29 11:09
閱讀 2652·2019-08-26 18:36
閱讀 2626·2019-08-26 13:55