Web开发 03
今天在学习JavaScript,我其实需要很多练习才可以巩固一点!需要更多!
1 const
用于声明常量的规则
在 JavaScript 中,const
用于声明常量,其核心规则是:声明时【必须】同时初始化(赋值),且后续无法重新赋值 。
关键知识点拆解
const
声明规则- 用
const
声明变量时,必须直接赋初始值,否则会触发SyntaxError: Missing initializer in const declaration
(语法错误:const
声明中缺少初始化器 )。 - 例:正确用法
const num = 5;
,错误用法const num;
(缺少初始值)。
- 用
与
let
/var
的区别let
/var
声明时可暂不赋值(如let num;
),后续再赋值;但const
严格要求 “声明即初始化”。const
赋值后,无法通过重新赋值改变其值(会触发TypeError
),而let
/var
支持重新赋值。
const num;
未在声明时赋值,违反 const
语法规则,
因此代码执行前就会抛出 SyntaxError
,不会执行到 console.log
步骤。
特点是一旦声明并初始化,就不能再被重新赋值 。
代码执行逻辑
const num = 2;
:声明并初始化常量num
,值为2
。num = 6;
:尝试给常量num
重新赋值,这违反了const
的规则,JavaScript 会直接抛出TypeError: Assignment to constant variable
(类型错误:对常量变量赋值 )。- 因为报错会阻断代码执行,所以
console.log(num)
根本没机会运行,自然不会输出2
。
2 let声明变量可以重新赋值
在 JavaScript 中,let
声明的变量是可以重新赋值的,这是 let
变量的基本特性之一
比如 let a = 1; a = 2;
是合法操作。
3 JavaScript 回调函数与抽象函数
回调函数的定义
在 JavaScript 中,回调函数是作为参数传递给另一个函数的函数,(有点像回锅肉?)当外层函数执行到合适的时机(比如异步操作完成、循环遍历数组中的每个元素时),就会调用这个作为参数传入的函数。
图中提到的使用回调函数的原因
- 可复用性(Reusability )
- 以
map
和filter
方法为例,在 JavaScript 中,map
用于遍历数组并返回一个新数组,filter
用于根据条件过滤数组中的元素。 - 它们都接受回调函数作为参数,通过传入不同的回调函数,我们可以对数组进行各种操作,而不需要每次都重新编写遍历数组的代码。比如,用
map
给数组每个元素乘以 2,用filter
筛选出数组中的偶数等 ,这样就实现了代码的复用。
- 以
- 抽象性(Abstraction )
- 回调函数允许我们编写 “当 x 发生时,执行这个操作” 的代码逻辑。
- 我们不需要关心具体实现的细节,例如在一些异步操作(如读取文件、发送网络请求)中,当操作完成时,会调用我们传入的回调函数来处理结果。JavaScript 库的开发者负责编写底层代码,我们只需要编写回调函数来处理特定的业务逻辑。
与抽象函数的区别
回调函数和抽象函数是不同的概念:
- 回调函数:侧重于函数的使用方式,即作为参数传递给其他函数 ,强调的是在特定时机被调用执行。
- 抽象函数:在 JavaScript 中没有像一些强类型语言(如 Java )中那样严格意义上的抽象函数概念。但如果类比其他语言,抽象函数是不能被实例化调用的,通常是为子类提供一个模板,子类需要重写抽象函数来实现具体功能 ,它侧重于类的继承和多态相关的概念。
总结来说,回调函数是一种灵活的编程方式,能增强代码的复用性和可维护性,它和抽象函数是不同维度的概念。
4 map
和 filter
函数
在 JavaScript 中,map
和 filter
都是数组的高阶函数,它们经常和回调函数配合使用,让对数组的操作更加简洁高效,以下是详细介绍:
map
方法
- 定义和功能:
map()
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。简单来说,它会遍历原数组,对每个元素执行传入的回调函数,并将回调函数的返回值组成一个新数组返回。 - 语法:
let newArray = array.map(callback(element[, index[, array]])[, thisArg]);
callback
:必选参数,为数组中每个元素执行的函数,该函数接收三个参数:element
:当前正在处理的数组元素。index
(可选):当前正在处理的数组元素的索引。array
(可选):调用map
方法的数组本身。
thisArg
(可选):执行callback
函数时使用的this
值。
- 示例
javascript
// 将数组中的每个元素都乘以2
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]// 对包含对象的数组进行操作,提取每个对象的某个属性值
const students = [{ name: 'Alice', age: 20 },{ name: 'Bob', age: 22 },{ name: 'Charlie', age: 21 }
];
const names = students.map(student => student.name);
console.log(names); // ["Alice", "Bob", "Charlie"]
filter
方法
- 定义和功能:
filter()
方法创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。它会遍历原数组,根据传入的回调函数返回的布尔值来决定当前元素是否要被包含在新数组中。 - 语法:
let newArray = array.filter(callback(element[, index[, array]])[, thisArg]);
callback
:必选参数,为数组中每个元素执行的函数,该函数需要返回一个布尔值,决定当前元素是否被包含在新数组中。函数接收三个参数,和map
方法的callback
参数接收的参数一致。thisArg
(可选):执行callback
函数时使用的this
值。
- 示例
javascript
// 从数组中筛选出偶数
const numbers = [1, 2, 3, 4];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]// 从包含对象的数组中筛选出年龄大于20的学生
const students = [{ name: 'Alice', age: 20 },{ name: 'Bob', age: 22 },{ name: 'Charlie', age: 21 }
];
const eligibleStudents = students.filter(student => student.age > 20);
console.log(eligibleStudents);
// [ { name: 'Bob', age: 22 }, { name: 'Charlie', age: 21 } ]
map
和 filter
对比
- 作用不同:
map
侧重于对数组中的每个元素进行转换,生成一个新的数组,原数组长度和新数组长度一致;而filter
侧重于筛选,根据条件过滤掉不符合的元素,新数组长度可能小于原数组。 - 回调函数返回值意义不同:
map
的回调函数返回值是新数组的元素;filter
的回调函数返回值是布尔值,用于判断当前元素是否保留在新数组中。
5 Web 开发里回调函数在定时器场景的应用
回调函数的典型用法
用updateAnimation()
函数举例,想让它每 10 毫秒执行一次(做动画更新这类操作)。手动写代码管理定时器很麻烦,JavaScript 提供了setInterval
这类工具函数。setInterval
与回调的关系setInterval(updateAnimation, 10)
就是利用回调机制:把updateAnimation
当作回调函数传给setInterval
,让 JS 引擎在定时器触发时(每 10 毫秒)自动调用它,帮开发者简化了定时器管理的工作。
“我们编写了 updateAnimation()
函数,想让它每 10 毫秒调用一次。由于手动管理定时器代码很麻烦,JavaScript 提供了 setInterval
工具,通过 setInterval(updateAnimation, 10)
,让定时器触发时(每 10 毫秒)自动调用 updateAnimation
函数,其中 updateAnimation
作为回调函数 。”
// 定义要定时调用的函数
function updateAnimation() {// 假设这里是动画更新逻辑...
}
// 利用 setInterval 传入回调,实现每 10 毫秒调用一次
setInterval(updateAnimation, 10); //(注:实际网页中 10 毫秒的间隔非常短,可能会有性能问题,一般动画场景常用 requestAnimationFrame ,不过不影响理解回调 + 定时器的案例逻辑 )
简单说,这是用定时器案例,直观展示回调函数如何让 “特定时机执行代码” 更简单 ,体现回调在 Web 开发里处理异步、定时逻辑的价值 。(如果有后续 PPT ,可能还会延伸讲更多回调应用场景,比如事件监听、异步请求等)
6 为什么要使用回调函数?
在 JavaScript 以及其他编程语言中,使用回调函数有以下多个重要原因:
实现异步操作的处理
在 JavaScript 中,像网络请求(如fetch
获取数据)、读取文件、定时器等操作都是异步的,即不会阻塞代码的执行。当异步操作完成时,我们需要一种方式来执行后续的处理逻辑,回调函数就派上了用场。
- 示例:使用
setTimeout
模拟异步操作
javascript
console.log('开始');
setTimeout(() => {console.log('异步操作完成');
}, 1000);
console.log('结束');
上述代码中,setTimeout
会在 1 秒后执行回调函数,而主程序会继续向下执行,不会等待这 1 秒,体现了异步特性。
提高代码的复用性
回调函数可以将通用的逻辑抽象出来,通过传入不同的回调函数,实现不同的具体操作。比如数组的map
、filter
、reduce
等方法,都是接收回调函数作为参数,让我们可以对数组进行不同的处理,而无需每次都编写遍历数组的代码。
- 示例:使用
map
方法对数组元素进行处理
javascript
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]
这里map
方法复用了数组遍历逻辑,通过传入不同的回调函数,可以实现对数组元素的各种转换操作。
实现事件驱动编程
在 Web 开发中,经常需要处理用户的交互事件,如点击按钮、鼠标移动等。可以将处理事件的函数作为回调函数,注册到对应的事件上。当事件发生时,浏览器会自动调用这些回调函数。
- 示例:按钮点击事件处理
html
预览
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8">
</head>
<body><button id="myButton">点击我</button><script>const button = document.getElementById('myButton');button.addEventListener('click', () => {console.log('按钮被点击了');});</script>
</body>
</html>
实现函数的链式调用
在一些库(如 jQuery)中,通过回调函数可以实现方法的链式调用,使代码更加简洁和易读。虽然 JavaScript 原生没有广泛采用这种链式调用方式,但回调函数在设计这种可链式调用的 API 时起到了关键作用。
- 示例:jQuery 链式调用(类似原理)
javascript
$(document).ready(() => {$('#myDiv').css('color','red').text('修改后的文本');
});
通过回调函数,可以在文档加载完成后,对特定元素进行一系列的操作。
实现抽象和分层
在大型项目中,回调函数有助于将不同的业务逻辑分离。比如在一个复杂的系统中,底层模块可以通过回调函数将处理结果返回给上层模块,上层模块不需要关心底层具体的实现细节,只需要关注回调函数中的处理逻辑,实现了代码的抽象和分层。
7 自定义函数可以作为回调函数吗?
在 JavaScript 中,自定义函数是可以作为回调函数的。以下从几个常见的应用场景来说明:
数组方法应用场景
JavaScript 中数组的许多方法,如map
、filter
、reduce
等,都接收回调函数作为参数。我们可以将自定义函数传入这些方法中,以实现特定的功能。
javascript
// 自定义函数作为map方法的回调
function double(num) {return num * 2;
}
const numbers = [1, 2, 3];
const result = numbers.map(double);
console.log(result); // [2, 4, 6]// 自定义函数作为filter方法的回调
function isEven(num) {return num % 2 === 0;
}
const numbersArray = [1, 2, 3, 4];
const filteredResult = numbersArray.filter(isEven);
console.log(filteredResult); // [2, 4]
定时器应用场景
setTimeout
和setInterval
等定时器函数也支持传入回调函数,我们可以把自定义函数作为回调,在特定的时间点或周期性地执行。
javascript
// 自定义函数作为setTimeout的回调
function sayHello() {console.log('Hello!');
}
setTimeout(sayHello, 1000); // 1秒后执行sayHello函数// 自定义函数作为setInterval的回调
function count() {static count = 0;console.log(count++);
}
setInterval(count, 2000); // 每隔2秒执行一次count函数
事件监听应用场景
在 DOM 事件处理中,addEventListener
方法用于给元素添加事件监听器,它接收的第二个参数就是事件触发时要执行的回调函数,我们可以使用自定义函数来处理特定的事件。
html
预览
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8">
</head>
<body><button id="myButton">点击我</button><script>function handleClick() {console.log('按钮被点击了');}const button = document.getElementById('myButton');button.addEventListener('click', handleClick);</script>
</body>
</html>
异步操作应用场景
在处理异步操作,比如使用fetch
进行网络请求时,也可以将自定义函数作为回调,在请求成功或失败时执行相应的处理逻辑。
javascript
function handleResponse(response) {return response.json();
}
function handleError(error) {console.log('请求出错:', error);
}
fetch('https://api.example.com/data').then(handleResponse).catch(handleError);
综上,只要自定义函数的参数和返回值符合回调函数所在上下文的要求,就可以作为回调函数使用。
8 和python这种注重简洁哲学的动态语言,java注重严谨的静态语言相比,js是一种怎么样的语言?
JavaScript(JS)与 Python、Java 相比,在多方面存在差异,呈现出独特的特点:
类型系统方面
- JavaScript:是弱类型且动态类型的语言。在声明变量时无需指定类型,变量的类型在运行时可以改变 ,比如
let num = 1; num = "hello";
,变量num
先是数值类型,后又变为字符串类型。这使得代码编写更加灵活,上手容易,开发效率高,但也可能导致一些潜在的类型错误,比如在进行计算时,没有注意类型转换,就会出现意料之外的结果。 - Python:是动态类型语言,但相对 JavaScript 来说,它对类型的要求更为明确一些。虽然变量不需要显式声明类型,但在代码运行时对类型的检查和推断相对严格。例如,不能直接将字符串和数字进行相加操作。Python 还提供了类型注解功能,虽然这不是强制的,但可以增强代码的可读性和可维护性,让代码在一定程度上具备静态类型语言的特性。
- Java:是静态类型语言,变量在声明时就必须指定类型,而且一旦声明,变量的类型就不能改变。例如
int num = 1;
,num
只能存储整数类型的数据。这种严格的类型系统在编译阶段就能发现很多类型相关的错误,提高了代码的稳定性和可维护性,尤其适合大型项目的开发,但在开发效率上,相比动态类型语言会稍低一些,因为需要更多的时间来定义类型。
应用场景方面
- JavaScript:主要用于 Web 开发,是浏览器的原生脚本语言,在前端开发中占据主导地位,可以实现网页的交互效果、动态内容更新等。同时,借助 Node.js,JavaScript 也可以用于后端开发,构建服务器端应用程序,形成了前后端统一使用 JavaScript 的开发模式。此外,在移动应用开发(如 React Native、Weex 等框架)、桌面应用开发(如 Electron 框架)等领域也有广泛应用。
- Python:应用场景非常广泛,在 Web 开发方面,有 Django、Flask 等优秀的框架。在数据科学领域,Python 凭借丰富的库(如 NumPy、Pandas、Matplotlib 等)成为数据处理、数据分析、机器学习等任务的首选语言。此外,Python 在自动化脚本、网络爬虫、人工智能、科学计算等领域都有大量的应用。
- Java:在企业级开发中占据重要地位,常用于开发大型后端系统、分布式系统、安卓应用开发等。许多银行、金融机构、大型企业的核心业务系统都是用 Java 开发的,因为它的稳定性、安全性和跨平台性非常适合企业级应用的需求。
语法和编程范式方面
- JavaScript:语法相对灵活,支持多种编程范式,包括面向对象编程、函数式编程和基于原型的编程。它的函数可以作为一等公民,方便进行函数式编程,比如使用回调函数、箭头函数等特性。其基于原型的继承方式与传统面向对象语言(如 Java)的类继承方式有所不同,理解起来可能需要一些时间。
- Python:语法简洁、优雅,注重代码的可读性,使用缩进来表示代码块,而不是像 JavaScript 和 Java 那样使用大括号。Python 也支持多种编程范式,在面向对象编程方面,具有清晰的类和对象概念,同时也能很好地支持函数式编程。
- Java:语法严谨,遵循严格的面向对象编程范式,一切都围绕类和对象展开。代码结构规范,有明确的访问修饰符、接口、抽象类等概念,对于大型项目的代码组织和管理有很大的优势,但相比之下,代码量可能会比 JavaScript 和 Python 多一些。
执行机制方面
- JavaScript:在浏览器中,JavaScript 引擎(如 V8 引擎)直接解释执行代码,也可以通过 JIT(即时编译)技术在运行时将部分代码编译为机器码以提高执行效率。在 Node.js 环境中,同样是基于 V8 引擎进行代码的执行。
- Python:Python 是解释型语言,代码通过 Python 解释器逐行解释执行。不过,也有一些工具(如 PyPy)可以通过 JIT 编译来提升 Python 代码的执行性能。
- Java:Java 代码首先需要通过编译器编译成字节码,然后在 Java 虚拟机(JVM)中运行。JVM 可以对字节码进行即时编译(JIT),将热点代码编译为机器码,以提高程序的执行效率。
9 JavaScript、Python、Java 和 C 语言在编程特性方面的对比表格
特性 | JavaScript | Python | Java | C 语言 |
---|---|---|---|---|
类型系统 | 动态类型、弱类型 | 动态类型、强类型(隐式类型转换少) | 静态类型、强类型 | 静态类型、弱类型(允许较多隐式转换) |
编译 / 解释 | 解释执行(JIT 编译优化) | 解释执行(部分 JIT 如 PyPy) | 编译(字节码)→ JVM 执行 | 编译为机器码直接执行 |
主要范式 | 多范式(函数式、原型继承 OOP) | 多范式(函数式、类 OOP) | 纯面向对象(一切基于类) | 过程式、结构化(支持简单 OOP) |
内存管理 | 自动垃圾回收(GC) | 自动垃圾回收(引用计数 + GC) | 自动垃圾回收(GC) | 手动管理(malloc/free) |
应用场景 | 前端、Node.js 后端、移动 / 桌面应用 | 数据科学、Web 后端、自动化、AI | 企业级后端、Android 应用、大数据 | 系统软件、嵌入式开发、游戏引擎、驱动 |
性能 | 中等(JIT 优化后接近原生) | 较慢(解释执行) | 较快(JIT 优化后) | 极快(直接操作硬件) |
语法特点 | 灵活(函数式特性丰富) | 简洁(强制缩进、可读性高) | 严谨(大量样板代码) | 底层(指针、位操作) |
异步支持 | 原生 Promise、async/await | asyncio、协程 | CompletableFuture、线程池 | 回调函数、多线程(需手动管理) |
跨平台性 | 浏览器原生支持,Node.js 跨平台 | 解释器跨平台(需环境配置) | 一次编写,到处运行(JVM 依赖) | 需针对不同平台编译 |
独特标识 | 函数是一等公民、原型继承 | 缩进代替大括号、动态类型注解 | 强制面向对象、严格访问控制 | 指针操作、直接内存访问 |
关键差异解释:
JavaScript
- 原型继承:不使用类,而是通过原型链实现继承(如
Object.create()
)。 - 函数式特性:闭包、箭头函数、高阶函数(如
map
/filter
)是核心特性。 - 前端统治:浏览器环境中无可替代,支持事件驱动编程。
- 原型继承:不使用类,而是通过原型链实现继承(如
Python
- 动态类型注解:可通过
typing
模块添加类型提示,但非强制。 - 胶水语言:易于调用 C/C++ 库(如 NumPy 底层用 C 实现)。
- 社区生态:数据科学领域(Pandas、TensorFlow)的首选语言。
- 动态类型注解:可通过
Java
- 严格 OOP:所有代码必须位于类中,不支持全局函数。
- 内存安全:无指针操作(除了
Unsafe
类),避免野指针问题。 - 企业级:依赖注入(Spring)、ORM(Hibernate)等框架成熟。
C 语言
- 底层控制:直接操作内存地址(指针),适合系统级编程。
- 高性能:无需运行时环境,编译后直接执行。
- 标准库精简:仅提供基础功能,依赖第三方库扩展(如 POSIX、GLib)。