JavaScript 基础:值、函数与数据处理
运行与学习顺序
JavaScript 的语言规则由 ECMAScript 规范定义;DOM、Fetch 等由运行环境提供。浏览器和 Node.js 都能执行 JavaScript,但 Node.js 没有浏览器页面的 document。
基础示例可在浏览器控制台运行,或保存为 demo.js 后执行 node demo.js。HTML与 CSS建立页面结构与样式;学完本篇,再用 DOM 与异步连接交互,最后进入 Vue 或 React。
下文 console.assert 用于控制台自检:条件为假会输出失败消息,但在现代浏览器和 Node.js 中不会因此抛异常或中止后续语句。需要失败时让自动化验收退出的检查,应使用显式 throw 或测试断言,不能只看进程退出码。见 Node.js console.assert。
声明、类型与比较
优先 const,需要重新赋值时使用 let;var 有函数作用域,是阅读旧代码时需要理解的写法。let/const 声明之前处于暂时性死区,访问会抛 ReferenceError,不能简单解释成“没有提升”。
const user = { name: "Alice" };
user.name = "Bob"; // const 限制重新绑定,不冻结对象
let count = 0;
count += 1;
console.assert(user.name === "Bob" && count === 1);
console.assert("5" !== 5);
console.assert((10 != "10") === false);
console.assert(typeof null === "object"); // 历史特殊情况
console.assert(Array.isArray([1, 2]));原始值包括 undefined、null、boolean、number、bigint、string、symbol;数组、函数、日期等属于对象。bigint 与 number 不能直接混合算术。一般用 ===/!== 避免隐式转换,对象比较的是引用身份。
const input = "42";
const number = Number(input);
console.assert(Number.isFinite(number));
console.assert(Number.isNaN(Number("abc")));
console.assert(Number("") === 0); // 需要整数输入时,应先拒绝空白
const page = 0;
console.assert((page ?? 1) === 0); // ?? 仅为 null/undefined 提供后备值parseInt("12px", 10) 得到 12,不会因尾部文本抛异常。输入校验不能只靠 try/catch 包裹转换函数。
条件、循环与函数
function countNonempty(lines) {
let count = 0;
for (const line of lines) {
if (line.trim() !== "") count += 1;
}
return count;
}
console.assert(countNonempty([" hello ", "", " ", "world"]) === 2);
console.assert(countNonempty([]) === 0);if/else 处理分支;for...of 遍历可迭代对象的值;while 在条件成立时继续;break/continue 结束或跳过循环。字符不是可递增计数器,"a"++ 不会得到 "b"。
函数声明、函数表达式、箭头函数都能封装操作。默认参数在实参为 undefined 时生效,rest 参数收集多余实参。
const sum = (...numbers) => numbers.reduce((total, value) => total + value, 0);
function createAdder(base) {
return (value) => base + value; // 闭包保留对外围绑定的访问
}
console.assert(sum(1, 2, 3) === 6);
console.assert(createAdder(5)(3) === 8);普通函数的 this 由调用方式决定;箭头函数捕获外围 this,不能替代所有对象方法。不要把“方法内部的 this 永远是当前对象”当作规则。
对象、数组与字符串
const people = [
{ id: 1, name: "Alice", age: 20 },
{ id: 2, name: "Bob", age: 30 },
];
const names = people.filter((person) => person.age >= 25).map((person) => person.name);
console.assert(JSON.stringify(names) === '["Bob"]');
const updated = people.map((person) => person.id === 1 ? { ...person, age: 21 } : person);
console.assert(people[0].age === 20 && updated[0].age === 21);
console.assert(people.some((person) => person.age >= 30));
console.assert(people.every((person) => person.age >= 18));| 操作 | 常用方法 | 是否修改原对象 |
|---|---|---|
| 列表增删 | push/pop/shift/unshift/splice | 修改数组 |
| 截取、拼接 | slice/concat | 返回新数组 |
| 转换、过滤、归约 | map/filter/reduce | 方法本身不修改数组,但回调仍可能修改元素 |
| 对象枚举 | Object.keys/values/entries | 返回自有可枚举属性的信息,常用版本只含字符串键 |
| 文本处理 | trim/split/replace/toLowerCase | 字符串不可变,返回新值 |
| 序列化 | JSON.stringify/parse | 不适用于任意对象的无损复制 |
展开语法 {...obj}、[...array] 只复制一层,嵌套对象仍可能共享。数组默认 sort() 按字符串比较且修改原数组,数值排序要提供比较函数。
类与原型
类语法建立在原型机制上;方法调用仍受 this 规则约束。# 声明私有字段,_name 只是命名约定;JavaScript 没有 TypeScript 风格的 public/private/protected 访问修饰符。
class Counter {
#value = 0;
constructor(step = 1) { this.step = step; }
increment() { this.#value += this.step; }
get value() { return this.#value; }
static fromStep(step) { return new Counter(step); }
}
const counter = Counter.fromStep(2);
counter.increment();
console.assert(counter.value === 2);extends 定义继承,子类构造函数需先 super(...) 才能用 this;getter/setter 控制属性访问。简单数据处理可直接使用对象与函数,无需为了形式引入抽象基类。
模块与运行环境
ES 模块使用 export/import:
// math.mjs
export const add = (a, b) => a + b;// app.mjs:执行 node app.mjs
import { add } from "./math.mjs";
console.assert(add(2, 3) === 5);浏览器可用 <script type="module" src="./app.mjs"></script> 加载同一个文件,并通过将.mjs作为JavaScript MIME类型提供的本地 HTTP 服务加载;相对导入写完整路径。Node.js 中 .mjs 明确表示 ES 模块,.cjs 表示 CommonJS;.js 的解释与包配置等有关,项目应明确 package.json 的 type。Node.js 包文档是具体规则来源。
CommonJS 使用 module.exports 与 require,不能把两套语法在没有工具说明的情况下混写。打包工具解决依赖整合与发布问题,不是学习模块语义的必要前提。
错误与练习
try {
JSON.parse("invalid JSON");
} catch (error) {
console.assert(error instanceof SyntaxError);
}只捕获自己能处理的错误,不能恢复时继续抛出。为 countNonempty 增加仅含空白和中文记录的断言,再写一个 cleanNames 函数完成去空白、过滤空字符串、排序。用模块导出此函数,再从另一个文件导入并验证结果。语言细节查 MDN 语法与类型。
参考解法
在countNonempty所在文件追加console.assert(countNonempty([" ", "\t"]) === 0)与console.assert(countNonempty([" 中文 ", ""]) === 1)。下面两文件保存到同一目录,排序明确使用默认字符串顺序:
// names.mjs
export function cleanNames(names) {
return names.map(name => name.trim()).filter(name => name !== "").sort();
}// check-names.mjs:读者执行 node check-names.mjs,预期无输出
import { cleanNames } from "./names.mjs";
const source = [" Bob ", "", " Alice "];
if (JSON.stringify(cleanNames(source)) !== '["Alice","Bob"]' || source[0] !== " Bob ") {
throw new Error("cleanNames failed");
}map先创建新数组,后续sort只修改这个新数组,原source不变。此函数前提是字符串数组;如需接收外部JSON,应另加运行校验。
深入课程已接续:闭包与不可变状态。基础内容与旧锚点保留,按当前路线进入完整机制和实践。
继续阅读
- 排错
- 语言与交互
- 语言与交互