0 前言
0-1 Screeps: World
- 眾所不周知,?
Screeps: World
是一款面向編程愛好者的開源大型多人在線即時戰略(MMORTS)沙盒游戲,其核心機制是通過編寫JavaScript
代碼來控制游戲中的單位(稱為“Creep”),以建立和發展自己的帝國。 - 在游戲中,玩家需要編寫代碼來自動化單位的行為,包括采集資源、建造建筑、擴張領土等。?這些代碼會在游戲服務器上持續運行,即使玩家離線,單位也會按照預設的指令執行任務。
- 那為了我們愉快的進入游戲玩耍之前,我們今天來速通一下
JavaScript
的基礎語法
0-2 JavaScript
JavaScript
(簡稱JS)是一種高級、解釋型、動態的編程語言,主要用于==網頁開發==。它最早由網景(Netscape)公司的布蘭登·艾奇(Brendan Eich)在1995年開發,用于增強網頁的交互性。如今,JavaScript
已經發展成為一種功能強大的通用編程語言,廣泛應用于前端、后端、移動端、游戲開發等領域。- JavaScript的特點:
解釋性語言
:無需編譯,瀏覽器或Node.js可以直接執行。支持動態類型
:變量的類型可以在運行時動態變化。弱類型
:允許不同類型的數據進行運算。基于對象
:JavaScript中的幾乎所有內容(如數組、函數等)都是對象。事件驅動和異步
:使用事件監聽和回調函數,使網頁能響應用戶操作。通過Promise和async/await支持異步編程,提高性能。跨平臺
:適用于Windows、Mac、Linux等各種操作系統,并且支持大多數瀏覽器(Chrome、Firefox、Edge等)。
0-3 食用本文前須知!!!
- 通常大部分的
JS
教程都會和HTML
和CSS
綁定(畢竟是網頁開發捏),但由于本教程的后續目的咱們是為了愉快的玩耍Screeps: World
,故本教程將專注于JS 純邏輯編程 為主,涵蓋變量、函數、對象、異步編程等核心內容。 - 本教程目錄:
- 編程環境搭建和HelloWorld
- 變量聲明和數據類型
- 函數、作用域與閉包
- 運算符
- 異常處理機制
- 異步機制、回調函數與Promise(Screeps用不上)
- 類、繼承與prototype
- 多態
- 數組(Array)
1 編程環境搭建和HelloWorld
1-1 Node.js
簡介
- 通常傳統的
JavaScript·
只能在瀏覽器環境中運行,無法直接訪問文件系統、操作系統資源或數據庫。為了解決上述局限性,我們引入Node.js
~ Node.js
是一個基于Chrome V8 引擎
的JavaScript
運行環境,允許 JS 在服務器端運行。
1-2 Node.js
的安裝
-
官網:Download Node.js
-
如官網所述,
windows
端的下載打開powershell
:
# Download and install fnm:
winget install Schniz.fnm# Download and install Node.js:
fnm install 22# 如果輸入node -v 出現報錯請輸入下列代碼
# 記得是在poweshell中輸出
fnm env --use-on-cd | Out-String | Invoke-Expression# Verify the Node.js version:
node -v # Should print "v22.14.0".# Verify npm version:
npm -v # Should print "10.9.2".
winget
是 Windows 10/11自帶的 包管理器(類似于 Linux 的apt
或brew
)。fnm(Fast Node Manager)
,是一個 Node.js 版本管理工具。方便管理多個 Node.js 版本并且可以快速進行切換。npm
(Node Package Manager)是 Node.js 自帶的包管理工具,用于安裝和管理 JavaScript 庫。- 值得一提的是,上述代碼在下載完成中驗證
Node.js
的版本時候也許會出現下述報錯:
fnm env --use-on-cd | Out-String | Invoke-Expression
- 上述代碼將
fnm
的環境變量加載到當前的 PowerShell 會話中,使得 Node.js 的相關命令(如node -v
)能夠在當前終端會話中正常使用。
1-3 HelloWorld與你的第一個js程序
- IDE的話這里大家就隨便自己選咯,我這里用的是
VSCode
- 我們新建一個文件命名為
Hello.js
console.log("Hello World!");
- 然后在終端直接使用
Node.js
運行js文件
node .\hellojs.js
-
至此我們的第一個
JS
程序就完成辣 -
當然可以考慮安裝一些擴展(當然你也可以純記事本進行編程~)
1-4 (補充)ESX是啥
- ESX 通常是一個非正式的表達,泛指 ECMAScript 的各種版本(ES6 及后續版本),即 ES6、ES7、ES8、ES9 等等。
- ES6(ECMAScript 2015) 是 JavaScript 語言的一次重大更新,引入了
let
、const
、箭頭函數、類(class)、模塊(import/export)等重要特性。
版本 | 發布時間 | 重要特性 |
---|---|---|
ES6 (ES2015) | 2015 | let / const 、箭頭函數、類(class)、模板字符串、解構賦值、默認參數、import/export |
ES7 (ES2016) | 2016 | Array.prototype.includes() 、指數運算符 (** ) |
ES8 (ES2017) | 2017 | async/await 、Object.entries() 、Object.values() |
ES9 (ES2018) | 2018 | Promise.finally() 、正則表達式改進(命名捕獲組等) |
ES10 (ES2019) | 2019 | Array.prototype.flat() , Object.fromEntries() |
ES11 (ES2020) | 2020 | BigInt 、Promise.allSettled() 、可選鏈 ?. |
ES12 (ES2021) | 2021 | String.replaceAll() 、邏輯賦值運算符 (`&&=, |
1-5 JavaScript 嚴格模式 ("use strict"
)
- 嚴格模式(
"use strict"
)是 ES5 引入的一種 JavaScript 執行模式,旨在 消除 JavaScript 的不安全或錯誤用法,提高代碼質量和執行效率。 - 開啟嚴格模式后,JavaScript 代碼會 拋出更多錯誤,阻止一些潛在的 Bug,同時提高 JS 引擎的優化效率。
- 如何開啟嚴格模式?
- 在代碼的第一行添加
"use strict";console.log("嚴格模式開啟!");
- 嚴格模式也可以局部應用于某個函數:
function strictFunction() {"use strict";let x = 10;console.log(x);
}
strictFunction();
- 嚴格模式總結:
規則 | 解釋 |
---|---|
必須顯式聲明變量 | x = 10; 會報錯 |
禁止重復變量聲明 | var a = 10; var a = 20; 會報錯 |
禁止刪除變量/函數 | delete x; 會報錯 |
普通函數的 this 變 undefined | function test() { console.log(this); } |
禁止 with 語句 | with (obj) { console.log(x); } 會報錯 |
禁止 eval() 影響作用域 | eval("var x = 10;"); console.log(x); |
禁止八進制數 | var num = 010; |
2 變量聲明和數據類型
2-1 變量聲明
- JavaScript 提供了
var
、let
和const
三種方式來聲明變量
2-1-1 var
(已過時,不推薦)
- 變量可以在聲明前使用(變量提升)。
- 作用域是函數作用域(function scope)。
- 可以重復聲明同名變量。
console.log(a); // undefined(變量提升)
var a = 10;
var a = 20; // 允許重復聲明
console.log(a); // 20
2-1-2 let
(推薦使用)
- 作用域是塊作用域(block scope)。
- 不能在聲明前使用(不會變量提升)。
- 不能重復聲明同名變量。
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
let b = 20; // ? SyntaxError: Identifier 'b' has already been declared
2-1-3 const
(推薦用于常量)
- 作用域是塊作用域(block scope)。
- 必須在聲明時初始化,且值不能更改(不可變)。
- 不能重復聲明。
const PI = 3.14159;
PI = 3.14; // ? TypeError: Assignment to constant variable
2-2 原始數據類型
- JavaScript 主要有七種原始數據類型和一種對象類型
2-2-1 number
(數字類型)
- 包括整數和浮點數
- 例如:
10
、3.14
、-5
- 還有特殊值:
Infinity
、-Infinity
和NaN
let num1 = 42;
let num2 = 3.14;
let notANumber = NaN;
2-2-2 string
(字符串類型)
- 由字符組成,使用單引號
'
、雙引號"
或反引號`
(模板字符串)。 - 例如:
"hello"
、'world'
、`Hello, ${name}`
let str1 = "Hello";
let str2 = 'World';
let name = "Alice";
let str3 = `Hello, ${name}`; // 模板字符串
2-2-3 boolean
(布爾類型)
- 只有兩個值:
true
和false
- 用于邏輯判斷
let isOnline = true;
let hasError = false;
2-2-4 undefined
(未定義類型)
- 變量聲明但未賦值時的默認值
let x;
console.log(x); // undefined
2-2-5 null
(空值)
- 表示“空值”或“無值”
- 通常用于手動賦值,表示變量為空
let y = null;
2-2-6 symbol
(符號類型,ES6 新增)
- 創建獨一無二的值,通常用于對象屬性鍵
let sym = Symbol("unique");
2-2-7 bigint
(大整數類型,ES11/ES2020 新增)
- 適用于處理比
Number.MAX_SAFE_INTEGER
更大的整數
let bigNum = 9007199254740991n;
2-3 對象類型
JavaScript 只有一種復雜數據類型——對象(Object),包括:
- 普通對象
{ key: value }
- 數組
[1, 2, 3]
- 函數
function() {}
- 日期
new Date()
- 正則表達式
/abc/
let person = {name: "Alice",age: 25
};let arr = [1, 2, 3];function sayHello() {console.log("Hello!");
}
- 可以使用
typeof
來檢查變量類型:
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object"(JavaScript 的歷史遺留問題)
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof function() {}); // "function"
console.log(typeof Symbol("id")); // "symbol"
console.log(typeof 9007199254740991n); // "bigint"
2-4 對象類型的創建,訪問,屬性引用
- 創建對象
- (下述內容部分超出范圍還沒說,但是相信熟悉其他編程語言的朋友們可以看懂)
- (如果沒看懂的話可以先往后看后面再回來看這一部分)
const person = {name: "Alice",age: 25,"home city": "New York", greet: function () {console.log("Hello, I'm " + this.name);}
};
2-4-1 訪問對象的屬性(一):使用 .
訪問(常用)
.
訪問方式只能用于符合變量命名規則的鍵(如name
和age
)。
console.log(person.name); // 輸出: Alice
console.log(person.age); // 輸出: 25
2-4-2 訪問對象的屬性(二):使用 []
訪問(適用于動態鍵)
- 可以訪問特殊字符(如空格、
-
等)的屬性(如"home city"
)。
- 可以用變量作為鍵名,適用于動態訪問。
console.log(person["name"]); // 輸出: Alice
console.log(person["home city"]); // 輸出: New York
- 動態鍵訪問
const key = "age";
console.log(person[key]); // 輸出: 25
2-4-4 訪問對象的方法
person.greet(); // 輸出: Hello, I'm Alice
2-4-5 檢查對象是否包含某個屬性
- 使用
in
關鍵字
console.log("name" in person); // true
console.log("gender" in person); // false
- 使用
hasOwnProperty()
方法
console.log(person.hasOwnProperty("age")); // true
console.log(person.hasOwnProperty("gender")); // false
2-4-6 遍歷對象的屬性
for (let key in person) {console.log(key + ": " + person[key]);
}
2-4-7 刪除對象屬性
- 訪問對象中不存在的屬性時,返回
undefined
,不會報錯。
delete person.age;
console.log(person.age); // 輸出: undefined
3 函數、作用域與閉包
- JavaScript 中,函數(Function) 是代碼的可復用塊,允許封裝邏輯并在需要時調用。同時,作用域(Scope) 決定了變量的可訪問性。
- JavaScript 提供了三種常見方式來定義函數:
3-1 函數聲明(Function Declaration)
- 關鍵字
function
開頭
- 支持函數提升(Hoisting),可以在定義前調用
console.log(square(5)); // 25function square(num) {return num * num;
}console.log(square(5)); // 25
3-2 函數表達式(Function Expression)
- 沒有函數名(匿名函數)
- 賦值給變量后使用
- 不支持函數提升(只能在定義后調用)
const greet = function(name) {return "Hello, " + name;
};
console.log(greet("Bob")); // Hello, Bob
3-3 箭頭函數(Arrow Function,ES6)
- 語法更簡潔
- 沒有
this
綁定(this 取決于外部作用域) - 適合回調函數 & 簡單邏輯
const add = (a, b) => {return a + b;
};
console.log(add(3, 4)); // 7
- 單行省略
{}
和return
:
const multiply = (a, b) => a * b;
console.log(multiply(2, 3)); // 6
3-4 作用域(Scope)
- 作用域決定了變量的可訪問范圍。JavaScript 有三種主要作用域:
全局作用域
(Global Scope)**: 聲明在函數外部的變量,在整個腳本或瀏覽器窗口中可訪問函數作用域
(Function Scope):在函數內部聲明的變量,只能在該函數內部訪問塊級作用域
(Block Scope,ES6):let
和const
具有塊級作用域,var
沒有塊級作用域
{let x = 10;const y = 20;var z = 30; // ?? var 例外
}
console.log(z); // ? 30
console.log(x); // ? ReferenceError
console.log(y); // ? ReferenceError
3-5 閉包(Closure)
- 閉包是指內部函數可以訪問外部函數的變量,即使外部函數已經執行完畢。4
- 閉包的作用:
- 數據私有化
- 模擬
static
變量
function outer() {let count = 0;return function inner() {count++;console.log("Count:", count);};
}const counter = outer();
counter(); // Count: 1
counter(); // Count: 2
- 關于閉包的調用方式及其注意點:
調用方式 | 是否形成閉包? | 變量是否累積? | 原因 |
---|---|---|---|
outer(); | ? 否 | ? 否 | inner 從未被執行 |
outer().inner(); | ? 否 | ? 否 | count 在每次 outer() 調用后重置 |
const counter = outer(); counter(); counter(); | ? 是 | ? 是 | counter 持有 count 的引用,形成閉包 |
3-6 this
關鍵字
this
是 JavaScript 中的特殊關鍵字,它的值取決于函數的調用方式。
1?? 全局作用域- 在非嚴格模式下,
this
指向window
(瀏覽器)或global
(Node.js)。 - 在嚴格模式下,
this
是undefined
"use strict";
console.log(this); // 輸出:undefined
2?? 作為對象方法調用
- 此時
this
指向對象本身
const obj = {name: "Alice",sayHi: function() {console.log(this.name);}
};obj.sayHi(); // 輸出:"Alice"
3??作為構造函數調用
- 在構造函數中,
this
指向新創建的對象。
function Person(name) {this.name = name;
}const p = new Person("Bob");
console.log(p.name); // 輸出:"Bob"
4??call
/ apply
/ bind
顯式綁定
- JavaScript 允許手動設置
this
,可以使用:call()
傳遞參數列表apply()
傳遞參數數組bind()
返回一個新的綁定函數
const user = { name: "Charlie" };function sayHello() {console.log("Hello, " + this.name);
}sayHello.call(user); // 輸出:"Hello, Charlie"
sayHello.apply(user); // 輸出:"Hello, Charlie"const boundFunc = sayHello.bind(user);
boundFunc(); // 輸出:"Hello, Charlie"
5?? 箭頭函數中的 this
- 箭頭函數不會創建自己的
this
,而是繼承外部作用域的this
。
const obj = {name: "Alice",sayHi: function() {const inner = () => {console.log(this.name);};inner();}
};obj.sayHi(); // 輸出:"Alice"
4 運算符
- 本節內容基礎就給出表格大家自行參閱!
4-1 比較運算符
運算符 | 描述 | 示例 | 結果 |
---|---|---|---|
== | 寬松相等(值相等,類型可轉換) | '5' == 5 | true |
=== | 嚴格相等(值相等,類型也必須相同) | '5' === 5 | false |
!= | 寬松不等(值不相等,類型可轉換) | '5' != 5 | false |
!== | 嚴格不等(值或類型不同) | '5' !== 5 | true |
> | 大于 | 10 > 5 | true |
< | 小于 | 10 < 5 | false |
>= | 大于等于 | 10 >= 10 | true |
<= | 小于等于 | 10 <= 5 | false |
4-2 算數運算符
運算符 | 描述 | 示例 | 結果 |
---|---|---|---|
+ | 加法 | 5 + 3 | 8 |
- | 減法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 除法 | 5 / 2 | 2.5 |
% | 取模(取余數) | 5 % 2 | 1 |
** | 指數(冪運算) | 2 ** 3 | 8 |
- 然后是自增自減老規矩~
形式 | 說明 | 示例 | 結果 |
---|---|---|---|
x++ | 后置自增(先返回值,再加 1) | let a = 5; let b = a++; | b = 5, a = 6 |
++x | 前置自增(先加 1,再返回值) | let a = 5; let b = ++a; | b = 6, a = 6 |
x-- | 后置自減(先返回值,再減 1) | let a = 5; let b = a--; | b = 5, a = 4 |
--x | 前置自減(先減 1,再返回值) | let a = 5; let b = --a; | b = 4, a = 4 |
4-3 賦值運算符
運算符 | 等價于 | 示例 |
---|---|---|
+= | x = x + y | x += 2; |
-= | x = x - y | x -= 2; |
*= | x = x * y | x *= 2; |
/= | x = x / y | x /= 2; |
**= | x = x ** y | x **= 2; |
%= | x = x % y | x %= 2; |
5 分支語句(條件語句) 和 循環語句
- 本節同樣基礎,我們飛速略過
5-1 if-else 語句
- 無需多言
if (/*condition1*/)
{ }
else if (/*condition2*/)
{ }
else
{ }
5-2 switch 語句
- 需要注意的是,js的
switch
語句可以用于 任何類型(string
、number
、boolean
,甚至object
和function
)。 - js的
switch
語句支持嵌套(不建議你這么干) - 其他部分和其他語言相同:
case
語句后必須是具體的值,不能是范圍或條件表達式。break
語句用于阻止穿透(fall-through),否則會繼續執行下一個case
代碼塊。
default
是可選的,但建議寫上,以防所有case
都不匹配。
let color = "紅色";
switch (color) {case "紅色":console.log("你選擇了紅色");break;case "藍色":console.log("你選擇了藍色");break;case "綠色":console.log("你選擇了綠色");break;default:console.log("未知顏色");
}
5-3 for 循環
for (let i = 1; i <= 5; i++) {console.log(i);
}
- 注意區分:
for...in
和for...of
語句 | 適用對象 | 作用 |
---|---|---|
for...in | 對象 | 遍歷對象的屬性名 |
for...of | 數組、字符串 | 遍歷數組的值 |
for...in
:
let person = { name: "張三", age: 25, city: "北京" };
for (let key in person) {console.log(key + ": " + person[key]);
}
for...of
let numbers = [10, 20, 30];
for (let num of numbers) {console.log(num);
}
5-4 while 循環
let count = 1;
while (count <= 3) {console.log(count);count++;
}
5-5 do-while循環
let num = 1;
do {console.log(num);num++;
} while (num <= 3);
5-6 break
和 continue
- 同理:
break
終止循環continue
跳過當前循環的剩余代碼,直接進入下一次循環,不會終止整個循環。
6 異常處理機制
- 在 JavaScript 開發中,程序運行時可能會遇到錯誤(比如變量未定義、JSON 解析失敗、網絡請求錯誤等)。為了防止這些錯誤導致程序崩潰,我們可以使用 異常處理機制 來捕獲和處理錯誤。
6-1 異常處理機制 try-catch-finally
try {// 可能會出錯的代碼
} catch (error) {// 處理錯誤console.log("捕獲錯誤:", error.message);
} finally{// 無論是否發生錯誤都會執行,適用于資源釋放、日志記錄等場景。
}
6-2 throw
關鍵字(手動拋出異常)
throw new Error("你的錯誤信息");
7 異步機制、回調函數與Promise
- 值得一提的是,Screeps 的代碼是 Tick 驅動的,而不是時間驅動的,所以要用
Game.time
來管理邏輯,而不是setTimeout
或Promise
! - 因此本節對
Screeps
編程毫無作用,不感興趣的朋友們可以直接跳過~
7-1 異步機制
- JavaScript 是 單線程 語言,為了防止長時間執行的任務(如網絡請求、文件讀寫)阻塞主線程,采用 異步編程 方式,使代碼可以在等待操作完成的同時執行其他任務。
- 常見的異步操作:
setTimeout
/setInterval
(定時器)- DOM 事件監聽 (
addEventListener
) - AJAX / Fetch 請求(HTTP 請求)
Promise
和async/await
7-2 setTimeout
/ setInterval
(定時器)
setTimeout
和setInterval
是 JavaScript 的內置函數。它屬于 Web API,由 瀏覽器或 Node.js 提供,并不直接屬于 JavaScript 語言核心。
方法 | 用途 | 特點 |
---|---|---|
setTimeout(fn, delay) | 延遲執行 fn 一次 | 僅執行一次,需要手動遞歸調用 |
setInterval(fn, delay) | 間隔執行 fn | 會一直執行,直到調用 clearInterval() |
- 下面我們來看二者具體的區別:
7-2-1 setTimeout
setTimeout
是 JavaScript 的內置函數,用于延遲執行代碼。
setTimeout(callback, delay, param1, param2, ...);
callback
(必填):延遲執行的 函數(匿名函數或函數引用)。delay
(必填):延遲時間(單位:毫秒 ms,1000ms = 1秒
)。param1, param2, ...
(可選):傳遞給callback
的參數。
- 注意可以使用
clearTimeout(timer)
; // 取消定時器
let timer = setTimeout(() => {console.log("不會執行");
}, 5000);clearTimeout(timer); // 取消定時器
7-2-2 ``setInterval`
setInterval
是 JavaScript 內置的定時器函數,用于按指定時間間隔重復執行某個函數,直到調用clearInterval()
停止。
setInterval(callback, delay, param1, param2, ...);
callback
要執行的函數(可以是匿名函數或函數引用)delay
時間間隔(毫秒),1000ms = 1秒param1
,param2
, … 傳遞給 callback 的參數(可選)
setInterval(() => {console.log("每 2 秒執行一次");
}, 2000);
- 使用
clearInterval(intervalID)
停止setInterval
。
let count = 0;
let intervalID = setInterval(() => {count++;console.log("執行次數:" + count);if (count === 5) {clearInterval(intervalID); // 停止定時器console.log("定時器已停止");}
}, 1000);
7-3 回調函數
- 回調函數 是一種最基本的異步處理方式,即將一個函數作為參數傳遞,待操作完成后調用該函數。
function fetchData(callback) {setTimeout(() => {console.log("數據獲取成功");callback("數據內容");}, 2000);
}fetchData((data) => {console.log("回調函數接收數據:", data);
});
缺點:
- 回調地獄(Callback Hell):多個回調嵌套使代碼變得難以維護。
- 錯誤處理不方便:錯誤需要通過回調手動傳遞,容易遺漏。
7-4 Promise
Promise
是 ES6 引入的一種異步編程解決方案,它可以更優雅地處理異步操作,避免回調地獄。- *Promise 三種狀態:
pending
(進行中)fulfilled
(已成功)rejected
(已失敗)
function fetchData() {return new Promise((resolve, reject) => {setTimeout(() => {let success = true; // 模擬成功或失敗if (success) {resolve("數據獲取成功");} else {reject("數據獲取失敗");}}, 2000);});
}fetchData().then((data) => console.log("成功:", data)) // 處理成功.catch((error) => console.log("失敗:", error)) // 處理失敗.finally(() => console.log("請求完成")); // 無論成功失敗都會執行
優勢:
- 避免回調地獄,使代碼更易讀。
- 提供
.then()
、.catch()
、.finally()
結構,方便管理異步操作。
7-5 async/await(基于 Promise)
async/await
是 ES8 引入的一種更直觀的異步編程方式,它是Promise
的語法糖,使異步代碼更接近同步寫法。
async function fetchData() {try {let data = await new Promise((resolve) => setTimeout(() => resolve("數據獲取成功"), 2000));console.log(data);} catch (error) {console.log("錯誤:", error);} finally {console.log("請求完成");}
}fetchData();
特點:
await
關鍵字會等待Promise
處理完成后再執行后續代碼。try/catch
可用于錯誤處理,比.catch()
更直觀。
8 類、繼承與prototype
- 在 JavaScript 中,類(Class) 和 原型(Prototype) 是實現面向對象編程(OOP)的核心。ES6 之前,JavaScript 使用 原型繼承(Prototype Inheritance),而在 ES6 引入了
class
語法,使面向對象編程更直觀。
8-1 class
(類)
- ES6 引入
class
關鍵字,使 JavaScript 的面向對象代碼更加清晰,但本質上它仍然是 基于原型的繼承。
class Person {constructor(name, age) {this.name = name;this.age = age;}// 定義方法(自動添加到原型上)sayHello() {console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);}
}// 創建對象
const person1 = new Person("Alice", 25);
person1.sayHello(); // 輸出: Hi, I'm Alice and I'm 25 years old.
constructor()
:構造函數,在創建對象時自動執行。sayHello()
:實例方法,所有Person
對象共享。
8-2 靜態方法 (Static Methods)
- 在 JavaScript 中,靜態方法是定義在類本身而不是類的實例上的方法。靜態方法可以通過類名直接訪問,而不能通過類的實例來調用。
- 使用
static
關鍵字定義靜態方法。
class MyClass {// 靜態方法static staticMethod() {console.log("This is a static method");}// 實例方法instanceMethod() {console.log("This is an instance method");}
}// 調用靜態方法
MyClass.staticMethod(); // 輸出: This is a static method// 創建類的實例
const myInstance = new MyClass();// 調用實例方法
myInstance.instanceMethod(); // 輸出: This is an instance method// 無法通過實例調用靜態方法
// myInstance.staticMethod(); // TypeError: myInstance.staticMethod is not a function
8-3 繼承(Inheritance)
- 在 ES6 中,可以使用
extends
關鍵字實現 類繼承,并使用super()
調用父類的構造函數。
class Student extends Person {constructor(name, age, grade) {super(name, age); // 調用父類的構造函數this.grade = grade;}study() {console.log(`${this.name} is studying in grade ${this.grade}.`);}
}const student1 = new Student("Bob", 20, "10th");
student1.sayHello(); // 繼承父類的方法
student1.study(); // 輸出: Bob is studying in grade 10th.
extends
讓子類繼承父類。super()
允許調用 父類的構造函數,必須在constructor
里第一行執行。- 子類可以擴展自己的方法。
- 靜態方法也可以被繼承,子類可以繼承父類的靜態方法,或者重寫靜態方法。
class Animal {static type() {console.log("I am an animal");}
}class Dog extends Animal {static type() {console.log("I am a dog");}
}// 調用父類靜態方法
Animal.type(); // 輸出: I am an animal// 調用子類重寫的靜態方法
Dog.type(); // 輸出: I am a dog
8-4 prototype
(原型)
- JavaScript 是基于 原型繼承 的語言,每個對象都有一個
__proto__
屬性,指向其 原型(prototype)。 - JavaScript 本質上仍然是基于原型的繼承,
class
只是語法糖。 - 類的方法實際上是添加到
prototype
上的。
function Animal(name) {this.name = name;
}// 添加方法到原型
Animal.prototype.makeSound = function () {console.log(`${this.name} makes a sound.`);
};const dog = new Animal("Dog");
dog.makeSound(); // 輸出: Dog makes a sound.
console.log(Person.prototype.sayHello === person1.sayHello); // true
prototype
讓所有實例共享方法,減少內存占用。- 直接操作
prototype
可實現手動繼承。
9 多態
多態(Polymorphism)
指的是相同的方法在不同對象上具有不同的行為。在 JavaScript 中,多態主要通過**方法重寫(Method Overriding)**
和**鴨子類型(Duck Typing)**
實現。- JavaScript 是支持多態的,但它不像 Java、C++ 那樣有嚴格的類型系統,而是依賴于其動態特性和原型繼承來實現多態。
9-1 方法重寫(Method Overriding)
- 子類可以重寫父類的方法,實現不同的功能。
class Animal {makeSound() {console.log("Some generic animal sound");}
}class Dog extends Animal {makeSound() {console.log("Woof! Woof!"); // 重寫父類的方法}
}class Cat extends Animal {makeSound() {console.log("Meow~"); // 重寫父類的方法}
}// 統一調用
const animals = [new Dog(), new Cat()];//不關心 `animal` 的具體類型,只調用 `makeSound()`。
animals.forEach(animal => animal.makeSound());
// 輸出:
// Woof! Woof!
// Meow~
9-2 鴨子類型(Duck Typing)
- “如果它會游泳、嘎嘎叫,那么它就是一只鴨子。” —— 鴨子類型 JavaScript 是動態語言,只要對象實現了相同的方法,就可以被當作同一種類型使用,而不關心它的類繼承關系。
class Bird {speak() {console.log("Chirp chirp");}
}class Robot {speak() {console.log("Beep boop");}
}// 統一處理不同對象
const entities = [new Bird(), new Robot()];
entities.forEach(entity => entity.speak());// 輸出:
// Chirp chirp
// Beep boop
Bird
和Robot
沒有繼承同一個父類,但都實現了speak()
方法。
entities.forEach(entity => entity.speak());
實現了多態,因為 JS 只在運行時檢查speak()
是否存在,而不檢查對象的類型。
9-3 函數多態(參數多態)
- JavaScript 的函數可以接收不同類型的參數,表現出函數多態(Function Polymorphism)。
function printMessage(msg) {if (typeof msg === "string") {console.log(`Text: ${msg}`);} else if (typeof msg === "number") {console.log(`Number: ${msg}`);} else {console.log("Unknown type");}
}printMessage("Hello"); // Text: Hello
printMessage(123); // Number: 123
printMessage(true); // Unknown type
10 數組(Array)
- 在JavaScript中,數組是存儲一組數據的對象。數組的元素可以是任何類型,包括其他對象和函數等。
10-1 數組的基本創建和訪問
- 有兩種常見的方式來創建數組:
// 使用數組字面量創建數組
let numbers = [1, 2, 3, 4]; // 數字數組
let names = ['Alice', 'Bob', 'Charlie']; // 字符串數組// 使用Array構造函數
let emptyArray = new Array(); // 創建一個空數組
let anotherArray = new Array(10); // 創建一個包含10個空位的數組
let filledArray = new Array(1, 2, 3); // 創建并初始化數組
10-2 元素訪問
- 數組的索引是從
0
開始的。我們可以通過索引來訪問數組的元素。
let arr = [10, 20, 30, 40];
console.log(arr[0]); // 輸出:10
console.log(arr[2]); // 輸出:30
indexOf()
:查找元素的索引位置。
let arr = [10, 20, 30, 40];
console.log(arr.indexOf(30)); // 輸出:2
10-3 數組內建方法
push()
: 向數組末尾添加一個或多個元素,返回新數組的長度。
let arr = [1, 2, 3];
arr.push(4); // arr變為 [1, 2, 3, 4]
console.log(arr); // 輸出:[1, 2, 3, 4]
pop()
: 刪除數組末尾的元素,返回刪除的元素。
let arr = [1, 2, 3, 4];
let poppedElement = arr.pop(); // poppedElement = 4
console.log(arr); // 輸出:[1, 2, 3]
shift()
: 刪除數組開頭的元素,返回刪除的元素。
let arr = [1, 2, 3, 4];
let shiftedElement = arr.shift(); // shiftedElement = 1
console.log(arr); // 輸出:[2, 3, 4]
unshift()
: 向數組的開頭添加一個或多個元素。
let arr = [1, 2, 3];
arr.unshift(0); // arr變為 [0, 1, 2, 3]
console.log(arr); // 輸出:[0, 1, 2, 3]
splice()
: 在任意位置添加或刪除元素。
let arr = [1, 2, 3, 4];
arr.splice(2, 1, 5); // 從索引2刪除1個元素,插入5
console.log(arr); // 輸出:[1, 2, 5, 4]
map()
:對數組中的每個元素執行一個函數并返回一個新數組。
let arr = [1, 2, 3];
let squared = arr.map(x => x * x); // [1, 4, 9]
console.log(squared);
filter()
:根據條件過濾數組元素,返回一個新數組。
let arr = [1, 2, 3, 4];
let evenNumbers = arr.filter(x => x % 2 === 0); // [2, 4]
console.log(evenNumbers);
reduce()
:將數組元素通過某個函數累積成一個單一的值。
let arr = [1, 2, 3, 4];
let sum = arr.reduce((acc, current) => acc + current, 0); // 10
console.log(sum);
10-4 數組遍歷
for
循環:傳統的遍歷方式。
let arr = [10, 20, 30];
for (let i = 0; i < arr.length; i++) {console.log(arr[i]);
}
forEach()
:對每個元素執行一個函數。
let arr = [10, 20, 30];
arr.forEach((element, index) => {console.log(`Index: ${index}, Value: ${element}`);
});
map()
:創建一個新數組,數組的每個元素是通過提供的函數對原數組每個元素進行處理的結果。
let arr = [1, 2, 3];
let doubled = arr.map(x => x * 2); // [2, 4, 6]
console.log(doubled);
10-5 注意點
- 數組大小:在JavaScript中,數組實際上是對象,且鍵名是數字(數組的索引)。因此,處理大型數組時,性能可能會成為問題。尤其是使用
shift()
和unshift()
操作時,數組的所有元素會被重新索引,可能導致性能下降。
11 總結
-
本教程我們從
Screeps: World
必須的JavaScript
,將JS
和CSS
與HTML
的教程分離,專注于講解JS
的原生基礎語法。 -
完成上述基礎知識學習,你就可以正式開始學習玩耍
Screeps
咯,事不宜遲那就從教程開始吧:Screeps官方教程 -
如有錯誤,歡迎指出!!!!!
-
感謝大家的支持!!!