
基础知识复习-Js
基础概念
JavaScript 是一种广泛使用的高级编程语言,主要用于实现网页和 Web 应用程序的交互功能。它是一种解释型、基于原型(prototype-based)、多范式(支持面向对象、命令式和函数式编程风格)的语言。JavaScript 最初由 Netscape 的 Brendan Eich 于 1995 年创建,旨在为网页添加动态行为。
起源与发展
起源:
JavaScript
由网景公司(Netscape)的布兰登·艾奇(Brendan Eich)于 1995 年创建,最初是为了在网页中添加动态交互功能。标准化:1997 年,
JavaScript
被提交给欧洲计算机制造商协会(ECMA),并成为国际标准(ECMAScript)。目前,JavaScript 的最新版本是 ECMAScript 2024。发展:随着互联网的发展,JavaScript 的功能不断扩展,从最初的简单网页特效,到如今可以用于开发复杂的前端框架(如 React、Vue、Angular)、服务器端应用(Node.js)、移动应用(React Native)等。
主要用途
网页交互:JavaScript 可以操作 HTML 和 CSS,实现动态效果,例如响应用户点击、鼠标悬停、表单验证等。
前端框架开发:现代前端开发中,JavaScript 是构建单页面应用(SPA)的核心技术。例如:
React:由 Facebook 开发,用于构建用户界面。
Vue.js:轻量级的前端框架,易于上手。
Angular:由 Google 维护,功能强大,适合大型应用。
服务器端开发:通过 Node.js,JavaScript 可以在服务器端运行,用于构建高性能的后端服务。
移动应用开发:使用 React Native 或其他框架,JavaScript 可以开发跨平台的移动应用。
游戏开发:结合 HTML5 和 Canvas,JavaScript 可以开发简单的网页游戏。
核心特点
解释型语言
:JavaScript 代码在运行时由浏览器或其他环境(如 Node.js)解释执行,不需要编译。基于事件驱动
:JavaScript 可以响应用户操作(如点击按钮、提交表单等),并根据这些事件执行相应的代码。跨平台
:可以在多种操作系统和设备上运行,包括 Windows、macOS、Linux 和移动设备。客户端与服务器端支持
:最初用于浏览器中的客户端脚本编写,现在也通过 Node.js 实现了服务器端开发。非阻塞 I/O 操作
:使用异步编程模型,允许高效的并发处理,避免长时间等待资源(如网络请求或文件读取)而阻塞主线程。丰富的生态系统
:拥有庞大的库和框架(如 React、Vue、Angular 等),以及 npm(Node Package Manager)提供的海量第三方包。
运行环境
浏览器:JavaScript 最初的运行环境是浏览器,通过浏览器内置的 JavaScript 引擎(如 Chrome 的 V8、Firefox 的 SpiderMonkey)执行代码。
Node.js:Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,允许 JavaScript 在服务器端运行。它提供了丰富的模块和库,用于构建网络应用、服务器等。
javascript 的组成
ECMAScript 是 JavaScript 的核心语法,定义了语言的基本特性。
DOM 是用于操作 HTML 文档的接口,允许 JavaScript 动态地修改文档的内容和样式。
BOM 是用于操作浏览器窗口和环境的接口,提供了与浏览器交互的功能。
ECMAScript
是 JavaScript 的核心语法部分,是 JavaScript 的标准化实现。它定义了语言的基本语法、数据类型、运算符、控制流、函数、对象等基本特性
DOM
是一个与平台和语言无关的接口,用于操作 HTML 和 XML 文档。DOM 将文档表示为一个由节点和对象组成的树形结构,允许 JavaScript 动态地访问和修改文档的内容、结构和样式。
BOM
是浏览器提供的对象模型,用于操作浏览器窗口和浏览器环境。BOM 提供了一系列的全局对象和方法,用于处理浏览器窗口、位置、历史记录、定时器等。
JS注释
在 JavaScript 中,注释用于解释代码的功能和用途,帮助开发人员更好地理解代码。以下是 JavaScript 中常见的注释类型:
单行注释
单行注释以 //
开头,该行后续的所有内容都会被视为注释,不会被解释器执行。
// 这是一个单行注释
console.log("Hello, World!"); // 这也是一个单行注释
多行注释
多行注释以 /*
开头,以 */
结尾。可以用于注释多行内容。
/*
这是一个多行注释
可以跨越多行
*/
文档注释
文档注释用于生成代码文档,通常以 /**
开头,以 */
结尾。文档注释中可以添加特殊标记,如 @param
、@return
等,用于描述函数的参数、返回值等。
/**
* 这是一个函数的文档注释
* @param {number} a - 第一个参数
* @param {number} b - 第二个参数
* @returns {number} 返回 a 和 b 的和
*/
function add(a, b) {
return a + b;
}
注释的用途
代码解释
:帮助开发人员和其他人更好地理解代码的功能和用途。调试
:在调试代码时,可以使用注释临时禁用某些代码块,以便快速定位问题。代码维护
:便于开发人员在日后的代码维护过程中快速理解代码的逻辑和功能。
注释应清晰、简洁,避免冗长和无关的内容
注释应与代码保持一致,避免出现注释与代码不符的情况,导致误解。
合理使用注释,避免过度注释,以免影响代码的可读性。
js的引入方式
在 JavaScript 中,有多种方式可以引入 JS 文件或代码,具体选择取决于项目需求和开发环境。以下是常见的几种引入方式:
使用 <script>
标签引入外部 JS 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="path/to/your/script.js"></script>
</body>
</html>
这种方式简单易用,但不适合大型项目,因为代码的可维护性较差
使用 ES6 模块
ES6 模块是现代 JavaScript 开发中推荐的方式,通过 import
和 export
语法实现模块化。
// file: module.js 导出模块:
export const myFunction = () => {
console.log('Hello, World!');
};
// file: main.js 导入模块:
import { myFunction } from './module.js';
myFunction();
// 在 HTML 中引入模块:
<script type="module" src="path/to/your/main.js"></script>
使用 CommonJS 模块
CommonJS
是 Node.js 中常用的模块化规范,通过 require
和 module.exports
实现模块化。
// file: module.js
function myFunction() {
console.log('Hello, World!');
}
module.exports = myFunction;
// file: main.js
const myFunction = require('./module.js');
myFunction();
这种方式主要用于 Node.js 环境
动态加载脚本
在某些情况下,需要在运行时动态加载 JavaScript
文件,可以使用 document.createElement
方法。
const script = document.createElement('script');
script.src = 'path/to/your/script.js';
document.head.appendChild(script);
这种方式适用于按需加载或延迟加载的场景
使用第三方模块加载器
在大型项目中,可以使用模块加载器(如 RequireJS、SystemJS)来实现动态加载和依赖管理。
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script>
require(['path/to/your/module'], function(module) {
module.myFunction();
});
</script>
使用构建工具
在现代前端开发中,使用构建工具(如 Webpack
、Parcel
)可以帮助进行代码打包、压缩和优化。
使用 Webpack:
# 安装 Webpack:
npm install --save-dev webpack webpack-cli
// 创建配置文件 webpack.config.js:
const path = require('path');
module.exports = {
entry: './scripts/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
# 运行 Webpack 打包:
npx webpack --config webpack.config.js
这种方式适合大型项目,可以提升性能和开发效率
代码分割和懒加载
在大型项目中,可以通过代码分割和懒加载来优化性能。
import(/* webpackChunkName: "module1" */ './module1')
.then(module => {
module.myFunction();
})
.catch(err => {
console.error('Failed to load module:', err);
});
这种方式可以显著提升页面加载速度
js的输出
在开发和调试过程中,
console.log()
是最常用的输出方式。如果需要将内容显示在页面上,可以使用
document.write()
或操作 DOM。alert()
适用于需要用户确认的场景。debugger; 和其他 console 方法可以辅助调试。
console.log()
这是最常用的输出方式,用于将内容输出到浏览器的控制台。\
console.log("Hello, World!"); // 输出字符串
console.log(123); // 输出数字
console.log(true); // 输出布尔值
console.log({ name: "Kimi", age: 25 }); // 输出对象
document.write()
在页面加载时,可以使用 document.write() 将内容直接输出到 HTML 页面中。
document.write("Hello, World!"); // 将内容输出到页面
注意: document.write()
只能在页面加载时使用,页面加载完成后使用会导致页面内容被清空。
操作 DOM
可以通过操作 DOM 将内容输出到 HTML 页面的指定元素中。
// 获取页面中的某个元素
const element = document.getElementById("myElement");
element.innerHTML = "Hello, World!"; // 将内容设置为元素的 HTML 内容
<!DOCTYPE html>
<html>
<head>
<title>示例</title>
</head>
<body>
<div id="myElement"></div>
<script>
const element = document.getElementById("myElement");
element.innerHTML = "Hello, World!";
</script>
</body>
</html>
alert()
可以使用 alert()
弹出一个警告框,显示内容。
alert("Hello, World!"); // 弹出警告框显示内容
alert()
是阻塞的,会暂停页面的执行,直到用户点击 “确定”。
debugger;:在代码中插入调试断点,可以在控制台中查看变量的值和调试代码。
console.error():用于输出错误信息。
console.warn():用于输出警告信息。
console.error("这是一个错误信息"); // 在控制台输出红色的错误信息
数据类型
JavaScript 是动态类型语言,变量是由值绝对,数据类型分为基础类型和引用类型。
基本数据类型(Primitive Data Types)
基本数据类型是 JavaScript 中最简单的数据类型,它们是不可变的,即直接存储在变量访问的位置。
String(字符串)
特点:字符串是由字符组成的序列,用单引号 '、双引号 " 或反引号 ` 包裹。
常见操作:字符串长度:“hello”.length → 返回 5。
字符串拼接:“hello” + “world” → 返回 “helloworld”。
字符串方法:
.toUpperCase()
: 将字符串转化为大写.toLowerCase()
:将字符串转化为小写.includes()
:判断字符串中是否包含某个字符串.charAt()
:获取到指定字符的索引位置.substring()
:截取字符串.split()
:将字符串分割为数组。
模板字符串:使用反引号 ` 和 ${} 插值,例如:
let name = 'John';
let gaessta =`Hello, ${name}!`
Number(数字)
特点:数字不分整数和小数,统一使用number表示
常见操作:四则运算:加 +、减 -、乘 *、除 /。
特殊值:
Infinity:表示正无穷。
-Infinity:表示负无穷。
NaN(Not a Number):表示非数字值。
数学方法:
Math.abs():绝对值。
Math.round():四舍五入。
Math.floor():向下取整。
Math.ceil():向上取整。
Math.random():生成 0 到 1 之间的随机数。
数据格式
.toFixed():指定小数点后保留的位数。
.toPrecision():指定数字的精度。
Boolean(布尔值)
特点:布尔值只有两个值:true 和 false。
逻辑运算符:
&&(逻辑与):true && true → true
||(逻辑或):true || false → true
!(逻辑非):!true → false。
类型转换:某些值在布尔上下文中会被转换为布尔值:
真值
:true、非空字符串、非零数字、对象等。假值
:false、null、undefined、0、-0、NaN、空字符串 “”。
Undefined
特点:表示变量已声明,但尚未赋值。
let x;
console.log(x); // undefined
Null
特点:表示一个空值,表示一个空对象。
let x = null;
console.log(x); // null
Symbol(ES6 新增)
特点:用于创建唯一的、不可变的值,常用于对象属性的键。
let mySymbol = Symbol("mySymbol");
console.log(mySymbol); // Symbol(mySymbol)
BigInt(ES2020 新增)
特点:用于表示大于 2^53 - 1 的整数。
let bigNumber = BigInt(1234567890123456789012345678901234567890n);
console.log(bigNumber); // 1234567890123456789012345678901234567890n
复合数据类型(Composite Data Types)
复合数据类型可以包含多个值,并且是可变的。
Object(对象)
特点: 对象是是由键值对组成的集合,用花括号 {} 包裹。
常见操作:
创建对象:
let person = {
id:1,
name:"John",
arry:[1,2,3]
arrbyor[{
out: "数值值",
tabis: "数值操作",
}]
}
访问属性:
点符号:person.name。
方括号:person[“name”]。
let names = person.name;
let arry = person.["name"];
console.log(names);
添加/修改属性:
person.job = "developer";
person.name = "Bob";
删除属性:
delete person.id;
遍历对象:
for (let key in person) {
console.log(key, person[key]);
}
Array(数组)
特点:数组是有序的值的集合,可以包含任意类型的数据。
常见操作:
创建数组:
let fruits = ["apple", "banana", "cherry"];
访问元素:
console.log(fruits[1]); // "banana"
添加/修改元素:
fruits.push("orange"); // 添加到数组末尾
fruits[1] = "grape"; // 修改元素
删除元素:
fruits.pop(); // 删除数组末尾的元素
fruits.shift(); // 删除数组开头的元素
数组方法:
.length:获取数组长度。
.slice():截取数组。
.splice():删除、替换、添加元素。
.map(): 对数组中的每个元素执行一个函数,并返回一个新数组。
.filter():根据条件过滤数组元素,并返回一个新数组。
.reduce():对数组中的每个元素执行一个函数,并返回一个值。
.sort():对数组进行排序。
特殊数据类型
Function(函数)
特点:函数是一种特殊的数据类型,可以被赋值给变量,也可以作为参数传递或作为返回值。
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet("John"));
类型检测
typeof
运算符:
基本数据类型:
typeof “hello” → “string”
typeof 42 → “number”
typeof true → “boolean”
typeof undefined → “undefined”
typeof Symbol() → “symbol”
typeof BigInt(123) → “bigint”
复合数据类型:
typeof {} → “object”
typeof [] → “object”(数组也是对象)
typeof null → “object”(这是 JavaScript 的一个历史遗留问题)
typeof function () {} → “function”
instanceof
运算符
用于检测对象是否是某个构造函数的实例:
let arr = [];
console.log(arr instanceof Array); // true
类型转换
隐式类型转换:
在某些操作中,JavaScript 会自动将数据类型转换为适合操作的类型,例如:
"123" + 456; // "123456"(字符串拼接)
"123" - 456; // -333(数字运算)
显式类型转换:
字符串转换:
String(value):将值转换为字符串。
value.toString():对象或数组调用 toString() 方法。
数字转换:
Number(value):将值转换为数字。
parseInt(value):将字符串解析为整数。
parseFloat(value):将字符串解析为浮点数。
布尔转换:
Boolean(value):将值转换为布尔值。
变量
在 JavaScript 中,变量是用于存储数据的容器,可以存储不同类型的数据,如数字、字符串、布尔值、对象等。以下是关于 JavaScript 中变量的详细知识点:
变量的声明
var(es5)
作用域:函数作用域或全局作用域
在函数内声明:作用域为整个函数
在块(如 if、for)中声明:变量会泄漏到块外部。
if (true) {
var x = 10;
}
console.log(x); // 输出 10
变量提升:声明会被提升到作用域顶部,但赋值不提升。
console.log(x); // 输出 undefined
var x = 10;
重复申明:容许重复申明,但会覆盖之前的值。
var a = 1;
var a = 2;
全局污染: 在全局作用域中声明的变量会污染全局作用域,可能影响其他代码。
var logdata = 'hello';
console.log(widow.logdata);
let(es6)
作用域:块作用域,只能在块中访问,不能在函数外部访问。{}
if (true) {
let x = 10;
}
console.log(x); // ReferenceError: x is not defined
暂时性死区(TDZ):声明前访问会报错。
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
不可重复声明:同一作用域内禁止重复声明。
let a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared
循环中的应用:每次迭代创建新的绑定,解决 var 的闭包问题。
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 0, 1, 2
}
const(es6)
常用申明:申明后不可以重新赋值
const pi = 3.14;
pi = 3.14159; // TypeError: Assignment to constant variable.
必须初始化:申明时必须初始化。
const pi; // SyntaxError: Missing initializer in const declaration
对象和数组的修改:允许修改属性或元素,但不可重新赋值。
const obg = {name: 'John'}
obg.name = 'Jane'
obg = {} // TypeError: Assignment to constant variable.
const arr = [1, 2, 3]
arr.push(4)
arr=[4] // TypeError
变量的赋值
变量的赋值是将数据存储到变量中的过程。可以使用以下方式进行赋值:
直接赋值
使用 = 运算符将值赋给变量。
let a;
a = 100; // 为变量 a 赋值为 100
console.log(a); // 输出: 100
解构赋值
这是一种从数组或对象中提取数据并赋值给变量的方式。
// 数组解构赋值
const [b, c] = [200, 300];
console.log(b, c); // 输出: 200 300
// 对象解构赋值
const { name, age } = { name: 'Alice', age: 25 };
console.log(name, age); // 输出: Alice 25
流程控制
流程控制语句用于控制程序的执行顺序,包括条件语句、循环语句和跳转语句等。
条件语句(Conditional Statements)
if
语句if
语句用于根据条件执行代码块。
if (condition) {
// condition 是条件
// condition 为 true 时执行代码块
}
// 示例:
let age = 18;
if (age >= 18) {
console.log("成年人");
}
if...else
语句if...else
语句用于根据条件选择执行不同的代码块。
if (condition) {
// condition 条件为 true 时执行代码块
} else {
// condition 条件为 false 时执行代码块
}
// 示例:
let age = 17;
if (age >= 18) {
console.log("成年人");
}else {
console.log("未成年人");
}
if...else if...else
语句if...else if...else
语句用于处理多个条件。
if (条件1) {
// 条件1 为 true 时执行的代码
} else if (条件2) {
// 条件1 为 false 且条件2 为 true 时执行的代码
}else {
// 所有条件都不满足时执行的代码
}
// 示例:
let score = 85;
if (score >= 90) {
console.log("优秀");
} else if (score >= 70) {
console.log("良好");
} else {
console.log("不及格");
}
switch
语句switch
语句用于根据变量的值选择执行不同的代码块。
switch (表达式) {
case 值1:
// 表达式等于值1时候执行的代码
break;
case 值2:
// 表达式等于值2时候执行的代码
break;
default:
// 所有条件都不满足时执行的代码
}
// 示例:
let times = 3;
switch (times) {
case 1:
console.log("星期一");
break;
case 2:
console.log("星期二");
break;
case 3:
console.log("星期三");
break;
default:
console.log("其他时间");
}
循环语句(Loop Statements)
for
循环for
循环用于在指定的条件下重复执行代码块。
for (初始化; 条件; 迭代) {
// 循环体
}
// 示例:
for (let i = 0; i < 5; i++) {
clonsole.log(i);
}
while
循环
while
循环用于在条件为 true
时重复执行代码块。
while (条件) {
// 循环体
}
// 示例:
let i = 4;
while (i < 5) {
console.log(i);
i++
}
do...while
循环
do...while
循环至少执行一次循环体,然后根据条件决定是否继续执行。
do {
// 循环体
} while (条件);
// 示例:
let i = 0;
do {
console.log(i);
i++;
} while (i < 5);
for...of
循环(ES6 新增)
for...of
循环用于遍历可迭代对象(如数组、字符串等)。
for (let value of 可迭代对象) {
// 循环体
}
// 示例:
let fruits = ["apple", "banana", "cherry"];
for (let fruit of fruits) {
console.log(fruit);
}
for...in
循环
for...in
循环用于遍历对象的属性名。
for (let key in 对象) {
// 循环体
}
// 示例:
let person = {name: "John", age: 30};
for (let key in person) {
console.log(key + ": " + person[key]);
}
跳转语句(Jump Statements)
break
语句break
语句用于终止循环或 switch
语句。
for (let i = 0; i < 5; i++) {
if (i === 3) {
break;
}
console.log(i);
}
continue
语句continue
语句用于跳过当前循环的剩余部分,继续执行下一次循环。
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue; // 跳过偶数
}
console.log(i);
}
return
语句return
语句用于从函数中返回值,并终止函数的执行。
function add(a, b) {
return a + b; // 返回结果
}
console.log(add(3, 5)); // 输出 8
运算符
JavaScript 中的运算符是编程的基础,用于执行各种操作,如算术计算、比较、逻辑判断等。以下是 JavaScript 中常见的运算符及其用法,帮助你更好地复习:
算术运算符(Arithmetic Operators)
自增和自减运算符的前置和后置区别:
let x = 5;
console.log(++x); // 6
console.log(x++); // 6,但 x 的值变为 7
赋值运算符(Assignment Operators)
比较运算符(Comparison Operators)
比较运算符用于比较两个值,返回布尔值 true 或 false。
== 和 === 的区别:
== 会进行类型转换,例如 5 == “5” 返回 true。
=== 不进行类型转换,类型不同直接返回 false,例如 5 === “5” 返回 false。
逻辑运算符(Logical Operators)
&&
:如果第一个操作数为 false,返回第一个操作数;否则返回第二个操作数
console.log(true && "hello"); // false
console.log(true && "hello"); // "hello"
||
:如果第一个操作数为 true,返回第一个操作数;否则返回第二个操作数。
console.log(true || "hello"); // true
console.log(false || "hello"); // "hello"
三元运算符(Ternary Operator)
三元运算符是一种简化的 if-else 结构。
let age = 18;
let message = age >= 18 ? "Adult" : "Minor";
console.log(message); // Adult
函数
函数是 JavaScript 中的一个 fundamental building block,它允许我们封装代码,以便于重复使用和重用。
函数定义方式
函数申明:函数声明(function declaration)是 JavaScript 中的一种函数定义方式,它使用 function
关键字来定义函数。
function add(参数1,参数2,参数3....... 参数n) {
// 函数体
return 返回值;
}
// 实例
function add(a, b) {
return a + b;
}
console.log(add(1, 2)); // 输出:3
函数表达式:函数表达式(function expression)是 JavaScript 中的一种函数定义方式,它使用 var
, let
, const
关键字来定义函数。
let 函数名 = function(参数1,参数2,参数3....... 参数n) { retrun 返回值; }
// 示例:
let add = function(name) { return "Hello, " + name + "!"; }
console.log(greet("Alice")); // 输出 "Hello, Alice!"
箭头函数:箭头函数(arrow function)是 JavaScript 中的一种函数定义方式,它使用 =>
来定义函数。
let 函数名 = (参数1,参数2,参数3....... 参数n) => { retrun 返回值; }
// 示例:
let dious =(x) => x * x;
console.log(dious(5)); // 25
let add = (name) => { "Hello, " + name + "!"; }
console.log(greet("Bob"));
函数参数
默认参数(ES6 新增):函数可以在定义时为参数指定默认值,当调用函数时,如果没有为参数赋值,则使用默认值。
function 函数名(参数1 = 默认值1, 参数2 = 默认值2, 参数3 = 默认值3) {}
// 示例:
function greet(name = 'World') {
return `Hello, ${name}!`;
}
function add(a, b = 1) {
return a + b;
}
console.log(add(5));
剩余参数(ES6 新增):在函数定义时,使用 … 表示一个参数可以接受一个数组作为参数。
function 函数名(...参数名) {
// 函数体
}
function sum(...numbers) {
return numbers.reduce((acc, cur) => acc + cur, 0);
}
let data = sum(1, 2, 3, 4, 5);
console.log(data); // 输出:15
arguments
对象:在函数内部,arguments
对象是一个类数组对象,包含了函数的参数。
function sum() {
let sumtos = 0;
for (let i = 0; i < arguments.length; i++) {
sumtos += arguments[i];
}
return sumtos;
}
console.log(sum(1, 2, 3, 4, 5)); // 输出:15
函数的调用方式
普通调用: 直接调用函数,如 functionName()
function getData() {
retrurn "Hello World";
}
//直接调用函数
getData();
作为方法调用:在对象上调用函数,如 object.method()
let obj = {
name: "John",
getName: function() {
return "你的名字" + this.name;
}
}
// 作为方法调用
obj.getName();
作为构造函数调用:使用 new 关键字调用函数,如 new FunctionName()
function Person(name) {
this.name = name;
}
let person = new Person("John");
通过call/apply调用:
call 和 apply 方法可以改变函数的 this 绑定,并调用函数。
// call 方法接受一个 this 值和一系列参数,并将函数的 this 绑定到指定的对象上,然后立即执行该函数。
function greet(greeting,punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // 输出: Hello, Alice!
// apply 方法接受一个 this 值和一个参数数组,并将函数的 this 绑定到指定的对象上,然后立即执行该函数。
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Bob' };
greet.apply(person, ['Hi', '.']); // 输出: Hi, Bob.
函数的返回值
函数可以通过 return 语句返回值。如果没有显式返回值,默认返回 undefined。
function add(a, b) {
return a + b;
}
console.log(add(3, 5)); // 输出 8
匿名函数与回调函数
匿名函数:没有名称的函数称为匿名函数,通常作为函数表达式或回调函数使用。
setTimeout(function() {
console.log("Hello after 1 second!");
}, 1000);
回调函数:回调函数是一个函数,它被传递给另一个函数作为参数,并在这个函数被调用后执行。
const fruits = ["apple", "banana", "cherry"];
fruits.forEach(function(fruit) {
console.log(fruit);
})
自定义回调函数:你可以创建一个自定义的回调函数,并在需要的时候使用它。
function delayedGreeting(name, callback) {
setTimeout(function() {
console.log(`Hello, ${name}!`);
callback();
}, 2000);
}
function sayGoodbye() {
console.log('Goodbye!');
}
delayedGreeting('Bob', sayGoodbye);
// 输出 (2秒后):
// Hello, Bob!
// Goodbye!
递归函数 递归函数是指在函数内部调用自身的函数。递归函数需要有终止条件,否则会导致栈溢出。
function factorial(n) {
if (n === 0) {
return 1; // 终止条件
}
return n * factorial(n - 1);
}
console.log(factorial(5)); // 输出 120 (5! = 5 × 4 × 3 × 2 × 1)
箭头函数与普通函数的区别
this 的绑定
this 在箭头函数中指向外部函数的 this,而普通函数中的 this 指向函数定义时的上下文。
let obj = {
name: 'John',
regularFunction:function() {
console.log(this.name);
},
arrowFunction: () => {
console.log(this.name);
}
}
obj.regularFunction(); // 输出 "Alice"
obj.arrowFunction(); // 输出 undefined
不可作为构造函数
箭头函数不能用作构造函数,即不能使用 new 关键字调用箭头函数。
高阶函数(Higher-Order Functions)
高阶函数是接受函数作为参数或返回函数的函数
高阶函数的特点:
01接受函数作为参数:这种函数可以将其他函数作为参数传递进来,并在内部调用这些函数。
02返回函数作为结果:这种函数可以返回一个新的函数,这个返回的函数可以在外部被调用。
高阶函数的应用场景
数组方法:map、filter、reduce
// map 方法接受一个回调函数作为参数,并返回一个新数组,其中每个元素都是回调函数处理后的结果。
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(number) {
return number * 2;
})
console.log(doubled); // [2, 4, 6, 8, 10]
// filter 方法接受一个回调函数作为参数,并返回一个新数组,其中包含所有通过回调函数测试的元素。
const nubers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(function(number) {
return number % 2 === 0;
})
console.log(evenNumbers); // [2, 4]
// reduce 方法接受一个回调函数和一个初始值作为参数,并返回一个累积的结果。
const numbers = [1, 2, 3, 4, 5];
* const sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
},0);
console.log(sum); // 15
函数的提升
函数声明会被提升到作用域的顶部,而函数表达式和箭头函数不会被提升。
console.log(add(3, 5)); // 输出 8(函数声明被提升)
function add(a, b) {
return a + b;
}
// console.log(multiply(3, 5)); // 报错,函数表达式未被提升
let multiply = function(a, b) {
return a * b;
};
JavaScript 作用域
作用域(Scope)是 JavaScript 中一个非常重要的概念,它决定了变量、函数和语句的可见性和生命周期
基本概念
作用域定义了变量、函数和语句的可访问性和生命周期。简单来说,它决定了哪些地方可以访问到某个变量或函数。
动态作用域 vs 词法作用域
JavaScript 使用词法作用域(lexical scoping),也称为静态作用域。词法作用域意味着在编写代码时(解析阶段),变量的作用域就已经确定了,而非在运行时动态确定。
function outer() {
let a = 10;
function inner() {
console.log(a);
}
return inner;
}
const fn = outer();
fn(); // 输出 10
词法作用域的好处
可预测性:变量的作用域在写代码时即可确定,便于理解和调试。
优化空间:浏览器或引擎可以提前解析和优化代码。
作用域链(Scope Chain)
什么是作用域链?
当访问一个变量时,JavaScript 引擎会按照作用域链逐层查找,直到找到该变量或到达全局作用域。
let globalVar = "I'm global";
function outer() {
let outerVar = "I'm outer";
function inner() {
let innerVar = "I'm inner";
console.log(globalVar, outerVar, innerVar);
}
inner();
}
outer(); // 输出 "I'm global I'm outer I'm inner"
作用域链的查找规则
当前作用域中查找变量。
如果没找到,向上一层作用域查找。
直到全局作用域,如果仍未找到,则抛出错误。
全局作用域(Global Scope)
全局作用域是最大的作用域,所有在函数外部定义的变量和函数都属于全局作用域。
let globalVar = "I'm global";
function globalFunc() {
console.log("I'm a global function");
}
避免滥用全局变量:全局变量可能会导致命名冲突和意外的副作用。
浏览器中全局对象:在浏览器中,全局作用域是 window 对象。
局部作用域(Local Scope)
函数作用域(Function Scope)在 JavaScript 中,函数内部的代码块(例如 {})会创建一个新的作用域。变量声明(var、let、const)的规则如下:
var:具有函数作用域,会变量提升,但不会在块级作用域中创建新的作用域。
let:具有块级作用域(block scope),在块级作用域(如 if、for、while 等)中声明的变量不会泄露到外部作用域。
const:与 let 类似,但声明的变量不可重新赋值。
function example() {
if (true) {
let a = 10;
var b = 20;
}
console.log(a); // 报错:a is not defined
console.log(b); // 输出 20
}
example();
块级作用域(Block Scope)
ES6 引入了 let 和 const,使得块级作用域变得更为重要。
{
let a = 10;
var b = 20;
}
console.log(a); // 报错:a is not defined
console.log(b); // 输出 20
作用域提升(Hoisting)变量声明(var、let、const 和 function)会在代码运行前被提升到其作用域的顶部,但仅有声明会被提升,赋值不会。
console.log(a); // 报错:ReferenceError
let a = 10;
console.log(b); // 输出 undefined
var b = 20;
console.log(c); // 输出 undefined
function c() {
console.log("I'm a function");
}
闭包
数组的基本定义
JavaScript 数组是一种用于存储有序数据集合的特殊对象,可包含任意类型的元素(如数字、字符串、对象、布尔值等),并通过从 0
开始的数字索引访问元素
核心特性
动态长度:数组长度可动态增减,无需预先声明固定大小
元素类型灵活:支持混合存储不同类型的数据
const arr = [1, "text", true, { key: "value" }, [2, 3]];
其中包含数字、字符串、布尔值、对象和嵌套数组
连续内存结构:数组在内存中表现为一段连续的内存地址,数组名指向首地址
与普通对象的区别与联系
联系:数组本质是由对象派生而来,可调用部分对象方法(如 toString
)
区别:索引顺序:数组元素按严格数字顺序排列,对象属性无序。操作方法:数组提供专有方法(如 push
、map
)用于操作元素集合,对象无此类内置方法
使用场景
数据列表存储:适用于需保持顺序的数据集合(如排行榜、表格行数据)
批量操作:通过循环或高阶函数(如 forEach
、filter
)高效处理多元素
注意事项
性能优化:频繁增删元素时,优先使用 push
/pop
(操作尾部)而非 unshift
/shift
(操作头部),避免索引重排开销
基本概念
数据集合:数组是相同类型元素的集合,所有元素存储于连续的内存空间中,元素类型可以是整型、字符型等任意一致的数据类型
元素约束:数组中的元素个数不能为0,且必须在定义时明确大小(C99标准前)或通过变长数组动态指定(C99标准后)
数组的元素组成
元素标识:数组元素是变量,通过数组名 + 下标唯一标识。例如 arr
表示数组 arr
的第一个元素
访问规则:元素需先通过数组定义分配内存空间后,才能逐个通过下标访问,不支持直接引用整个数组
下标范围:数组下标从 0
开始,依次递增至数组长度减1。例如长度为 n
的数组,最大有效下标为 n-1
索引作用:下标用于定位元素在数组中的顺序位置,类似于数学集合中的索引概念,确保数据的有序性和快速访问
数组操作
JavaScript 数组使用方括号 []
表示,元素以逗号分隔,可包含任意类型的数据(如数值、字符串、对象、其他数组等)
// 混合类型数组
const mixedArr = [1, "text", true, { key: "value" }, [4, 5]];
// 空数组
const emptyArr = [];
数组的创建方式
字面量方式 直接使用方括号 []
定义元素,是最简洁、常用的方式
const arr1 = [1, 2, 3];
const arr2 = ["a", "b", "c"];
构造函数方式 使用 new Array()
或 Array()
创建,但需注意参数特性
单一数值参数:表示数组长度(生成空槽数组)
多个参数或非数值参数:作为数组元素
const arr3 = new Array(3); // [empty × 3](稀疏数组)
const arr4 = new Array(1, "a"); // [1, "a"]
其他创建方式
扩展操作符:复制或合并现有数组
const arr5 = [...arr1, ...arr2]; // [1, 2, 3, "a", "b", "c"]
Array.of()
:明确将参数作为元素,避免构造函数歧义
const arr6 = Array.of(5);
Array.from()
:将可迭代对象(如字符串、Set
)转换为数组
const arr7 = Array.from("abc");
元素访问与长度获取
索引访问:通过下标(从 0 开始)直接获取元素。
const fruits = ["apple", "banana"];
console.log(fruits);
console.log(fruits.length);
首尾元素:通过 arr
和 arr[arr.length-1]
获取首尾元素
数组长度:使用 length
属性获取元素数量。
遍历数组元素
for
循环:通过索引遍历。
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
forEach
方法:遍历元素并执行回调。
fruits.forEach((item, index) => {
console.log(index, item);
});
高阶函数:如 map
、filter
、reduce
等。
onst lengths = fruits.map(fruit => fruit.length); // 计算长度
const longFruits = fruits.filter(fruit => fruit.length > 5); // 筛选结果
元素查找与筛选
按值查找:
indexOf()
:返回第一个匹配的索引(无匹配返回 -1
)。
lastIndexOf()
:返回最后一个匹配的索引
const idx = fruits.indexOf("banana");
条件查找:
find()
:返回第一个符合条件的元素。
findIndex()
:返回第一个符合条件的索引
const item = fruits.find(fruit => fruit.startsWith("b"));
批量筛选:filter()
返回符合条件的新数组
嵌套数组的检索
需在数组中查找子数组,可通过以下方式:
循环检查:使用 for
循环结合 Array.isArray()
判断元素是否为数组。
const arr = [1, [2, 3], 4];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
console.log("子数组:", arr[i]); // [2, 3] :ml-citation{ref="4,6" data="citationList"}
}
}
批量筛选:通过 filter()
提取所有子数组。
const subArrays = arr.filter(item => Array.isArray(item));
数组的扩展操作
ES6 扩展运算符
复制数组 用扩展运算符可快速创建数组的浅拷贝,避免引用传递问题
const original = [1, 2, 3];
const copy = [...original]; // 新数组,与原数组无引用关系
合并数组
合并多个数组时无需调用 concat
,直接通过扩展运算符实现
const arr1 = [1, 2], arr2 = [3, 4];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
函数参数传递
将数组元素展开为函数参数,替代 apply
方法
function sum(a, b, c) { return a + b + c; }
const nums = [1, 2, 3];
sum(...nums); // 6
动态添加元素
在现有数组中插入新元素,保持代码简洁性
const base = [2, 3];
const newArr = [1, ...base, 4]; // [1, 2, 3, 4]
实例方法的扩展
数据操作基础方法
增删元素:
push()
/pop()
:尾部操作元素
unshift()
/shift()
:头部操作元素
splice()
:任意位置增删或替换元素
let arr = [1, 2];
arr.push(3); // [1, 2, 3]
arr.splice(1, 1, 4); // [1, 4, 3]
高阶函数处理数据
遍历与转换:
map()
:映射新数组
filter()
:筛选符合条件的元素
reduce()
:累计计算为单个值
const nums = [1, 2, 3];
const doubled = nums.map(x => x * 2); // [2, 4, 6]
const sum = nums.reduce((acc, cur) => acc + cur, 0); // 6
查找与判断:
find()
:返回首个匹配元素
some()
/every()
:判断元素是否满足条件
const users = [{id: 1}, {id: 2}];
const user = users.find(u => u.id === 2); // {id: 2}
高级技巧
解构赋值结合扩展运算符
提取数组首尾元素时,可配合扩展运算符快速实现
const [first, ...rest] = [1, 2, 3];
console.log(first); // 1
console.log(rest); // [2, 3]
与 Math
函数结合
直接传递数组参数进行数学计算
const nums = [5, 2, 8];
Math.max(...nums); // 8
数组的增删改查
添加元素
末尾添加 push()
:添加一个或多个元素到数组末尾,返回新数组长度。
const arr = [1, 2];
arr.push(3); // 返回 3,数组变为 [1, 2, 3]
支持链式调用:arr.push(4).push(5)
(需注意返回值变化)
开头添加 unshift()
:添加一个或多个元素到数组开头,返回新数组长度。
arr.unshift(0); // 返回 4,数组变为 [0, 1, 2, 3]
性能较低,需移动所有元素索引
中间插入 splice(startIndex, 0, newElement)
:从指定位置插入元素,不删除原元素。
arr.splice(2, 0, "a", "b"); // 数组变为 [0, 1, "a", "b", 2, 3]
删除元素
末尾删除 pop()
:删除并返回最后一个元素。
const last = arr.pop(); // last = 3,数组变为 [0, 1, "a", "b", 2]
开头删除 shift()
:删除并返回第一个元素。
const first = arr.shift(); // first = 0,数组变为 [1, "a", "b", 2]
指定位置删除
splice(startIndex, deleteCount)
:删除指定数量元素,返回被删除元素的数组
const deleted = arr.splice(1, 2); // deleted = ["a", "b"],数组变为 [1, 2]
修改元素
直接索引赋值 通过下标直接修改元素
使用 splice
替换元素 splice(startIndex, deleteCount, newElement)
:删除并插入新元素。
arr.splice(0, 1, "start"); // 删除第一个元素并插入 "start",数组变为 ["start", "newValue"]
查询元素
索引访问 通过下标直接访问元素:
const elem = arr[1];
条件查询 includes()
:判断是否包含某元素,返回布尔值。
arr.includes("start"); // true
find()
:返回第一个满足条件的元素。
const result = arr.find(item => item === "start"); // "start"
遍历查询 forEach()
、map()
等方法遍历数组处理数据