他是为语言学习者准备的:《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种被称为原始类型的标量以及一个称为对象的数据结构类型。
原始类型实际上保存了值,因此进行值传递,而对象是引用,只能进行引用传递。

基本原语

データ型概要booleantrue または falsestring文字列型number整数及び浮動小数点数

特殊的原始结构 (tè shū de jí

データ型概要nullnull値を示す特殊なキーワード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运行时环境提供的特定内置类型。

以下只列举了一些典型的例子。

オブジェクト概要Array配列ObjectオブジェクトDate日付RegExp正規表現Errorエラー型Set値コレクションMapKey-ValueコレクションMath数学計算ArrayBufferバイト配列(操作不可)TypedArrayバイナリデータビューBufferNodeJSにおけるバイト配列Promise非同期オブジェクト

处理二进制数据:第一部分

简单的字节数组可以通过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提供了以下几种选项。

型値の範囲サイズ (バイト数)Web IDL型Int8Array-128 から 1271byteUint8Array0 から 2551octetUint8ClampedArray0 から 2551octetInt16Array-32768 から 327672shortUint16Array0 から 655352unsigned shortInt32Array-2147483648 から 21474836474longUint32Array0 から 42949672954unsigned longFloat32Array-3.4E38 から 3.4E38 および 1.2E-38 (最小の正の数)4unrestricted floatFloat64Array-1.8E308 から 1.8E308 および 5E-324 (最小の正の数)8unrestricted doubleBigInt64Array-263 to 263 – 18bigintBigUint64Array0 to 264 – 18bigint

请查阅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的执行顺序

    1. 在上面的例子中,我们创建了一个Promise实例myPromise。

 

    1. 这个实例的创建是立即完成的,并显示ok。

 

    1. 在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的概念。

广告
将在 10 秒后关闭
bannerAds