跳到主要内容

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 的场景:声明需要重新赋值的变量,比如循环计数器、条件赋值等。

避免使用 varvar 存在变量提升和作用域问题,在现代 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)

布尔值只有两个值:truefalse

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 引入,只有当左侧为 nullundefined 时才使用右侧值。

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 });

小结

本章我们学习了:

  1. 变量声明varletconst 的区别和使用场景
  2. 数据类型:原始类型(number、string、boolean、undefined、null、symbol、bigint)和引用类型(object、array、function)
  3. 类型判断typeofArray.isArray()instanceof
  4. 类型转换:隐式转换和显式转换,特别是 ===== 的区别
  5. 运算符:算术、赋值、比较、逻辑、条件、位运算符
  6. 字符串方法:查找、截取、替换、分割等常用方法
  7. 解构赋值:数组和对象的解构语法

练习

  1. 声明变量存储你的名字、年龄和是否在职,使用合适的声明方式
  2. 编写代码判断 0""nullundefinedNaN 在布尔上下文中的值
  3. 使用 === 改写使用 == 的比较代码
  4. 使用模板字符串拼接多个变量的值
  5. 编写一个函数,接收一个对象参数,使用解构赋值提取属性

参考资源