一、初识JavaScript
0、简介
JavaScript是一种函数优先特性的轻量级、解释型或者说即时编译型的编程语言,可用于使网页具有交互性。它和 HTML 和 CSS 都是 Web 核心技术。所有现代浏览器都支持 JavaScript。
1、变量
变量是编程中最重要的概念之一。一个变量指向一个用来存储值的特定内存地址。 变量被赋予一个名称,可以在整个代码中用来访问该值。声明一个变量意味着为它命名。变量的声明位置决定了它在代码中的使用位置。第一个范围是全局范围。 在任何“块”之外声明的变量都位于全局范围内。变量也可以在函数内部声明。 这些变量被认为处于局部作用域或块级作用域内。 在函数内部声明的变量只能在该函数内部使用。 如果你尝试在函数外部访问它,则会收到引用错误。
在 JavaScript 中,通常使用 let 关键字来实现变量声明。
let 变量名;
当变量名超过一个单词时,对于如何将单词大写,有特定的命名约定。 在 JavaScript 中,使用的约定是驼峰式命名法。驼峰式大小写是指名称中的第一个单词全部小写,但后续单词均采用首字母大写。
当你声明一个变量但没有初始化它时,该变量被视为未初始化。未初始化变量的默认值是 undefined。 这是一种特殊的数据类型,表示尚未定义的值。
关键字 let 并不是声明变量的唯一新方法。
var 关键字也可以声明变量,但它最大问题之一是可以轻松覆盖变量声明。随着你的代码库变大,你可能会意外地覆盖一个你不打算覆盖的变量。 由于此行为不会引发错误,因此搜索和修复错误变得更加困难。
var 变量名;
还可以使用 const 关键字声明变量。const 具有 let 的所有出色功能,另外还有一个额外的好处,即使用 const 声明的变量是只读的。 它们是一个常量值,这意味着一旦一个变量被赋值为 const,它就不能被重新赋值const 关键字命名不想重新分配的变量。 这有助于避免给一个常量进行额外的再次赋值。
const 变量名=常量;
2、数据类型
JavaScript的数据类型,分为原始数据类型和引用数据类型。
①原始数据类型
(1)数字
在 JavaScript 里,所有数字都以 64 位浮点数格式(IEEE 754 标准)存储,这意味着它能处理整数和小数。
(2)字符串
在 JavaScript 中,字符串代表的是一串字符内容,被包裹在单引号 (')或者双引号(")之中。字符串是不可变的,这意味着一旦被创建,它们就无法被更改,但仍然可以给该变量重新分配另一个值。常见的字符串操作有:
连接:使用加号 (+) 将两个字符串连接起来。
长度:使用 length 属性获取字符串的长度。
提取字符:使用方括号加索引来提取字符串中的特定字符(索引从0开始)。
查找:使用 indexOf() 方法查找子字符串在主字符串中的位置(如果存在)。
替换:使用 replace() 方法替换字符串中的部分内容。
重复:使用repeat(),接受一个数字作为参数,指定重复目标字符串的次数。
(3)布尔值
用于表示逻辑上的 true 或 false。
(4)空值
表示一个空值对象,通常用于初始化变量。
(5)未定义
表示一个未赋值的变量或未声明的属性,即未初始化变量的默认值。
②引用数据类型
(1)数组
可以保存一系列值。 引用数据类型与原始数据类型的不同之处在于前者可以保存更复杂的数据。 字符串和数字等原始数据类型一次只能保存一个值。
数组使用方括号 ([]) 表示,以逗号分隔值或元素:
let array1 = [1, 2, 3];
let array2 = [];
值的索引(index)可以访问数组内的值。 索引是一个数字,表示值在数组中的位置,从 0 开始表示第一个值。你可以使用括号表示法访问该值,例如:数组名[0]访问第一个数字。
.length 将返回数组中元素的数量。
.push() 允许你去“推入”一个值到一个数组的末尾。
.pop()会将一个数组中的最后一个元素移除,并且 returns(返回)该元素。换句话说就是 .pop() 函数移除数组末尾的元素并返回这个元素。
.shift() 的工作原理就像 .pop(),但它移除的是第一个元素。
.unshift()允许你将值添加到数组的开头
至于多维数组,则看作成是数组中的数组。 当你使用括号访问你的数组时,第一组括号指的是最外层(第一层)数组中的条目,而每一对额外的括号指的是里面下一层的条目。
(2)对象
对象和数组类似,区别在于数组使用索引来访问和修改数据,而对象中的数据是通过 属性访问的。对象非常适合用来存储结构化数据,可以表示真实世界中的物体。
const objectName = {
// 属性名: 属性值,
property1: value1,
property2: value2,
// 方法名: function() { 方法体 }
method1: function() {
// ...
},
method2() {
//...
}
};
(3)函数
是一种可调用的对象,用于封装可重复使用的代码块。函数可以接受参数并返回值,在 JavaScript 中,函数是一等公民,可以作为变量传递、作为参数传递给其他函数或作为返回值返回。
function 函数名{
...;
}
你可以通过函数名加上后面的小括号来调用这个函数,就像这样: functionName();
每次调用函数时,大括号之间的所有代码都将被执行。
函数的参数在函数调用中充当传入函数的输入占位符(也叫形参)。 函数调用时,参数可以为一个或多个。 调用函数时输入(或传递 )的实际值被称为参数。 函数可以多次调用,每次调用时传递的参数会决定参数的实际值。
基本类型(如数字、字符串、布尔值等)是通过值传递的方式传递给函数的,而对象、数组等复杂类型则是通过引用传递的方式传递给函数的。因此,对于基本类型,函数内部对参数的修改不会影响到原始变量;而对于复杂类型,函数内部对参数的修改会影响到原始变量。通过函数的参数把值传入函数, 也可以使用 return 语句把数据从一个函数中传出来。
③获取数据类型
在js中,定义的变量是弱类型的,typeof 是 JavaScript 中的一个运算符,用于获取操作数的数据类型。它返回一个表示操作数类型的字符串。
基本用法:
typeof 变量名;
返回值:
undefined:如果操作数是未定义的值。
boolean:如果操作数是布尔值。
number:如果操作数是数值。
string:如果操作数是字符串。
bigint:如果操作数是大整数。
symbol:如果操作数是符号。
object:如果操作数是对象或 null。
function:如果操作数是函数。
我们注意到:typeof null 返回 "object",这是 JavaScript 的一个历史遗留问题,因为在 JavaScript 早期版本中,null 被错误地归类为对象。这个问题由来已久,为了保持向后兼容性,没有修复这个问题。此外,对于对象和数组,它只能告诉我们它们是对象,无法进一步区分具体的类型。因此,当需要详细了解对象的类型时,通常需要使用其他方法,比如 instanceof 运算符或检查对象的构造函数。
instanceof:instanceof 是 JavaScript 中的一个运算符,用于检测一个对象是否是某个构造函数的实例。它用于检查一个对象是否是指定类(或类的原型链上的类)的实例,返回一个布尔值。
基本用法:
object instanceof constructor;
object:要检查的对象。
constructor:要检查对象是否是其实例的构造函数。
返回值:instanceof运算符返回一个布尔值,表示对象是否是指定构造函数的实例。如果 object 是 constructor 的实例,则返回 true;否则返回 false。
呵呵,虽然 instanceof 可以用来检测数组,但是需要注意的是,如果在多个全局执行上下文之间使用了多个 JavaScript 引擎实例,那么可能会出现 instanceof不正确的情况。因此,如果需要检测对象是否是数组,最好使用 Array.isArray() 方法,它更可靠。
Array.isArray(需要判断类型的变量名);
3、输出
console(控制台指令)允许你打印并查看 JavaScript 的输出。console.log() 可以用来向控制台发送信息。
console.log(变量名);
4、注释
被注释的代码块在 JavaScript 之中是不会执行的。 在代码中写注释,是一个可以让你自己和以后的其他人理解代码作用的好方法。
JavaScript有两种写注释的方法。
使用 // 注释掉当前行的代码,多行注释使用 /* 开始, */ 结束。
5、运算符
①算数运算符
| 运算符 | 描述 |
|---|---|
+ | 加法 (可以连接字符串) |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取模(余数) |
++ | 自增(分为前增和后增) |
-- | 自减 |
②赋值运算符
赋值运算符用于给变量赋值。
| 运算符 | 描述 | 等同于 |
|---|---|---|
= | 赋值运算符,将右侧表达式的值赋给左侧变量 | 无 |
+= | 加法赋值运算符,将变量与右侧值相加后再赋值给该变量 | x = x + y |
-= | 减法赋值运算符,将变量减去右侧值后再赋值给该变量 | x = x - y |
*= | 乘法赋值运算符,将变量与右侧值相乘后再赋值给该变量 | x = x * y |
/= | 除法赋值运算符,将变量除以右侧值后再赋值给该变量 | x = x / y |
%= | 取模赋值运算符,将变量除以右侧值得到的余数赋值给该变量 | x = x % y |
③比较运算符
比较运算符在逻辑语句中使用,并返回一个布尔值(true 或 false)
| 运算符 | 描述 |
|---|---|
== | 等于 |
=== | 绝对等于(值和类型均相等) |
!= | 不等于 |
!== | 不绝对等于(值和类型有一个不相等,或两个都不相等) |
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
④逻辑运算符
| 运算符 | 描述 |
|---|---|
&& | 逻辑与,两侧条件都为 true 时,整个表达式才返回 true,否则返回 false |
| || | 逻辑或,两侧条件只要有一个为 true,整个表达式就返回 true,只有两侧都为 false 时,才返回 false |
! | 逻辑非,对布尔值取反,若原布尔值为 true,则取反后为 false;若原布尔值为 false,则取反后为 true |
⑤条件运算符
语法是:a ? b : c
a 是条件,当条件返回 true 的时候运行代码 b,当条件返回 false 的时候运行代码 c。
二、入门JavaScript
JavaScript 包含多种不同类型的结构,这些结构可用于组织代码、控制程序流程、存储和处理数据等。在此部分,我们将学习到程序的控制结构——顺序、选择、循环,以及JavaScript的几个特有元素。
1、顺序结构
顺序结构是程序设计中最基本的结构,体现了程序执行的基本顺序性,它按照代码书写的先后顺序依次执行每一条语句,这本身就是对程序执行流程的一种基本控制。虽然顺序结构相对简单,但它是构建复杂程序控制逻辑的基础,其他控制结构往往会嵌套在顺序结构之中,共同完成程序的各种功能。
2、选择结构
通常在写代码时,总是需要为不同的决定来执行不同的动作。可以在代码中使用选择语句来完成该任务。
①if选择
if (condition)
{
//当条件为 true 时执行的代码
}
只有当指定条件为 true 时,该语句才会执行代码
②if...else选择
if (condition)
{
//当条件为 true 时执行的代码
}
else
{
//当条件不为 true 时执行的代码
}
在条件为 true 时执行代码,在条件为 false 时执行其他代码。
③if...else if...else选择
if (condition1)
{
//当条件 1 为 true 时执行的代码
}
else if (condition2)
{
//当条件 2 为 true 时执行的代码
}
else if(condition ...){
//当条件 ... 为 true 时执行的代码
}
else
{
//当条件 1 和 条件 2 都不为 true 时执行的代码
}
④switch选择
用于基于不同的条件来执行不同的动作:
switch(n)
{
case 1:
//执行代码块 1
break;
case 2:
//执行代码块 2
break;
default:
//与 case 1 和 case 2 不同时执行的代码
}
工作原理:首先设置表达式 n(通常是一个变量)。随后表达式的值会与结构中的每个 case 的值做比较。如果存在匹配,则与该 case 关联的代码块会被执行。请使用 break 来阻止代码自动地向下一个 case 运行。如果switch 的每一条 case没有添加 break,那么后续的 case 会一直执行,直到遇见 break 为止。
3、循环结构
循环结构用于重复执行一段代码,直至满足特定条件。
①for循环
for (初始化表达式; 条件表达式; 迭代表达式) {
// 循环体,每次循环执行的代码
}
初始化表达式:在循环开始前执行,一般用于初始化循环变量。
条件表达式:每次循环开始前检查该条件,若为 true 则执行循环体;若为 false 则跳出循环。
迭代表达式:每次循环体执行完毕后执行,通常用于更新循环变量。
②while循环
while (条件表达式) {
// 循环体
}
条件表达式:每次循环开始前检查,若为 true 则执行循环体;若为 false 则跳出循环。
③do...while 循环
do...while 循环与 while 循环类似,但它会先执行一次循环体,再检查条件。因此,do...while 循环至少会执行一次循环体。
do {
// 循环体
} while (条件表达式);
④for...in 循环
主要用于遍历对象的可枚举属性,包括对象自身的属性和继承的属性。
for (变量 in 对象) {
// 循环体
}
⑤for...of 循环
用于遍历可迭代对象,如数组、字符串、Set、Map 等。
for (变量 of 可迭代对象) {
// 循环体
}
4、函数
①编写函数
箭头函数(Arrow Function)是 ES6(ECMAScript 2015) 引入的一种简洁的函数语法
如果返回值是一个,则语法如下:
const 函数名 = (参数1, 参数2) => 返回值;
如果是多个返回值:
const 函数名 = (参数) => {
......
return 返回值;
};
②在对象里编写函数
在 ES5 中,当我们需要在对象中定义一个函数的时候,必须像这样使用 function 关键字:
const objectName = {
property1: value1,
property2: value2,
methodName: function(parameters) {
// 方法体,包含具体要执行的代码
return result;
}
};
用 ES6 的语法在对象中定义函数的时候,可以删除 function 关键词和冒号:
const objectName = {
property1: value1,
property2: value2,
methodName(parameters) {
// 方法体
return result;
}
};
5、模板字符串
模板字符串(Template Strings/Literals)是 ES6引入的特性,使用反引号(`)包裹内容,支持字符串插值、多行文本和高级用法(如标签模板)。这是一种可以轻松构建复杂字符串的方法。基本语法:
用反引号 ` 代替单引号或双引号;通过 ${} 嵌入变量或表达式;直接换行,无需 \n 或 +:
const expr = `变量:${variable}`; // 变量:值
const multiLine = `
第一行
...
...
`;
6、操作对象
和访问数组类似,访问对象属性有两种方式:点号表示法(.)和方括号表示法([])。
如果我们已经提前知道要访问的属性名,使用点号表示法是最方便的。如果你想访问的属性名中包含空格,就必须使用方括号表示法来获取它的属性值,并使用引号(单引号或双引号)将它们包裹起来。使用连续使用点号表示法和方括号表示法可以来访问对象的嵌套属性。当然,如果属性名不包含空格,也可以使用方括号表示法。这样可以直接增加、更新对象属性。
delete 操作符用于删除对象的属性。
7、解构赋值
解构赋值是 ES6 引入的新语法,用来从数组和对象中提取值,并优雅地对变量进行赋值。
①获取对象的值
基本的对象解构赋值允许你通过对象的属性名来提取对应的值,并赋值给同名的变量。
const { property1, property2, ... } = object;
②从对象中分配变量
可以给解构的值赋予一个新的变量名, 通过在赋值的时候将新的变量名放在冒号后面来实现。
const { property1: alias1, property2: alias2, ... } = object;
③从嵌套对象中分配变量
若对象内部包含其他对象,可以通过嵌套的解构语法提取嵌套对象里的属性值。
const {
outerProperty: {
innerProperty1,
innerProperty2
}
} = nestedObject;
④从数组中分配变量
可以按照数组元素的顺序将值赋给对应的变量。
const [variable1, variable2, ...] = array;
在解构数组时,可以通过逗号跳过某些元素,只提取需要的值。
const [, variable2, ...] = array;
使用剩余参数语法(...)可以将数组中剩余的元素收集到一个新的数组中。
const [variable1, variable2, ...rest] = array;
rest 参数只能对数组列表最后的元素起作用。 这意味着你不能使用 rest 语法来省略原数组最后一个元素、截取中间的元素作为子数组。
⑤函数参数解构对象
function functionName({ property1, property2, ... }) {
// 函数体,可以直接使用 property1, property2 等变量
}
8、拷贝
在 JavaScript 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是常用的两种对象复制方式,它们在复制对象时有着不同的行为。
①深拷贝
深拷贝是指将一个值复制给一个新对象时,新对象的属性值是原始对象属性的副本,而不是引用。因此,新对象与原始对象相互独立,修改一个对象不会影响另一个对象。
深拷贝复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变。
实现深拷贝通常需要递归地遍历对象,并复制其属性及属性值。可以通过多种方式实现深拷贝,比如手动递归、使用 JSON.parse(JSON.stringify(obj))、使用第三方库等。
JSON.parse(JSON.stringify(obj)):
const deepCopiedObject = JSON.parse(JSON.stringify(originalObject));
originalObject:这是你要进行深拷贝的原始 JavaScript 对象,它可以是普通对象、数组,或者是嵌套结构的对象(对象中包含其他对象或数组)。
JSON.stringify(originalObject):该方法将 originalObject 转换为一个符合 JSON 格式的字符串。在转换过程中,它会递归地遍历对象的所有可枚举属性,把对象的结构和属性值转换为字符串形式。不过要注意,一些特殊类型的值(如函数、正则表达式、Symbol 类型的属性等)在转换时会被忽略。
JSON.parse(...):这个方法接收 JSON.stringify(originalObject) 生成的 JSON 字符串作为参数,将其解析回一个新的 JavaScript 对象。由于新对象是基于字符串重新构建的,所以在内存中它和原对象是相互独立的,对新对象的修改不会影响原对象,反之亦然。
使用扩展运算符实现数组深拷贝:
const newArray = [...originalArray];
originalArray:这是你要进行拷贝的原始数组。
...originalArray:扩展运算符将 originalArray 中的元素展开。
newArray:通过扩展运算符展开 originalArray 的元素后,将这些元素重新组合成一个新的数组并赋值给 newArray。由于基本数据类型在赋值时是值传递,所以 newArray 和 originalArray 中的元素在内存中是相互独立的,对 originalArray 元素的修改不会影响 newArray,反之亦然,从而实现了深拷贝的效果。
②浅拷贝
浅拷贝是指将一个值复制给一个新对象时,新对象的属性值是原始对象的引用,而不是原始对象的副本。因此,新对象与原始对象共享同一个引用类型的属性。
直接赋值是一种浅拷贝的方式。这意味着对于原始类型数据,直接赋值会创建一个值的副本,而对于引用类型数据,直接赋值会创建一个新的引用,但是新旧引用指向的是同一个对象。因此,原始类型数据是浅拷贝,而引用类型数据也是浅拷贝。
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(地址),所以改变新对象,旧对象也会改变,因为新旧对象共享一块内存。
Object.assign()拷贝:
Object.assign(target, ...sources)
target:目标对象,即接收属性的对象。它会被修改并返回。
...sources:一个或多个源对象,这些对象的可枚举属性将被复制到目标对象。可以传入多个源对象,属性会按照从左到右的顺序依次复制,如果存在相同属性,后面的源对象的属性会覆盖前面的。
9、遍历
“遍历” 指的是按照一定的顺序依次访问数据结构中的每一个元素,对这些元素执行特定的操作。
①数组遍历
forEach() 是数组的一个高阶函数,用于遍历数组的每个元素,并对每个元素执行提供的回调函数。forEach() 方法不会改变原始数组,但允许对数组中的每个元素进行操作。
array.forEach(function(currentValue, index, array) {
// 对当前元素执行的操作
});
或
array.forEach((currentValue, index, array) => {
// 对当前元素执行的操作
});
currentValue:当前遍历的数组元素的值。
index:当前遍历的数组元素的索引。
array:调用 forEach() 方法的数组。
同样地,map() 方法是数组对象的一个高阶函数,用于创建一个新数组,其中每个元素都是原始数组经过某种转换后的值。map() 方法会对数组中的每个元素都调用一个提供的函数,并将函数的返回值组成一个新数组返回,原始数组不受影响。
const newArray = array.map(function(currentValue, index, array) {
// 返回新数组的每个元素
});
或
const newArray = array.map((currentValue, index, array) => expression);
currentValue:当前遍历的数组元素的值。
index:当前遍历的数组元素的索引。
array:调用 map() 方法的数组。
expression:表示一条表达式,其结果会作为新数组对应位置的元素。由于是单条返回语句,这里省略了 return 关键字和花括号。
map() 方法创建一个新数组,原始数组不受影响。回调函数可以访问当前元素的值、索引以及原始数组本身。返回的新数组的长度和原始数组相同。返回的新数组中的元素是根据原始数组中的每个元素经过回调函数处理后得到的结果。
在使用 map() 方法时,要确保回调函数不会修改原始数组的元素,因为 map() 方法创建的是一个新数组,而不是对原始数组的直接修改。
②对象属性遍历
for/in 语句用来循环遍历对象的属性:
for (variable in object) {
// 代码块,用于处理每个属性
}
variable:在每次迭代中,会将对象的一个属性名赋值给这个变量。通常使用一个变量名来接收属性名。
object:要遍历的对象。
③可迭代对象遍历
for...of 循环是一种用于遍历可迭代对象(如数组、字符串、Map、Set 等)的语法结构。它提供了一种简洁、直观的方式来遍历可迭代对象的元素,相比传统的 for 循环和 forEach() 方法,for...of 更加灵活和方便。
for (variable of iterable) {
// 循环体,每次迭代执行的代码
}
variable:在每次迭代中,会将可迭代对象的当前元素赋值给这个变量。你可以使用 let、const 或 var 来声明该变量。
iterable:要遍历的可迭代对象。
三、进阶JavaScript
在此部分,我们将逐步学习面向对象编程的三个基本特征:封装、继承、多态。
1、类的使用
①类的定义、实例化及方法创建
类是一种用于创建对象的蓝图或模板,它定义了对象的属性和方法。在 ES6(ECMAScript 2015)之前,JavaScript 并没有类的概念,而是通过构造函数和原型链来实现面向对象编程。ES6 引入了 class 关键字,使得在 JavaScript 中定义类更加直观和易于理解。用特殊关键字 class 在 JavaScript 中定义一个类:
class className {
// The body of class
}
上面的代码定义了一个类 类名。大括号 { } 界定了类的主体。请注意,此语法称为 类声明 。
当你创建类的 实例(instance) 时,该类将变得很有用。实例是一个包含类描述的数据和行为的对象,new 运算符可实例化该类。
const ...=new className;
constructor()是类中初始化实例的特殊方法。在这里你可以设置字段的初始值或针对对象进行任何类型的设置。在构造函数中,this 值等于新创建的实例。用于实例化类的参数成为构造函数的参数:
class ClassName {
constructor(parameters) {
// 初始化对象属性的代码
this.property1 = value1;
this.property2 = value2;
// 其他初始化逻辑
}
}
②封装
封装就像是把一个对象看作一个黑盒子,外部只需要知道这个盒子能做什么(即提供了哪些功能),而不需要了解它是如何实现的(内部的具体细节)。对象的属性和方法被封装在这个盒子内部,通过特定的接口与外部进行交互。你可以从对象中获得一个值,也可以给对象的属性赋值。这些操作通常被称为 getters 以及 setters。它们提供了一种封装机制,允许你在获取或设置对象属性时执行自定义逻辑,而不仅仅是简单的读写操作。
get 函数的作用是可以让对象返回一个私有变量,而不需要直接去访问私有变量。
set 函数的作用是可以基于传进的参数来修改对象中私有变量。 这些修改可以是计算,或者是直接替换之前的值。
const object = {
// 私有变量
_property: value,
// getter 方法
get property() {
// 自定义逻辑
return this._property;
},
// setter 方法
set property(newValue) {
// 自定义逻辑
this._property = newValue;
}
};
③继承
继承允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码的复用和扩展。JavaScript 中的类通过 extends 关键字支持单继承,也就是说一个子类只能有一个直接的父类。
class ParentClass {
// 字段(属性)声明
fieldName;
// 构造函数,用于初始化实例的属性
constructor(parameter1, parameter2, ...) {
this.fieldName = parameter1;
// 可以有更多的属性初始化语句
}
// 公共方法
methodName() {
// 方法体,包含具体执行的代码
return someValue;
}
}
class ChildClass extends ParentClass {
// 子类新增的字段(属性)
newFieldName = initialValue;
// 子类可以重写父类的方法
methodName() {
// 重写后的方法体
return someOtherValue;
}
// 子类也可以定义自己的新方法
newMethodName() {
// 新方法的方法体
return anotherValue;
}
}
如果你想在子类中调用父构的造函数,则需要使用子构造函数中提供的特殊功能 super(),注意,在子构造函数内部,必须在使用 this 关键字之前执行 super()。调用 super() 确保父级构造函数初始化实例。如果你想在子方法中访问父方法,则可以使用特殊的快捷方式 super。
class ChildClass extends ParentClass {
// 子类新增的字段(属性)
newFieldName;
// 子类自定义的构造函数
constructor(parameter1, parameter2, newParameter) {
// 调用父类的构造函数
super(parameter1, parameter2);
// 初始化子类新增的字段
this.newFieldName = newParameter;
}
// 子类的其他方法
// ...
}
④重用代码块
在 JavaScript 中,随着项目规模的增大,代码会变得越来越复杂,为了提高代码的可维护性和复用性,ES6 引入了模块系统。
(1)export导出代码块实现复用
export 关键字就是模块系统中用于将模块内的变量、函数、类等导出,以便其他模块可以使用的重要工具。export 关键字用于将模块内的变量、函数、类等代码块暴露出去,使得其他模块能够使用这些代码。通过 export,可以把一个模块封装成一个独立的单元,将其中有价值的部分提供给其他模块使用,从而实现代码的复用。
// 导出变量
export const variableName = value;
// 导出函数
export function functionName(parameters) {
// 函数体
}
// 导出类
export class className {
// 类的构造函数、方法等
constructor(parameters) {
// 构造函数体
}
methodName() {
// 方法体
}
}
(2)import导入代码块实现复用
import 关键字用于从其他模块中引入已导出的代码块。当一个模块使用 export 导出了一些代码后,其他模块就可以使用 import 将这些代码导入到自己的作用域中,进而使用这些代码,达成代码复用的目的。
// 导入命名导出的成员,使用大括号指定要导入的成员名称
// module-path 是源模块的路径,可以是相对路径或绝对路径
import { member1, member2 } from 'module-path';
// 导入命名导出的成员并为其指定别名
// 使用 as 关键字将 member1 重命名为 alias1,member2 重命名为 alias2
// 避免与当前模块中的名称冲突
import { member1 as alias1, member2 as alias2 } from 'module-path';
// 导入默认导出的成员
// defaultMember 是为源模块默认导出成员指定的名称
// 不需要使用大括号
import defaultMember from 'module-path';
// 同时导入默认导出和命名导出的成员
// 前面是默认导出成员,后面大括号内是命名导出成员
import defaultMember, { member1, member2 } from 'module-path';
// 将整个模块作为一个对象导入
// 使用 * as 语法,moduleObject 是表示整个导入模块的对象名称
// 通过该对象可以访问模块中所有导出的成员
import * as moduleObject from 'module-path';
// 仅执行模块代码而不导入任何成员
// 当只需要执行模块中的代码(如初始化全局状态)时使用
import 'module-path';
⑤this
this 关键字在不同的上下文中具有不同的含义。this 的值取决于函数的调用方式:
(1)this指向
在全局上下文中,也就是任何函数外部,this 指向全局对象,在浏览器环境中通常指向 window 对象;
当函数独立调用时,this 指向全局对象,但在严格模式下有所不同;
当函数作为对象的方法调用时,this 指向调用该方法的对象;
当函数用作构造函数(通过 new 关键字调用)时,this 指向新创建的对象;
在类中,this 指向实例化的对象即类的实例,不过在类的方法里使用普通函数时,函数内部的 this 会丢失上下文,可能指向全局对象,此时需使用箭头函数来绑定正确的上下文。
(2)改变this指向
call() 方法允许显式设置函数执行时的 this 指向,并且可以接受多个参数,这些参数会依次传递给被调用的函数。
// 定义一个函数
function functionName(arg1, arg2, ..., argN) {
// 函数体
}
// 使用 call 方法改变 this 指向并调用函数
functionName.call(thisArg, param1, param2, ..., paramN);
apply() 方法和 call() 方法类似,也是用于改变函数执行时 this 的指向。不同之处在于,apply() 方法接受的是一个数组作为参数,这个数组中的元素会依次传递给被调用的函数。
// 定义一个函数
function functionName(arg1, arg2, ..., argN) {
// 函数体
}
// 使用 apply 方法改变 this 指向并调用函数
functionName.apply(thisArg, [param1, param2, ..., paramN]);
bind() 方法会创建一个新的函数,当这个新函数被调用时,this 将被绑定到 bind() 方法指定的对象。与 call() 和 apply() 不同,bind() 方法不会立即执行函数,而是返回一个新的绑定了 this 的函数。
// 定义一个函数
function functionName(arg1, arg2, ..., argN) {
// 函数体
}
// 使用 bind 方法创建一个新的绑定函数
const boundFunction = functionName.bind(thisArg, param1, param2, ..., paramN);
// 调用绑定后的函数
boundFunction(otherParam1, otherParam2, ...);
区别:
三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为或null,则默认指向全局window。
三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行 。
⑥闭包
JavaScript 使用词法作用域(也称为静态作用域),即作用域由代码中函数声明的位置决定。这意味着在函数内部可以访问外部函数定义的变量,但是外部函数不能访问内部函数的变量。而闭包就是函数和其词法环境的组合,它允许函数访问其外部作用域中的变量,即使函数在外部作用域执行完毕后仍然能够访问这些变量。换句话说,闭包使函数拥有了“记忆力”,可以记住创建它的上下文。
使属性私有化最简单的方法就是在构造函数中创建变量。 可以将该变量范围限定在构造函数中,而不是全局可用。 这样,属性只能由构造函数中的方法访问和更改。
⑦多态
函数是一等公民,可以作为参数传递给其他函数。通过传递不同的函数作为参数,可以实现不同的行为,这也是一种多态的表现。而回调函数是一种常见的编程模式,用于处理异步操作和事件处理。回调函数是作为参数传递给另一个函数,当某个特定的事件发生或异步操作完成时,该回调函数会被调用执行。可用于:
处理异步操作:当需要处理耗时的操作(例如网络请求、文件读取等)时,可以将回调函数作为异步操作的回调,在操作完成后执行。
事件处理:在事件驱动的编程中,可以将回调函数注册为事件的处理函数,在事件发生时执行。
function outerFunction(callback) {
// 执行一些前置操作
// ...
// 检查传入的参数是否为函数
if (typeof callback === 'function') {
// 调用回调函数
callback();
}
// 执行一些后置操作
// ...
}
outerFunction 是一个接收回调函数作为参数的函数。
callback 是传入的回调函数,在 outerFunction 内部,通过 typeof callback === 'function' 检查其是否为函数类型,以确保可以安全调用。
callback() 语句用于调用传入的回调函数。
⑧异步编程
Promise 是异步编程的一种解决方案 - 它在未来的某时会生成一个值。 任务完成,分执行成功和执行失败两种情况。 Promise 是构造器函数,需要通过 new 关键字来创建。 构造器参数是一个函数,该函数有两个参数 - resolve 和 reject。 通过它们来判断 promise 的执行结果。 用法如下:
Promise是一种用于处理异步操作的对象,它代表了一个异步操作的最终完成或失败,以及其结果值。Promise对象有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当Promise对象的状态从pending转变为fulfilled或rejected时,将会执行相应的回调函数。
const myPromise = new Promise((resolve, reject) => {
// 当操作成功时,调用 resolve(value) 将 Promise 状态变为 fulfilled
// 当操作失败时,调用 reject(reason) 将 Promise 状态变为 rejected
});
当程序需要花费未知的时间才能完成时(比如一些异步操作),一般是服务器请求,promise 很有用。 服务器请求会花费一些时间,当结束时,需要根据服务器的响应执行一些操作。 这可以用 then 方法来实现, 当 promise 完成 resolve 时会触发 then 方法。
myPromise.then(result => {
});
当 promise 失败时会调用 catch 方法。 当 promise 的 reject 方法执行时会直接调用。 用法如下:
myPromise.catch(error => {
});
Promise对象也可以链式调用,每个 then() 或 catch() 方法都可以返回一个新的Promise对象,因此可以串联多个异步操作。这种机制被称为Promise链。
Promise.all() 接收一个Promise数组作为参数,当所有Promise都成功时,返回一个包含所有Promise结果的数组;如果任何一个Promise失败,立即返回失败状态。
Promise.race() 接收一个Promise数组作为参数,返回第一个完成的Promise的结果或失败原因。
Promise.all(iterable)
.then((results) => {
// 当 iterable 中所有 Promise 都成功时执行
// results 是一个数组,包含所有 Promise 的结果,顺序和 iterable 中 Promise 的顺序一致
})
.catch((error) => {
// 当 iterable 中任何一个 Promise 失败时执行
// error 是第一个失败的 Promise 的错误原因
});
Promise.race(iterable)
.then((firstResult) => {
// 当 iterable 中第一个 Promise 完成(成功或失败)时执行
// firstResult 是第一个完成的 Promise 的结果
})
.catch((firstError) => {
// 如果第一个完成的 Promise 是失败状态,执行此回调
// firstError 是第一个失败的 Promise 的错误原因
});
566

被折叠的 条评论
为什么被折叠?



