JavaScript 基础语法
本章将深入介绍 JavaScript 的基础语法,包括变量声明、数据类型和运算符。掌握这些基础知识对后续学习至关重要。
变量
变量是存储数据的容器。JavaScript 提供了三种声明变量的方式,每种都有不同的特性和使用场景。
var 关键字
var 是 ES5 及之前版本使用的变量声明方式:
var name = "张三";
var age = 20;
var 声明的变量有以下特点:
函数作用域:var 声明的变量只在函数内部可见,在函数外部不可见。如果在函数外声明,则是全局变量。
function test() {
var x = 10; // 只在 test 函数内可见
}
// console.log(x); // ReferenceError: x is not defined
变量提升:var 声明的变量会被提升到作用域顶部,但赋值不会提升。
console.log(x); // undefined(不会报错,因为声明被提升了)
var x = 10;
console.log(x); // 10
// 上面的代码等价于:
var x; // 声明被提升到顶部
console.log(x); // undefined
x = 10; // 赋值留在原地
console.log(x); // 10
可以重复声明:同一个变量名可以多次使用 var 声明,这可能导致意外的变量覆盖。
var x = 1;
var x = 2; // 不会报错,但可能导致 bug
console.log(x); // 2
let 关键字(推荐)
let 是 ES6 引入的变量声明方式,解决了 var 的诸多问题:
let name = "张三";
let age = 20;
let 声明的变量有以下特点:
块级作用域:let 声明的变量只在所在的代码块({})内可见。
if (true) {
let x = 10;
}
// console.log(x); // ReferenceError: x is not defined
for (let i = 0; i < 3; i++) {
// i 只在这个循环内可见
}
// console.log(i); // ReferenceError: i is not defined
不存在变量提升:let 声明的变量不会被提升,必须先声明后使用。
// console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
暂时性死区:从进入作用域到变量声明之间的区域,变量存在但不能被访问。
let x = "outer";
if (true) {
// console.log(x); // ReferenceError(进入块级作用域后,x 在暂时性死区)
let x = "inner"; // 声明后才可访问
console.log(x); // "inner"
}
不能重复声明:同一个作用域内不能用 let 重复声明同名变量。
let x = 1;
// let x = 2; // SyntaxError: Identifier 'x' has already been declared
const 关键字(推荐)
const 用于声明常量,一旦赋值就不能修改:
const PI = 3.14159;
const SITE_NAME = "编程教程";
const 声明的变量有以下特点:
必须初始化:声明时必须立即赋值。
const x; // SyntaxError: Missing initializer in const declaration
const x = 10; // 正确
不能重新赋值:对于基本类型,值不能改变。
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable
对象和数组可以修改内部属性:const 保证的是变量绑定的引用不变,而不是引用的对象不变。
const person = { name: "张三", age: 20 };
person.age = 21; // 可以修改属性
// person = {}; // 错误:不能重新赋值
const arr = [1, 2, 3];
arr.push(4); // 可以添加元素
// arr = []; // 错误:不能重新赋值
选择哪种声明方式?
使用 const 的场景:声明不会重新赋值的变量,比如常量、函数表达式、导入的模块等。这应该是你的默认选择。
使用 let 的场景:声明需要重新赋值的变量,比如循环计数器、条件赋值等。
避免使用 var:var 存在变量提升和作用域问题,在现代 JavaScript 中应该避免使用。
// 好的做法
const API_URL = "https://api.example.com"; // 常量用 const
const add = (a, b) => a + b; // 函数表达式用 const
let count = 0; // 需要修改的变量用 let
count = count + 1;
// 避免
var x = 10; // 不推荐使用 var
变量命名规范
JavaScript 社区有一套约定俗成的命名规范:
// 驼峰命名法(camelCase)- 变量和函数名
let userName = "张三";
let isActive = true;
function getFullName() {}
// 帕斯卡命名法(PascalCase)- 类名和构造函数
class UserProfile {}
function CreateUser() {}
// 全大写下划线 - 常量
const MAX_CONNECTIONS = 100;
const API_BASE_URL = "https://api.example.com";
// 下划线开头 - 私有变量(约定,不是真正的私有)
let _internalCounter = 0;
数据类型
JavaScript 有两种数据类型:原始类型(Primitive)和引用类型(Reference)。
原始类型
原始类型的值是不可变的,存储在栈内存中。
1. 数值(Number)
JavaScript 中所有数字都是浮点数,没有整数和浮点数的区分。
let integer = 42; // 整数
let float = 3.14; // 浮点数
let negative = -10; // 负数
let scientific = 2.5e5; // 科学计数法(250000)
let infinity = Infinity; // 无穷大
let nan = NaN; // 非数字(Not a Number)
特殊数值的行为:
console.log(10 / 0); // Infinity
console.log(-10 / 0); // -Infinity
console.log(0 / 0); // NaN
console.log(Math.sqrt(-1)); // NaN
判断数值的方法:
// 判断是否为 NaN(不能用 ===)
console.log(NaN === NaN); // false
console.log(Number.isNaN(NaN)); // true
// 判断是否为有限数
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(Infinity)); // false
// 判断是否为整数
console.log(Number.isInteger(3.0)); // true(3.0 是整数)
console.log(Number.isInteger(3.14)); // false
// 安全整数范围
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
2. 字符串(String)
字符串是不可变的字符序列,使用 UTF-16 编码。
// 三种创建方式
let name1 = '张三'; // 单引号
let name2 = "李四"; // 双引号
let name3 = `你好,${name1}`; // 反引号(模板字符串)
// 转义字符
let str = "他说:\"你好\"";
let path = "C:\\Users\\Admin";
let newline = "第一行\n第二行";
模板字符串(ES6):
let name = "张三";
let age = 20;
// 插值
let greeting = `你好,${name}!你今年 ${age} 岁。`;
// 多行字符串
let html = `
<div>
<h1>${name}</h1>
<p>年龄:${age}</p>
</div>
`;
// 表达式
let result = `2 + 3 = ${2 + 3}`;
3. 布尔值(Boolean)
布尔值只有两个值:true 和 false。
let isActive = true;
let isDeleted = false;
真假值转换:JavaScript 中有些值在布尔上下文中被视为 false,称为假值(falsy)。
// 以下都是假值
Boolean(false) // false
Boolean(0) // false
Boolean(-0) // false
Boolean(0n) // false(BigInt 零)
Boolean("") // false(空字符串)
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
// 以下都是真值
Boolean(true) // true
Boolean(1) // true
Boolean("hello") // true
Boolean([]) // true(空数组是真值!)
Boolean({}) // true(空对象是真值!)
Boolean(function(){}) // true
4. undefined
undefined 表示变量已声明但未赋值。
let x;
console.log(x); // undefined
function test() {
// 没有 return 语句
}
console.log(test()); // undefined
// 访问对象不存在的属性
let obj = {};
console.log(obj.name); // undefined
5. null
null 表示有意的空值或不存在的对象。
let empty = null;
// null 和 undefined 的区别
console.log(null === undefined); // false(类型不同)
console.log(null == undefined); // true(值相等)
console.log(typeof null); // "object"(历史遗留 bug)
console.log(typeof undefined); // "undefined"
使用建议:
- 如果变量将来会被赋值为对象,初始值用
null - 如果变量将来会被赋值为基础类型,初始值用
undefined或不赋值
6. Symbol(ES6)
Symbol 是一种原始数据类型,表示独一无二的值。
// 创建 Symbol
let sym1 = Symbol();
let sym2 = Symbol("description"); // 可选的描述字符串
console.log(typeof sym1); // "symbol"
// 每个 Symbol 都是唯一的
console.log(Symbol("foo") === Symbol("foo")); // false
// Symbol 作为对象属性键
let obj = {};
let mySymbol = Symbol();
obj[mySymbol] = "hello";
console.log(obj[mySymbol]); // "hello"
// Symbol.for() 全局注册表
let globalSym1 = Symbol.for("key");
let globalSym2 = Symbol.for("key");
console.log(globalSym1 === globalSym2); // true(全局共享)
Symbol 的常见用途:
// 1. 定义对象的迭代行为
let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();
// 2. 定义对象的字符串标签
let obj = {
[Symbol.toStringTag]: "MyObject"
};
console.log(Object.prototype.toString.call(obj)); // "[object MyObject]"
// 3. 定义对象的原始值转换
let obj2 = {
[Symbol.toPrimitive](hint) {
if (hint === "number") return 42;
if (hint === "string") return "hello";
return true;
}
};
console.log(+obj2); // 42
console.log(`${obj2}`); // "hello"
7. BigInt(ES2020)
BigInt 用于表示任意精度的整数,可以安全地处理超过 Number.MAX_SAFE_INTEGER 的整数。
// 创建 BigInt
let bigInt1 = 9007199254740991n; // 字面量添加 n 后缀
let bigInt2 = BigInt("9007199254740991");
let bigInt3 = BigInt(123);
// BigInt 运算
let a = 123456789012345678901234567890n;
let b = 987654321098765432109876543210n;
console.log(a + b); // 1111111110111111111011111111100n
// BigInt 不能与 Number 混合运算
// let result = 1n + 2; // TypeError
// 比较运算
console.log(1n == 1); // true(值相等)
console.log(1n === 1); // false(类型不同)
// 除法会向零取整
console.log(5n / 2n); // 2n(不是 2.5n)
引用类型
引用类型的值存储在堆内存中,变量存储的是指向堆内存的引用。
对象(Object)
let person = {
name: "张三",
age: 20,
greet() {
return `你好,我是${this.name}`;
}
};
// 访问属性
console.log(person.name); // "张三"
console.log(person["age"]); // 20
console.log(person.greet()); // "你好,我是张三"
// 引用类型的特点
let person2 = person; // person2 和 person 指向同一个对象
person2.age = 21;
console.log(person.age); // 21(person 也被修改了)
数组(Array)
let fruits = ["苹果", "香蕉", "橙子"];
console.log(fruits[0]); // "苹果"
// 数组也是引用类型
let fruits2 = fruits;
fruits2.push("葡萄");
console.log(fruits); // ["苹果", "香蕉", "橙子", "葡萄"](原数组被修改)
函数(Function)
函数在 JavaScript 中是一等公民,可以作为值传递。
// 函数声明
function greet(name) {
return `你好,${name}`;
}
// 函数表达式
const greet2 = function(name) {
return `你好,${name}`;
};
// 箭头函数
const greet3 = (name) => `你好,${name}`;
// 函数可以作为参数传递
function execute(fn, value) {
return fn(value);
}
console.log(execute(greet, "张三")); // "你好,张三"
typeof 操作符
typeof 用于检测变量的类型。
console.log(typeof 42); // "number"
console.log(typeof 3.14); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object"(历史遗留 bug)
console.log(typeof {}); // "object"
console.log(typeof []); // "object"(数组也是对象)
console.log(typeof function(){}); // "function"
console.log(typeof Symbol()); // "symbol"
console.log(typeof 123n); // "bigint"
判断数组的正确方法:
let arr = [1, 2, 3];
console.log(typeof arr); // "object"(不准确)
console.log(Array.isArray(arr)); // true(推荐方法)
console.log(arr instanceof Array); // true
类型转换
JavaScript 是弱类型语言,会自动进行类型转换。理解类型转换规则对写出正确的代码很重要。
隐式转换
JavaScript 在某些操作中会自动转换类型。
// 字符串拼接(+ 号)
console.log("5" + 3); // "53"(数字转字符串)
console.log("5" + "3"); // "53"
console.log(5 + "3"); // "53"
// 数学运算(其他运算符)
console.log("5" - 3); // 2(字符串转数字)
console.log("5" * "2"); // 10(都转数字)
console.log("10" / 2); // 5
// 布尔转换
console.log(true + 1); // 2(true 转为 1)
console.log(false + 1); // 1(false 转为 0)
// null 和 undefined
console.log(null + 1); // 1(null 转为 0)
console.log(undefined + 1); // NaN(undefined 转为 NaN)
相等性比较的隐式转换(这是一个常见陷阱):
// == 会进行类型转换
console.log("" == false); // true(都转为 0)
console.log("0" == false); // true(都转为 0)
console.log(0 == false); // true
console.log(null == undefined); // true
console.log(null == 0); // false(特殊情况)
console.log("" == 0); // true(空字符串转为 0)
// === 不进行类型转换
console.log("" === false); // false
console.log("0" === false); // false
console.log(0 === false); // false
console.log(null === undefined); // false
显式转换
推荐使用显式转换,代码意图更清晰。
// 转字符串
String(123); // "123"
(123).toString(); // "123"
123 + ""; // "123"(不推荐,但常见)
// 转数字
Number("123"); // 123
Number("12.34"); // 12.34
Number(""); // 0
Number("hello"); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
parseInt("123"); // 123
parseInt("12.34"); // 12(截断小数)
parseInt("10", 2); // 2(二进制)
parseInt("0xff"); // 255(十六进制)
parseInt("hello"); // NaN
parseFloat("3.14"); // 3.14
parseFloat("3.14.15"); // 3.14
// 转布尔值
Boolean(1); // true
Boolean(0); // false
Boolean(""); // false
Boolean("hello"); // true
Boolean(null); // false
Boolean(undefined); // false
Boolean([]); // true(空数组是真值)
Boolean({}); // true(空对象是真值)
运算符
算术运算符
let a = 10;
let b = 3;
console.log(a + b); // 13 - 加法
console.log(a - b); // 7 - 减法
console.log(a * b); // 30 - 乘法
console.log(a / b); // 3.333... - 除法
console.log(a % b); // 1 - 取余
console.log(a ** b); // 1000 - 幂运算
自增/自减运算符:
let x = 5;
console.log(++x); // 6(先加后用)
console.log(x++); // 6(先用后加)
console.log(x); // 7
console.log(--x); // 6(先减后用)
console.log(x--); // 6(先用后减)
console.log(x); // 5
赋值运算符
let x = 10;
x += 5; // x = x + 5 = 15
x -= 3; // x = x - 3 = 12
x *= 2; // x = x * 2 = 24
x /= 2; // x = x / 2 = 12
x %= 5; // x = x % 5 = 2
x **= 2; // x = x ** 2 = 4
比较运算符
JavaScript 有两种比较运算符:相等运算符(==)和严格相等运算符(===)。
// 相等运算符(==)- 会进行类型转换
console.log(5 == 5); // true
console.log(5 == "5"); // true(字符串转数字)
console.log(0 == false); // true(false 转为 0)
console.log("" == false); // true(都转为 0)
// 严格相等运算符(===)- 不进行类型转换
console.log(5 === 5); // true
console.log(5 === "5"); // false(类型不同)
console.log(0 === false); // false(类型不同)
console.log("" === false);// false(类型不同)
// 不等运算符
console.log(5 != 3); // true
console.log(5 !== "5"); // true(类型不同)
// 关系运算符
console.log(5 > 3); // true
console.log(5 < 3); // false
console.log(5 >= 5); // true
console.log(5 <= 3); // false
== 和 === 的区别:
== 会进行类型转换,然后再比较值。这可能导致一些意想不到的结果。
=== 不进行类型转换,直接比较类型和值。如果类型不同,直接返回 false。
最佳实践:始终使用 === 和 !==,除非你明确需要类型转换。
// 推荐
if (value === 0) { }
if (name === "张三") { }
// 不推荐
if (value == 0) { } // 可能产生意外结果
逻辑运算符
// AND(&&):两边都为 true 才为 true
console.log(true && true); // true
console.log(true && false); // false
console.log(false && false); // false
// OR(||):任意一边为 true 就为 true
console.log(true || false); // true
console.log(false || false); // false
// NOT(!):取反
console.log(!true); // false
console.log(!false); // true
短路求值:逻辑运算符会短路,即如果第一个操作数已经能确定结果,就不会计算第二个操作数。
// && 短路:左边为 false,右边不执行
false && console.log("不会执行");
// || 短路:左边为 true,右边不执行
true || console.log("不会执行");
// 实际应用:设置默认值
let name = "";
let displayName = name || "匿名用户";
console.log(displayName); // "匿名用户"
// 实际应用:条件执行
let score = 85;
score >= 60 && console.log("及格");
空值合并运算符(??):ES2020 引入,只有当左侧为 null 或 undefined 时才使用右侧值。
let a = null ?? "默认值"; // "默认值"
let b = undefined ?? "默认值"; // "默认值"
let c = "" ?? "默认值"; // ""(空字符串不是 null/undefined)
let d = 0 ?? "默认值"; // 0(0 不是 null/undefined)
let e = false ?? "默认值"; // false
// 与 || 的区别
let name1 = "" || "默认值"; // "默认值"(空字符串是假值)
let name2 = "" ?? "默认值"; // ""(空字符串不是 null/undefined)
可选链操作符(?.):ES2020 引入,安全地访问嵌套对象属性。
let user = { profile: { name: "张三" } };
// 传统写法 - 需要逐层检查
let name1 = user && user.profile && user.profile.name;
// 可选链写法
let name2 = user?.profile?.name; // "张三"
let city = user?.address?.city; // undefined(不会报错)
// 方法调用
let result = user?.getName?.(); // undefined(方法不存在)
// 数组元素
let arr = [1, 2, 3];
let item = arr?.[1]; // 2
条件运算符(三元运算符)
let age = 20;
let status = age >= 18 ? "成年人" : "未成年人";
console.log(status); // "成年人"
// 嵌套使用(不推荐过度嵌套)
let grade = 85;
let result = grade >= 90 ? "A"
: grade >= 80 ? "B"
: grade >= 60 ? "C"
: "D";
console.log(result); // "B"
位运算符
位运算符直接操作数字的二进制位,在某些场景下可以提高性能。
let a = 5; // 二进制: 0101
let b = 3; // 二进制: 0011
console.log(a & b); // 1 - 按位与: 0001
console.log(a | b); // 7 - 按位或: 0111
console.log(a ^ b); // 6 - 按位异或: 0110
console.log(~a); // -6 - 按位取反
console.log(a << 1); // 10 - 左移: 1010
console.log(a >> 1); // 2 - 右移: 0010
console.log(-5 >>> 0); // 4294967291 - 无符号右移
字符串方法
JavaScript 提供了丰富的字符串操作方法。
let str = "Hello, World!";
// 大小写转换
console.log(str.toUpperCase()); // "HELLO, WORLD!"
console.log(str.toLowerCase()); // "hello, world!"
// 获取长度
console.log(str.length); // 13
// 查找
console.log(str.indexOf("World")); // 7(返回索引,未找到返回 -1)
console.log(str.lastIndexOf("l")); // 10(最后一次出现的位置)
console.log(str.includes("World")); // true
console.log(str.startsWith("Hello")); // true
console.log(str.endsWith("!")); // true
// 截取
console.log(str.slice(0, 5)); // "Hello"
console.log(str.substring(7, 12)); // "World"
console.log(str.substr(7, 5)); // "World"(已废弃,但常见)
// 替换
console.log(str.replace("World", "JavaScript")); // "Hello, JavaScript!"
console.log(str.replaceAll("l", "L")); // "HeLLo, WorLd!"
// 分割和连接
let csv = "apple,banana,orange";
console.log(csv.split(",")); // ["apple", "banana", "orange"]
let fruits = ["apple", "banana", "orange"];
console.log(fruits.join("-")); // "apple-banana-orange"
// 去除空白
let text = " hello ";
console.log(text.trim()); // "hello"
console.log(text.trimStart()); // "hello "
console.log(text.trimEnd()); // " hello"
// 填充
console.log("5".padStart(3, "0")); // "005"
console.log("5".padEnd(3, "0")); // "500"
// 重复
console.log("ha".repeat(3)); // "hahaha"
解构赋值
ES6 引入的解构赋值语法,可以从数组或对象中提取值。
// 数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 跳过元素
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3
// 默认值
const [x = 0, y = 0] = [1];
console.log(x, y); // 1 0
// 剩余元素
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
// 对象解构
const { name, age } = { name: "张三", age: 20 };
console.log(name, age); // 张三 20
// 重命名
const { name: userName } = { name: "张三" };
console.log(userName); // 张三
// 默认值
const { city = "北京" } = { name: "张三" };
console.log(city); // 北京
// 嵌套解构
const { profile: { email } } = { profile: { email: "[email protected]" } };
console.log(email); // [email protected]
// 函数参数解构
function greet({ name, age }) {
console.log(`你好,${name},你今年${age}岁`);
}
greet({ name: "张三", age: 20 });
小结
本章我们学习了:
- 变量声明:
var、let、const的区别和使用场景 - 数据类型:原始类型(number、string、boolean、undefined、null、symbol、bigint)和引用类型(object、array、function)
- 类型判断:
typeof、Array.isArray()、instanceof - 类型转换:隐式转换和显式转换,特别是
==和===的区别 - 运算符:算术、赋值、比较、逻辑、条件、位运算符
- 字符串方法:查找、截取、替换、分割等常用方法
- 解构赋值:数组和对象的解构语法
练习
- 声明变量存储你的名字、年龄和是否在职,使用合适的声明方式
- 编写代码判断
0、""、null、undefined、NaN在布尔上下文中的值 - 使用
===改写使用==的比较代码 - 使用模板字符串拼接多个变量的值
- 编写一个函数,接收一个对象参数,使用解构赋值提取属性