# JavaScript
JavaScript (opens new window) 是一种具有函数优先特性的轻量级、解释型的编程语言。
# 一、语言基础
# 变量声明
let
let(opens new window) 允许声明一个作用域被限制在块作用域中的局部变量(块作用域)。let(opens new window) 声明的变量不会在作用域中被提升。let x = 1; if (x === 1) { let x = 2; console.log(x); // 2 } console.log(x); // 1var
var(opens new window) 语句用于声明一个函数范围或全局范围的变量(函数作用域)。var x = 1; if (x === 1) { var x = 2; console.log(x); // 2 } console.log(x); // 2无论在何处声明变量,都会在执行任何代码之前进行处理。
这被称为变量提升,这些变量的初始值为
undefined。console.log(x); var x = 1; console.log(x);const
const(opens new window) 声明的变量称为常量(块作用域)。常量是块级范围的,非常类似用
let语句定义的变量。但常量的值无法改变,也不能被重新声明。
const number = 42; try { number = 99; } catch (err) { console.log(err); } console.log(number);
# 数据类型
最新的 ECMAScript 标准定义了 8 种数据类型 (opens new window):
- undefined:表示变量未赋值时的属性。
- null:代表无、空或值未知的特殊值。
- Boolean:仅包含两个值,
true和false。 - Number:代表整数和浮点数,是一种基于 IEEE 754 标准的双精度 64 位二进制格式的值。
- BigInt:表示任意大小的整数。通过将
n附加到整数末尾或调用BigInt()函数来创建。 - String:表示文本值的字符序列。内部将其编码为
UTF-16的整数值序列,字符串的长度是其中的UTF-16代码单元的数量,这可能与 Unicode 字符的实际数量不符。 - Symbol:用于创建对象的唯一标识符。
- Object:用于储存数据集合和更复杂的实体。是一个特殊的类型,其他所有的数据类型都被称为原始类型,因为它们的值只包含一个单独的内容。
# 严格模式
ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。
为了保证旧的功能能够使用,大部分修改是默认不生效的。
需要一个特殊的指令 use strict 来明确地激活这些特性。
启用方式:
脚本
为脚本开启严格模式。
"use strict"; var v = "Hi! I'm a strict mode script!";函数
为函数开启严格模式。
function strict() { 'use strict'; function nested() { return "And so am I!"; } return "Hi! I'm a strict mode function! " + nested(); }
主要限制:
八进制语法
严格模式禁止八进制数字语法。
ECMAScript 并不包含八进制语法,但所有的浏览器都支持这种以零开头的八进制语法:
0644 === 420。在 ECMAScript 6 中支持为一个数字加
0o的前缀来表示八进制数。"use strict"; console.log(0644 === 420);声明全局变量
非严格模式下,如果在函数内部使用未声明的变量,JavaScript 会自动将其创建为全局变量。
在严格模式下,这种行为是被禁止的。
"use strict"; function myFunction() { x = 10; } myFunction(); console.log(x);
# 二、核心语法
# 字面量
String
字符串字面量 (opens new window)是由双引号或单引号括起来的零个或多个字符,支持使用转义字符 (opens new window)表示特殊字符。
// 基础字符串 "A string primitive"; 'Also a string primitive'; // 转义字符序列 console.log("换行符:第一行\n第二行"); console.log('路径:C:\\Users\\Name'); // 十六进制转义序列 console.log("\x43"); // Unicode console.log("Unicode 字符:\u00A9"); // 两个码元 Unicode console.log("\uD87E\uDC04"); console.log("\u{2F804}");
# 表达式与运算符
# 模板字符串
在 ES2015 中,提供了一种模板字面量 (opens new window),模板字符串提供了一些语法糖来构造字符串。
这与 Perl、Python 还有其他语言中的字符串插值的特性非常相似。
var name = "Bob";
var time = "today";
`Hello ${name}, how are you ${time}?`
# 展开运算符
展开语法 (opens new window)可以在函数调用或数组构造时,将数组表达式或 string 在语法层面展开。
const obj1 = { name: 'Alice', age: 25 };
const obj2 = { job: 'Developer', age: 26 }; // 后者覆盖前者
const merged = { ...obj1, ...obj2 };
console.log(merged); // {name: 'Alice', age: 26, job: 'Developer'}
# 解构赋值
解构赋值 (opens new window)是 JavaScript 中一种快速提取数组或对象中的值,并赋值给变量的语法。
数组解构
const colors = ['red', 'green', 'blue'];基础解构
const [first, second] = colors; console.log(first); // 'red' console.log(second); // 'green'跳过某些值
const [,, third] = colors; console.log(third); // 'blue'结合默认值
const [a = '默认值', b] = [null, 100]; console.log(a); // null(默认值仅在 undefined 时生效) console.log(b); // 100
对象解构
const user = { name: 'Alice', age: 30, address: { city: 'Shanghai' } };基础解构
const { name, age } = user; console.log(name); // 'Alice'重命名变量 + 默认值
const { age: userAge = 18, job = 'Engineer' } = user; console.log(userAge); // 30(已存在则不使用默认值) console.log(job); // 'Engineer'(默认值)嵌套解构
const { address: { city } } = user; console.log(city); // 'Shanghai'
# 对象系统
# 对象初始化
对象初始化器 (opens new window)是一个用大括号括起来的以逗号分隔的列表,包含了一个对象的零个或多个属性名称和相关值。
o = {
a: "foo",
b: 42,
}
foo: foo 形式的属性赋值可以简写。
let foo = '我的世界';
o = {
a: 18,
foo
}
对象属性也可以是一个函数、getter 或 setter 方法。
const o = {
property() {
return '我的世界'
},
};
const obj = {
log: ['a', 'b', 'c'],
get latest() {
return this.log;
},
};
const language = {
log: [],
set current(name) {
this.log.push(name);
},
};
展开语法 (opens new window)可以在构造字面量对象时,将对象表达式按 key-value 的方式展开。
const item = { id: "123", name: "zh" };
const new_item = { ...item, value: item.id, label: item.name };
# 属性描述符
属性描述符有两种主要类型:
- 数据描述符
- 访问器描述符
# 原型继承机制
JavaScript 的原型继承机制是其实现对象间共享属性和方法的核心机制。
它是基于原型链的动态继承模型,与传统的类继承有本质区别。
构造函数(Constructor)
- 通过
new关键字创建实例的函数 - 拥有一个显式的
prototype属性(函数独有) - 实例的隐式原型
[[Prototype]]默认指向构造函数的prototype属性对象
- 通过
原型(Prototype)
- 每个对象都有一个原型(
[[Prototype]]),用于实现继承 - 可通过
Object.getPrototypeOf(obj)或obj.__proto__访问 [[Prototype]]指向其原型对象,即该对象的构造函数的prototype。
- 每个对象都有一个原型(
原型对象(Prototype Object)
- 构造函数的
prototype属性,它是一个预先存在的对象。 - 包含可被所有实例共享的属性和方法
- 当定义一个函数时,会自动为该函数创建一个
prototype属性。 - 本质是普通对象,其自身可通过
[[Prototype]]指向更高层原型(如Object.prototype)
- 构造函数的
原型链(Prototype Chain)
- 隐式原型引用:每个对象拥有
[[Prototype]]内部属性 - 链式查找机制:访问对象属性时,若自身不存在,则递归沿
[[Prototype]]链向上查找,直至null - 实现继承和多层级属性共享的核心机制
- 隐式原型引用:每个对象拥有
__proto__
- 非标准历史实现,后通过
Object.prototype.__proto__被标准化 - 提供对
[[Prototype]]的读写访问(建议优先使用Object.getPrototypeOf()和Object.setPrototypeOf())
- 非标准历史实现,后通过
原型关系:
// 构造函数、原型对象、实例的关系
function Constructor() {}
const instance = new Constructor();
// 原型指向关系
instance.__proto__ === Constructor.prototype; // true
Object.getPrototypeOf(instance) === Constructor.prototype; // true
// 原型链终点
Object.getPrototypeOf(Object.prototype) === null; // true
以一个现有对象作为原型,创建一个新对象(Object.create)。
function Person(name, age) {
this.name = name;
this.age = age;
}
Object.create(Person.prototype).__proto__ === Person.prototype
Object.create(Person.prototype).__proto__ === new Person().__proto__
基本原型链:
// 父类构造函数
function Animal(name) {
this.name = name;
}
// 在原型上添加方法(所有实例共享)
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
// 子类构造函数
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 继承父类原型(关键步骤)
Dog.prototype = Object.create(Animal.prototype); // 实现 Dog.prototype.__proto__ === Animal.prototype
Dog.prototype.constructor = Dog; // 修复构造函数指向
// 子类扩展方法
Dog.prototype.bark = function() {
console.log(`${this.name} (${this.breed}) barks!`);
};
// 创建实例
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // "Buddy makes a noise."(来自父类原型)
myDog.bark(); // "Buddy (Golden Retriever) barks!"(来自子类原型)
原型链验证:
// 验证原型关系
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
console.log(myDog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null(原型链终点)
# 现代继承方式
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name} (${this.breed}) barks!`);
}
}
// 功能与之前的原型继承示例完全等价
const myDog = new Dog('Buddy', 'Golden Retriever');
# 原型链验证
原型、原型链遵循以下两个准则。
function Person(name, age) {
this.name = name;
this.age = age;
}
let person = new Person('小明', 18);
Person.prototype.constructor === Person;
person.__proto__ === Person.prototype;
根据以上两个准则,原型链查找逻辑如下。
person.__proto__ === Person.prototype;
Person.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;
Person.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;
Object.__proto__ === Function.prototype; // Object 本质也是函数
Function.__proto__ === Function.prototype;
# 函数体系
函数 (opens new window)是 JavaScript 中的基本组件之一。
# 函数声明
函数声明 (opens new window)定义一个具有指定参数的函数。
function add(a, b) {
console.log(a + b)
}
# 函数表达式
函数也可以由函数表达式 (opens new window)创建,function 关键字用来在一个表达式中定义一个函数。
var add = function (a, b) {
console.log(a + b)
}
# 箭头函数
箭头函数 (opens new window)表达式的语法比函数表达式更简洁,并且没有自己的 this,arguments,super 或 new.target。
箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
(param1, param2, ..., paramN) => { statements }
// 相当于:(param1, param2, ..., paramN) => { return expression; }
(param1, param2, ..., paramN) => expression
// 当只有一个参数时,圆括号是可选的
(singleParam) => { statements }
singleParam => { statements }
// 没有参数的函数应该写成一对圆括号
() => { statements }
# 三、面向对象
# 构造函数
构造函数是用于创建对象的模板。
实例属性定义:
function Person(name) {
this.name = name;
}
原型方法挂载:
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}.`);
};
静态方法实现:
Person.createAnonymous = function() {
return new Person('Anonymous'); // 通过 new 调用自身构造函数
};
# Class 语法糖
类 (opens new window)是用于创建对象的模板。
实际上,类是特殊的函数,是构造函数的一种更现代、更清晰的语法糖。
# 类声明
定义类的一种方法是使用类声明。
要声明一个类,可以使用带有 class 关键字的类名。
class Car {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
# 类表达式
类表达式是定义类的另一种方法。
类表达式可以命名或不命名。
let Car = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
# 原型方法
原型方法是在构造函数的原型对象上定义的方法,只能通过实例对象来访问。
let Car = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.height * this.width;
}
};
# 静态方法
静态方法是直接在构造函数本身上定义的方法。
它们并不属于构造函数的实例,而是属于构造函数本身。
class Point {
static displayName = "Point";
static distance(a, b) {
return Math.max(a, b);
}
}