JavaScript核心语法
Web开发专题 · 掌握Web前端交互的编程语言
专题:Web前端开发核心技术
关键词:JavaScript, ES6, Promise, 闭包, 箭头函数, async/await, 前端编程
一、JavaScript概述
1.1 什么是JavaScript
JavaScript是Web前端最核心的编程语言,与HTML和CSS并列为Web三大核心技术。HTML负责页面结构,CSS负责样式呈现,而JavaScript负责实现交互逻辑和动态行为。JavaScript是一种轻量级、解释型、面向对象的脚本语言,支持函数式编程和面向对象编程范式。
1995年由Brendan Eich在Netscape公司创建,仅用10天完成了第一个版本。随后经历多年标准化,ECMAScript(简称ES)成为JavaScript的语言规范标准。其中最重要的里程碑是2015年发布的ES6(ECMAScript 2015),它带来了大量现代化语言特性,彻底改变了JavaScript的编程方式。
1.2 ES6+ 重要更新
ES6及后续版本引入了一系列重大新特性,使得JavaScript代码更加简洁、可读性更强:
- let / const — 块级作用域变量声明,替代var
- 箭头函数 — 更简洁的函数语法,不绑定this
- 模板字符串 — 支持字符串插值、多行字符串
- 解构赋值 — 从数组或对象中快速提取值
- 展开运算符 — 数组和对象的合并与复制
- Promise — 标准化的异步编程解决方案
- Class — 类语法糖,更接近传统OOP风格
- 模块化 — import / export 原生模块支持
- async / await — 基于Promise的异步语法糖
- Symbol / BigInt — 新的原始数据类型
1.3 运行环境
JavaScript最初只在浏览器中运行,如今已经扩展到多个环境:
- 浏览器环境 — 每个现代浏览器都内置JavaScript引擎(Chrome的V8、Firefox的SpiderMonkey、Safari的JavaScriptCore),提供DOM、BOM、事件等Web API
- Node.js — 基于V8引擎的服务端运行时,提供文件系统、网络、进程等系统级API
- Deno / Bun — 新一代JavaScript运行时,内置TypeScript支持和更现代的标准库
1.4 script标签引入方式
在HTML中引入JavaScript有三种方式:
<!-- 方式一:内联脚本(直接在HTML中写代码) -->
<script>
console.log('Hello, World!');
</script>
<!-- 方式二:外部脚本文件(推荐) -->
<script src="js/app.js"></script>
<!-- 方式三:使用defer或async属性控制加载行为 -->
<script src="js/app.js" defer></script>
<script src="js/analytics.js" async></script>
defer属性:HTML解析完毕后按顺序执行脚本,多个defer脚本保持加载顺序。async属性:脚本下载完成后立即执行,不保证执行顺序,适用于独立工具脚本。defer通常优于async,因为它不影响DOM解析顺序。
二、变量与数据类型
2.1 var、let、const 的区别
JavaScript中有三种声明变量的方式,理解它们的区别是掌握JS的基础:
| 特性 |
var |
let |
const |
| 作用域 |
函数作用域 |
块级作用域 |
块级作用域 |
| 变量提升 |
会提升(初始值为undefined) |
会提升(暂时性死区) |
会提升(暂时性死区) |
| 重复声明 |
允许 |
不允许 |
不允许 |
| 重新赋值 |
允许 |
允许 |
不允许(对象属性可改) |
| 挂载到window |
是 |
否 |
否 |
// var — 函数作用域,存在变量提升
var a = 1;
if (true) {
var a = 2; // 同一个变量
}
console.log(a); // 2
// let — 块级作用域,不存在重复声明
let b = 1;
if (true) {
let b = 2; // 不同变量
}
console.log(b); // 1
// const — 常量,声明时必须赋值
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 允许,对象本身可修改
// obj = {}; // TypeError
2.2 基本类型(Primitive Types)
JavaScript有7种基本数据类型:
- number — 数字类型,包括整数和浮点数,还有特殊值Infinity、NaN
- string — 字符串,可用单引号、双引号或反引号(模板字符串)包裹
- boolean — 布尔值,true 和 false
- null — 空值,表示"没有对象",typeof返回"object"(历史遗留bug)
- undefined — 未定义,声明未赋值的变量默认值
- symbol — 唯一值,用于对象属性键,防止属性名冲突
- bigint — 大整数,用于表示超过2^53-1的整数
2.3 引用类型(Reference Types)
引用类型包括Object、Array、Function等。与基本类型的值传递不同,引用类型通过引用传递:
// 基本类型 — 值传递
let x = 10;
let y = x;
y = 20;
console.log(x); // 10 — x不受影响
// 引用类型 — 引用传递
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4] — arr1也被修改
2.4 typeof运算符与类型转换
typeof用于检测变量类型:
typeof 42; // "number"
typeof 'hello'; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" (历史bug)
typeof Symbol(); // "symbol"
typeof 1n; // "bigint"
typeof {}; // "object"
typeof []; // "object"
typeof function(){}; // "function"
JavaScript是弱类型语言,支持隐式类型转换。常见转换场景:
- 字符串拼接:数字 + 字符串 → 数字转为字符串
- 比较运算:== 会进行类型转换,=== 严格比较不转换
- 布尔上下文:falsy值(0、''、null、undefined、NaN、false)转为false
三、运算符与流程控制
3.1 运算符分类
JavaScript提供丰富的运算符用于数据处理:
- 算术运算符:+、-、*、/、%、**(幂运算)、++、--
- 比较运算符:>、<、>=、<=、==、===、!=、!==
- 逻辑运算符:&&(与)、||(或)、!(非)、??(空值合并)
- 赋值运算符:=、+=、-=、*=、/=、%=、**=
- 三元运算符:条件 ? 值1 : 值2
- 可选链运算符:?.(安全访问嵌套属性)
3.2 == 与 === 的区别
这是JavaScript最常被问及的面试题:
// == 宽松相等:先做类型转换再比较
1 == '1'; // true
0 == false; // true
'' == false; // true
null == undefined; // true
// === 严格相等:不转换类型,类型不同直接返回false
1 === '1'; // false
0 === false; // false
'' === false; // false
null === undefined; // false
最佳实践:总是使用 === 和 !==,避免隐式类型转换带来的意外结果。
3.3 条件语句
// if/else if/else
let score = 85;
if (score >= 90) {
console.log('优秀');
} else if (score >= 60) {
console.log('及格');
} else {
console.log('不及格');
}
// switch
let day = 3;
switch (day) {
case 1: console.log('周一'); break;
case 2: console.log('周二'); break;
default: console.log('其他');
}
// 三元表达式
let status = age >= 18 ? '成年' : '未成年';
3.4 循环语句
// for循环
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
// while循环
let count = 0;
while (count < 3) {
console.log(count);
count++;
}
// do-while(至少执行一次)
let n = 0;
do {
console.log(n);
n++;
} while (n < 3);
// for...of(遍历可迭代对象)
for (const item of [1, 2, 3]) {
console.log(item);
}
// for...in(遍历对象属性)
for (const key in {a: 1, b: 2}) {
console.log(key);
}
break用于立即退出循环,continue用于跳过本次迭代进入下一次。
四、函数
4.1 函数声明与函数表达式
JavaScript中创建函数的两种主要方式:
// 函数声明(会被提升)
function add(a, b) {
return a + b;
}
// 函数表达式(不会被提升)
const subtract = function(a, b) {
return a - b;
};
// 命名函数表达式
const multiply = function multiplyFn(a, b) {
return a * b;
};
函数声明会被提升到作用域顶部,因此在声明前就可以调用。函数表达式则不会提升,遵循变量作用域规则。
4.2 箭头函数
箭头函数是ES6引入的简洁语法,它不绑定自己的this,没有arguments对象,不能用作构造函数:
// 基础语法
const square = x => x * x;
// 多参数需要括号
const sum = (a, b) => a + b;
// 多语句需要花括号和return
const getStats = (arr) => {
const max = Math.max(...arr);
const min = Math.min(...arr);
return { max, min };
};
// this绑定差异(重要!)
const obj = {
name: 'Alice',
greet1: function() {
console.log(this.name); // 'Alice' — 指向obj
},
greet2: () => {
console.log(this.name); // undefined — 指向外层
}
};
4.3 参数处理
// 默认参数
function greet(name = '访客') {
console.log(`你好,${name}!`);
}
// 剩余参数(Rest Parameters)
function sumAll(...numbers) {
return numbers.reduce(function(total, n) {
return total + n;
}, 0);
}
console.log(sumAll(1, 2, 3, 4)); // 10
// arguments对象(仅普通函数)
function showArgs() {
console.log(arguments); // 类数组对象
}
4.4 高阶函数
高阶函数是接受函数作为参数或返回函数的函数。数组的map、filter、reduce是最常用的高阶函数:
const numbers = [1, 2, 3, 4, 5];
// map — 映射转换
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10]
// filter — 筛选过滤
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4]
// reduce — 累积归约
const total = numbers.reduce(function(acc, cur) {
return acc + cur;
}, 0);
// 15
4.5 闭包(Closure)
闭包是指内部函数可以访问外部函数作用域中变量的能力,即使外部函数已经执行完毕。闭包是JavaScript最强大的特性之一:
// 闭包示例:计数器
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// 闭包应用:数据私有化
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) {
return '余额不足';
}
balance -= amount;
return balance;
},
getBalance() {
return balance;
}
};
}
闭包的作用域链有三个层级:局部作用域(函数内部)→ 外部函数作用域 → 全局作用域。闭包的核心应用场景包括:数据私有化、函数柯里化、防抖与节流、模块模式。
4.6 IIFE(立即执行函数表达式)
// 立即执行函数 — 创建独立作用域
(function() {
const privateVar = '外部无法访问';
console.log('立即执行');
})();
// 带参数的IIFE
(function(name) {
console.log(`你好,${name}`);
})('JavaScript');
// 箭头函数IIFE
(() => {
console.log('箭头IIFE');
})();
IIFE主要用于创建独立的作用域,避免变量污染全局空间。在ES6模块普及之前,IIFE是JavaScript模块化的主要方式。
五、对象与数组
5.1 对象字面量与属性访问
// 创建对象
const person = {
name: '张三',
age: 28,
'full-name': '张三丰',
greet() {
console.log(`你好,我是${this.name}`);
}
};
// 属性访问
console.log(person.name); // 点号语法
console.log(person['name']); // 方括号语法
console.log(person['full-name']); // 含特殊字符必须用方括号
// 属性操作
person.email = 'zhangsan@example.com'; // 新增
person.age = 29; // 修改
delete person.email; // 删除
console.log('name' in person); // 检查 — true
5.2 数组方法详解
数组是JavaScript中最常用的数据结构,提供丰富的方法:
// 栈操作(后进先出)
const stack = [];
stack.push(1, 2, 3); // 尾部添加 → [1, 2, 3]
stack.pop(); // 尾部移除 → 3, 数组变为[1, 2]
// 队列操作(先进先出)
const queue = [];
queue.push(1, 2, 3); // 尾部添加 → [1, 2, 3]
queue.shift(); // 头部移除 → 1, 数组变为[2, 3]
queue.unshift(0); // 头部添加 → [0, 2, 3]
// splice — 万能操作(插入/删除/替换)
const arr = [1, 2, 3, 4, 5];
arr.splice(2, 1); // 从索引2删除1个元素 → [1, 2, 4, 5]
arr.splice(1, 0, 'a', 'b'); // 在索引1插入a,b → [1, 'a', 'b', 2, 4, 5]
arr.splice(2, 1, 'x'); // 替换索引2的1个元素为x
// slice — 提取子数组(不修改原数组)
const sub = arr.slice(1, 4); // 索引1到3(不含4)
// concat / join
const merged = [1, 2].concat([3, 4]); // [1, 2, 3, 4]
const str = ['a', 'b', 'c'].join('-'); // 'a-b-c'
5.3 数组遍历与转换
const numbers = [1, 2, 3, 4, 5];
// forEach — 遍历(无返回值)
numbers.forEach(n => console.log(n));
// map — 映射为新数组
const squares = numbers.map(n => n ** 2);
// [1, 4, 9, 16, 25]
// filter — 过滤元素
const bigNums = numbers.filter(n => n > 3);
// [4, 5]
// find — 查找第一个匹配元素
const found = numbers.find(n => n > 3);
// 4
// some — 是否有任意元素满足条件
const hasEven = numbers.some(n => n % 2 === 0);
// true
// every — 是否所有元素满足条件
const allPositive = numbers.every(n => n > 0);
// true
5.4 解构赋值
// 数组解构
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]
// 对象解构
const user = { name: 'Alice', age: 25, city: '北京' };
const { name, age, city: location } = user;
console.log(name); // 'Alice'
console.log(location); // '北京'
// 嵌套解构
const data = { person: { name: 'Bob', scores: [90, 85] } };
const { person: { name: personName, scores: [math] } } = data;
console.log(personName); // 'Bob'
console.log(math); // 90
5.5 展开运算符
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
// [1, 2, 3, 4, 5, 6]
// 对象展开
const defaults = { host: 'localhost', port: 3000 };
const config = { ...defaults, port: 8080, ssl: true };
// { host: 'localhost', port: 8080, ssl: true }
// 函数调用中展开
const max = Math.max(...[3, 7, 1, 9, 4]);
// 9
六、异步编程
6.1 异步编程概览
JavaScript是单线程语言,但通过事件循环机制实现了非阻塞的异步编程。异步编程的发展经历了三个阶段:回调函数 → Promise → async/await,每一次演进都让异步代码更加直观易读。
6.2 回调函数(Callback)
回调是最古老的异步模式,将函数作为参数传递给异步操作:
// 回调函数示例
function fetchData(callback) {
setTimeout(() => {
callback('数据加载完成');
}, 1000);
}
fetchData(function(result) {
console.log(result); // 1秒后输出
});
// 回调地狱(Callback Hell)
getUser(id, function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
console.log(comments);
});
});
});
深层嵌套的回调会导致代码难以阅读和维护,即所谓的"回调地狱"(Callback Hell)。Promise正是为了解决这一问题而生。
6.3 Promise
Promise是ES6引入的标准异步解决方案,代表一个异步操作的最终完成或失败,及其结果值:
// 创建Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
// 使用Promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('完成'));
// 链式调用 — 告别回调地狱
getUser(id)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.error(err));
// Promise.all — 等待所有完成
const p1 = fetch('/api/users');
const p2 = fetch('/api/posts');
Promise.all([p1, p2])
.then(([users, posts]) => {
console.log('全部加载完成');
});
// Promise.race — 取最先完成的
Promise.race([fetch('/api/data'), timeout(5000)])
.then(result => console.log(result))
.catch(err => console.log('请求超时'));
6.4 async/await
async/await是ES2017(ES8)引入的语法糖,让异步代码看起来像同步代码一样清晰:
// async函数
async function getUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
// 串行执行
async function loadSequential() {
const user = await getUser(1);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
return comments;
}
// 并行执行(比串行更快)
async function loadParallel() {
const [users, posts] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts')
]);
return { users: await users.json(), posts: await posts.json() };
}
// 错误处理
async function safeLoad() {
try {
const data = await riskyOperation();
} catch (err) {
console.error('操作失败,使用默认值');
return defaultValue;
}
}
6.5 事件循环(Event Loop)
事件循环是JavaScript异步编程的底层机制,理解它对于写出正确代码至关重要:
console.log('1'); // 同步任务
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步任务
// 输出顺序:1 → 4 → 3 → 2
事件循环的执6行顺序:
- 同步代码(调用栈)优先执行
- 执行完所有同步代码后,检查微任务队列(Microtask Queue),依次执行所有微任务(Promise.then/catch/finally、queueMicrotask、MutationObserver)
- 微任务清空后,从宏任务队列(Macrotask Queue)取出一个任务执行(setTimeout、setInterval、I/O、UI渲染)
- 重复上述步骤
6.6 宏任务与微任务总结
| 任务类型 |
常见API |
执行时机 |
| 同步任务 |
普通代码执行 |
立即执行 |
| 微任务 |
Promise.then/catch/finally、queueMicrotask、MutationObserver |
当前宏任务结束后、下一个宏任务开始前 |
| 宏任务 |
setTimeout、setInterval、setImmediate、I/O、UI渲染 |
每次事件循环取一个执行 |
"理解事件循环和任务队列的优先级,是写出正确JavaScript异步代码的关键。记住:微任务优先于宏任务,Promise的回调属于微任务。"
总结
JavaScript核心语法是Web前端开发的基础。从变量声明到异步编程,每一步都至关重要。ES6+的引入使得JavaScript从一门简单的脚本语言进化为功能完备的现代编程语言,足以支撑从浏览器到服务端的全栈开发。
重点回顾:
1. 使用let/const替代var,遵循块级作用域规则
2. 始终使用 === 进行相等比较,避免隐式类型转换
3. 箭头函数简洁且不绑定this,适合回调场景
4. 闭包是JavaScript的核心特性,掌握它对理解作用域和模块化至关重要
5. 数组的map/filter/reduce是函数式编程的基石,优先于for循环
6. async/await是现代异步编程的标准写法,配合try/catch处理错误
7. 理解事件循环机制,区分宏任务与微任务的执行顺序