他是为语言学习者准备的:《JavaScript/ECMAScript文法速学参考》@2023-12
请注意:此文章是我们公司在2023年第四季度公开的新员工入职指南资料。
这是面向具有其他语言经验的人的JS/ECMAScript基本语法。
执行环境
在JavaScript的处理系统中有各种不同的存在。大致可以分为以下几类。
-
- ブラウザ上での実行
- NodeJS上での実行
在不同的环境下,ECMAScript的可运行版本、可用的运行环境和模块解析方法都有所不同。
-
- バージョンに関しては、ECMAScript2020相当のコードであればほぼすべての環境でサポートされており、2023年現在で困ることはありません。
-
- ランタイムに関しては、ブラウザとNodeJSで大きく異なります
- モジュール解決方法には、 CommonJS方式とECMAScript Module(ESM)方式が混在しています。将来的にESMに統一される方向性とはなるものの、時間がかかりそうです
基本文法
-
- 大文字・小文字は区別されます
-
- ステートメントは、セミコロンで区切ることができます
{}を用いてブロックを構成します。
{
statement_1;
statement_2;
⋮
statement_n;
}
- ブロックは変数のスコープとして機能し、 let または const を用いて変数を宣言できます。
{
let var1 = 'text'; // 上書き可能
var1 = 'text2';
const var2 = new Date(); // constの場合、再宣言は不可
}
基本数据类型
在JavaScript中,有7种被称为原始类型的标量以及一个称为对象的数据结构类型。
原始类型实际上保存了值,因此进行值传递,而对象是引用,只能进行引用传递。
基本原语
boolean
true
または false
string
文字列型number
整数及び浮動小数点数特殊的原始结构 (tè shū de jí
null
null値を示す特殊なキーワードundefined
値が未定義であることを示すundefined は他の言語にない概念で、その実態はグローバルスコープの変更不可のプロパティ です。変数に値・参照のいずれも代入されていないこと、もしくは関数が何も返却しないことを表す場合に用いられます。
意図的に変数が空であることを示すには null を用いることになります。NodeJSではnullを渡すことで、ストリームの末端を示すなど特殊な役割を担う場合もあります。
null は typeof 演算子を用いると、”object”が返ってくるため、場合わけには注意が必要です。
在考虑与其它语言的兼容性时,很多情况下使用null更好。然而,在实际的JS实现中,并没有太多需要细分使用这两者的场景。
最近引入的原始方式
bigint
自由精度整数値。大きな整数を取り扱うために用います。ES2020で導入されました。symbol
インスタンスが固有で不変となるデータ型。ES2015(ES6)で導入されました。请注意,Symbol在Ruby的Symbol类中与其大不相同。
-
- Rubyでは唯一性が担保されたimmutableなオブジェクトで「シンボルが同じであれば同じオブジェクト」ですが、JSではその逆で「シンボルが同じであっても絶対に異なるオブジェクト」 を生成します。
- 主にプロパティのキーとして、他のいかなるプロパティとも絶対に衝突しないキーを設定する際に用いられます。
物件类型
几乎所有的 JavaScript 对象都是 Object 数据类型的实例。
一般对象通常从Object.prototype继承属性(包括方法),但这些属性可能被隐藏(也称为覆盖)。
对象是对JavaScript线程中某些数据的引用,与原始类型不同,它不直接持有数据。
处理数值类型
在JavaScript中,没有int或float这样的概念,基本的数字类型都是用Number类型来表示的。
它的实际表现形式是IEEE 754双精度64位二进制格式,与Java或C#的double类型类似。它可以表示小数值,但存储的数字大小和精度是有限制的。
-
- 小数の計算では、精度に注意
-
- 大型の整数計算ではBigIntを用いることを検討する(互換性や型変換に注意)
-
- 数値変換は次のルールに基づく
undefined は NaN (特殊な数値定数)になる
null は 0 になる
true は 1 に、false は 0 になる
另外,如果使用gRPC或者Avro,通常会通过库隐式或显式地进行数据转换。
结构体、元组、指针类型
直到ES2022版本,没有像结构体或元组这样的东西,一切都是对象类型。指针类型也不存在。
这些情况也是有原因的,在JavaScript处理系统中,几乎不会直接从代码调用操作系统的API。
也许在不久的将来,Record和Tuple这两个新的複合プリミティブ型可能会被引入(在编写本文时,处于Stage2阶段)。
由于是新的原始型,出于兼容性的考虑,可能最好不要过早采用。
运算符的重载
与Java、C#和Python不同,ECMAScript没有运算符重载功能。因此,无法对某个数据类型和数据类型之间的加法等操作进行描述。例如,通常情况下无法实现向量之间的加法表达式。
如果要进行计算处理,请考虑这一点,并考虑使用其他处理系统。
嵌入式对象
所谓逻辑数据类型,将基本数据类型组合而成的对象来表示。
事先,在JS的运行时环境中存在许多内置对象。此外,在NodeJS环境中,还有由NodeJS运行时环境提供的特定内置类型。
以下只列举了一些典型的例子。
处理二进制数据:第一部分
简单的字节数组可以通过ArrayBuffer在内存中进行配置。
// 8バイトのバッファ
const buffer = new ArrayBuffer(8);
一些内置数据类型对象包括ArrayBuffer,在存储数据时直接使用内存,因此它们是可传递(Transferable)的对象。
可传递对象可以快速复制到其他上下文(例如Worker)中。
处理二进制数据:第2部分
在服务器端实现中,使用Node.js提供的Buffer类型。
import { Buffer } from 'node:buffer';
Buffer型是一个固定长度的字节数组,也可以从字符串或数字数组转换而来。
// 8バイト
Buffer.alloc(8);
// <Buffer 00 00 00 00 00 00 00 00>
// 他データからの変換
Buffer.from('my-text','utf8');
Buffer.from([1, 64, 255, 256]); // <Buffer 01 40 ff 00>
处理二进制数据:第三部分
在浏览器或非NodeJS的执行环境中,不能使用Buffer。由于ArrayBuffer类型不允许进行内存操作,因此需要使用相应的TypedArray类型作为其数据视图。
const buffer = new ArrayBuffer(8);
// 4バイト(long型相当)への変換
const view = new Int32Array(buffer);
TypedArray提供了以下几种选项。
请查阅MDN文档,以了解每个储存的编码方式是如何进行的。
- 値のエンコード方式と正規化
标准输入输出
作为对应于print的功能,已经准备了console对象。
console.log('Hello, world');
console.error('Error!); // NodeJSにおいては、標準エラー出力
標準輸入通常只能用於NodeJS。由於描述基於使用串流,因此在這裡忽略。
物体
大多数的JavaScript代码是用于描述对象操作的。对象是对内存中的数据引用进行结构化的,它是按引用传递的。此外,它没有指针的概念。
对象由 {} 声明。
即使内容相同,它们引用的是不同的内存数据,因此比较运算符返回false。
通过 . 或 [] 使用索引进行元素访问。
const obj1 = { name: 'John Smith' }
cosnt obj2 = { name: 'John Smith' }
obj1 === obj2 // false
console.log(obj1.name) // 'John Smith'
obj2.name = 'Kevin mitonic' // const宣言であっても、プロパティの値の入れ替えは可能
console.log(obj2['name']) // Kevin mitonic'
数组可以用 [] 进行声明。
通过使用数字索引来访问元素。
const ary = ['john', 'smith'];
console.log(ary[0]); // `john`
虽然可以混合使用不同的数据类型,但由于使用不便且无法推断数据类型,因此很少使用。
cosnt ary = ['text', 0 , false]
对象和数组可以结合在一起形成复杂的结构。
const obj1 = {
message: 'Hello, world',
isDisabled: false,
profile: {
name: 'John Smith',
age: 20
},
tags: ['user','member' ]
}
JSON (JavaScript Object Notation)
对象可以被编码为一个称为JSON的字符串数据。
cosnt str = JSON.stringify(obj1);
console.log(str);
转换后将得到下面的文本数据。
{"message":"Hello, world","isDisabled":false,"profile":{"name":"John Smith","age":20},"tags":["user","member"]}
要将字符串解码为对象,则可以使用JSON.parse函数。
const obj = JSON.parse(str);
此外,尽管JSON的描述方式有些冗长,但其规范非常严格,因此JSON.parse的处理速度实际上相当快。
将内嵌对象转化为JSON格式。
需要特别注意的是,在JSON.stringify与嵌入的对象一起使用时的行为。
编码成什么样的值取决于该嵌入的对象。
由于无法自动将原始内置对象转换为解码结果,因此无法将JSON.stringify/JSON.parse用作相互对应的序列化器和反序列化器。
const obj = { date: new Date() }
// { date: 2023-12-03T03:40:29.401Z } であり、dateプロパティにはDate型オブジェクトが格納されている
const str = JSON.stringify(obj);
const parsedObj = JSON.parse(str);
// { date: '2023-12-03T03:40:29.401Z' } が得られる。dateプロパティはstring型になっている。
具有循环引用的JavaScript对象
请注意,尝试将其转换为JSON会导致错误,因此要小心。
有许多库可以避免这些问题。
-
- https://www.npmjs.com/package/safe-stable-stringify
-
- https://www.npmjs.com/package/fast-json-stable-stringify
- https://www.npmjs.com/package/json-stable-stringify
語法
Java和C#具有相似的语法。
函数
函数通过 function 语句进行声明。
可以定义多个参数,并指定默认参数等。
函数体是一个代码块,可以在其中声明和使用变量。
function myFunc(arg1, arg2, arg3='defaultValue') {
// 変数宣言は原則として const (定数宣言)を用いる
const var1 = 'text';
const var2 = arg2;
return var1 + var2; // 返り値は
}
可以使用箭头符号(=>)来表示匿名函数。
const arrowFunc = (arg) => {
return arg.toString()
}
尝试-捕获语法
try {
otherFunc();
} catch(e) {
console.error(e);
} finally {
console.log('end');
}
条件分支
假如,否則如果,否則構文
if ( a === 'test' ) {
console.log('ok');
else if ( a === 'not test') {
console.warn('warning!');
} else {
console.error('error!');
}
三个操作符
const result = a === 'test' ? 'ok' : 'error';
切换语法
这看起来像C型的形状。
switch (b) {
case 'test':
console.log('ok');
break;
// C同様、複数のcase文を重ねることができる
case 'not test':
case 'not test2':
console.warn('warning!');
break;
default:
console.error('error!');
}
}
重复
循环控制结构
很像C
for (let index = 0; index < array.length; index++) {
const element = array[index];
// breakやcontinueも利用できます。
}
当文
同时,既可以使用while,也可以使用do-while。
while (condition) {
/** ループ処理 */
}
do {
/** ループ処理 */
} while (condition);
迭代处理
还有针对数组和对象元素的可迭代对象列举处理语法。
// 要素の列挙
for (const iterator of object) {
}
// キーの列挙
for (const key in object) {
const element = object[key];
}
文字的字符串操作
範本
const username = 'john';
const age = 20;
console.log(`${username} ${20}`) // 'john 20' と表示される
切分和合併
const ary = 'a,b,c'.split(',');
const txt = ary.join('-'); // 'a-b-c'となる
切割
const str = 'Mozilla';
console.log(str.substring(1, 3)); // oz
console.log(str.substring(2)); // zilla
搜索
'abcd'.indexOf('cd'); // 2
'abcd'.match(/ABC/i); // [ 'abc', index: 0, input: 'abcd', groups: undefined ]
'abcd'.search(/cd/i); // 2
'abcd'.includes('cd'); // true
数组操作
一般而言,在JavaScript中,数组的处理速度并不快。
特别是在处理大规模数据集时,其处理速度往往较慢。请仔细考虑使用场景。
将元素添加到数组中并删除元素。
因为它是可变长度的数组,所以可以进行元素的添加和提取。
const ary = ['b','c']
// 先頭の処理
ary.unshift('a') // ary => ['a','b','c']
ary.shift() // ary => ['b','c']
// 末尾の処理
ary.push('d') // ary => ['b','c','d']
ary.pop() // ary => ['b','c']
另外,在数组操作中,还提供了更复杂的处理方式 splice。
const ary = ['月', '水', '木', 'June'];
// index=1 に挿入
let index = 1;
ary.splice(index, 0, '火'); // ary => ["月", "火", "水", "木", "土"]
// index=4から変更(1つ削除し、挿入)
index = 4;
ary.splice(index, 1, '金'); // ary => ["月", "火", "水", "木", "金"]
尽管不是直接的参数,但请记住可以修改从第一个参数的第i个元素开始的要素。
重新生成或切割数组。
有一些操作不涉及对数组本身的处理,而是创建新数组。
需要注意的是,即使是新数组,在其中的元素是对象时,它们仍然引用相同的数据。
重新生成处理
筛选
[1,2,3,4,5].filter(n => 3 < n); // [4,5]
取出
// 2番目の次の要素から、-1番目(最後から1番目の手前)まで取り出す
["月", "火", "水", "木", "金"].slice(2, -1); // ["水","木"]
再加工 jiā
[1,2,3,4,5].map(a => a * 2); // => [2,4,6,8,10]
排序
[3,1,4,2,5].sort(); // => [1,2,3,4,5]
// 並び替えのための評価式を追加する。
[3,1,4,2,5].sort((a,b) => a - b); // => [1,2,3,4,5]
[3,1,4,2,5].sort((a,b) => b - a); // => [5,4,3,2,1]
翻转
[1,2,3,4,5].reverse(); // => [5,4,3,2,1]
原型导向
请参考此处的详细文章,JavaScript采用了原型导向。
计时器
从历史背景上来看,JavaScript内置了计时器功能。
经典的例子是setTimeout。
它会在指定的毫秒数等待后执行一次回调函数。
setTimeout(() => {
console.log('waited!')
}, 1000);
还有一种可以定期进行回调调用的函数 setInterval。
const timer = setInterval(() => {
console.log('hello!');
}, 1000);
setInterval 是一旦定义后一直会持续执行的函数。要取消定时器,可以使用 clearInterval。
clearInterval(timer)
鉴于setInterval函数容易引发内存泄漏,因此在不明确删除的情况下,使用该函数时应谨慎处理。
承诺
Promise是为了描述异步处理,如执行外部HTTP API或磁盘访问而引入的。它与C#的Promise非常相似,而在Java中,CompletableFuture是一个类似的概念。
在Promise中,可以通过then / catch / finally回调的形式来定义将来执行的处理操作。
const myPromise = new Promise((resolve, reject) => {
try {
const result = someHeavyProcess();
resolve(result); // 完了したら呼び出すのがresolve
} catch(e) {
reject(e); // 失敗したら呼び出すのがreject
}
});
myPromise
.then(() => console.log('finished')) // 完了時コールバック
.catch((e) => console.error(e)); // エラー発生時コールバック
console.log('ok');
Promise的执行顺序
-
- 在上面的例子中,我们创建了一个Promise实例myPromise。
-
- 这个实例的创建是立即完成的,并显示ok。
-
- 在Promise内部定义的处理会在后台持续执行,直到完成或失败,在其中一个阶段调用resolve或reject。
- 然后,事先注册的then或catch回调函数会被执行。
通常,由于这些处理期间JavaScript的本地处理会继续进行,因此Promise通常用于异步处理。
敢于等待承诺
根据情况,有时候可能会故意等待Promise。在这种情况下,我们会使用await。
const myPromise = new Promise((resolve, reject) => {
try {
const result = someHeavyProcess();
resolve(result); // 完了したら呼び出すのがresolve
} catch(e) {
reject(e); // 失敗したら呼び出すのがreject
}
});
try {
await myPromise
console.log('ok');
} catch(e) {
console.error('failed')
}
通过使用”await”关键词,可以实现类似普通JavaScript的顺序编写。
你可能会想:“哎呀?为了引入Promise而不需要等待,为什么要用await?这不是和原本的写法一样吗?”
使用Promise能大幅提升读取性,尤其在需要组合执行多个异步函数的情况下。下一节将举例说明。
异步函数 async function
ECMAScript 规定了 `await` 只能在异步函数的内部使用(将来可能会更改)。
只要使用了一次 `await`,就会将包含该部分的整个代码块视为异步执行。
因此,存在用于定义异步函数的 `async function` 语句。
async function fetch(){
const userId = await getUserIdFromAPI();
const data = await fetchUserDataFromAPI(userId);
return data;
}
fetch();
这句话与下面的描述具有相同的意思。
function fetch(){
return new Promise((resolve, reject) => {
getUserIdFromAPI()
.then(userId => {
return fetchUserDataFromAPI(userId)
.then(data => {
resolve(data);
})
.catch(e => {
reject(e)
})
});
}
fetch();
通过使用await,我们可以看到写起来非常简单。
async和await在使用上有一些小技巧,尤其是在NodeJS中,可以避免回调地狱,同时编写非阻塞的代码。因此,我们需要熟练掌握它们的使用方法。
其他重要概念
现在,我认为你已经学习了基本的JavaScript语法和概念。请详细阅读MDN提供的JavaScript参考手册以获取更多资料。
- JavaScript | MDN
在这里,我还会列举其他重要概念的追加部分。
NodeJS中的EventEmitter
作为浏览器中没有的概念之一,在NodeJS中有一个叫做EventEmitter的东西。
它是用来实现非阻塞处理的重要内置对象,提供比Promise更加灵活和强大的异步处理机制。
承诺、事件发射器和流是NodeJS的核心概念,最大程度发挥libuv的作用,建议务必掌握。
TypeScript 是一种原生的中国语言编程。
透過添加註釋,TypeScript擴展了JavaScript,使其能夠實現型別宣告。它是基於轉換為JavaScript的前提,本身並不擁有處理系統(也可以直接透過Deno等執行系統執行)。
由于JavaScript本身不支持静态类型,所以TypeScript只是提供了注解功能,但通过宽松的类型定义,它可以使更大规模的应用开发变得更容易。
中止控制器
AbortController是一种内置类型对象,可以用于编写用于中断Promise等异步操作的操作。在JS应用程序中,由于经常使用HTTP执行外部API,因此中止或重新执行需要AbortController的概念。