面向C++程序员的JavaScript 语法实战学习1
面向C++程序员的 JavaScript 语法实战学习1
前言
谈不上开新坑,笔者最近终于可以仔细的学习JavaScripts和进一步的TypeScripts了,这个系列的博客是打算给一些熟悉C++,正在打算入手学习JavaScripts的朋友阅读的。因此,特别的开了这个系列的博客进行一个简单的记录。
所有的来源都是基于MDN文档学习,放心参考。
这一片,我们大致讲。。。
- 注释(Comments)
- 变量/函数声明:
var、let、const、函数声明与函数表达式、提升(hoisting)与 TDZ - JavaScript 的类型系统(Primitive 与 Object)
- 常见类型细节与陷阱(
nullvsundefined、NaN、-0、相等比较) - 常用数据结构:Array、Object、Map、Set、WeakMap、WeakSet、TypedArray 等
- 字面量(Literals):数值、字符串、模板、对象、数组、正则、函数/箭头、类、二进制/八进制/十六进制等
注释(Comments)
注释,C++中也有注释。这个没有什么奇淫技巧,甚至可以说,跟C++的完全一致。官方的说:注释用于给代码写说明,不会被 JavaScript 引擎执行。常见三种形式:
- 单行注释:
//
// 这是单行注释
let x = 1; // 行尾注释
- 多行注释:
/* ... */
/*多行注释可以跨越多行
*/
- JSDoc 风格(用于生成文档或给 IDE 提供类型/注释信息):
/** ... */
/*** 计算矩形面积* @param {number} w 宽度* @param {number} h 高度* @returns {number} 面积*/
function area(w, h) {return w * h;
}
声明(Declarations)与提升(Hoisting)
变量的声明总是是程序语言设计的第一步。在JavaScript 中常见的声明有变量声明、函数声明、类声明等。我们快速过的重点是理解 var、let、const 的区别,以及函数声明 vs 函数表达式的行为。
var:不被推介的全局访问声明
var的产生笔者认为是一种不完备,因为它的作用域是整个函数内甚至是全局的,意味着,这样的代码会神奇的被通过执行而不会产生错误:
console.log(x) // undefined
var x = 2
console.log(x) // 2
你知道的,这就像神秘的访问未初始化变量一般,这也就导致一种很烦人的问题:你也不知道你的同事在哪里搞了一个x出来,甚至还是用var声明的,导致很有可能会造成程序的变量引用错乱,难以排查。
相比之下,let就显然更好一点:
let
let是一种更加现代的声明方式,笔者认为let声明变量才是合理的程序编程行为。它的作用域就跟我们理解的C/C++变量一样,必须声明后访问。
console.log(y)
let y = 3
console.log(y)
这段代码就会触发错误:
ReferenceError: Cannot access 'y' before initializationat Object.<anonymous> (/home/charliechen/js_practice/pr2/var.js:5:13)at Module._compile (node:internal/modules/cjs/loader:1760:14)at Object..js (node:internal/modules/cjs/loader:1893:10)at Module.load (node:internal/modules/cjs/loader:1480:32)at Module._load (node:internal/modules/cjs/loader:1299:12)at TracingChannel.traceSync (node:diagnostics_channel:328:14)at wrapModuleLoad (node:internal/modules/cjs/loader:244:24)at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)at node:internal/main/run_main_module
const
const绑定的值是不可变的,但是他不像C++,会给所有的成员上const,所以这个const只是在说,你没法更新const修饰的变量存储的值,但是存储的东西本身的自身方法或者是成员仍然可以随意使用。或者更加确切的说,js的const是纯粹的禁用了被修饰对象的赋值行为
const obj = { a: 1 };
obj.a = 2; // 合法
obj = {}; // TypeError:不能重新赋值
函数声明 vs 函数表达式
js的函数声明非常的自由,大致的结构是:
function func_name(var1, var2){// do something// return if request// return var_res;
}
function修饰的函数就跟我们的C++函数一样了,你声明且实现了,那就可以在该js文件中随意使用
foo(); // 可以
function foo() { console.log('hi'); }
比起来,采用函数表达式更加像是生成了函数对象,在js中称之为函数表达式(包括箭头函数)
bar(); // TypeError: bar is not a function
var bar = function() {};
或用 let/const:
baz(); // ReferenceError(TDZ)
const baz = () => {};
JavaScript 的类型系统(概览)
JavaScript 有两类值:
- 原始类型(Primitive):
Undefined、Null、Boolean、Number、BigInt、String、Symbol - 复杂类型(Object):对象、函数(在 JS 中函数就是对象)、数组、日期、正则等(所有非原始值都是对象)
typeof 操作符能返回一些基本类型信息,但有些值得注意:
typeof undefined; // "undefined"
typeof null; // "object" —— 这是历史遗留的怪癖(注意)
typeof true; // "boolean"
typeof 123; // "number"
typeof 1n; // "bigint"
typeof "abc"; // "string"
typeof Symbol(); // "symbol"
typeof {}; // "object"
typeof []; // "object"
typeof function(){}; // "function" —— 这是对象的一种特殊返回值
原始类型细节
Number(数字)
- JS 把整数和浮点数都归为
Number,遵循 IEEE 754 双精度浮点数。 - 特殊值:
NaN(非数)、Infinity、-Infinity。 Number.isNaN(x)推荐用于判断NaN,它不会把非数字误判。- 常见陷阱:浮点精度问题,如
0.1 + 0.2 !== 0.3。 - -0 的存在:
-0 === 0为真,但1 / -0 === -Infinity可用来区分。
BigInt(大整数)
- 用于表示任意精度的整数,字面量以
n结尾:123n。 - 与
Number不可混合运算(会抛出 TypeError),需要显式转换。
const a = 10n;
const b = 5n;
a + b; // 15n
// a + 1; // TypeError
String(字符串)
- 用单引号、双引号或反引号(模板字符串)。
- 字符串是不可变的(每次修改都会生成新字符串)。
Boolean(布尔)
- 两个字面量:
true/false - 有“真值/假值”概念(参见后文的 truthy/falsy)。
Symbol
- 用于创建唯一且不可变的标识符,常用于对象属性键以避免命名冲突:
const id = Symbol('id');
const obj = {};
obj[id] = 1;
Null 与 Undefined
undefined:变量声明了但未赋值时的默认值;函数没有返回值时返回undefined。null:表示“没有值”或“空值”,通常由程序员显式赋值表示“故意没有值”。typeof null === "object"(历史遗留问题)。
Object(对象)
- 键值对集合。键(property)可以是字符串或
Symbol(非Number,数字键会被转为字符串)。 - 对象可以包含函数(方法)。
- 常见子类型:Array、Function、Date、RegExp、Map、Set、Promise 等。
常见类型细节与陷阱
相等比较:== vs ===
这个算是大坑了,请注意,非常建议js中直接使用===而不是==,防止导致意外产生
===(严格相等):不进行类型转换,类型不同直接为false。==(抽象相等):会发生类型转换,有复杂规则,容易导致意外。
0 == false; // true
0 === false; // false
'' == 0; // true
null == undefined; // true
null === undefined; // false
NaN(不是数字)
NaN !== NaN(自己不等于自己)- 用
Number.isNaN(x)检测NaN isNaN(x)会先尝试把 x 转为数,可能误判(例如isNaN('hello')为 true,因为'hello'转为数是 NaN)。
Truthy / Falsy(真值与假值)
在布尔上下文中(如 if、&&、||)下面的值被视为假(falsy),其余为真(truthy):
Falsy 值:false, 0, -0, 0n(BigInt 0), ""(空字符串), null, undefined, NaN
其它 都是 truthy(包括空对象 {}、空数组 [])
注意:
if ([]) console.log('[] 被视为 truthy'); // 会输出
if ({}) console.log('{} 也是 truthy'); // 会输出
属性访问:点(.) vs 方括号([])
- 点语法:对象属性必须是有效标识符(不能是数字开头、有空格或特殊字符)。
- 方括号语法:属性名可以是字符串表达式或变量,支持
Symbol键。
const key = 'name';
obj[key]; // 通过变量访问
obj['first-name']; // 用方括号访问包含连字符的属性
克隆 / 合并(浅拷贝与深拷贝)
- 浅拷贝(只拷贝第一层):
Object.assign({}, obj)、{...obj}(展开运算符) - 深拷贝:
JSON.parse(JSON.stringify(obj))(有局限性,无法复制函数、Symbol、undefined、循环引用),或使用递归/库(如 lodash 的cloneDeep)
常见数据结构(与用法)
Array(数组)
- 用途:有序集合,索引从 0 开始。
- 创建:
const a = [];
const b = [1, 2, 3];
- 常用方法:
push,pop,shift,unshift,slice,splice,map,filter,reduce,forEach,find,findIndex - 遍历:
for、for...of(元素),for...in(不推荐用于数组——会遍历可枚举属性) - 展开与拷贝:
[...arr] - 注意:数组是对象,可以包含稀疏元素(如
const a = []; a[5] = 10;),长度属性自动更新。
示例:
const arr = [1, 2, 3];
arr.push(4); // [1,2,3,4]
const doubled = arr.map(x => x * 2); // [2,4,6,8]
Object(对象字面量)
- 创建:
{}或new Object(),推荐字面量。 - 属性访问:
obj.key或obj['key'] - 属性操作:
Object.keys(obj),Object.values(obj),Object.entries(obj),Object.assign() - 属性描述符(高级):
Object.defineProperty可设置writable,enumerable,configurable等。
对象字面量新特性(ES6+):
- 简写属性:
{a}等价{ a: a } - 方法简写:
{ greet() { ... } } - 计算属性名:
{ [keyName]: value }
示例:
const name = 'alice';
const obj = {name, // 简写greet() { console.log(`Hello ${this.name}`); } // 方法简写
};
Map(映射)
- ES6 引入,键可以是任意类型(包括对象)。
- 有序(按插入顺序)。
- API:
new Map(),map.set(k,v),map.get(k),map.has(k),map.delete(k),map.size - 遍历:
for (const [k,v] of map) {}
使用场景:当你需要以对象为键或保证插入顺序并高效查找时,用 Map 比普通对象更合适。
Set(集合)
- ES6 引入,保存唯一值(不重复)。
- API:
new Set(),add,has,delete,size - 常用于去重:
const unique = [...new Set([1,2,2,3])]; // [1,2,3]
WeakMap / WeakSet
- 键(WeakMap)或值(WeakSet)是弱引用,允许垃圾回收(当没有其它引用时)——用于缓存或元数据,避免内存泄漏。
- 无
size,不可遍历(设计上就是为了隐私与 GC 性能)。
TypedArray(类型化数组)
- 用于处理大量数值的高性能数组,例如
Int8Array,Uint8Array,Float32Array,常用于 WebGL、文件/二进制数据处理。 - 与普通数组不同,元素类型固定、长度固定。
其他:Date、RegExp、Promise
Date表示日期时间,注意时区与Date的构造字符串差异。RegExp(正则)有字面量/pattern/flags与构造new RegExp('pattern', 'g')。Promise用于异步编程(then/catch/finally,以及async/await语法糖)。
字面量(Literals)详细介绍
字面量(literal)是直接写在代码里的固定值表达方式。JavaScript 中字面量形式很多,这里逐一说明并举例。
数字字面量(Numeric literals)
支持多种形式:
- 十进制:
123,3.14,0.5,.5 - 科学计数法:
1e3(等于 1000) - 十六进制:
0xFF(255) - 二进制:
0b1010(10) - 八进制:
0o77(63) - 分隔符(从 ES2021):可用下划线
_改善可读性:1_000_000
例子:
const a = 255; // 十进制
const b = 0xff; // 十六进制
const c = 0b1010; // 二进制
const d = 1_000; // 可读性分隔符
字符串字面量(String literals)
三种括法:
- 单引号:
'hello' - 双引号:
"hello" - 模板字符串(反引号):
hello ${name},支持插值与多行字符串。
字符串常用操作:拼接、模板、长度、索引、slice、substring、replace、split、toLowerCase、toUpperCase。
模板字符串示例:
const name = 'Alice';
const msg = `Hello ${name}, today is ${new Date().toLocaleDateString()}`;
对象字面量(Object literals)
基本形式:
const obj = { a: 1, b: 2 };
高级写法:
- 简写属性:
{ a } - 方法简写:
{ greet() {} } - 计算属性名:
{ [key]: value } - 可定义 getter/setter:
const o = {_x: 1,get x() { return this._x; },set x(val) { this._x = val; }
};
数组字面量(Array literals)
const arr = [1, 2, 3];
数组解构赋值:
const [a, b] = [1, 2]; // a=1, b=2
const [first, ...rest] = [1,2,3,4]; // rest=[2,3,4]
正则字面量(RegExp literals)
两种创建方式:
- 字面量:
/ab+c/g - 构造器:
new RegExp('ab+c', 'g')(当 pattern 动态生成时用构造器)
注意:正则字面量在函数或循环中被静态解析,可能被缓存,构造器每次都会创建新实例。
函数字面量(Function literal)
- 函数声明:
function foo() { }(不是字面量,但常见) - 函数表达式:
const f = function() {}(可匿名) - 箭头函数(ES6):
const f = (x) => x * 2(没有this、arguments,不能作为构造函数)
箭头函数示例:
const squares = [1,2,3].map(x => x * x);
类字面量(Class)
类不是严格“字面量”,但可以用类声明或表达式:
class Person {constructor(name) { this.name = name; }greet() { console.log(this.name); }
}
常见问题速查与示例集合
1. 字符串拼接哪种更好?
推荐使用模板字符串:
const name = 'Alice';
const msg = `Hello, ${name}!`;
2. 如何复制数组 / 对象?
浅拷贝:
const arr2 = [...arr];
const obj2 = {...obj};
深拷贝(简单对象,不含函数/Symbol):
const deep = JSON.parse(JSON.stringify(obj));
3. 判断数组:
Array.isArray(x); // 推荐方法
4. 判断对象是否为空:
Object.keys(obj).length === 0;
5. 将类数组(arguments)转为数组:
const args = Array.from(arguments); // 或 [...arguments]
6. 去重数组:
const unique = [...new Set(arr)];
