本文主要分享JavaScript基础笔记
JavaScript简介
JavaScript 是一种轻量级的、解释型的或即时编译型的编程语言。它是互联网的核心技术之一,几乎所有的网站都会使用它,并且它也是众多Web开发框架、库和应用程序的基础。以下是对 JavaScript 的详细介绍:
特性
动态类型:JavaScript 是动态类型的语言,这意味着您不需要提前声明变量的类型,变量的类型会由其值自动决定。
面向对象:JavaScript 支持面向对象的编程,包括封装、继承和多态等特性。
函数优先:JavaScript 中的函数是一等公民,它们可以被赋值给变量、作为参数传递或作为其他函数的返回值。
异步编程:JavaScript 支持异步编程模型,通过回调函数、Promises、async/await 等机制,可以处理非阻塞的 I/O 操作。
浏览器兼容性:几乎所有的现代浏览器都内置了 JavaScript 引擎,因此 JavaScript 可以直接在浏览器中运行,无需额外的解释器或编译器。
组成
ECMAScript:这是 JavaScript 的核心,定义了语言的基础语法、数据类型、语句、关键字、保留字、运算符、对象等。
DOM(文档对象模型):DOM 是 HTML 和 XML 文档的编程接口,它提供了对文档的结构化表示,并定义了一种方式可以使程序和脚本能够动态地访问和更新文档的内容、结构和样式。
BOM(浏览器对象模型):BOM 提供了独立于任何特定文档的对象,用于浏览器窗口和脚本之间的交互。例如,它可以用于弹出新窗口、导航、打开和关闭浏览器窗口等。
JavaScript的常识
注释
单行注释使用//
多行注释使用/**/
结束符
;代表语句结束,可有可不有,但是有的话建议全局都有
引入
JavaScript在html中使用<script>javascript代码</script>
或者使用src属性添加文件地址<script src="yourfile.js"> </script>
JavaScript变量
JavaScrip的变量命名建议使用小驼峰命名法
变量的声明
JavaScript变量的声明有三种:旧版的var、新版的let、常量const三个关键字来声明变量
下面介绍三种关键字声明变量的区别:
使用
var
关键字声明变量:var
是最早的变量声明关键字,它可以在任何作用域(包括全局作用域和函数作用域)中声明变量。使用var
声明的变量会进行变量提升(hoisting),这意味着无论它们在代码中的位置如何,都会被视为位于所在作用域的顶部。同时,var
声明的变量可以重新赋值。var myVariable = 'Hello, World!'; console.log(myVariable); // 输出 'Hello, World!' myVariable = 'Goodbye, World!'; console.log(myVariable); // 输出 'Goodbye, World!'
需要注意的是,由于
var
的变量提升特性,如果在声明之前就访问该变量,其值会是undefined
。此外当你使用
var
时,可以根据需要多次声明相同名称的变量,但是let
不能。var myName = "Chris"; var myName = "Bob";
上面的声明是可以滴,但是同样的方法用于let就报错,let只能用如下
let myName = "Chris"; myName = "Bob";
let的这种作坊可以让代码不至于混乱,重新声明
使用
let
关键字声明变量:let
是在ES6(ECMAScript 2015)中引入的新的变量声明关键字。它提供了块级作用域(block scope),这意味着let
声明的变量只在包含它的块或声明它的函数中有效。let
不会像var
那样进行变量提升,因此在声明之前访问let
变量会抛出ReferenceError
。此外,let
声明的变量也可以重新赋值。let myLetVariable = 'Hello, World!'; console.log(myLetVariable); // 输出 'Hello, World!' myLetVariable = 'Goodbye, World!'; console.log(myLetVariable); // 输出 'Goodbye, World!'
在
if
语句、for
循环等代码块中使用let
声明的变量,只在该代码块内部有效。使用
const
关键字声明常量:const
也是ES6中引入的,用于声明一个只读的常量。一旦一个变量被const
声明,它的值就不能再被改变(但如果是对象或数组,其内部状态可以被修改,只是引用不能被重新赋值)。const
同样具有块级作用域。const myConstant = 'Hello, World!'; console.log(myConstant); // 输出 'Hello, World!' // myConstant = 'Goodbye, World!'; // 这行会抛出TypeError
需要注意的是,使用
const
声明的变量实际上是变量的引用不可变,但如果变量引用的是一个对象或数组,那么对象或数组的内部状态是可以改变的。
变量的赋值
JavaScript的变量赋值直接用=赋值,变量声明的时候,var和let可以先只声明,不初始化(不立即赋值,此时undefined)也可以声明的时候进行初始化,需要注意的是const声明变量必须立即赋值
下面简单介绍一下JavaScript变量赋值的例子和说明
基本数据类型赋值
对于基本数据类型(如数字、字符串和布尔值),赋值操作会创建一个值的副本并将其存储在变量中。
let number = 42; // 数字赋值
let string = "Hello, World!"; // 字符串赋值
let boolean = true; // 布尔值赋值
引用类型赋值
对于引用类型(如对象和数组),赋值操作实际上是复制了对象的引用,而不是对象本身。这意味着两个变量现在指向内存中的同一个对象。
let object = { name: "Alice" }; // 对象赋值
let array = [1, 2, 3]; // 数组赋值
// 现在object1和object2指向同一个对象
let object1 = object;
let object2 = object;
// 修改object1的属性也会影响object2和object,因为它们引用的是同一个对象
object1.name = "Bob";
console.log(object2.name); // 输出 "Bob"
// 同理,修改array1也会影响array,因为它们引用的是同一个数组
let array1 = array;
array1.push(4);
console.log(array); // 输出 [1, 2, 3, 4]
函数赋值
在JavaScript中,函数也是对象,因此它们也可以被赋值给变量。
function greet() {
console.log("Hello!");
}
let greetFunction = greet; // 函数赋值
greetFunction(); // 输出 "Hello!"
重新赋值
变量在JavaScript中是可以重新赋值的,这意味着你可以随时改变一个变量所引用的值。
let variable = "initial value";
console.log(variable); // 输出 "initial value"
variable = "new value"; // 重新赋值
console.log(variable); // 输出 "new value"
解构赋值
ES6引入了解构赋值,它允许你从数组或对象中提取数据,然后将数据赋值给单独的变量。
// 数组解构赋值
let [first, second] = [1, 2];
console.log(first); // 输出 1
console.log(second); // 输出 2
// 对象解构赋值
let { name, age } = { name: "Alice", age: 30 };
console.log(name); // 输出 "Alice"
console.log(age); // 输出 30
默认值赋值
在解构赋值时,你还可以为变量指定默认值,以防提取的值是undefined
。
let [missingValue = 'default'] = [];
console.log(missingValue); // 输出 'default'
变量的类型
JavaScript是一个动态类型的语言,所以声明变量不需要指明他的变量类型,它很贴心的自动推导滴
JavaScript的基本数据类型:
Number:用于表示数字,包括整数和浮点数。例如:
42
,3.14159
。String:用于表示文本或字符序列。字符串必须用引号(单引号或双引号)括起来。例如:
"Hello, World!"
。Boolean:是逻辑数据类型,只有两个值:
true
或false
。Null:表示一个空值或“无”的值。它只有一个值,即
null
。Undefined:当一个变量被声明了,但没有赋值时,它的值就是
undefined
。Object:这是 JavaScript 中最复杂的数据类型。它用于存储多个值作为属性,这些属性可以是数据或函数。JavaScript 中的数组、日期、正则表达式等也是对象类型。
let dog = { name: "Spot", breed: "Dalmatian" };
Symbol(ES6 引入):是一种新的数据类型,表示唯一的、不可变的原始值,通常用作对象的属性键。
let obj = {}; let key1 = Symbol('key1'); let key2 = Symbol('key2'); obj[key1] = 'Hello'; obj[key2] = 'World'; console.log(obj[key1]); // 输出:Hello console.log(obj[key2]); // 输出:World // 使用 for...in 循环不会枚举 Symbol 属性 for (let prop in obj) { console.log(prop); // 这里不会输出任何内容,因为 for...in 不枚举 Symbol 属性 } // 使用 Object.getOwnPropertySymbols 可以获取所有的 Symbol 属性 let symbols = Object.getOwnPropertySymbols(obj); for (let sym of symbols) { console.log(sym, obj[sym]); // 输出:Symbol(key1) Hello 和 Symbol(key2) World }
此外,JavaScript 还支持两种复合类型:
Array:用于表示有序的元素集合。例如:
[1, 2, 3, 4, 5]
。let myNameArray = ["Chris", "Bob", "Jim"]; let myNumberArray = [10, 15, 40];
Function:在 JavaScript 中,函数也是对象,但它们有一些特殊的属性,使它们可以被调用。(详细之后再说)
JavaScript里面,一切皆可对象,一切都可以储存在变量里面!!!
JavaScript的运算符
算术运算符
加法 (
+
): 用于将两个数字相加,或者将数字与字符串相加(此时数字会被转换为字符串)。减法 (
-
): 用于从第一个数中减去第二个数。乘法 (
*
): 用于将两个数相乘。除法 (
/
): 用于将第一个数除以第二个数。取模 (
%
): 返回两数相除的余数。递增 (
++
): 增加变量的值(前缀或后缀)。递减 (
--
): 减少变量的值(前缀或后缀)。
比较运算符
等于 (
==
): 检查两个值是否相等(进行类型转换后)。严格等于 (
===
): 检查两个值是否严格相等(不进行类型转换)。不等于 (
!=
): 检查两个值是否不相等(进行类型转换后)。严格不等于 (
!==
): 检查两个值是否严格不相等(不进行类型转换)。大于 (
>
): 检查左边的值是否大于右边的值。小于 (
<
): 检查左边的值是否小于右边的值。大于等于 (
>=
): 检查左边的值是否大于或等于右边的值。小于等于 (
<=
): 检查左边的值是否小于或等于右边的值。
逻辑运算符
逻辑与 (
&&
): 如果两个操作数都为真,则条件为真。逻辑或 (
||
): 如果两个操作数中至少有一个为真,则条件为真。逻辑非 (
!
): 反转操作数的逻辑状态。
位运算符
按位与 (
&
): 对每一个比特执行 AND 操作。按位或 (
|
): 对每一个比特执行 OR 操作。按位异或 (
^
): 对每一个比特执行 XOR 操作。按位非 (
~
): 反转操作数的比特。左移 (
<<
): 将数字的二进制表示向左移动指定的位数。有符号右移 (
>>
): 将数字的二进制表示向右移动指定的位数,保持符号位。无符号右移 (
>>>
): 将数字的二进制表示向右移动指定的位数,忽略符号位。
赋值运算符
赋值 (
=
): 将右边的值赋给左边的变量。加法赋值 (
+=
): 将右边的值加到左边的变量上,然后将结果赋给左边的变量。减法赋值 (
-=
): 从左边的变量中减去右边的值,然后将结果赋给左边的变量。乘法赋值 (
*=
): 将左边的变量与右边的值相乘,然后将结果赋给左边的变量。除法赋值 (
/=
): 将左边的变量除以右边的值,然后将结果赋给左边的变量。取模赋值 (
%=
): 将左边的变量对右边的值取模,然后将结果赋给左边的变量。
类型运算符
类型检测 (
typeof
): 返回操作数的数据类型。实例检测 (
instanceof
): 检查一个对象是否是一个类的实例。
条件(三元)运算符
条件 (
?:
): 也称为三元运算符,根据条件表达式的值返回两个可能的结果之一。
指数运算符
指数 (
**
): 用于计算数字的指数(ES2016 引入)。
其他运算符
删除 (
delete
): 用于删除对象的属性或数组的元素。void: 用于计算一个表达式并返回
undefined
。逗号 (
,
): 用于分隔变量声明或函数参数。
JavaScript语句
这里介绍一下条件语句和循环语句
1 条件语句
条件语句用于根据条件执行不同的代码块。JavaScript中有两种主要的条件语句:if
语句和 switch
语句。
1.1 if
语句
if
语句用于根据指定的条件执行代码块。其语法如下:
if (condition) {
// 当 condition 为 true 时执行的代码块
} else {
// 当 condition 为 false 时执行的代码块(可选)
}
1.2 switch
语句
switch
语句用于基于不同的情况执行不同的代码块。其语法如下:
switch (expression) {
case value1:
// 当 expression 等于 value1 时执行的代码块
break;
case value2:
// 当 expression 等于 value2 时执行的代码块
break;
// 可以有更多 case...
default:
// 当没有 case 匹配时执行的代码块(可选)
}
2. JavaScript循环吧!
循环语句用于重复执行一段代码,直到满足某个条件为止。JavaScript中有几种循环语句,包括 for
、while
和 do...while
。
2.1 for
循环
for
循环用于在指定次数内重复执行代码块。其语法如下:
for (let i = 0; i < length; i++) {
// 循环体,将执行 length 次
}
2.2 while
循环
while
循环会在指定的条件为真时重复执行代码块。其语法如下:
let i = 0;
while (i < length) {
// 循环体
i++;
}
2.3 do...while
循环
do...while
循环至少会执行一次代码块,然后在指定的条件为真时继续执行。其语法如下:
let i = 0;
do {
// 循环体
i++;
} while (i < length);
break退出循环,continue跳过当前循环
JavaScript的函数
函数作用不多讲,直接来干货
1. 函数声明
你可以使用 function
关键字来声明一个函数:
function functionName(parameters) {
// 函数体,执行某些操作
return value; // 可选,返回某个值
}
例如:
function greet(name) {
console.log('Hello, ' + name + '!');
}
2. 函数表达式(匿名函数)
函数也可以通过表达式来定义,这种方式常用于创建匿名函数或将其立即作为值传递给其他函数(也称为立即执行函数表达式,IIFE):
var greet = function(name) {
console.log('Hello, ' + name + '!');
};
// 立即执行函数表达式 (IIFE)
(function() {
console.log('This runs immediately!');
})();
3. 箭头函数
ES6 引入了箭头函数,它提供了一种更简洁的函数语法:
const greet = (name) => {
console.log('Hello, ' + name + '!');
};
// 如果函数体只有一条语句,可以省略大括号,并隐式返回结果
const sum = (a, b) => a + b;
4. 参数
函数可以接受任意数量的参数,也可以不接受任何参数。参数在函数声明时定义,并在函数调用时提供。
function add(a, b) {
return a + b;
}
console.log(add(5, 3)); // 输出 8
5. 默认参数
ES6 允许你为函数参数设置默认值,这样当调用函数时没有提供某个参数时,就会使用默认值。
function greet(name = 'World') {
console.log('Hello, ' + name + '!');
}
greet(); // 输出 "Hello, World!"
greet('Alice'); // 输出 "Hello, Alice!"
6. 剩余参数
使用剩余参数(rest parameters),你可以将不定数量的参数作为数组传递给函数。
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 输出 10
7. 函数作用域和闭包
JavaScript 中的函数可以创建自己的作用域,这意味着在函数内部声明的变量不会影响到函数外部的变量。此外,函数可以访问其外部作用域中的变量,这称为闭包。闭包允许函数记住并访问其词法作用域,即使函数在其词法作用域之外执行。
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log('outerVariable:', outerVariable);
console.log('innerVariable:', innerVariable);
}
}
const myInnerFunction = outerFunction('Hello');
myInnerFunction('World'); // 输出: outerVariable: Hello, innerVariable: World
8. 作为值的函数
在 JavaScript 中,函数是一等公民,这意味着函数可以作为值被传递,可以作为参数传递给其他函数,也可以从其他函数中返回。这种特性使得 JavaScript 的函数非常灵活和强大。
9. 回调函数和高阶函数
回调函数是一个在特定事件发生时(或者特定函数完成其任务后)被调用的函数。高阶函数是接受函数作为参数或返回函数的函数。这些概念在 JavaScript 中非常常见,特别是在处理异步操作或构建可重用的函数库时。
10. this
关键字
在 JavaScript 函数中,this
关键字引用的是调用该函数的对象。在不同的上下文中,this
的值可能会变化,这是 JavaScript 中一个复杂但重要的概念。
JavaScript的数组
JavaScript 的数组是一种特殊的对象,用于在单个变量中存储多个值,即元素。这些元素可以是任何数据类型,包括数字、字符串、布尔值、对象甚至是其他数组。数组的大小是动态的,可以随着元素的添加或删除而改变。
定义数组的方法
字面量定义:
let arr1 = [1, 2, 3, "four", true];
构造函数定义:
let arr2 = new Array(1, 2, 3, "four", true);
使用
Array.of
方法:let arr3 = Array.of(1, 2, 3); // 创建具有可变数量参数的新数组实例
使用
Array.from
方法:let arr4 = Array.from([1, 2, 3]); // 从类似数组或可迭代的对象创建一个新的数组实例
常用数组操作
访问元素:
let value = arr1[0]; // 访问数组的第一个元素
添加元素:
使用索引添加:
arr1[arr1.length] = "new element"; // 在数组末尾添加新元素
使用
push
方法:arr1.push("new element"); // 在数组末尾添加一个或多个元素,并返回新的长度
使用
unshift
方法:arr1.unshift("new element"); // 在数组开头添加一个或多个元素,并返回新的长度
删除元素:
使用
pop
方法:let removed = arr1.pop(); // 删除并返回数组的最后一个元素
使用
shift
方法:let removed = arr1.shift(); // 删除并返回数组的第一个元素
使用
splice
方法:arr1.splice(index, howmany, item1, ....., itemX); // 通过删除现有元素和/或添加新元素来更改一个数组的内容
遍历数组:
使用
for
循环:for (let i = 0; i < arr1.length; i++) { console.log(arr1[i]); }
使用
for...of
循环:for (let element of arr1) { console.log(element); }
使用
forEach
方法:arr1.forEach(function(element) { console.log(element); });
查找元素:
使用
indexOf
方法:let index = arr1.indexOf("four"); // 查找元素并返回其索引,如果未找到则返回 -1
使用
includes
方法:let exists = arr1.includes("four"); // 判断数组是否包含某个值,返回布尔值
修改数组:
使用
map
方法:let newArr = arr1.map(function(element) { return element * 2; }); // 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果
使用
filter
方法:let filteredArr = arr1.filter(function(element) { return typeof element === 'number'; }); // 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
排序数组:
使用
sort
方法:arr1.sort(); // 对数组的元素进行排序,并返回数组
连接数组:
使用
concat
方法:let combinedArr = arr1.concat(arr2); // 连接两个或多个数组,并返回一个新数组,而不会更改现有数组
转换数组:
使用
join
方法:let str = arr1.join(', '); // 把数组的所有元素放入一个字符串中,元素通过指定的分隔符进行分隔
使用
slice
方法:let slicedArr = arr1.slice(1, 3); // 返回一个新的数组对象,是一个由开始到结束(不包括结束)选择的、由原数组的浅拷贝构成
反转数组:
arr1.reverse(); // 反转数组的元素顺序,就地修改原数组,并返回它
使用
reverse
方法:
JavaScript的其余方法
1. 字符串方法
charAt()
返回在指定位置的字符。
let str = "Hello";
let char = str.charAt(1); // "e"
substring()
返回字符串中两个指定的索引号之间的字符。
let str = "Hello, World!";
let sub = str.substring(0, 5); // "Hello"
toUpperCase()
和 toLowerCase()
将字符串转换为大写或小写。
let str = "Hello";
let upper = str.toUpperCase(); // "HELLO"
let lower = str.toLowerCase(); // "hello"
split()
将字符串分割为字符串数组。
let str = "apple,banana,cherry";
let arr = str.split(","); // ["apple", "banana", "cherry"]
模板字符串
模板字符串是ES6中引入的一种新的字符串表示方法,它使用反引号(```)来定义,并允许在字符串中嵌入表达式。模板字符串为字符串的拼接和格式化提供了更简洁、更直观的方式。
下面是一个模板字符串使用的示例:
let name = 'Achen';
let age = 25;
// 使用模板字符串拼接和格式化字符串
let greeting = `Hello, my name is ${name} and I'm ${age} years old.`;
console.log(greeting); // 输出: Hello, my name is Achen and I'm 25 years old.
2. 类型转换
parseInt()
和 parseFloat()
将字符串转换为整数或浮点数。
let numStr = "123";
let intNum = parseInt(numStr); // 123
let floatNum = parseFloat("123.45"); // 123.45
Number()
将给定的值转换为数字,如果转换失败则返回 NaN
。
let num = Number("123"); // 123
let notANum = Number("hello"); // NaN
String()
将给定的值转换为字符串。
let strNum = String(123); // "123"
let strBool = String(true); // "true"
3. 数值方法
Math.round()
四舍五入到最接近的整数。
let num = Math.round(123.456); // 123
Math.floor()
和 Math.ceil()
向下或向上取整。
let floorNum = Math.floor(123.456); // 123
let ceilNum = Math.ceil(123.456); // 124
Math.random()
返回 0(包括) 到 1(不包括) 之间的一个伪随机数。
let randomNum = Math.random(); // 例如:0.7345678901234567
4. 日期方法
Date()
创建日期对象。
let date = new Date(); // 当前日期和时间
getDate()
, getMonth()
, getFullYear()
获取日期的日、月、年。
let date = new Date();
let day = date.getDate(); // 1-31
let month = date.getMonth(); // 0-11
let year = date.getFullYear(); // 例如:2023
setDate()
, setMonth()
, setFullYear()
设置日期的日、月、年。
let date = new Date();
date.setDate(15);
date.setMonth(5); // 注意月份是从0开始的,所以5表示6月
date.setFullYear(2024);
5. 对象方法
Object.keys()
返回一个由对象的自身可枚举属性键(不包括原型链中的属性)组成的数组。
let obj = { a: 1, b: 2, c: 3 };
let keys = Object.keys(obj); // ["a", "b", "c"]
Object.values()
返回一个由对象自身的可枚举属性值组成的数组。
let obj = { a: 1, b: 2, c: 3 };
let values = Object.values(obj); // [1, 2, 3]
Object.assign()
用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。
let obj1 = { a: 1 };
let obj2 = { b: 2 };
let mergedObj = Object.assign({}, obj1, obj2); // { a: 1, b: 2 }
6. 函数方法
Function.prototype.call()
和 Function.prototype.apply()
调用一个具有给定 this
值的函数,以及作为一个数组(或类似数组对象)提供的参数。
function greet(name) {
console.log(`Hello, ${name}!`);
}
const obj = { name: "World" };
greet.call(obj, "World"); // 使用 call 调用,输出 "Hello, World!"
greet.apply(obj, ["World"]); // 使用 apply 调用,输出 "Hello, World!"
Function.prototype.bind()
创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,其余的参数将作为新函数的参数供调用时使用。
function list() {
return Array.prototype.slice.call(arguments);
}
const list1 = list(1, 2, 3); // [1, 2, 3]
// 创建一个拥有预设初始参数的新函数
function listWithThree() {
return list.apply(this, arguments.concat([4, 5, 6]));
}
const list2 = listWithThree.bind(null, 1, 2); // 创建一个新函数,预设初始参数为1和2
const list3 = list2(3); // 调用新函数,并传入最后一个参数3,输出 [1, 2, 3, 4, 5, 6]
类的使用
在ES6中,JavaScript引入了类的概念,这使得我们可以使用面向对象的方式编写代码。虽然JavaScript的类在语法上看起来与传统的面向对象语言(如Java或C++)的类相似,但它们实际上是基于原型链的语法糖。
下面是一个简单的类定义和使用的示例:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
// 创建类的实例
let john = new Person('Achen', 28);
// 调用实例的方法
john.greet(); // 输出: Hello, my name is Achen and I'm 28 years old.
在这个例子中,我们定义了一个名为Person
的类,它有一个构造函数constructor
用于初始化实例的属性,以及一个方法greet
用于打印问候语。然后,我们使用new
关键字创建了Person
类的一个实例john
,并调用了它的greet
方法。
此外JavaScript的类还有其他特性,如实例字段、静态字段、静态方法、静态块、私有成员
实例字段是类的实例所特有的属性。在类的构造函数中或通过类的方法,你可以访问和修改这些字段。
实例方法是定义在类上,由类的实例调用的函数。
静态字段是定义在类上而非实例上的属性,通过类本身来访问。
静态方法是定义在类上而非实例上的函数,通过类本身来调用。
静态块是在类加载时执行的代码块,用于初始化静态字段或执行其他只需在类加载时执行一次的操作。
从ES2020开始,JavaScript引入了私有类的字段和方法。它们以#
符号开头,并且只能在类的内部访问。
示例代码
class Person {
// 静态字段
static species;
// 静态方法
static sayHello() {
console.log('Hello from the Person class.');
}
// 静态块
static {
Person.species = 'Homo sapiens';
console.log('Static block executed.');
}
#name;
constructor(name) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}.`);
}
#privateMethod() {
return 'This is a private method.';
}
// 调用私有方法
myPrint(){
this.#privateMethod();
}
}
const person = new Person('Achen');
// 调用静态方法
Person.sayHello();//Hello from the Person class.
// 调用实例方法
person.greet(); // 输出: Hello, my name is Achen.
// person.#name; // 语法错误,不能从外部访问私有字段
// person.#privateMethod(); // 语法错误,不能从外部调用私有方法
person.myPrint();
//his is a private method.// 语法错误,不能从外部调用私有方法
迭代器和生成器
当然可以,让我们分别深入了解JavaScript中的迭代器和生成器。
迭代器 (Iterator)
迭代器是一个对象,它实现了迭代协议(iteration protocol),即它有一个next()
方法。每次调用next()
方法时,它都会返回一个对象,该对象具有两个属性:value
和done
。value
属性是当前迭代的元素的值,done
属性是一个布尔值,当没有更多元素可迭代时,它为true
。
迭代器的主要用途是允许我们遍历数据结构(如数组、Map、Set等)中的元素,而无需了解数据结构的内部表示。
下面是一个简单的迭代器示例:
let iterator = {
index: 0,
data: [1, 2, 3, 4, 5],
next() {
if (this.index < this.data.length) {
return {
value: this.data[this.index++],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
// ...
console.log(iterator.next()); // { value: undefined, done: true }
JavaScript的内置对象,如Array
、Map
、Set
等,都有一个[Symbol.iterator]()
方法,它返回一个迭代器,用于遍历该对象的元素。
生成器 (Generator)
生成器是一种特殊的函数,它允许你暂停和恢复函数的执行。生成器函数使用function*
语法定义,并且可以使用yield
关键字来暂停和恢复执行。
当调用一个生成器函数时,它不会立即执行,而是返回一个生成器对象。这个生成器对象有一个next()
方法,与迭代器对象的next()
方法类似,但yield
关键字允许在函数内部控制执行流程。
下面是一个简单的生成器示例:
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
let generator = numberGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
生成器的一个常见用途是创建无限序列或惰性序列,因为你可以根据需要逐个生成值,而不是一次性生成所有值。
此外,生成器还可以与for...of
循环一起使用,这使得遍历生成器产生的值变得非常简单。
function* numberGenerator() {
let i = 0;
while (true) {
yield i++;
}
}
for (let num of numberGenerator()) {
if (num > 5) break;
console.log(num); // 输出 0 到 5
}
在这个例子中,numberGenerator
是一个无限生成器,但通过使用break
语句,我们只在for...of
循环中遍历了前6个值。
JavaScript的模块
模块的导入导出
ES6引入了原生的模块系统,它使用import
和export
关键字来管理模块的导入和导出
导入模块:
import moduleName from './path/to/module';
// 或者
import { namedExport1, namedExport2 } from './path/to/module';
// 或者
import * as allExports from './path/to/module';
导出模块:
在模块的源文件中,你可以使用export
关键字来导出内容:
// 导出默认内容
export default function() { /* ... */ }
// 导出命名内容
export const namedExport = /* ... */;
export function anotherNamedExport() { /* ... */ }
动态导入(现代浏览器和Node.js)
ES6还提供了动态导入的功能,允许你在运行时导入模块。这通常用于代码分割和懒加载。
import('./path/to/module')
.then(module => {
// 使用module对象
})
.catch(err => {
// 处理导入失败的情况
});
DOM操作
JavaScript 的 DOM(文档对象模型)操作涵盖了众多方面,包括获取元素、修改元素内容、修改元素属性、修改元素样式、添加和删除元素等。
1. 获取元素
可以通过多种方式来获取 DOM 元素,比如 getElementById
、getElementsByClassName
、getElementsByTagName
、querySelector
和 querySelectorAll
等。
// 通过 id 获取元素
var elementById = document.getElementById('myElementId');
// 通过类名获取元素集合
var elementsByClassName = document.getElementsByClassName('myClassName');
// 通过标签名获取元素集合
var elementsByTagName = document.getElementsByTagName('p');
// 通过选择器获取元素,可以是任何有效的 CSS 选择器
var elementBySelector = document.querySelector('.myClass .mySubElement');
// 通过选择器获取元素集合
var elementsBySelector = document.querySelectorAll('.myClass .mySubElement');
2. 修改元素内容
可以通过修改元素的 innerHTML
或 textContent
属性来修改元素的内容。
// 修改元素的 HTML 内容
elementById.innerHTML = '<p>新的内容</p>';
// 修改元素的文本内容
elementById.textContent = '新的文本内容';
3. 修改元素属性
可以通过 .setAttribute()
方法设置元素属性,通过 .getAttribute()
方法获取元素属性,通过 .removeAttribute()
方法删除元素属性。
// 设置元素属性
elementById.setAttribute('data-custom', 'value');
// 获取元素属性
var attributeValue = elementById.getAttribute('data-custom');
// 删除元素属性
elementById.removeAttribute('data-custom');
4. 修改元素样式
可以通过修改元素的 style
对象来直接修改内联样式,或者通过修改元素的 classList
来添加、删除或切换类名。
// 修改内联样式
elementById.style.color = 'red';
elementById.style.fontSize = '16px';
// 添加类名
elementById.classList.add('newClass');
// 删除类名
elementById.classList.remove('oldClass');
// 切换类名(如果不存在则添加,如果存在则删除)
elementById.classList.toggle('toggleClass');
5. 添加和删除元素
可以通过创建新的 DOM 元素,然后使用 appendChild
、insertBefore
等方法将其添加到 DOM 树中,或者使用 removeChild
方法从 DOM 树中删除元素。
// 创建新的元素
var newElement = document.createElement('div');
newElement.textContent = '新的元素';
// 将新元素添加到父元素的子元素列表末尾
elementById.appendChild(newElement);
// 在参考元素之前插入新元素
var referenceElement = elementById.children[0];
elementById.insertBefore(newElement, referenceElement);
// 从父元素中删除元素
elementById.removeChild(newElement);
这些只是 DOM 操作的一部分,实际上 JavaScript 提供了非常丰富的 API 来操作 DOM,包括处理事件、修改元素的属性、获取和设置元素的尺寸和位置等。在实际开发中,根据具体需求,可以选择合适的 API 来操作 DOM。
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM 操作示例</title>
</head>
<body>
<h1 id="myTitle">原始标题</h1>
<p class="myParagraph">原始段落。</p>
<button onclick="addParagraph()">添加段落</button>
<script src="script.js"></script>
</body>
</html>
//script.js
// 获取元素
var titleElement = document.getElementById('myTitle');
var paragraphElements = document.getElementsByClassName('myParagraph');
// 修改元素内容
titleElement.textContent = '修改后的标题';
paragraphElements[0].innerHTML = '<b>修改后的段落内容</b>';
// 新增元素
function addParagraph() {
// 创建一个新的段落元素
var newParagraph = document.createElement('p');
// 设置新段落的内容
newParagraph.textContent = '这是新添加的段落。';
// 将新段落添加到 body 元素的末尾
document.body.appendChild(newParagraph);
}
更多操作可以参考官方文档
事件处理
JavaScript中的事件处理操作是前端开发的核心之一,它允许我们响应用户在浏览器中的各种行为,如点击按钮、滚动页面、输入文本等。下面将详细介绍一些常用的事件处理操作:
1. 事件监听
事件监听是JavaScript中处理事件的主要方式。通过addEventListener()
方法,我们可以为DOM元素添加事件监听器。
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
alert('按钮被点击了!');
});
在这个例子中,我们为ID为myButton
的按钮添加了一个点击事件监听器。当按钮被点击时,会弹出一个警告框。
2. 事件类型
JavaScript支持多种类型的事件,包括但不限于:
click
: 用户点击元素时触发。dblclick
: 用户双击元素时触发。mousedown
: 用户按下鼠标按键时触发。mouseup
: 用户释放鼠标按键时触发。mousemove
: 鼠标指针在元素内部移动时触发。mouseover
: 鼠标指针移入元素时触发。mouseout
: 鼠标指针移出元素时触发。keydown
: 用户按下键盘按键时触发。keyup
: 用户释放键盘按键时触发。keypress
: 用户按下并释放一个可以产生字符的键时触发。submit
: 用户提交表单时触发。scroll
: 用户滚动元素时触发。load
: 元素(如图像、脚本、框架、样式表等)加载完成时触发。unload
: 用户离开页面时触发。focus
: 元素获得焦点时触发。blur
: 元素失去焦点时触发。
3. 事件对象
事件监听器的回调函数通常接收一个事件对象作为参数,该对象包含了与事件相关的各种信息,如触发事件的元素、按键的编码、鼠标的位置等。
button.addEventListener('click', function(event) {
console.log(event.target); // 输出触发事件的元素
});
在这个例子中,event.target
指向触发点击事件的元素(即按钮本身)。
4. 事件委托
事件委托是一种通过利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件的技术。通常将事件监听器添加到父元素上,然后在事件处理程序中检查事件的目标(event.target
)来确定实际被操作的元素。
var list = document.getElementById('myList');
list.addEventListener('click', function(event) {
var target = event.target;
if (target.tagName.toLowerCase() === 'li') {
alert('你点击了一个列表项!');
}
});
在这个例子中,我们为列表(ul
)添加了一个点击事件监听器。当用户点击列表中的任何一项(li
)时,事件会冒泡到ul
,然后我们在事件处理程序中检查事件的目标,以确定是否点击了列表项。
5. 移除事件监听器
使用removeEventListener()
方法可以移除之前通过addEventListener()
添加的事件监听器。
button.removeEventListener('click', clickHandler);
在这个例子中,假设clickHandler
是之前用于添加点击事件监听器的函数。通过传递相同的函数引用和事件类型,我们可以移除该事件监听器。
6. 阻止默认行为和事件冒泡
事件对象提供了方法来阻止元素的默认行为和阻止事件冒泡。
event.preventDefault()
: 阻止元素的默认行为。例如,阻止表单的提交或链接的跳转。event.stopPropagation()
: 阻止事件冒泡。即阻止事件进一步传播到父元素。
7. 自定义事件
除了内置的事件类型,JavaScript还允许创建和触发自定义事件。
var event = new CustomEvent('myCustomEvent', {
detail: {
message: 'Hello, world!'
}
});
document.dispatchEvent(event);
在这个例子中,我们创建了一个名为myCustomEvent
的自定义事件,并附带了一个包含消息的detail
对象。然后,我们使用dispatchEvent()
方法触发了这个事件。任何监听这个自定义事件的元素都会收到通知。
异步编程
JavaScript 的异步编程是其核心特性之一,它使得 JavaScript 能够非阻塞地执行代码,从而允许在执行长时间运行的任务(如网络请求或 I/O 操作)时,浏览器或 Node.js 环境能够继续处理其他任务。异步编程在 JavaScript 中主要通过以下几种方式实现:回调函数、Promises、async/await 和事件循环。
1. 回调函数(Callbacks)
回调函数是最早的异步编程方式,它通过将函数作为参数传递给其他函数,并在某个特定事件(如异步操作完成)发生时调用这个函数。
const fs = require('fs');
fs.readFile('/path/to/file', 'utf8', function(err, data) {
if (err) {
console.error("Error reading file:", err);
return;
}
console.log("File contents:", data);
});
在这个例子中,fs.readFile
是一个异步函数,它接受一个回调函数作为参数。当文件读取完成后,这个回调函数会被调用。
2. Promises
Promises 是对回调函数的改进,它提供了一种更优雅的方式来处理异步操作的结果或错误。Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。
const fs = require('fs').promises;
fs.readFile('/path/to/file', 'utf8')
.then(data => {
console.log("File contents:", data);
})
.catch(err => {
console.error("Error reading file:", err);
});
在这个例子中,fs.readFile
返回一个 Promise 对象,我们可以使用 .then()
方法来处理成功的情况,使用 .catch()
方法来处理错误。
3. async/await
async/await 是基于 Promise 的语法糖,它使得异步代码看起来更像同步代码,更易于理解和维护。
const fs = require('fs').promises;
async function readFileAsync() {
try {
const data = await fs.readFile('/path/to/file', 'utf8');
console.log("File contents:", data);
} catch (err) {
console.error("Error reading file:", err);
}
}
readFileAsync();
在这个例子中,readFileAsync
是一个 async 函数,它使用 await
关键字等待 Promise 的结果。如果在等待过程中 Promise 被 rejected,则会抛出异常,我们可以在 try...catch
块中捕获这个异常。
4. 事件循环(Event Loop)
JavaScript 的事件循环是其异步编程模型的核心。它不断地从任务队列中取出任务并执行,从而实现了非阻塞的 I/O 操作。在事件循环中,任务可以是宏任务(如 script、setTimeout、setInterval、setImmediate、I/O、UI rendering)或微任务(如 Promise.then、process.nextTick、MutationObserver)。每次事件循环都会先执行完所有的微任务,再执行一个宏任务,然后进入下一个循环。
示例
console.log('Start of the script'); // 1. 同步代码,直接执行
setTimeout(function() {
console.log('setTimeout callback'); // 5. 宏任务,被放入宏任务队列
}, 0);
Promise.resolve().then(function() {
console.log('Promise.then callback'); // 3. 微任务,被放入微任务队列
});
process.nextTick(function() {
console.log('process.nextTick callback'); // 4. Node.js 特有的微任务,优先级高于 Promise.then
});
console.log('End of the script'); // 2. 同步代码,直接执行
// 事件循环开始
// 1. 执行同步代码
// 2. 当同步代码执行完后,开始检查微任务队列
// a. 执行 process.nextTick
// b. 执行 Promise.then
// 3. 微任务队列清空后,开始执行宏任务队列中的任务(如果有的话)
// a. 执行 setTimeout 回调
有几个关键的注意事项需要牢记:
错误处理
异步操作很容易因为各种原因而失败,比如网络问题、文件读取错误等。因此,对于每个异步操作,都需要有相应的错误处理机制。在 Promise 中,可以使用 .catch()
方法来处理错误;在 async/await 中,可以使用 try...catch
块来捕获异常。
回调地狱(Callback Hell)
当异步操作层层嵌套时,代码会变得难以阅读和维护,这就是所谓的“回调地狱”。Promise 和 async/await 都是用来解决这个问题的工具,它们可以让异步代码更加扁平化,易于理解。
异步函数的返回值
异步函数(无论是使用回调函数、Promise 还是 async/await)都不会立即返回结果,而是返回一个表示未来结果的占位符(在 Promise 的情况下)或者一个挂起的异步操作(在 async/await 的情况下)。因此,你不能直接对异步函数的返回值进行操作,而是需要等待异步操作完成后再处理结果。
并发与并行
并发和并行在异步编程中常常被提及。并发是指同时处理多个任务,而并行是指同时执行多个任务。在 JavaScript 中,由于单线程的特性,真正的并行是不可能的,但我们可以通过异步编程实现并发,即同时处理多个异步操作。
资源管理
异步操作可能涉及到资源的创建和销毁(如数据库连接、文件句柄等)。在使用这些资源时,需要确保在操作完成后正确释放它们,以避免资源泄漏。
请求网络
在JavaScript中,请求接口(即发起网络请求)通常涉及到使用某种HTTP客户端库或浏览器提供的原生API。下面,我将解释两种常见的方法:使用fetch
API(现代浏览器提供的原生API)和使用axios
库(一个流行的第三方库)。
1. 使用Fetch API
fetch
API 提供了一个原生的、基于Promise的方式来执行网络请求。它返回一个Promise
对象,这个对象在请求成功时解析为Response
对象,或者在请求失败时拒绝。
下面是一个使用fetch
API 发起GET请求的示例:
fetch('https://api.example.com/data')
.then(response => {
// 检查响应状态码
if (!response.ok) {
throw new Error('Network response was not ok');
}
// 解析JSON数据
return response.json();
})
.then(data => {
console.log(data); // 处理响应数据
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
在这个例子中,我们首先使用fetch
函数发送一个GET请求到指定的URL。然后,我们链式调用.then()
来处理响应。第一个.then()
检查响应是否成功(通常是通过检查状态码是否在200-299之间)。如果响应成功,我们解析响应体为JSON。第二个.then()
处理解析后的数据。如果在任何阶段发生错误,.catch()
会捕获错误并处理它。
2. 使用Axios库
axios
是一个基于Promise的HTTP客户端,用于浏览器和node.js。它提供了许多有用的功能,比如拦截请求和响应、转换请求和响应数据、取消请求等。
首先,你需要安装axios
(如果你是在Node.js环境中工作的话):
npm install axios
或者,如果你是在浏览器中直接使用,可以通过CDN引入axios
:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
下面是一个使用axios
发起GET请求的示例:
const axios = require('axios'); // 如果你在Node.js环境中
axios.get('https://api.example.com/data')
.then(function (response) {
// 处理响应数据
console.log(response.data);
})
.catch(function (error) {
// 处理错误
console.log(error);
});
在这个例子中,我们使用axios.get
方法发送GET请求,并处理响应或错误。response.data
包含了从服务器返回的数据。
无论是使用fetch
还是axios
,你都需要考虑处理网络请求的各个方面,包括错误处理、请求超时、请求头设置、请求体发送等。此外,出于安全考虑,当处理跨域请求时,你需要确保服务器支持CORS(跨源资源共享)。
JavaScript基础部分内容就分享到这里,更多内容大家可以参考官方文档,JavaScript内容较多,这只是一个最简单的开始,后续需要大家逐步深入学习!
官方文档地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript