learnxinyminutes-docs 仓库中的 JavaScript 完整入门指南:从基础语法到原型链与 ES6
文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载本篇技术指南以本仓库 zh-tw/javascript.md及其英文原版 javascript.md为骨架系统讲解 JavaScript 这门语言的完整知识体系从数字、字符串与运算符到变量、数组与对象再到函数、作用域、闭包、原型链与 ES6 新语法。读完本文你将掌握 JavaScript 与 C/Java 在语法上的异同、其独特的对象模型原型继承以及现代 ES6 编码方式并能在 Node.js 或浏览器控制台中直接运行文中的所有示例进行验证。本仓库learnxinyminutes-docs的核心理念是把代码文档写成代码本身见 README.md每份教程都以可直接运行的带注释代码呈现边写代码边讲解。因此本文延续这一风格所有代码块均可复制到 Node.js 环境或浏览器开发者工具中逐行执行注释即讲解。一、JavaScript 的历史定位与运行环境JavaScript 由網景公司Netscape的布蘭登·艾克Brendan Eich於 1995 年創建。它最初被設計為一種更簡單的網站腳本語言用於補足 Java 在更複雜的網路應用程式中的使用但由於它與網頁的高度整合以及瀏覽器對它的原生支援使得它在網頁前端的應用遠比 Java 更加普及。JavaScript 並不僅限於網頁瀏覽器Node.js 提供了一個可執行 Google Chrome 的 V8 JavaScript 引擎的獨立運行環境正變得越來越熱門。這意味著本文的絕大多數示例既可以在瀏覽器控制台運行也可以透過node命令在終端執行——後者是驗證本文代碼最便捷的方式。JavaScript 具備類似 C 語言的語法所以若您曾使用過 C 或 Java許多基本語法會相當熟悉。但請特別注意雖然在語法和名稱上與 Java 很像JavaScript 的物件模型原型繼承卻與 Java 有顯著不同——這是本文後半部分的重點。二、註解、陳述式與分號// 這是單行註解 /* 這是 多行註解 */ // 陳述式可以用分號;終止 doStuff(); // ... 然而不一定要加分號當換行時會自動插入分號除了某些特殊情況。 doStuff() // 避免意外結果的情況本文會繼續使用分號這裡的自動插入分號Automatic Semicolon Insertion簡稱 ASI是初學者最容易踩的坑之一。ASI 的觸發時機與換行位置密切相關如果某一行以可能結束陳述式的字符結尾解析器會在換行處補入分號。後文第 5 節會展示一個由 ASI 導致的經典陷阱return換行導致回傳undefined請務必留意。三、數字、字串和運算子3.1 唯一的數字型別// JavaScript 只有一種數字型別也就是 64 位元 IEEE 754 雙精度浮點數。 // 雙精度浮點數有一個 52 位的尾數足以精確儲存整數最大至 9✕10¹⁵ 的整數。 3; // 3 1.5; // 1.5這是 JavaScript 與 C/Java 最大的差異之一沒有int、float、double之分一切數字都是雙精度浮點數。52 位尾數意味著超過9✕10¹⁵的整數可能無法精確表示這在處理大數例如時間戳、ID時要格外小心。3.2 基本算術與浮點精度// 所有基本算術運算都如您預期。 1 1; // 2 0.1 0.2; // 0.30000000000000004 8 - 1; // 7 10 * 2; // 20 35 / 5; // 7 // 包括無法整除的除法運算。 5 / 2; // 2.5 // 以及餘數運算。 10 % 2; // 0 30 % 4; // 2 18.5 % 7; // 4.5 // 位元運算也是如此當執行位元運算時浮點數會轉換「最多」32位元的有符號整數 1 2; // 4 // 以括號決定運算優先級。 (1 3) * 2; // 8注意0.1 0.2的結果是0.30000000000000004這是 IEEE 754 浮點數表示法的固有現象並非 JavaScript 的 bug任何使用雙精度浮點數的語言包括 Java 的double都會有同樣的表現。另外餘數運算%對浮點數同樣有效如18.5 % 7。位元運算時浮點數會被截斷為最多 32 位元的有符號整數。3.3 特殊數值與布林值// 有三個非數值的值 Infinity; // 1/0 的結果 -Infinity; // -1/0 的結果 NaN; // 0/0 的結果 // 也有布林值。 true; false;NaNNot a Number是非數值的結果注意NaN不等於自身NaN NaN為false判斷時應使用Number.isNaN()或全域isNaN()。3.4 比較、相等與型別強制轉型// 以驚嘆號!執行否定運算 !true; // false !false; // true // 相等運算 1 1; // true 2 1; // false // 不相等運算 ! 1 ! 1; // false 2 ! 1; // true // 更多比較運算子 1 10; // true 1 10; // false 2 2; // true 2 2; // true // 以加號()進行字串的串接 Hello world!; // Hello world! // 也可以串接字串以外的資料型別 1, 2, 3; // 1, 2, 3 Hello [world, !]; // Hello world,! // 這可能導致一些奇怪的行為 13 !0; // 14 13 !0; // 13true // 以 和 進行比較運算 a b; // true // 使用「兩個等號」做運算時會執行資料強制轉型 5 5; // true null undefined; // true // 除非使用「三個等號」 5 5; // false null undefined; // false這是 JavaScript 中最需要警惕的語義之一/!嚴格相等比較值與型別不會做任何強制轉型是推薦的寫法/!寬鬆相等會執行型別強制轉型type coercion例如5 5為true、null undefined為true容易產生非直覺的結果。同時用於數值加法與字串串接當操作數中混有字串時數值會被轉成字串如1, 2, 3甚至產生13 !0!0為true相加得 14與13 !0字串串接得13true這類怪異行為——這正是後文要強調的隱含型別轉換的威力與危險。3.5 字串操作與特殊值// 您可以使用 charAt 來取得字串中的字符 This is a string.charAt(0); // T // 或使用 substring 獲得更大的區塊。 Hello world.substring(0, 5); // Hello // length 是一個屬性因此不要使用 ()。 Hello.length; // 5 // 還有 null 和 undefined 兩個特殊值。 null; // 用於表示刻意指定為空值 undefined; // 用來表示目前尚未指定值(儘管 undefined 實際上本身是一個值) // false、null、undefined、NaN、0 及 空字串皆為偽值falsy // 其他的皆為真值truthy。 // 特別注意0 是偽值0 則是真值儘管 0 0。因為隱含地轉型關鍵知識點charAt(0)取第一個字符substring(0, 5)取前 5 個字符含頭不含尾length是屬性而非方法不要加()null表示刻意設定為空undefined表示尚未指定值兩者通過相等、通過不相等見上文偽值falsy完整清單false、null、undefined、NaN、0、空字串。其餘皆為真值。特別注意0是偽值而0是真值——儘管0 0因隱含轉型而成立。四、變數、陣列與物件4.1 變數宣告與作用域陷阱// 以 var 關鍵字宣告變數。JavaScript 是動態型別因此無需指定型別。 // 使用一個等號 來賦值。 var someVar 5; // 若忽略使用 var 關鍵字也不會得到錯誤 someOtherVar 10; // ...但是您定義的變數將在自動建立在全域而不在您定義的作用域中。 // 若在定義變數時未賦予初始值則預設為 undefined。 var someThirdVar; // undefined // 若要一次宣告多個變數可以使用逗號分隔 var someFourthVar 2, someFifthVar 4; // 變數的數學運算有一些簡寫法 someVar 5; // 等效於 somevar somevar 5現在為 10 someVar * 10; // 現在 someVar 為 100 // 對於增減 1 的運算還有更簡略的寫法 someVar; // 現在 someVar 為 101 someVar--; // 回到 100重要警示宣告變數時不要省略var。省略後雖然不會報錯但變數會被建立在全域作用域而不是你所在的作用域——這會造成命名污染是經典的 bug 來源後文的 IIFE 模式正是為了解決這個問題。未賦初值的變數預設為undefined。、*、、--等簡寫與 C/Java 一致。4.2 陣列有序列表// 陣列是以任何型別的資料所組成、有順序性的列表。 var myArray [Hello, 45, true]; // 可使用中括號方括號[] 語法存取其成員。 // 陣列的索引值index從 0 開始。 myArray[1]; // 45 // 陣列是可變的並有隨成員數量變動的長度 length。 myArray.push(World); myArray.length; // 4 // 於指定的索引值新增/修改陣列的成員 myArray[3] Hello; // 從陣列的最前端或最後端新增或刪除元素 myArray.unshift(3); // 新增元素至最前端 someVar myArray.shift(); // 移除第一個元素並回傳 myArray.push(3); // 新增元素至最後端 someVar myArray.pop(); // 移除最後一個元素並回傳 // 以分號 ; 結合陣列的所有元素 var myArray0 [32, false, js, 12, 56, 90]; myArray0.join(;); // 32;false;js;12;56;90 // 取得索引 1包括到 4排除元素的子陣列 myArray0.slice(1, 4); // [false, js, 12] // 從索引 2 開始刪除 4 個元素然後在那裡插入字串 hi、wr 和ld並回傳刪除的子陣列 myArray0.splice(2, 4, hi, wr, ld); // [js, 12, 56, 90] // myArray0 [32, false, hi, wr, ld]陣列操作速查方法作用是否修改原陣列push(x)/pop()在尾端新增 / 移除並回傳是unshift(x)/shift()在頭端新增 / 移除並回傳是join(sep)以分隔符串接所有元素為字串否slice(a, b)取[a, b)子陣列含頭不含尾否splice(a, n, ...items)從索引a刪除n個並插入新元素回傳被刪除的子陣列是特別注意slice與splice的區別slice是非破壞性的取子陣列splice是破壞性的刪除插入兩者參數語義不同極易混淆。4.3 物件鍵值對集合// JavaScript 的物件等同於其他語言中的「字典」Dictionary或「映射」Map // 無順序性的鍵值對key value pair集合。 var myObj { key1: Hello, key2: World }; // 鍵的名稱是字串若它們是有效的 JavaScript 標識字則不需要使用引號。 // 值則可以是任何資料型別。 var myObj { myKey: myValue, my other key: 4 }; // 物件屬性也可以使用下標語法存取 myObj[my other key]; // 4 // ...或使用 . 來存取前提是該鍵值必須是有效的標識字。 myObj.myKey; // myValue // 物件是可變的其值可修改並可新增新的鍵值。 myObj.myThirdKey true; // 如果您嘗試存取不存在的值將得到 undefined 值。 myObj.myFourthKey; // undefined物件的鍵是字串但若鍵名是有效的標識字identifier如myKey可省略引號。存取有兩種方式點語法obj.key要求鍵是有效標識字與下標語法obj[my other key]可處理含空格等特殊字符的鍵。物件是可變的可隨時修改值或新增鍵存取不存在的鍵回傳undefined。五、邏輯與控制結構5.1 if / while / do-while / for// if 結構如同其他語言 var count 1; if (count 3){ // count 等於 3 時執行 } else if (count 4){ // count 等於 4 時執行 } else { // 其他情況下執行 } // while 迴圈 while (true){ // 無窮迴圈 } // Do-while 迴圈類似 While 迴圈但它至少執行一次 var input; do { input getInput(); } while (!isValid(input)); // for 迴圈和 C、Java 語言一樣 // 初始化; 繼續條件; 迭代。 for (var i 0; i 5; i){ // 會執行 5 次 }if、while、do-while、for的語法與 C/Java 幾乎完全相同。do-while與while的區別在於至少執行一次再檢查條件適合先取輸入再驗證的場景如上面的getInput()示例。5.2 迴圈標籤label與 break// 類似 Java利用迴圈標籤label來終止外層的迴圈 outer: for (var i 0; i 10; i) { for (var j 0; j 10; j) { if (i 5 j 5) { break outer; // 不但終止內部迴圈也終止外部迴圈 } } }在巢狀迴圈中單獨的break只能跳出最內層迴圈。透過在迴圈前加標籤如outer:break outer可以直接終止外層迴圈——這與 Java 的行為一致在需要提前退出多層迴圈時非常實用。5.3 for/in 與 for/of// for/in 陳述式可以迭代物件中的所有屬性。 var description ; var person { fname: Paul, lname: Ken, age: 18 }; for (var x in person){ description person[x] ; } // description 為 Paul Ken 18 // for/of 陳述式允許迭代可迭代物件包括內建的字串、陣列、例如類陣列array-like參數 // 或 NodeList 物件、Typedarray 物件、映射Map和集合Set // 及用戶自定義的可迭代物件iterables。 var myPets ; var pets [cat, dog, hamster, hedgehog]; for (var pet of pets){ myPets pet ; } // myPets 為 cat dog hamster hedgehog 兩個迭代陳述式的分工不同for/in迭代物件的屬性名key常配合下標語法person[x]取值它會沿著原型鏈向上查找見第 7 節原型鏈的說明for/of迭代可迭代物件iterable的值支援字串、陣列、類陣列的arguments/NodeList、TypedArray、Map、Set以及用戶自定義的 iterable。5.4 邏輯運算子、短路求值與 switch// 是邏輯且and, || 是邏輯或or if (house.size big house.colour blue){ house.contains bear; } if (colour red || colour blue){ // colour 等於 red 或 blue 時執行 } // || 可用來設定初始值稱做「短路」short circuit陳述式 var name otherName || default; // switch 陳述式使用 來檢查相等性。 // 在每個 case 後使用 break否則之後的 case 也會被執行。 grade B; switch (grade) { case A: console.log(Great job); break; case B: console.log(OK job); break; case C: console.log(You can do better); break; default: console.log(Oy vey); break; }**短路求值short-circuit**是極常用的技巧在左側為假時不會求值右側||在左側為真時不會求值右側。因此var name otherName || default可以在otherName為偽值如undefined、空字串時自動回退到default——這是最經典的設定默認值方式。switch使用嚴格相等判斷每個case結尾必須break否則會穿透fall-through執行後續所有 case——這是有意設計但也容易出錯的特性。六、函式、作用域與閉包6.1 函式宣告與 ASI 陷阱// JavaScript 函式是使用 function 關鍵字來宣告的。 function myFunction(thing) { return thing.toUpperCase(); } myFunction(foo); // FOO // 值得注意的是要回傳的值必須開始於關鍵字 return 那一行 // 否則會因為自動插入分號而將回傳 undefined。 // 在使用 Allman 程式碼風格時要特別注意。 function myFunction() { return // - 分號在此自動插入 { thisIsAn: object literal }; } myFunction(); // undefined第二個例子是 ASI自動插入分號的經典陷阱由於return後緊跟換行解析器在return之後自動插入分號函式立即回傳undefined後面的物件字面量永遠不會被執行。return的回傳值必須與return關鍵字在同一行。使用 Allman 風格大括號另起一行時尤其容易踩坑。6.2 函式是一等物件first-class// JavaScript 函式為一等物件first-class objects // 所以它們可以被重新賦值給不同的變數 // 並作為參數傳遞給其他函式 - 例如一個事件處理函式 function myFunction() { // 這段程式碼將在 5 秒後執行 } setTimeout(myFunction, 5000); // 註setTimeout 並不是 JS 語言的一部分而是由瀏覽器和 Node.js 提供的 API。 // setInterval 是瀏覽器提供的另一個 API function myFunction() { // 這段程式碼每 5 秒執行一次 } setInterval(myFunction, 5000); // 函式物件甚至不需要用名稱來宣告 - 你可以直接在另一個函數的參數中直接定義匿名函式。 setTimeout(function(){ // 這段程式碼將在 5 秒後執行 }, 5000);一等物件意味著函式可以賦值給變數、作為參數傳遞、作為回傳值回傳。setTimeout延遲執行一次與setInterval週期執行是瀏覽器與 Node.js 提供的 API它們接受函式作為回呼callback。匿名函式可以直接內聯在參數中這是事件驅動編程的基礎。6.3 函式作用域與 IIFE 模式// JavaScript 具有函式作用域scope函式擁有自己的作用域但其他區塊block則沒有。 if (true){ var i 5; } i; // 5 - 並非在其他語言所預期的 undefined // 這導致了一種常見的使用方式即「立即執行匿名函式」它可以防止臨時變數洩漏到全域作用域。 (function(){ var temporary 5; // 我們可以透過賦值給「全域物件」來訪問全域作用域 // 在網頁瀏覽器中這個全域物件始終是 window。 // 在非瀏覽器環境中例如 Node.js全域物件可能有不同的名稱。 window.permanent 10; })(); temporary; // 拋出錯誤 ReferenceError permanent; // 10這是理解 JavaScript 作用域的關鍵函式作用域function scopevar宣告的變數在整個函式內可見但if、for等區塊不會建立獨立作用域——所以if (true) { var i 5; }之後i依然是 5而非其他區塊作用域語言中的undefinedIIFE立即執行函式(function(){ ... })()將臨時變數封閉在函式作用域內避免洩漏到全域。在全域物件瀏覽器中是windowNode.js 中名稱不同上顯式賦值才能建立全域變數。6.4 閉包ClosureJavaScript 最強大的特性// 閉包JavaScript 最強大的特性之一。 // 若一個函式在另一個函式內部定義即使外部函式已經執行結束 // 內部函式仍可以存取外部函式的所有變數。 function sayHelloInFiveSeconds(name){ var prompt Hello, name !; // 預設情況下內部函式會被置於局部作用域中就如同它們是以 var 宣告。 function inner(){ alert(prompt); } setTimeout(inner, 5000); // setTimeout 是非同步的所以 sayHelloInFiveSeconds 函式會立即退出。 // 而 setTimeout 會在之後呼叫 inner 函式。 // 然而由於 inner 函式被「閉合包含」closed over在 sayHelloInFiveSeconds 函式中 // 因此當它最終被呼叫時仍然可以存取 prompt 變數。 } sayHelloInFiveSeconds(Adam); // 將在 5 秒後跳出 Hello, Adam! 訊息閉包的運作原理inner函式被定義在sayHelloInFiveSeconds內部因此它捕獲closed over了外部函式的整個詞法環境包括prompt變數。setTimeout是非同步的sayHelloInFiveSeconds會立即返回但 5 秒後inner被呼叫時依然能讀取到prompt的值——這就是閉包內部函式記住了它被定義時所在的作用域。閉包被廣泛用於事件處理、回呼、模組封裝與狀態保存。七、物件進階this、call/apply/bind 與建構函式7.1 this 的動態綁定// 物件可以包含函式 var myObj { myFunc: function(){ return Hello world!; } }; myObj.myFunc(); // 回傳 Hello world! // 當物件裡的函式被呼叫時它們可以使用 this 關鍵字來存取所屬物件的其他成員。 myObj { myString: Hello world!, myFunc: function() { return this.myString; } }; myObj.myFunc(); // 回傳 Hello world! // this 的設定與函式如何被呼叫有關而非定義的位置。 // 因此若我們的函式不是在物件的脈絡context中被呼叫就無法正常運作。 var myFunc myObj.myFunc; myFunc(); // undefined // 反之一個函式可以被賦值給物件並透過 this 獲得對它的存取權限 // 即使它在定義時並未依附於該物件上。 var myOtherFunc function() { return this.myString.toUpperCase(); }; myObj.myOtherFunc myOtherFunc; myObj.myOtherFunc(); // 回傳 HELLO WORLD!this的取值取決於函式如何被呼叫而非在何處定義——這是 JavaScript 初學者最大的困惑點。當myObj.myFunc()以物件方法形式呼叫時this指向myObj但把函式取出賦值給變數後再呼叫myFunc()this不再指向myObj非嚴格模式下指向全域物件因此this.myString為undefined。反之任何函式只要被賦值為物件的方法再以物件方法形式呼叫就能透過this存取該物件。7.2 call / apply / bind顯式控制 this// 我們也可以在呼叫函式時使用 call 或 apply 來指定函式的脈絡。 var anotherFunc function(s) { return this.myString s; }; anotherFunc.call(myObj, And Hello Moon!); // Hello World! And Hello Moon! // apply 函式的用法幾乎一樣差別在於要用陣列的格式傳遞參數 anotherFunc.apply(myObj, [ And Hello Sun!]); // Hello World! And Hello Sun! // 這在處理接受一系列參數時很有用特別是當您想要傳遞一個陣列時。 Math.min(42, 6, 27); // 6 Math.min([42, 6, 27]); // NaN噢 Math.min.apply(Math, [42, 6, 27]); // 6 // 然而 call 和 apply 只是暫時的。當我們希望它永久有效時我們可以使用 bind。 var boundFunc anotherFunc.bind(myObj); boundFunc( And Hello Saturn!); // Hello World! And Hello Saturn! // bind 也可以用於部分應用例如柯里化curry的函式。 var product function(a, b) { return a * b; }; var doubler product.bind(this, 2); doubler(8); // 16三種顯式指定this的方式方法參數傳遞方式是否永久綁定func.call(ctx, arg1, arg2, ...)逐一列出否僅本次呼叫func.apply(ctx, [arg1, arg2, ...])陣列形式否僅本次呼叫func.bind(ctx, ...)可同時做部分應用是產生新函式apply特別適合把陣列展開為參數列表Math.min不接受陣列直接傳陣列會得到NaN但Math.min.apply(Math, [42, 6, 27])可以正確得到 6。bind還會產生**柯里化currying**效果product.bind(this, 2)把第一個參數固定為 2得到加倍器doubler。7.3 建構函式與 new// 當您使用 new 關鍵字呼叫一個函式時會建立一個新的物件 // 並透過 this 關鍵字使該物件可用於該函式。用此方式呼叫的函式被稱為「建構函式」。 var MyConstructor function(){ this.myNumber 5; }; myNewObj new MyConstructor(); // { myNumber: 5 } myNewObj.myNumber; // 5使用new呼叫函式時建立一個新物件 → 透過this讓該函式可以初始化它 → 回傳該物件。這類函式被稱為建構函式constructor。注意此時this.myNumber 5實際上就是在初始化新物件的屬性。八、原型Prototype與繼承JavaScript 的物件模型核心8.1 原型鏈的查找機制// 與大多數其他流行的物件導向語言不同JavaScript 沒有從類別class藍圖建立 // 實例instance的概念相反地JavaScript 將實例化instantiation和 // 繼承inheritance結合成單一的概念「原型」prototype。 // 每個 JavaScript 物件都有一個「原型」prototype。 // 當你試圖存取一個物件上不存在的屬性時直譯器interpreter會嘗試查找它的原型。 // 某些 JS 實作允許你透過屬性 __proto__ 存取原型。雖然這對於解釋原型很有幫助 // 但它不是標準的操作方式稍後會介紹使用原型的標準方法。 var myObj { myString: Hello world! }; var myPrototype { meaningOfLife: 42, myFunc: function() { return this.myString.toLowerCase(); } }; myObj.__proto__ myPrototype; myObj.meaningOfLife; // 42 // 函式照常運作。 myObj.myFunc(); // hello world! // 當然若您的屬性不在原型上則會搜尋原型的原型以此類推。 myPrototype.__proto__ { myBoolean: true }; myObj.myBoolean; // true // 這裡沒有涉及任何複製每個物件都儲存了一個指向其原型的參考。 // 這意味著我們可以修改原型而此修改將反映在任何地方。 myPrototype.meaningOfLife 43; myObj.meaningOfLife; // 43JavaScript 的物件模型與 Java 的類別繼承有本質區別沒有類別藍圖 → 實例的概念而是把實例化與繼承合而為一每個物件都有一個原型當存取物件上不存在的屬性時解釋器沿著**原型鏈prototype chain**逐層查找物件的原型、原型的原型……直到null這裡沒有任何複製物件儲存的是指向原型的參考因此修改原型如把meaningOfLife從 42 改成 43會即時反映到所有繼承它的物件上__proto__是部分實作提供的非標準屬性僅適合教學演示生產代碼應使用下文標準方式。8.2 for/in 與 hasOwnProperty// for/in 陳述句允許循覽物件的屬性沿著原型鏈向上查找直到遇到 null 原型為止。 for (var x in myObj){ console.log(myObj[x]); } // 印出 // Hello world! // 43 // [Function: myFunc] // true // 若只考慮直接附加在物件本身而非其原型上的屬性請使用 hasOwnProperty() 做檢查。 for (var x in myObj){ if (myObj.hasOwnProperty(x)){ console.log(myObj[x]); } } // 印出 // Hello world!for/in會沿著原型鏈一路向上列出屬性包括繼承來的meaningOfLife、myFunc、myBoolean。若只想處理物件自身的屬性必須用hasOwnProperty(x)過濾——這是遍歷物件的標準防護寫法。8.3 指定原型建立物件的兩種標準方式// 我們提到 __proto__ 是非標準用法而且沒有標準的方法來修改現有物件的原型。 // 然而有兩種方法可以建立具有指定原型的新物件。 // 第一種方法是 Object.create。 var myObj Object.create(myPrototype); myObj.meaningOfLife; // 43 // 第二種方法與建構函式有關可在任何地方使用。建構函式有一個稱為 prototype 的屬性。 // 這「不是」建構函式本身的原型而是當使用該建構函式和 new 關鍵字建立新物件時所賦予的原型。 MyConstructor.prototype { myNumber: 5, getMyNumber: function(){ return this.myNumber; } }; var myNewObj2 new MyConstructor(); myNewObj2.getMyNumber(); // 5 myNewObj2.myNumber 6; myNewObj2.getMyNumber(); // 6建立帶指定原型的新物件有兩種標準方法Object.create(proto)直接建立一個以proto為原型的新物件建構函式的prototype屬性MyConstructor.prototype不是建構函式自身的原型而是每次用new MyConstructor()建立新物件時賦予新物件的原型。因此myNewObj2可以透過原型繼承getMyNumber方法同時可以覆寫自有屬性myNewObj2.myNumber 6而不影響原型。8.4 內建型別的包裝物件與原型擴充// 內建型別如字串和數字也有建構函式可以建立等效的包裝物件wrapper object。 var myNumber 12; var myNumberObj new Number(12); myNumber myNumberObj; // true // 然而它們並非嚴格相等。 typeof myNumber; // number typeof myNumberObj; // object myNumber myNumberObj; // false if (0){ // 這段程式碼不會執行因為 0 是偽值。 } if (new Number(0)){ // 這段程式碼將會執行因為包裝過的數字是物件而物件皆為真值。 } // 然而包裝物件和常見的內建型別共享一個原型所以你實際上可以為字串擴充功能例如 String.prototype.firstCharacter function(){ return this.charAt(0); }; abc.firstCharacter(); // a內建型別Number、String等也有建構函式可建立包裝物件。包裝物件與原始值透過相等但typeof分別是object與number因此不相等且包裝物件永遠是真值new Number(0)為真而0為偽。關鍵技巧是包裝物件與普通原始值共享同一個原型因此可以透過修改String.prototype來為所有字串增加方法如firstCharacter。8.5 Polyfill用原型擴充實現新功能// 此技巧常被用於「填充」polyfilling即在舊版的 JavaScript 中 // 實作新版 JavaScript 才有的功能以便它們可以在較舊的環境例如過時的瀏覽器中使用。 // 舉例來說方才提到的 Object.create 並非在舊瀏覽器上使用 // 但我們仍然可以透過這個 polyfill 來補足其功能 if (Object.create undefined){ // 若此方法存在則不覆蓋 Object.create function(proto){ // 製作一個具有正確原型的暫時建構函式 var Constructor function(){}; Constructor.prototype proto; // 然後使用它來建立一個具有適當原型的新物件 return new Constructor(); }; }**Polyfill填充**是前端兼容性的核心技巧用舊版 JavaScript 的子集重新實現新版本才有的功能使舊瀏覽器也能運行。上述Object.create的 polyfill 是極佳範例——它巧妙地利用臨時建構函式 設定其prototypenew來合成一個具有指定原型的新物件完全基於我們在前文學到的原型機制。注意開頭的if (Object.create undefined)保護若環境已原生支援則不覆蓋。九、ES6 新語法let、const 與箭頭函式9.1 let真正的區塊作用域// let 關鍵字讓您在語彙作用域lexical scope中定義變數 // 而不像 var 關鍵字那樣在函式作用域function scope中定義。 let name Billy; // 以 let 關鍵字定義的變數可以賦予新值。 name William;let解決了第 6.3 節提到的問題它提供詞法區塊作用域if、for等區塊內宣告的變數不會洩漏出去——這是對var函式作用域的重大改進也是現代 JavaScript 的首選宣告方式。9.2 const不可重新賦值// const 關鍵字如同 let 可在語彙作用域中定義變數差別在於一旦賦值後就不能更改其值。 const pi 3.14; pi 4.13; // 此操作並不合法。const在let的詞法作用域基礎上增加了不可重新賦值的約束對pi重新賦值會拋出錯誤。注意const保證的是綁定不可變而非值不可變——物件的屬性仍可修改。9.3 箭頭函式lambda 語法// ES6 中有一種新的函式語法稱為「lambda 語法」lambda syntax。 // 這允許函式在語彙作用域中定義如同 const 和 let 來定義變數。 const isEven (number) { return number % 2 0; }; isEven(7); // 回傳 false 值 // 「等效」於以下傳統函式的宣告方式 function isEven(number) { return number % 2 0; }; // 前面特別強調「等效」一詞是因為使用 lambda 語法定義的函式不能在定義之前被呼叫。 // 以下爲錯誤示範 add(1, 8); const add (firstNumber, secondNumber) { return firstNumber secondNumber; };箭頭函式(params) { ... }是 ES6 的核心新語法讓函式可以像const/let變數一樣在詞法作用域中宣告。與傳統function宣告的關鍵差異傳統函式宣告會被提升hoisting可在定義前呼叫而箭頭函式是賦值給const的不能在定義之前呼叫上面的add(1, 8)會拋出錯誤。此外箭頭函式不綁定自己的this詞法綁定這在回呼函式場景中有重要意義。十、延伸閱讀與實戰驗證本指南聚焦 JavaScript語言本身。若想深入可依以下方向延伸均為本倉庫內相關資源便於對照學習多語言對照若您熟悉其他語言可對比閱讀倉庫內 python.md、java.md、c.md 等同風格教程快速理解 JavaScript 在語法與物件模型上的異同簡體中文版本對照閱讀 zh-cn/javascript.md 可獲得不同譯法與表述Typescript 進階學習 typescript.md 可了解如何在 JavaScript 之上引入靜態型別Node.js 生態閱讀 nodejs.md若倉庫內存在或 npm.md若倉庫內存在可掌握伺服器端 JavaScript 的開發流程——請以倉庫實際文件清單為準。驗證方式本文所有代碼示例都是可執行的。最便捷的方式是安裝 Node.js 後將任一節的程式碼存入test.js並執行node test.js或在瀏覽器開發者工具的 Console 面板中逐行貼上執行即可即時觀察註解中標注的結果值。建議重點動手驗證三個易錯點0.1 0.2的浮點精度、return換行的 ASI 陷阱、以及this在不同呼叫方式下的取值。本指南的內容結構基礎語法 → 資料結構 → 控制流程 → 函式與閉包 → 物件模型與原型 → ES6完整對應 zh-tw/javascript.md 的章節脈絡讀者可隨時回到原文逐段核對若希望參與完善本教程修訂翻譯、補充示例請遵循 CONTRIBUTING.md 中的貢獻規範提交 Pull Request。赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载相关推荐learnxinyminutes-docs 项目 JavaScript 快速入门从基础语法到原型链与 ES6 的完整实战指南learnxinyminutes docs 项目 JavaScript 快速入门从基础语法到原型链与 ES6 的完整实战指南 导读 本篇以 learnxiny文档教程learnxinyminutes-docs 西班牙语版 JavaScript 入门指南从基础语法到原型链的完整实战解析learnxinyminutes docs 西班牙语版 JavaScript 入门指南从基础语法到原型链的完整实战解析 本指南以 learnxinyminut文档教程learnxinyminutes-docs 仓库 JavaScript 速成指南从基础语法到作用域、闭包与原型链learnxinyminutes docs 仓库 JavaScript 速成指南从基础语法到作用域、闭包与原型链 本篇技术指南以 learnxinyminut文档教程上一篇Super-linter 升级指南从旧版本平滑迁移到新版本下一篇多语言支持实践OSS-Fuzz如何适配不同编程语言生态创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考