JavaScript宝典下
小哆啦闭关修炼已久,潜心攻读专业秘技,方才下山考研本欲大展宏图,怎奈山河虽壮志难酬,终是觉察考研无望。思来想去,不若弃考研之念,重拾敲代码之道,复盘前端奇术,以备闯荡职场江湖。 今日小哆啦提笔,将闭关期间整理的JavaScript宝典略作总结,与诸位共赏。此番分享,虽不敢言妙笔生花,但聊胜于空谈胡诌,且看能否在欢笑中助尔等拨开前端迷雾!
ES6问题总结
1、let和const和var的区别
(1)块级作用域: 块作用域由 { }
包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
特性 | var | let | const |
---|---|---|---|
作用域 | 函数作用域(函数内有效),块级作用域无效 | 块级作用域(仅在所在代码块内有效) | 块级作用域(仅在所在代码块内有效) |
变量提升 | 会被提升到顶部,但值为 undefined | 会被提升到顶部,但存在“暂时性死区”(TDZ) | 会被提升到顶部,但存在“暂时性死区”(TDZ) |
重新赋值 | 可以重新赋值 | 可以重新赋值 | 不能重新赋值,必须初始化且值不能修改 |
重新声明 | 可以在同一作用域内多次声明同名变量 | 不能在同一作用域内声明同名变量 | 不能在同一作用域内声明同名变量 |
全局对象 | 在全局作用域中声明的变量成为全局对象的属性(window ) | 在全局作用域中声明的变量不会成为全局对象的属性 | 在全局作用域中声明的常量不会成为全局对象的属性 |
常用场景 | 传统的变量声明方式,适用于函数作用域的情况 | 适用于块级作用域的变量, |
2. const对象的属性可以修改吗?
const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
3. 如果new一个箭头函数的会怎么样
在 JavaScript 中,箭头函数(Arrow Function) 是一种特殊的函数表达式,不能作为构造函数使用。如果尝试使用 new
操作符调用箭头函数,会抛出错误。
原因
- 箭头函数没有
[[Construct]]
方法,因此不能被实例化。 - 箭头函数本质上是一个简单的语法糖,主要用于简化回调函数的书写,同时继承其定义上下文的
this
值。它并非设计为构造函数。
如果需要定义可以用 new
调用的构造函数,请使用普通函数声明或类(class
),而不是箭头函数。箭头函数的主要用途是简洁地书写回调或短函数,而不是用来创建对象。
4. 箭头函数与普通函数的区别
特性 | 箭头函数 | 普通函数 |
---|---|---|
this 绑定 | this 是从定义时的上下文中继承的,不会被重新绑定。 | this 是动态绑定的,取决于调用时的上下文。 |
arguments 对象 | 没有自己的 arguments 对象,使用的是外层函数的 arguments 。 | 有自己的 arguments 对象,包含调用时传递的参数。 |
用作构造函数 | 不能作为构造函数,调用 new 会抛出错误。 | 可以作为构造函数,通过 new 创建实例对象。 |
原型属性 | 没有 prototype 属性,不能用于创建原型方法。 | 有 prototype 属性,可以定义原型方法。 |
函数体语法 | 更简洁,适合简单函数或内联回调。 | 语法灵活,适合复杂逻辑和定义构造函数。 |
super 关键字 | 可直接访问外层上下文中的 super 。 | 需要明确调用 super ,通常用于类中。 |
动态上下文 | 不支持动态上下文绑定,this 始终指向定义时的上下文。 | 支持动态上下文绑定,this 依赖调用方式。 |
适用场景 | 简单的回调函数、内联函数。 | 构造函数、方法定义,以及需要动态 this 的场景。 |
this
绑定
- 箭头函数:
this
是从定义时的外部作用域继承的,与调用方式无关。 - 普通函数:
this
是动态的,依赖于函数的调用方式。
arguments
对象
- 箭头函数:没有自己的
arguments
对象,使用的是外层函数的arguments
。 - 普通函数:有自己的
arguments
对象,包含调用时传递的所有参数。
- 用作构造函数
- 箭头函数:不能用作构造函数,调用
new
会抛出错误。 - 普通函数:可以用作构造函数,通过
new
创建实例。
prototype
属性
- 箭头函数:没有
prototype
属性,不能定义原型方法。 - 普通函数:有
prototype
属性,可以定义原型方法。
- 简洁语法
- 箭头函数:支持简化写法,适合单表达式函数。
- 普通函数:需要明确的
return
语句和花括号。
5. 箭头函数的this指向哪⾥?
箭头函数的 this
指向由其定义时的上下文决定,而不是调用时的上下文。它的 this
值与其外层作用域的 this
一致,完全取决于函数创建时的环境。
特点
- 箭头函数不会创建自己的
this
。 - 箭头函数的
this
永远与其定义时所在的作用域中的this
一致。 - 无论箭头函数如何被调用(作为方法调用、普通函数调用等),其
this
都不会改变。
具体分析
- 全局作用域中的箭头函数
在全局作用域中,箭头函数的 this
通常指向全局对象(浏览器中是 window
,在 Node.js 中是 global
)。
const arrow = () => {console.log(this);
};arrow();
// 在浏览器中输出:window
// 在 Node.js 中输出:{...}(全局对象)
- 对象中的箭头函数
箭头函数的 this
指向其定义时的作用域,与调用方式无关。在对象的方法中定义的箭头函数,其 this
不会绑定到该对象,而是继承自外层作用域。
const obj = {value: 42,arrow: () => {console.log(this.value);},regular() {console.log(this.value);},
};obj.arrow(); // undefined(箭头函数的 `this` 继承全局作用域)
obj.regular(); // 42(普通函数的 `this` 绑定到 `obj`)
- 箭头函数嵌套在普通函数中
箭头函数继承外层普通函数的 this
。
function outer() {this.value = 42;const arrow = () => {console.log(this.value);};arrow();
}outer.call({ value: 100 }); // 100(箭头函数的 `this` 继承自外层函数的 `this`)
- 作为回调函数
箭头函数在回调中使用时,其 this
继承自定义时的作用域。
const obj = {value: 42,method() {setTimeout(() => {console.log(this.value); // `this` 继承自 `method` 的上下文}, 1000);},
};obj.method(); // 42
如果使用普通函数,则 this
会指向 setTimeout
的调用者(通常是全局对象):
const obj = {value: 42,method() {setTimeout(function () {console.log(this.value); // undefined,普通函数的 `this` 指向 `window`(浏览器)或 `global`(Node.js)}, 1000);},
};obj.method();
5. 使用 bind
、call
或 apply
无效
尝试用 bind
、call
或 apply
改变箭头函数的 this
是无效的,因为箭头函数的 this
在定义时已确定。
const arrow = () => {console.log(this);
};arrow.call({ value: 42 }); // 全局对象(`this` 不受影响)
arrow.bind({ value: 42 })(); // 全局对象
总结
箭头函数的 this
:
- 定义时绑定,而非调用时。
- 继承自箭头函数所在的外层作用域的
this
。 - 不会被
bind
、call
或apply
改变。
在需要动态 this
的场景中(如方法调用或构造函数),不要使用箭头函数。
6. 扩展运算符的作用及使用场景
功能 | 描述 | 示例代码 | 输出结果 |
---|---|---|---|
数组展开 | 将数组的元素展开为独立值。 | javascript<br>const arr = [1, 2, 3];<br>console.log(...arr);<br> | 1 2 3 |
数组合并 | 合并多个数组为一个新数组。 | javascript<br>const arr1 = [1, 2];<br>const arr2 = [3, 4];<br>const merged = [...arr1, ...arr2];<br>console.log(merged);<br> | [1, 2, 3, 4] |
数组复制 | 浅拷贝数组,生成一个新数组。 | javascript<br>const original = [1, 2, 3];<br>const copy = [...original];<br>console.log(copy);<br> | [1, 2, 3] |
对象展开 | 将对象的属性展开为独立键值对。 | javascript<br>const obj1 = { a: 1, b: 2 };<br>const obj2 = { b: 3, c: 4 };<br>const merged = { ...obj1, ...obj2 };<br>console.log(merged);<br> | { a: 1, b: 3, c: 4 } |
对象复制 | 浅拷贝对象,生成一个新对象。 | javascript<br>const original = { x: 1, y: 2 };<br>const copy = { ...original };<br>console.log(copy);<br> | { x: 1, y: 2 } |
函数参数展开 | 将数组元素作为独立参数传递给函数。 | javascript<br>function sum(a, b, c) { return a + b + c; }<br>const nums = [1, 2, 3];<br>console.log(sum(...nums));<br> | 6 |
Rest 参数 | 收集函数中多余的参数为数组。 | javascript<br>function collect(...args) { console.log(args); }<br>collect(1, 2, 3);<br> | [1, 2, 3] |
数组解构赋值 | 提取数组部分内容,同时保留剩余元素。 | javascript<br>const [first, ...rest] = [1, 2, 3, 4];<br>console.log(first);<br>console.log(rest);<br> | 1 和 [2, 3, 4] |
对象解构赋值 | 提取对象部分内容,同时保留剩余属性。 | javascript<br>const { a, ...rest } = { a: 1, b: 2, c: 3 };<br>console.log(a);<br>console.log(rest);<br> | 1 和 { b: 2, c: 3 } |
动态属性合并 | 在对象中动态添加或更新属性。 | javascript<br>const base = { a: 1, b: 2 };<br>const updated = { ...base, b: 3, c: 4 };<br>console.log(updated);<br> | { a: 1, b: 3, c: 4 } |
7、对对象与数组的解构的理解
解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。
对象解构
从对象中按属性名提取值。
基本用法
const obj = { a: 1, b: 2 };
const { a, b } = obj; // 提取 a 和 b
console.log(a, b); // 1, 2
特性
-
默认值
:当属性不存在时可设置默认值。
const { c = 3 } = obj; console.log(c); // 3
-
重命名
:将属性值赋给新的变量名。
const { a: x } = obj; console.log(x); // 1
嵌套解构
const nested = { p: { q: 10 } };
const { p: { q } } = nested;
console.log(q); // 10
数组解构
从数组中按索引提取值。
基本用法
const arr = [1, 2, 3];
const [x, y] = arr; // 提取前两个值
console.log(x, y); // 1, 2
特性
-
跳过元素
:使用逗号跳过某些值。
const [, second] = arr; console.log(second); // 2
-
默认值
:未匹配到值时使用默认值。
const [a, b, c = 4] = [1, 2]; console.log(c); // 4
嵌套解构
const nestedArr = [[1, 2], [3, 4]];
const [[a], [b]] = nestedArr;
console.log(a, b); // 1, 3
对象与数组混合解构
const data = { name: "Alice", scores: [10, 20] };
const { name, scores: [first, second] } = data;
console.log(name, first, second); // Alice, 10, 20
解构在函数参数中的应用
-
对象参数解构:
function greet({ name, age }) {console.log(`${name} is ${age} years old.`); } greet({ name: "Bob", age: 25 });
-
数组参数解构:
function sum([a, b]) {return a + b; } console.log(sum([3, 7])); // 10
8、 如何提取高度嵌套的对象里的指定属性?
1. 使用可选链(?.
)
可选链运算符允许安全访问嵌套的属性,即使某些中间层级为 undefined
或 null
。
const data = { a: { b: { c: 42 } } };// 提取指定属性
const value = data?.a?.b?.c;
console.log(value); // 42// 如果路径不存在
const nonExistent = data?.a?.b?.d;
console.log(nonExistent); // undefined
特点
- 避免手动检查中间层是否存在。
- 直接返回
undefined
,不会抛出错误。
2. 使用解构赋值
通过解构赋值,逐层提取目标属性。可以设置默认值以防止属性不存在时报错。
const data = { a: { b: { c: 42 } } };// 解构提取
const { a: { b: { c = 0 } = {} } = {} } = data;
console.log(c); // 42// 如果路径不存在
const { x: { y: { z = 0 } = {} } = {} } = data;
console.log(z); // 0
特点
- 适合需要提取多个嵌套属性的场景。
- 必须明确路径结构,代码较冗长。
3. 使用递归函数
对于路径动态生成的场景,可以使用递归函数来安全提取指定属性。
const data = { a: { b: { c: 42 } } };// 递归提取属性
function getNestedValue(obj, keys) {return keys.reduce((acc, key) => acc?.[key], obj);
}const value = getNestedValue(data, ['a', 'b', 'c']);
console.log(value); // 42const nonExistent = getNestedValue(data, ['a', 'b', 'd']);
console.log(nonExistent); // undefined
特点
- 动态路径支持,适合属性层级不固定的情况。
- 简洁而灵活。
4. 使用第三方库(如 Lodash)
Lodash 提供了专门的方法 _.get
来安全访问嵌套属性。
const _ = require('lodash');const data = { a: { b: { c: 42 } } };// 使用 _.get 提取
const value = _.get(data, 'a.b.c');
console.log(value); // 42const nonExistent = _.get(data, 'a.b.d', 'default');
console.log(nonExistent); // 'default'
特点
- 内置默认值支持。
- 适合项目中大量使用时提高代码一致性。
5. 使用 Proxy 动态访问
通过 Proxy
动态访问嵌套属性,可实现更灵活的对象操作。
const data = { a: { b: { c: 42 } } };function createSafeProxy(obj) {return new Proxy(obj, {get(target, prop) {return prop in target ? target[prop] : undefined;}});
}const safeData = createSafeProxy(data.a.b);
console.log(safeData.c); // 42
console.log(safeData.d); // undefined
特点
- 可自定义逻辑,适合复杂需求。
- 性能较递归稍弱,但灵活性高。
9、对 rest 参数的理解
Rest
参数是 JavaScript 中用于表示函数参数的剩余部分的语法。它允许我们将不确定数量的参数聚合为一个数组,从而更方便地处理可变参数的函数。
语法
function func(...args) {// args 是一个数组,包含所有剩余参数
}
特点
- 语法简洁:通过
...
语法,可以捕获所有剩余参数。 - 形成数组:
Rest
参数始终是一个真正的数组,而不是类数组对象。 - 必须是最后一个参数:
Rest
参数在函数参数列表中只能出现在最后。
使用场景
1. 处理不定数量的参数
Rest
参数可以捕获所有未明确列出的参数。
function sum(...nums) {return nums.reduce((total, num) => total + num, 0);
}console.log(sum(1, 2, 3, 4)); // 10
2. 与普通参数结合使用
可以将某些参数单独定义,其余参数用 Rest
捕获。
function greet(greeting, ...names) {return `${greeting} ${names.join(", ")}`;
}console.log(greet("Hello", "Alice", "Bob", "Charlie"));
// Hello Alice, Bob, Charlie
3. 用于替代 arguments
对象
相比 arguments
对象,Rest
参数具有以下优势:
- 是真正的数组,可以直接使用数组方法。
- 只包含明确指定的剩余参数,而
arguments
包括所有参数。
function printArgs(...args) {console.log(args); // [1, 2, 3]
}printArgs(1, 2, 3);
结合解构赋值
Rest
参数可以与解构赋值一起使用。
1. 数组解构中的 Rest
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]
2. 对象解构中的 Rest
const { a, ...rest } = { a: 1, b: 2, c: 3 };
console.log(a); // 1
console.log(rest); // { b: 2, c: 3 }
注意事项
-
Rest 参数只能出现在最后一个位置:
function invalidFunc(a, ...rest, b) { } // SyntaxError
-
与
Spread
运算符不同:Rest
参数是用于收集剩余参数。Spread
运算符是用于展开数组或对象。
// Rest function sum(...args) { return args.reduce((a, b) => a + b); }// Spread const nums = [1, 2, 3]; console.log(sum(...nums)); // 6
总结
- 定义:
Rest
参数将函数剩余参数聚合为一个数组。 - 优势
- 处理不定数量的参数更方便。
- 是真正的数组,可以直接使用数组方法。
- 替代了
arguments
的不足。
- 应用:在可变参数函数、数组/对象解构、动态数据处理中非常实用。
Rest
参数让函数定义更加灵活,提升了代码的简洁性和可维护性。
10、 ES6中模板语法与字符串处理
ES6 引入了模板字面量(Template Literals),大大增强了字符串操作的功能。模板字面量不仅提供了更简洁的字符串拼接方式,还允许多行字符串和嵌入表达式。
1. 模板字面量(Template Literals)
模板字面量使用反引号(```)来定义,可以包含插值和表达式。它解决了传统字符串拼接的繁琐,并支持多行字符串。
基本语法
const name = "Alice";
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, Alice!"
嵌入表达式
可以在 ${}
中插入任意表达式,不仅限于变量。
const a = 5;
const b = 10;
const sum = `The sum of a and b is: ${a + b}`;
console.log(sum); // "The sum of a and b is: 15"
多行字符串
模板字面量允许跨多行编写字符串,无需使用换行符或拼接操作。
const message = `This is line 1
This is line 2
This is line 3`;
console.log(message);
标签模板(Tagged Templates)
标签模板允许通过自定义函数来处理模板字面量的内容。这在进行字符串处理、HTML模板渲染等操作时非常有用。
function tag(strings, ...values) {console.log(strings); // ["Hello, ", "!", " How are you?"]console.log(values); // ["Alice", "Bob"]return `${strings[0]}${values[0]}${strings[1]}${values[1]}${strings[2]}`;
}const result = tag`Hello, ${"Alice"}! How are you, ${"Bob"}?`;
console.log(result); // "Hello, Alice! How are you, Bob?"
2. 字符串处理方法
ES6 引入了许多新的字符串处理方法,使字符串操作更加简洁和高效。
字符串插值
模板字面量允许直接插入变量和表达式。
includes()
判断字符串是否包含某个子字符串。
const str = "Hello, world!";
console.log(str.includes("world")); // true
startsWith()
和 endsWith()
检查字符串是否以某个子字符串开始或结束。
console.log(str.startsWith("Hello")); // true
console.log(str.endsWith("!")); // true
repeat()
重复字符串指定次数。
const str = "Hi!";
console.log(str.repeat(3)); // "Hi!Hi!Hi!"
padStart()
和 padEnd()
在字符串开始或结束处填充指定的字符,直到字符串达到指定长度。
const str = "5";
console.log(str.padStart(3, "0")); // "005"
console.log(str.padEnd(3, "0")); // "500"
normalize()
将字符串标准化为 Unicode 形式,常用于处理字符比较。
const str = 'é';
console.log(str.normalize('NFD')); // 'é'
trim()
和 trimStart()
/ trimEnd()
去除字符串两端的空白字符。
const str = " Hello, world! ";
console.log(str.trim()); // "Hello, world!"
fromCharCode()
和 charCodeAt()
fromCharCode()
:将字符编码转为字符。charCodeAt()
:返回字符的 Unicode 编码。
const char = String.fromCharCode(65); // 'A'
console.log(char); // 'A'const code = char.charCodeAt(0); // 65
console.log(code); // 65
3. 字符串模板与多行支持
ES6 模板字面量解决了多行字符串拼接的问题,不需要使用换行符或连接符。
多行字符串示例
const text = `This is a string
that spans multiple
lines.`;
console.log(text);
字符串拼接
在没有模板字面量之前,拼接多行字符串常常需要使用 +
运算符或者 \n
来进行换行:
const text = "This is a string\n" + "that spans multiple\n" + "lines.";
console.log(text);
4. 常见字符串方法总结
方法 | 说明 |
---|---|
includes() | 检查字符串是否包含子字符串。 |
startsWith() | 检查字符串是否以指定子字符串开始。 |
endsWith() | 检查字符串是否以指定子字符串结束。 |
repeat() | 将字符串重复指定次数。 |
padStart() | 在字符串前面填充指定字符直到达到指定长度。 |
padEnd() | 在字符串后面填充指定字符直到达到指定长度。 |
trim() | 去掉字符串两端的空白字符。 |
normalize() | 标准化字符串为 Unicode 形式。 |
总结
- 模板字面量 提供了更简洁和灵活的方式来处理字符串,支持嵌入表达式、多行字符串等功能。
- 字符串方法 提供了丰富的操作,解决了常见的字符串处理需求,如查找、替换、填充等。
通过模板字面量和新增的字符串方法,ES6 大大提升了字符串处理的便捷性和效率。
JavaScript基础
1、new操作符的实现原理
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象
function objectFactory() {let newObject = null;let constructor = Array.prototype.shift.call(arguments);let result = null;// 判断参数是否是一个函数if (typeof constructor !== "function") {console.error("type error");return;}// 新建一个空对象,对象的原型为构造函数的 prototype 对象newObject = Object.create(constructor.prototype);// 将 this 指向新建对象,并执行函数result = constructor.apply(newObject, arguments);// 判断返回对象let flag = result && (typeof result === "object" || typeof result === "function");// 判断返回结果return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);
2、map和Object的区别
比较维度 | Map | Object |
---|---|---|
数据类型 | Map 是一种内置的数据结构。 | Object 是 JavaScript 的基础数据类型之一。 |
键的类型 | 键可以是任意类型(对象、基本类型)。 | 键只能是字符串或符号(Symbol )。 |
键的顺序 | 按插入顺序存储键值对。 | 键的顺序不一定是插入顺序(特定实现)。 |
初始化方式 | 使用 new Map() 创建。 | 使用字面量 {} 或 new Object() 创建。 |
迭代方式 | 支持直接使用迭代器(如 for...of )。 | 必须使用 for...in 或 Object 方法间接迭代。 |
键值对的存储 | 存储的键值对数量通过 size 属性获取。 | 没有专门的属性获取键值对数量,需要手动计算。 |
性能 | 针对频繁增删键值对优化,性能通常较高。 | 性能可能不如 Map ,尤其是大量键值对时。 |
键值对操作方法 | 提供丰富的 API,如 set() , get() , has() , delete() 。 | 需要手动操作键值对或通过 Object 的方法操作。 |
继承属性的干扰 | 不继承原型链,只有显式添加的键值对存在。 | 会继承原型链上的属性(如 toString )。 |
表示意图 | 更适合用来表示映射关系(key-value 数据结构)。 | 更适合用来表示结构化数据(如对象模型)。 |
是否可以清空 | 通过 clear() 方法快速清空。 | 需要逐个删除属性,或者重新创建对象。 |
Map
:适合需要频繁增删、查询键值对的场景,特别是键的类型多样化时。
Object
:更适合用来存储结构化数据,表示实体对象。
3、map和weakMap
的区别
Map map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。
WeakMap
WeakMap
对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。
比较维度 | Map | WeakMap |
---|---|---|
键的类型 | 键可以是任意类型,包括基本类型和对象。 | 键必须是对象类型(不能是基本类型)。 |
键是否强引用 | 对键的引用是强引用,键不会被垃圾回收机制清除。 | 对键的引用是弱引用,键无其他引用时会被回收。 |
值是否可迭代 | 支持迭代,能通过 for...of 或 entries() 遍历所有键值对。 | 不可迭代,没有暴露遍历方法。 |
存储的稳定性 | 键值对会一直存在,除非显式删除。 | 键对应的对象若被垃圾回收,则键值对会自动移除。 |
用法场景 | 适合存储需要明确控制生命周期的键值对。 | 适合存储临时数据,尤其是与 DOM 节点等弱引用关联时。 |
清空方式 | 提供 clear() 方法,可一次性清空所有键值对。 | 不支持清空,键被垃圾回收后自动移除。 |
适用场景总结
场景 | 使用 Map | 使用 WeakMap |
---|---|---|
需要存储任意类型的键值对 | ✅ | ❌(键只能是对象) |
数据需要遍历或统计 | ✅ | ❌(不可迭代) |
数据需要手动清空 | ✅(clear() 方法) | ❌(自动清除,无法手动清空) |
临时存储对象的关联数据,并避免内存泄漏 | ❌ | ✅(弱引用键自动垃圾回收,适合这种场景) |
Map
:适用于一般的键值对存储,支持任意键类型和遍历。
WeakMap
:适用于临时存储对象相关数据,例如缓存 DOM 节点,避免内存泄漏。
4、 JavaScript有哪些内置对象
JavaScript 提供了多种内置对象,这些对象在语言中扮演着不同的角色,分为以下几类:
1. 基本对象
基本对象是 JavaScript 中的核心对象,其他对象通常由这些对象派生而来。
对象 | 描述 |
---|---|
Object | 所有对象的基础,其他对象均继承自它。 |
Function | 用于定义函数的构造函数,每个函数都是 Function 的实例。 |
Boolean | 表示布尔值的对象(true 和 false )。 |
Symbol | 用于生成唯一的标识符。 |
2. 数值和日期相关
这些对象用于数值运算、日期处理和相关操作。
对象 | 描述 |
---|---|
Number | 表示数值的对象,包含常用的数值方法(如 toFixed 、toExponential )。 |
BigInt | 用于表示任意精度的大整数。 |
Math | 提供数学常量和函数(如 Math.PI ,Math.sqrt )。 |
Date | 用于处理日期和时间。 |
3. 字符串和正则表达式
这些对象用于字符串操作和模式匹配。
对象 | 描述 |
---|---|
String | 表示字符串的对象,提供多种字符串操作方法(如 substring 、indexOf )。 |
RegExp | 表示正则表达式,用于模式匹配和文本搜索替换。 |
4. 集合和键值对
这些对象用于存储和操作集合或键值对。
对象 | 描述 |
---|---|
Map | 键值对集合,键可以是任意类型,按插入顺序存储。 |
WeakMap | 弱引用键值对集合,键必须是对象,支持垃圾回收机制。 |
Set | 值的集合,不允许重复值。 |
WeakSet | 弱引用值的集合,值必须是对象,支持垃圾回收机制。 |
5. 错误对象
用于处理和表示错误。
对象 | 描述 |
---|---|
Error | 所有错误对象的基础对象。 |
TypeError | 表示变量或参数的类型错误。 |
ReferenceError | 表示引用了未定义的变量或对象。 |
SyntaxError | 表示代码中存在语法错误。 |
RangeError | 表示数值超出范围的错误。 |
URIError | 表示 URI 处理函数中的错误。 |
EvalError | 表示全局 eval() 函数的错误(已较少使用)。 |
6. 与迭代相关
这些对象支持或实现迭代协议。
对象 | 描述 |
---|---|
Array | 表示数组对象,提供多种数组操作方法(如 map 、filter 、reduce )。 |
TypedArray | 表示类型化数组,用于操作二进制数据(如 Int8Array 、Uint8Array )。 |
ArrayBuffer | 用于存储二进制数据的固定长度缓冲区。 |
DataView | 提供对 ArrayBuffer 数据的视图和操作。 |
Promise | 表示异步操作的结果,提供 then 和 catch 方法。 |
Iterator | 一种接口,允许对象被迭代(如 for...of 循环)。 |
Generator | 一种函数,返回一个可迭代的 Iterator 对象。 |
7. Web API 和其他内置对象
这些对象通常与浏览器环境相关。
对象 | 描述 |
---|---|
GlobalThis | 全局对象的标准化访问方式,代替不同环境下的全局变量(如 window 或 global )。 |
console | 提供日志和调试功能(如 console.log )。 |
JSON | 用于解析和序列化 JSON 数据。 |
Intl | 提供国际化支持(如格式化日期、时间、货币等)。 |
5.、常用的正则表达式有哪些?
// (1)匹配 16 进制颜色值
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;// (2)匹配日期,如 yyyy-mm-dd 格式
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;// (3)匹配 qq 号
var regex = /^[1-9][0-9]{4,10}$/g;// (4)手机号码正则
var regex = /^1[34578]\d{9}$/g;// (5)用户名正则
var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;
6、 对JSON的理解
JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。
在项目开发中,使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为 JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。
因为 JSON 的语法是基于 js
的,因此很容易将 JSON
和 js
中的对象弄混,但是应该注意的是 JSON 和 js
中的对象不是一回事,JSON
中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN
这样的属性值等,因此大多数的 js
对象是不符合 JSON 对象的格式的。
在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理,
JSON.stringify()
函数,通过传入一个符合 JSON 格式的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不符合 JSON 格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。在前端向后端发送数据时,可以调用这个函数将数据对象转化为 JSON 格式的字符串。JSON.parse()
函数,这个函数用来将 JSON 格式的字符串转换为一个js
数据结构,如果传入的字符串不是标准的 JSON 格式的字符串的话,将会抛出错误。当从后端接收到 JSON 格式的字符串时,可以通过这个方法来将其解析为一个js
数据结构,以此来进行数据的访问。
7、 JavaScript脚本延迟加载的方式有哪些
延迟加载就是等页面加载完成之后再加载 JavaScript 文件。 js 延迟加载有助于提高页面加载速度。
一般有以下几种方式:
- defer 属性: 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
- async 属性: 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
- 动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
- 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
- 让 JS 最后加载: 将
js
脚本放在文档的底部,来使js
脚本尽可能的在最后来加载执行。
8、说一说你对JavaScript类数组对象的理解
在 JavaScript 中,类数组对象(Array-like Object) 是一种类似数组但不完全符合数组特性的对象。
1. 类数组对象的特点
- 有索引属性:像数组一样,包含表示元素位置的数字键(从
0
开始)。 - 有
length
属性:用于表示元素的数量。 - 不是数组实例:类数组对象没有数组的原型方法(如
push
、pop
、map
等)。
2. 常见的类数组对象
1. 函数的 arguments
对象
arguments
是函数内部的内置对象,包含函数调用时传递的所有参数。
function demo() {console.log(arguments); // 类数组对象console.log(arguments.length); // 参数个数console.log(arguments[0]); // 第一个参数
}
demo(10, 20, 30);
// 输出:
// [10, 20, 30]
// 3
// 10
2. DOM 方法返回的结果
许多 DOM 方法返回的结果也是类数组对象,例如 document.querySelectorAll
。
const elements = document.querySelectorAll("div");
console.log(elements); // NodeList,类数组对象
console.log(elements.length); // div 元素的个数
console.log(elements[0]); // 第一个 div 元素
3. 字符串
字符串虽然看起来像数组,但它是类数组对象,支持通过索引访问字符。
const str = "hello";
console.log(str.length); // 5
console.log(str[0]); // "h"
console.log(Array.isArray(str)); // false
4. 自定义对象
你可以手动创建一个类数组对象:
const classArray = {0: "apple",1: "banana",2: "cherry",length: 3
};
console.log(classArray[0]); // "apple"
console.log(classArray.length); // 3
3. 类数组与数组的区别
特性 | 类数组对象 | 数组 |
---|---|---|
原型方法 | 没有数组的原型方法(如 map 、filter 等)。 | 具备数组的所有原型方法。 |
类型检查 | Array.isArray(obj) 返回 false 。 | Array.isArray(obj) 返回 true 。 |
用途 | 通常用作暂存数据结构,例如 arguments 、NodeList 。 | 适合操作数据,支持大量数组操作方法。 |
灵活性 | 需要手动转换为数组以使用数组方法。 | 可直接使用数组方法。 |
4. 类数组对象转为数组
由于类数组对象没有数组的原型方法,通常需要将其转换为数组。以下是常用的方法:
1. 使用 Array.from()
Array.from()
是 ES6 提供的工具,用于将类数组或可迭代对象转换为数组。
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const arr = Array.from(arrayLike);
console.log(arr); // ["a", "b", "c"]
2. 使用扩展运算符 ...
扩展运算符可以将类数组对象转为数组。
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const arr = [...Array.from(arrayLike)];
console.log(arr); // ["a", "b", "c"]
3. 使用 Array.prototype.slice
slice
方法可以间接用于类数组对象,将其转为数组。
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ["a", "b", "c"]
5. 使用场景
- 遍历 DOM 元素:处理
NodeList
等 DOM 操作结果。 - 操作函数参数:对函数中的
arguments
进行数组操作。 - 兼容性:实现某些功能时,通过类数组对象模拟数组。
总结
- 类数组对象具有类似数组的索引和
length
属性,但不具备数组的方法。 - 常见类数组对象包括
arguments
、NodeList
、HTMLCollection
和字符串。 - 为方便操作,通常将类数组对象转换为数组后处理。
9、数组有哪些原生方法?
方法 | 实现思路 | 注意事项 |
---|---|---|
forEach() | 遍历数组的每个元素,并执行提供的回调函数。 | 1. 不可提前终止循环:无法使用 break 或 return 提前退出,若需要提前退出可考虑使用 some() 或 every() 。2. 回调函数的 this 值:可以通过第二个参数 thisArg 来设置 this 的值,若未设置,this 默认为全局对象。 |
map() | 创建一个新数组,将数组每个元素传入回调函数进行处理,返回处理后的新数组。 | 1. 返回新数组:map() 会返回一个新数组,原数组不变。2. 避免副作用:map() 主要用于生成新数组,避免直接修改原数组。如果需要修改原数组,可以使用 forEach() 。 |
filter() | 创建一个新数组,包含所有符合条件的元素。 | 1. 返回新数组:filter() 返回符合条件的元素的新数组,原数组不变。2. 可能返回空数组:如果没有符合条件的元素,返回空数组。 |
reduce() | 将数组的每个元素按顺序传递给回调函数进行累加处理,最终返回一个单一值。 | 1. 初始值问题:没有提供初始值时,reduce() 会使用数组的第一个元素作为初始值。2. 数组为空时的行为:如果数组为空且没有提供初始值,reduce() 会抛出 TypeError 。 |
reduceRight() | 从数组的最后一个元素开始,按顺序传递给回调函数进行累加处理,最终返回一个单一值。 | 1. 初始值问题:如果没有提供初始值,reduceRight() 会将数组的最后一个元素作为初始值。2. 数组为空时的行为:如果数组为空且没有提供初始值,reduceRight() 会抛出 TypeError 。 |
every() | 检查数组中的每个元素是否都符合条件,返回布尔值。 | 1. 返回布尔值:若数组中所有元素都符合条件,返回 true ,否则返回 false 。2. 回调函数的 this 值:可通过 thisArg 参数设置 this 值。 |
some() | 检查数组中是否有至少一个元素符合条件,返回布尔值。 | 1. 返回布尔值:如果数组中有任何元素符合条件,返回 true ,否则返回 false 。2. 回调函数的 this 值:可通过 thisArg 参数设置 this 值。 |
find() | 返回数组中第一个符合条件的元素。如果没有符合条件的元素,返回 undefined 。 | 1. 返回第一个符合条件的元素:如果找不到符合条件的元素,返回 undefined 。2. 回调函数的 this 值:可通过 thisArg 参数设置 this 值。 |
findIndex() | 返回数组中第一个符合条件的元素的索引。如果没有符合条件的元素,返回 -1 。 | 1. 返回第一个符合条件的索引:若没有符合条件的元素,返回 -1 。2. 回调函数的 this 值:可通过 thisArg 参数设置 this 值。 |
includes() | 检查数组中是否包含指定元素,返回布尔值。 | 1. 元素的比较:includes() 使用严格相等(=== )来比较元素。如果数组中存在与目标元素相等的元素,返回 true ,否则返回 false 。2. 性能考虑:当数组较大时,includes() 的性能可能较差,特别是需要查找的元素位置较靠后时。 |
indexOf() | 返回指定元素在数组中首次出现的索引,如果不存在返回 -1 。 | 1. 使用严格相等比较:indexOf() 使用严格相等(=== )来查找元素。2. 返回 -1 :若元素不存在,返回 -1 。 |
lastIndexOf() | 返回指定元素在数组中最后一次出现的索引,如果不存在返回 -1 。 | 1. 从末尾开始查找:lastIndexOf() 从数组的最后一个元素开始查找。2. 返回 -1 :若元素不存在,返回 -1 。 |
join() | 将数组元素连接成一个字符串,元素之间使用指定分隔符。 | 1. 默认分隔符为逗号:如果不指定分隔符,join() 使用逗号(, )作为分隔符。2. 返回字符串:返回的是由数组元素组成的字符串。 |
toString() | 将数组转换为字符串,元素之间用逗号分隔。 | 1. 默认使用逗号分隔:将数组的所有元素转换为字符串并使用逗号连接。2. 性能问题:当数组很大时,toString() 可能会影响性能。 |
sort() | 将数组按指定的排序方式进行排序,默认按字符串进行排序。(快排) | 1. 默认按字符串排序:sort() 默认按元素的字符串顺序排序。如果需要自定义排序顺序,需要传入比较函数。2. 排序的原地修改:sort() 会修改原数组,且排序过程是稳定的(ES6 中稳定排序)。 |
reverse() | 颠倒数组中的元素顺序。 | 1. 原地修改:reverse() 会直接修改原数组。2. 性能考虑:如果需要保持原数组不变,可以先使用 slice() 创建副本。 |
flat() | 将多维数组拍平为一维数组,支持指定拍平的深度。 | 1. 默认深度为 1 :默认将数组拍平一层,可以通过传递 depth 参数来指定拍平深度。2. 处理嵌套数组:如果数组中有深层嵌套的数组,flat() 会根据深度进行递归拍平。 |
splice() | 用于删除、替换、添加数组元素。 | 1. 修改原数组:splice() 会直接修改原数组,不会返回新的数组。2. 删除与插入操作:可以用来同时删除和插入元素。3. 返回删除的元素:返回一个包含被删除元素的新数组。 |
10、Unicode、UTF-8、UTF-16、UTF-32的区别?
特性 | Unicode | UTF-8 | UTF-16 | UTF-32 |
---|---|---|---|---|
定义 | 一种字符编码标准,分配给每个字符一个唯一的代码点(编号)。 | 一种变长编码,用 1 至 4 个字节表示 Unicode 字符。 | 一种变长编码,用 2 或 4 个字节表示 Unicode 字符。 | 一种固定长度编码,用 4 个字节表示 Unicode 字符。 |
字节长度 | 无固定字节表示(标准而非实现)。 | 1 到 4 个字节。 | 2 或 4 个字节(基本多语言平面用 2 字节,补充平面用 4 字节)。 | 固定 4 个字节。 |
编码范围 | 支持 0 至 1,114,111(共约 110 万个)代码点。 | 支持 Unicode 全部字符(0 至 1,114,111)。 | 支持 Unicode 全部字符,但补充平面字符需使用 4 字节。 | 支持 Unicode 全部字符(0 至 1,114,111)。 |
存储效率 | 不涉及具体存储。 | 对 ASCII 字符(0-127)非常高效(1 字节),但对补充平面字符效率较低(4 字节)。 | 对基本多语言平面(BMP)字符高效(2 字节),对补充平面字符需要 4 字节,增加了一些冗余。 | 固定 4 字节存储,空间利用率低,但实现简单。 |
使用场景 | 提供统一的字符集定义,支持多种字符编码方案。 | 常用于文件存储、网络传输等需要节省空间的场景,例如 HTML、JSON、XML。 | 常用于内存中需要较高效率的处理,如 Windows 和 Java 内部字符串表示。 | 常用于简单、直接的编码场景,极少使用(浪费存储空间)。 |
是否包含 BOM | 不涉及具体编码实现。 | 可选 BOM(字节序标记),通常不需要 BOM。 | 可选 BOM,用于区分大小端字节序(Big Endian 或 Little Endian)。 | 通常不需要 BOM,因其字节顺序固定为 4 字节。 |
优点 | 提供一个通用字符集,支持世界上所有语言的字符。 | 节省存储空间,兼容 ASCII,跨平台、跨协议使用广泛。 | 适中长度,较高效处理 BMP 字符,适合大多数现代语言字符。 | 实现简单,直接按 4 字节存储和访问。 |
缺点 | 需要具体编码实现才能用于存储或传输。 | 对非 ASCII 字符(如汉字、Emoji 等)需要较多字节(最多 4 字节)。 | 对补充平面字符(如罕见汉字和 Emoji)存储效率低,需额外处理代理对(surrogate pair)。 | 存储空间浪费严重,通常不适合大规模应用。 |
11、 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
- 历史原因:
- JavaScript 的早期设计中,为了简单地实现函数参数的动态捕获,引入了
arguments
对象。 - 它在内部以对象的形式实现(key 为索引,value 为参数值),以节省性能开销,而不是使用真正的数组。
- JavaScript 的早期设计中,为了简单地实现函数参数的动态捕获,引入了
- 轻量化设计:
- 数组拥有许多原型方法(如
push
、map
等),但arguments
对象只需要存储参数,并不需要其他复杂功能,因此设计为类数组以减少性能开销。
- 数组拥有许多原型方法(如
- 语义性:
arguments
的主要目的是作为函数的动态参数容器,不需要直接具备数组的所有功能。- 如果需要数组功能,开发者可以显式将其转换为数组。
12、什么是 DOM 和 BOM?
DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。
13、对类数组对象的理解,如何转化为数组
对类数组对象的理解
类数组对象 是一种类似数组的数据结构,具有以下特性:
-
定义:
- 它是一个对象,但具有类似数组的结构。通常包含:
- 类似数组的 索引属性(0、1、2…)。
- 一个
length
属性,表示可用的元素数量。
- 它是一个对象,但具有类似数组的结构。通常包含:
-
与数组的区别:
特性 类数组对象 数组 数据结构 对象,按键值对存储 数组,继承自 Array.prototype
原型方法 不具备数组的方法(如 push
、map
)拥有数组方法 示例 {0: 'a', 1: 'b', length: 2}
['a', 'b']
常见实例 arguments
、DOM 集合(如NodeList
)等[]
,开发者创建的数组实例 -
常见的类数组对象:
arguments
对象:函数内部用来存储传递的参数。- DOM 集合:例如
document.querySelectorAll()
返回的NodeList
。 - 自定义对象:手动定义
length
和索引属性的对象。
如何将类数组对象转化为数组
1. 使用 Array.from
(推荐)
Array.from
是 ES6 提供的方法,可以轻松将类数组对象或可迭代对象转换为数组。
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = Array.from(arrayLike);
console.log(arr); // ['a', 'b']
2. 使用扩展运算符 ...
将类数组对象展开为数组元素,然后赋值给数组。
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = [...arrayLike];
console.log(arr); // ['a', 'b']
注意:扩展运算符仅适用于实现了 Symbol.iterator
的对象。如果类数组对象不可迭代,需要先转换。
3. 使用 Array.prototype.slice.call
这是传统方法,通过调用数组方法的方式,借助 call
将类数组对象转换为数组。
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ['a', 'b']
4. 使用 Array.prototype.concat.apply
使用 apply
将类数组对象与空数组连接,效果类似于 slice
。
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = [].concat.apply([], arrayLike);
console.log(arr); // ['a', 'b']
5. 使用 for
循环手动转换
最基础的方法,通过手动遍历索引属性构造数组。
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = [];
for (let i = 0; i < arrayLike.length; i++) {arr.push(arrayLike[i]);
}
console.log(arr); // ['a', 'b']
选择转换方法的建议
- 推荐使用现代方法:
Array.from
或...
(扩展运算符),语义清晰,代码简洁。 - 兼容性需求:在需要支持旧版本浏览器时,可以选择
slice.call
或手动遍历。
总结
类数组对象本质上是一个对象,模拟了数组的结构(索引和 length
),但不具备数组的方法。通过现代 JavaScript 提供的工具(如 Array.from
和扩展运算符),可以轻松将类数组对象转化为真正的数组,从而使用数组的各种方法进行操作。
14、对AJAX的理解,实现一个AJAX请求
AJAX(Asynchronous JavaScript and XML)是一种在网页无需刷新页面的情况下与服务器通信的技术。它是一种技术组合,并不是单一的技术。
核心特点:
- 异步更新:无需重新加载整个页面,仅更新部分内容。
- 技术组合:
- JavaScript:用于操作 DOM 和发送 HTTP 请求。
- XMLHttpRequest 或 Fetch API:发送请求并接收响应。
- (可选)XML/JSON:作为数据传输格式,现代开发多使用 JSON。
- 优势:
- 提高用户体验:页面响应更快。
- 减少带宽消耗:只更新需要的部分。
- 缺点:
- 可能增加开发复杂性。
- 不支持 JavaScript 的浏览器可能无法使用。
function sendAjaxRequest() {// 1. 创建 XMLHttpRequest 对象const xhr = new XMLHttpRequest();// 2. 配置请求方法和 URLxhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1", true);// 3. 注册回调函数,监听响应结果xhr.onreadystatechange = function () {if (xhr.readyState === 4) { // 请求完成if (xhr.status === 200) { // HTTP 状态码 200 表示成功console.log("成功的响应数据:", xhr.responseText);} else {console.error("请求失败,状态码:", xhr.status);}}};// 4. 发送请求xhr.send();
}sendAjaxRequest();
15、JavaScript为什么要进行变量提升,它导致了什么问题?
变量提升(Hoisting) 是 JavaScript 中的一个行为,指的是 在执行代码之前,变量声明(var
、let
、const
)和函数声明会被提升到作用域的顶部。
- JavaScript 作为解释性语言,在执行代码时是从上到下逐行解释的。为了方便开发者,JavaScript 允许在使用变量之前声明它们,从而提高了灵活性。
- 变量提升的设计使得开发者可以在函数或块级作用域内部使用声明在下方的变量,增加了代码书写的灵活性和容错性。
变量提升导致的问题
- 理解困惑和错误:
- 不可预测的行为:由于变量提升发生在代码执行前,开发者可能会对代码的实际执行顺序产生困惑。例如,
var
会导致变量在函数顶部被提前声明,但赋值位置不会被提升,容易导致逻辑错误。 - 暂时性死区(TDZ):对于
let
和const
,尽管它们也会提升,但不能在声明之前访问。未正确理解这一行为的开发者可能会遇到意外的ReferenceError
。
- 不可预测的行为:由于变量提升发生在代码执行前,开发者可能会对代码的实际执行顺序产生困惑。例如,
- 函数表达式的问题:
- 函数声明和函数表达式的差异:函数声明会被提升,但函数表达式不会。这导致一些开发者可能误以为函数表达式会像函数声明一样被提升,从而导致错误的调用顺序。
- 调试困难:
- 当代码中存在多处变量提升时,可能会造成难以追踪的错误,尤其是在大规模的代码中。
解决方法
- 避免使用
var
:使用let
和const
替代var
,它们具有块级作用域,并且能避免变量提升带来的问题。 - 遵循从上到下书写代码的习惯:尽量将变量和函数声明放在使用它们之前,保持代码的清晰和可预测。
- 函数表达式要小心使用:确保在调用函数表达式之前声明它。
16、什么是尾调用?
尾调用(Tail Call) 是指一个函数的最后一步调用另一个函数。当一个函数在它的返回语句中直接调用另一个函数时,就称为尾调用
尾调用的特点是:
- 调用语句必须是函数的 最后一步操作。
- 函数调用的结果直接返回,而没有额外的操作。
尾调用的好处
-
减少栈内存消耗:
- 普通递归调用需要为每一层递归保留调用栈信息(如参数、返回地址)。
- 尾调用可以在执行时 复用当前函数的调用栈,避免多余的栈开销,从而防止 栈溢出。
-
提升性能:
- 尾调用优化(Tail Call Optimization, TCO)可以减少函数调用的开销,使递归调用与普通循环的性能几乎相同。
-
递归更安全:
- 对于深层次的递归调用,尾调用优化可以避免程序因超出栈限制而崩溃。
尾调用的使用条件
-
必须是最后一步调用:
-
调用函数后没有额外的操作,如算术运算或逻辑操作。
-
示例(非尾调用):
return anotherFunction(a) + 1; // 不是尾调用,因为有额外的加法运算
-
-
调用结果直接返回:
- 函数调用必须作为
return
语句的返回值。
- 函数调用必须作为
-
在支持 TCO 的环境中有效:
- 并非所有 JavaScript 引擎都支持尾调用优化(如现代浏览器的大部分环境不支持,但 Node.js 在严格模式下支持)。
17、 ES6模块与**CommonJS
**模块有什么异同?
特性 | ES6模块 | CommonJS模块 |
---|---|---|
语法关键字 | import / export | require / module.exports / exports |
加载时机 | 静态加载:在编译时解析模块的依赖关系(静态分析),模块加载更高效 | 动态加载:模块在运行时加载,无法进行静态分析 |
是否支持动态加载 | 不支持动态加载(但可以结合动态导入 import() 进行) | 支持动态加载,require 可以放在代码的任何地方 |
导入的是值还是引用 | 导入的是变量的引用(变量绑定):值会随被导出模块的变化而变化 | 导入的是对象的副本,导入的值是静态的,导出后不会实时更新 |
作用域 | 严格的模块作用域:每个模块有自己的独立作用域 | 模块作用域同样独立,但依赖运行时加载,易造成耦合 |
兼容性 | 需要 ES6 支持或通过 Babel 等工具转换,浏览器支持度逐渐提高 | Node.js 原生支持,浏览器需要工具(如 Browserify 或 Webpack)打包 |
导出方式 | 支持 命名导出 和 默认导出:可以导出多个成员,也可以指定一个默认导出 | 仅支持导出一个模块对象,可以通过 exports 设置多个成员 |
模块依赖关系 | 静态分析:在编译阶段确定模块依赖关系 | 动态解析:模块依赖关系在运行时才确定 |
性能 | 由于静态加载,ES6 模块优化潜力更高,可实现 Tree Shaking(移除未使用的代码) | 动态加载使得优化较难 |
跨平台支持 | 原生支持浏览器和服务端 | 主要用于 Node.js 环境 |
顶层 this 的值 | 在 ES6 模块中,顶层 this 是 undefined (严格模式) | 在 CommonJS 中,顶层 this 是指向 module.exports |
18、常见的DOM操作有哪些
1)DOM 节点的获取
DOM 节点的获取的API及使用:
getElementById // 按照 id 查询
getElementsByTagName // 按照标签名查询
getElementsByClassName // 按照类名查询
querySelectorAll // 按照 css 选择器查询// 按照 id 查询
var imooc = document.getElementById('imooc') // 查询到 id 为 imooc 的元素
// 按照标签名查询
var pList = document.getElementsByTagName('p') // 查询到标签为 p 的集合
console.log(divList.length)
console.log(divList[0])
// 按照类名查询
var moocList = document.getElementsByClassName('mooc') // 查询到类名为 mooc 的集合
// 按照 css 选择器查询
var pList = document.querySelectorAll('.mooc') // 查询到类名为 mooc 的集合
2)DOM 节点的创建
创建一个新节点,并把它添加到指定节点的后面。 已知的 HTML 结构如下:
<html><head><title>DEMO</title></head><body><div id="container"> <h1 id="title">我是标题</h1></div> </body>
</html>
要求添加一个有内容的 span 节点到 id 为 title 的节点后面,做法就是:
// 首先获取父节点
var container = document.getElementById('container')
// 创建新节点
var targetSpan = document.createElement('span')
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'
// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan)
3)DOM 节点的删除
删除指定的 DOM 节点, 已知的 HTML 结构如下:
<html><head><title>DEMO</title></head><body><div id="container"> <h1 id="title">我是标题</h1></div> </body>
</html>
需要删除 id 为 title 的元素,做法是:
// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)
或者通过子节点数组来完成删除:
// 获取目标元素的父元素var container = document.getElementById('container')// 获取目标元素var targetNode = container.childNodes[1]// 删除目标元素container.removeChild(targetNode)
4)修改 DOM 元素
修改 DOM 元素这个动作可以分很多维度,比如说移动 DOM 元素的位置,修改 DOM 元素的属性等。
将指定的两个 DOM 元素交换位置, 已知的 HTML 结构如下:
<html><head><title>DEMO</title></head><body><div id="container"> <h1 id="title">我是标题</h1><p id="content">我是内容</p></div> </body>
</html>
现在需要调换 title 和 content 的位置,可以考虑 insertBefore
或者 appendChild
:
// 获取父元素
var container = document.getElementById('container') // 获取两个需要被交换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')
// 交换两个元素,把 content 置于 title 前面
container.insertBefore(content, title)
19 use strict是什么意思 ? 使用它区别是什么?
use strict 是一种 ECMAscript5 添加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目的如下:
- 消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
- 消除代码运行的不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 Javascript 做好铺垫。
区别:
- 禁止使用 with 语句。
- 禁止 this 关键字指向全局对象。
- 对象不能有重名的属性
20、for…in和for…of的区别
for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下
- for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
- for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
- 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;
总结: for…in 循环主要是为了遍历对象而生,不适用于遍历数组;for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
21、ajax
、axios
、fetch
的区别
方式 | 概念 |
---|---|
AJAX | Asynchronous JavaScript And XML,基于浏览器内置的 XMLHttpRequest 对象,实现异步 HTTP 请求。 |
Axios | 一个基于 Promise 的现代 HTTP 客户端,支持浏览器和 Node.js,提供更简洁的 API。 |
Fetch | JavaScript 提供的原生接口,用于替代 XMLHttpRequest ,基于 Promise 实现,现代浏览器支持。 |
具体对比
特性 | AJAX | Axios | Fetch |
---|---|---|---|
实现方式 | 基于 XMLHttpRequest | 基于 Promise 封装的库 | 基于 Promise 的原生浏览器 API |
支持的环境 | 仅支持浏览器 | 支持浏览器和 Node.js | 仅支持浏览器 |
Promise 支持 | 不支持,需要手动封装 | 原生支持 Promise | 原生支持 Promise |
语法简洁性 | 较为繁琐,需手动处理回调 | 简洁的 API,支持拦截器和更多扩展 | 相对简洁,但不支持更多功能,需要二次封装 |
拦截器功能 | 不支持 | 支持请求和响应拦截器 | 不支持 |
自动转换 JSON | 需手动解析 JSON 数据 | 自动解析 JSON 数据 | 需手动解析 JSON 数据 |
请求取消 | 不支持 | 支持取消请求(通过 CancelToken ) | 支持(通过 AbortController ) |
超时设置 | 需手动实现 | 支持通过配置超时 | 不支持(需结合 setTimeout 模拟) |
错误处理 | 需手动实现 | 提供更完整的错误信息 | 需手动实现 |
浏览器兼容性 | 支持所有主流浏览器 | 支持所有主流浏览器 | IE 不支持(需使用 polyfill) |
文件上传 | 支持,直接通过 FormData | 支持,直接通过 FormData | 支持,直接通过 FormData |
22、 forEach
和map
方法有什么区别
这方法都是用来遍历数组的,两者区别如下:
- forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
- map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;
原型与原型链
1、说一说你对原型链和原型的理解
1. 原型的概念
在 JavaScript 中,每个对象都有一个与之关联的对象,这个关联对象称为该对象的 原型(prototype)。通过原型,一个对象可以继承另一个对象的属性和方法。
- 每个函数(包括构造函数)都有一个特殊的属性:
prototype
,它指向该函数创建的对象的原型。 - 每个对象都有一个隐式属性(
[[Prototype]]
,在现代 JavaScript 中可以通过__proto__
访问),指向其原型对象。
2. 原型链的概念
原型链是多个对象通过原型关联起来形成的一种结构。它的主要作用是实现 对象的继承。
当访问一个对象的属性时:
- JavaScript 引擎会先检查对象自身是否有该属性。
- 如果没有,会沿着原型链向上查找(通过
__proto__
)。 - 如果最终没有找到,会返回
undefined
。
原型链是一种 逐层向上的查找机制,直到找到顶层的原型对象 Object.prototype
,它的原型为 null
,表示原型链的终点。
核心概念对比
概念 | 定义 | 作用 |
---|---|---|
原型(prototype) | 对象的内部链接,用于定义对象的共享属性和方法。 | 共享属性和方法,减少内存占用 |
原型链 | 对象通过原型关联形成的一种层级链式结构,实现继承机制。 | 让一个对象可以继承多个对象的能力 |
原型与原型链的示例
原型的使用
function Person(name) {this.name = name;
}Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name}`);
};const alice = new Person('Alice');
alice.sayHello(); // 输出: Hello, my name is Alice
Person.prototype
是alice
对象的原型。sayHello
方法是定义在原型上的,所有通过Person
创建的实例都共享此方法。
原型链的查找
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
- 当访问
alice.sayHello
时,JavaScript 首先检查alice
是否有sayHello
属性,如果没有,则沿着原型链查找。 alice
的原型是Person.prototype
,它有sayHello
方法,因此查找成功。
原型链的注意事项
-
性能问题:
- 如果原型链过长,查找属性会消耗更多时间,影响性能。
-
覆盖原型属性:
- 如果对象自身有与原型同名的属性,优先访问对象自身的属性。
alice.sayHello = function() {console.log('This is overridden.'); }; alice.sayHello(); // 输出: This is overridden.
-
修改原型影响所有实例:
- 修改原型上的属性或方法,会立即影响所有共享此原型的对象。
Person.prototype.greet = function() {console.log('Greetings!'); };const bob = new Person('Bob'); bob.greet(); // 输出: Greetings!
-
循环引用问题:
- 如果不小心创建了循环引用,可能导致内存泄漏。
-
hasOwnProperty
检查:- 使用
hasOwnProperty
方法可以区分属性是定义在对象自身还是继承自原型链。
console.log(alice.hasOwnProperty('name')); // true console.log(alice.hasOwnProperty('sayHello')); // false
- 使用
原型链的常见场景
继承实现
通过构造函数与原型链,可以实现继承:
function Animal(name) {this.name = name;
}Animal.prototype.makeSound = function() {console.log(`${this.name} makes a sound.`);
};function Dog(name) {Animal.call(this, name); // 调用父类构造函数
}Dog.prototype = Object.create(Animal.prototype); // 原型链继承
Dog.prototype.constructor = Dog; // 修正 constructorDog.prototype.bark = function() {console.log(`${this.name} barks!`);
};const dog = new Dog('Buddy');
dog.makeSound(); // 输出: Buddy makes a sound.
dog.bark(); // 输出: Buddy barks!
总结
- 原型 是对象继承的基础,它是一个对象,用于存储共享的属性和方法。
- 原型链 是多个对象通过原型关联起来形成的链式结构,用于实现继承。
- 通过原型和原型链,JavaScript 实现了对象的继承和属性的共享,但需要注意性能问题和覆盖属性的副作用。
执行上下文/作用域链/闭包
1、说一下对闭包的理解
它指的是函数能够“记住”并访问它定义时的作用域,即使这个函数在外部执行时,也能访问到这个作用域中的变量。简单来说,闭包是函数与其词法作用域的结合。
闭包的特点
- 函数内部访问外部作用域: 闭包允许函数访问其外部函数的变量,即使外部函数已经执行完毕,内部函数依然能“记住”外部函数中的变量。
- 词法作用域: JavaScript 的作用域是词法作用域,也就是说,函数的作用域在定义时就已经确定,而不是在调用时。
- 延长作用域链: 闭包通过保存作用域链的引用,使得函数即使在外部被调用,仍然能够访问到其外部的变量。
闭包的工作原理
闭包通过一个外部函数返回一个内部函数来实现。当外部函数执行时,它的作用域被保留下来,即使外部函数已经执行结束,内部函数依然可以访问这些变量。
示例
function outer() {let outerVar = 'I am from outer function';function inner() {console.log(outerVar); // 内部函数可以访问外部函数的变量}return inner; // 返回内部函数,形成闭包
}const closureFunc = outer(); // 执行外部函数并返回内部函数
closureFunc(); // 输出: 'I am from outer function'
在这个例子中,inner
是一个闭包,它能够访问 outer
函数中的 outerVar
,即使 outer
函数已经执行完毕。
闭包的常见应用场景
-
数据封装与私有变量: 闭包可以用于创建私有变量,外部无法直接访问这些变量,只能通过暴露的方法来间接操作。
function counter() {let count = 0; // count 是私有变量return {increment: function() {count++;return count;},decrement: function() {count--;return count;},getCount: function() {return count;}}; }const myCounter = counter(); console.log(myCounter.increment()); // 1 console.log(myCounter.getCount()); // 1
这里,
count
变量是封装的,外部无法直接修改它,只能通过提供的increment
、decrement
方法来改变其值。 -
模拟块级作用域: 通过闭包可以模拟块级作用域的行为,避免全局作用域污染。
for (var i = 0; i < 3; i++) {(function(i) {setTimeout(function() {console.log(i); // 闭包确保每次输出正确的值}, 1000);})(i); }
使用闭包确保了
setTimeout
中的回调函数能正确输出每次循环的值。 -
异步操作与回调函数: 在异步编程中,闭包允许我们在异步操作完成时依然可以访问之前的状态。
function fetchData(url) {let data = null;setTimeout(function() {data = 'Fetched data from ' + url;console.log(data);}, 2000);return function() {return data;}; }const getData = fetchData('http://example.com'); setTimeout(function() {console.log(getData()); // 获取到数据 }, 3000);
在这个例子中,闭包使得
getData
函数可以在异步操作完成后依然访问到data
变量。
闭包的优点
- 封装与信息隐藏: 闭包能够将变量封装在函数内部,外部无法直接访问这些变量,只能通过函数提供的接口来访问或修改。
- 持久化状态: 闭包可以在多个函数调用之间保持状态,允许你在不同的时间访问和更新同一个状态。
- 异步编程支持: 闭包在异步编程中非常有用,尤其是在回调函数和定时器等场景中。
闭包的注意事项
- 内存泄漏: 由于闭包会保留对外部变量的引用,长时间存在的闭包可能会导致不再使用的变量无法被垃圾回收器回收,导致内存泄漏。
- 性能问题: 闭包会增加内存消耗,尤其是当闭包保存了大量的数据时。因此需要小心使用,避免性能问题。
- 变量共享问题: 在循环中使用闭包时,如果没有处理好,可能会导致变量共享的问题。通常我们会通过传递参数来解决这个问题。
总结
- 闭包 是函数和它的词法作用域的结合体,允许函数访问并操作外部函数的变量。
- 闭包的应用场景包括 数据封装、私有变量、回调函数、异步操作 等。
- 需要注意的是,闭包也可能导致 内存泄漏 和 性能问题,因此要谨慎使用。
2. 对作用域、作用域链的理解
在 JavaScript 中,作用域和作用域链是管理变量访问、查找和生命周期的重要机制。它们帮助 JavaScript 引擎决定如何处理变量的查找和作用域。
1. 作用域(Scope)
作用域指的是一个变量或函数的可访问范围。在 JavaScript 中,作用域通常分为两类:
- 全局作用域(Global Scope):程序中所有代码都能访问到的作用域。通常,浏览器中的全局作用域是
window
对象。 - 局部作用域(Local Scope):在函数内部定义的变量,只有该函数及其嵌套函数可以访问。
在 JavaScript 中,变量的作用域是由 词法作用域(Lexical Scope)决定的,意味着作用域是在代码编写时(而非运行时)就已确定。
2. 作用域链(Scope Chain)
作用域链是 JavaScript 用来查找变量的机制。当一个变量在当前作用域中无法找到时,JavaScript 引擎会按照作用域链查找父级作用域,直到找到变量或者到达全局作用域为止。如果最终找不到,返回 undefined
。
作用域链 是由嵌套函数的作用域和其父作用域组成的链条。在执行代码时,作用域链决定了变量的查找顺序。
作用域链的工作原理
- 函数创建时作用域链建立: 当函数被调用时,它会创建一个新的执行环境,并与其父执行环境连接,从而形成一个作用域链。
- 查找顺序: JavaScript 引擎首先会在当前的作用域中查找变量。如果没有找到,就会查找其父级作用域,直到到达最外层的作用域(全局作用域)。如果全局作用域也没有该变量,则会返回
undefined
。 - 嵌套作用域: 当函数内部定义了变量或函数时,它们的作用域链会在外部函数的作用域链基础上延续。
例子
let globalVar = 'I am global'; // 全局作用域变量function outerFunction() {let outerVar = 'I am from outer function'; // 外部函数作用域变量function innerFunction() {let innerVar = 'I am from inner function'; // 内部函数作用域变量console.log(innerVar); // 在内层函数中,查找变量优先查当前作用域console.log(outerVar); // 如果在当前作用域找不到,会查找外部函数作用域console.log(globalVar); // 如果外部函数作用域也没有,就查找全局作用域}innerFunction(); // 调用内层函数
}outerFunction();
执行过程
- 在执行
innerFunction
时,首先查找innerVar
,因为它是定义在当前作用域内的变量。 - 如果
innerVar
在当前作用域找不到,JavaScript 会继续查找outerVar
,这是因为outerVar
定义在父作用域(outerFunction
)内。 - 如果
outerVar
也没有找到,JavaScript 引擎会继续向全局作用域查找globalVar
。 globalVar
是全局作用域中的变量,最终会被成功查找到。
作用域链的总结
- 作用域链是由当前作用域及其父级作用域构成的链条,用于确保正确地查找变量。
- 查找变量时,JavaScript 引擎会从当前作用域开始查找,如果找不到会逐级向上查找父作用域,直到全局作用域。
- 词法作用域决定了函数的作用域链,函数在定义时就已经确定了作用域链。
作用域和作用域链的核心要点
- 作用域:定义了变量的可访问范围。
- 作用域链:决定了变量查找的顺序(从当前作用域到父级作用域,最终到达全局作用域)。
- 词法作用域:作用域是在代码编写时(而非运行时)就确定的。
通过作用域和作用域链,JavaScript 能够有效地管理变量、函数的访问范围,避免变量冲突,确保程序的正确执行。
3、对执行上下文的理解
执行上下文是 JavaScript 中代码执行的一个重要概念。它代表了代码执行时的环境,每当函数被调用,或者代码块(如全局代码)被执行时,都会创建一个新的执行上下文。
在执行过程中,JavaScript 引擎需要管理多个执行上下文,确保每一段代码都能在正确的作用域内执行。执行上下文为变量、函数和代码的执行提供了环境,它确定了变量和函数的可访问性、执行顺序和上下文。
执行上下文的类型
- 全局执行上下文(Global Execution Context)
- 这是 JavaScript 代码在全局范围内执行时所处的上下文。
- 全局上下文的
this
指向全局对象(在浏览器中是window
,在 Node.js 中是global
)。 - 在全局上下文中,定义的变量和函数是全局可访问的。
- 函数执行上下文(Function Execution Context)
- 每当函数被调用时,都会为该函数创建一个新的执行上下文。
- 函数上下文中的
this
指向调用该函数的对象。 - 在函数上下文中,函数的参数和局部变量都属于该函数作用域。
- Eval 执行上下文(Eval Execution Context)
eval()
函数执行的代码也会创建一个执行上下文,但这种用法不常见。
执行上下文的创建过程
每个执行上下文的创建过程是一个 2阶段的过程:
1. 创建阶段(Creation Phase)
在此阶段,JavaScript 引擎会做以下几件事情:
- 创建变量对象(Variable Object, VO):为当前上下文创建一个包含所有变量、函数声明和函数参数的对象。
- 建立作用域链(Scope Chain):构建作用域链,确保变量和函数能够根据作用域链进行查找。
- 确定
this
的值:根据函数调用的方式确定this
的指向。
2. 执行阶段(Execution Phase)
在此阶段,JavaScript 引擎会:
- 初始化变量:将变量对象中的变量赋予其初始值。对于函数声明,函数会被提升到作用域的顶部(会创建函数声明并初始化)。
- 执行代码:逐行执行代码,访问变量和函数。
执行上下文栈(Execution Context Stack)
执行上下文是一个栈结构(也称为调用栈),它管理着当前执行的代码。每当一个新的执行上下文被创建时,它会被推入栈中。每当当前执行的代码完成时,它会从栈中弹出。
栈的工作原理:
- 全局执行上下文 被压入栈中,成为第一个执行上下文。
- 当一个函数被调用时,创建一个新的函数执行上下文并压入栈中。
- 当函数执行完毕时,该函数的执行上下文会从栈中弹出,控制流返回到上一层的执行上下文。
执行上下文与栈的示例
var a = 10;function foo() {var b = 20;console.log(a + b); // 30
}foo(); // 函数调用时,创建新的执行上下文
执行过程:
-
全局执行上下文
创建并被推入执行上下文栈:
- 变量
a
被创建并初始化为 10。 - 变量
foo
被创建并初始化为函数foo
。
- 变量
-
调用
foo()
时,创建
函数执行上下文
并推入栈中:
- 变量
b
被创建并初始化为 20。 - 执行
console.log(a + b)
,a
和b
通过作用域链访问到。
- 变量
-
函数执行完成,弹出函数执行上下文,回到全局执行上下文。
执行上下文中的重要概念
- 变量对象(Variable Object, VO):
- 在每个执行上下文中都会创建一个变量对象(VO),它包含了函数的参数、变量声明以及函数声明。
- 对于函数执行上下文,变量对象被称为 活动对象(AO, Activation Object),它包含了该函数作用域内的所有信息。
- 作用域链(Scope Chain):
- 作用域链决定了变量和函数的查找顺序。每个执行上下文都会生成一个作用域链,它由当前上下文和所有外部上下文(直到全局上下文)组成。
this
的值:- 每个执行上下文中都有一个
this
,它指向函数的调用对象或全局对象。this
的值在不同的执行上下文中会有所不同。
- 每个执行上下文中都有一个
总结
- 执行上下文 是 JavaScript 中用于执行代码的环境,它包含了当前代码执行时的变量、函数、作用域和
this
的信息。 - 执行上下文栈 用于管理代码执行的顺序,函数调用时会创建新的执行上下文,完成后弹出。
- 执行上下文分为 全局执行上下文、函数执行上下文 和 eval 执行上下文。
- 执行上下文的创建过程分为两个阶段:创建阶段 和 执行阶段,其中创建阶段负责初始化作用域链、
this
和变量对象,执行阶段逐行执行代码。
this/call/apply/bind
1. 对this对象的理解
对 this
对象的理解
在 JavaScript 中,this
是一个非常重要的概念,它指向当前执行上下文中的“对象”。它是 JavaScript 中的一个关键字,决定了函数执行时访问的上下文对象(即当前执行环境中的对象)。
然而,this
并不是固定的,它的值是动态确定的,取决于函数的调用方式。理解 this
需要掌握它的绑定规则和调用方式。
this
的绑定规则
JavaScript 中的 this
值会根据以下几种情况而变化:
1. 全局作用域中的 this
-
在全局作用域中,
this
指向全局对象。
- 在浏览器中,
this
指向window
对象。 - 在 Node.js 中,
this
指向global
对象。
- 在浏览器中,
console.log(this); // 在浏览器中输出 window 对象
2. 函数调用中的 this
- 普通函数调用:
- 在普通函数调用中,
this
默认指向全局对象(在严格模式下,this
是undefined
)。
- 在普通函数调用中,
function foo() {console.log(this); // 非严格模式下,this 指向全局对象
}
foo(); // 在浏览器中,this 指向 window
- 严格模式下的函数调用:
- 如果函数处于严格模式 (
'use strict';
),this
会是undefined
,而不是全局对象。
- 如果函数处于严格模式 (
'use strict';
function foo() {console.log(this); // 严格模式下,this 是 undefined
}
foo();
3. 对象方法中的 this
- 在对象的方法中,
this
指向调用该方法的对象。
const obj = {name: 'Alice',greet: function() {console.log(this.name);}
};obj.greet(); // 输出 'Alice',this 指向 obj
4. 构造函数中的 this
- 在构造函数中,
this
指向新创建的实例对象。
function Person(name) {this.name = name;
}const person = new Person('John');
console.log(person.name); // 输出 'John'
5. bind
、call
和 apply
的 this
-
- 可以显式地指定函数调用时
call()
和apply()
this
的值。
call()
:接收参数列表,调用函数。apply()
:接收参数数组,调用函数。
function greet() {console.log(this.name);
}const person = { name: 'Bob' };greet.call(person); // 输出 'Bob',this 被显式绑定到 person
greet.apply(person); // 输出 'Bob',this 被显式绑定到 person
bind()
: 创建一个新的函数,将this
绑定到指定对象,并返回该新函数。
const greetBob = greet.bind(person);
greetBob(); // 输出 'Bob'
6. 箭头函数中的 this
- 箭头函数 与普通函数不同,箭头函数没有自己的
this
。它的this
继承自外部上下文(即在箭头函数定义时this
所指向的对象)。
const obj = {name: 'Alice',greet: function() {setTimeout(() => {console.log(this.name); // 箭头函数的 this 继承自 greet 方法的上下文}, 1000);}
};obj.greet(); // 输出 'Alice',this 指向 obj
普通函数的 this
是在调用时确定的,而箭头函数的 this
在定义时就已经确定。
总结
this
的动态绑定:this
的值是根据函数的调用方式决定的,而不是在定义时确定的。- 全局作用域中的
this
:在全局作用域中,this
指向全局对象(window
或global
)。 - 函数调用中的
this
:普通函数中的this
指向全局对象(非严格模式),在严格模式下,this
为undefined
。 - 对象方法中的
this
:在对象的方法中,this
指向该对象。 - 构造函数中的
this
:在构造函数中,this
指向新创建的实例对象。 call()
、apply()
和bind()
:这些方法允许显式绑定this
,可以改变this
的指向。- 箭头函数中的
this
:箭头函数没有自己的this
,它从外部上下文继承this
。
2、call() 、apply()和bind() 的区别
在 JavaScript 中,call()
、apply()
和 bind()
都是用来改变函数的 this
指向的,它们之间的主要区别在于调用时机和参数传递方式。
共同点
- 都可以显式地指定函数内部的
this
。 - 都能将某个对象绑定为函数的上下文环境。
- 区别对比
(1) 调用时机
-
call()
直接调用函数,改变this
指向并立即执行函数。function greet(greeting) {console.log(`${greeting}, ${this.name}`); } const person = { name: "Alice" }; greet.call(person, "Hello"); // 输出: Hello, Alice
-
apply()
和call()
类似,改变this
指向并立即执行函数,但参数以数组形式传递。function greet(greeting) {console.log(`${greeting}, ${this.name}`); } const person = { name: "Alice" }; greet.apply(person, ["Hello"]); // 输出: Hello, Alice
-
bind()
不会立即执行,而是返回一个新函数,绑定的this
在调用时生效。function greet(greeting) {console.log(`${greeting}, ${this.name}`); } const person = { name: "Alice" }; const boundGreet = greet.bind(person, "Hello"); boundGreet(); // 输出: Hello, Alice
(2) 参数传递方式
-
call()
参数逐个传递:function greet(greeting, punctuation) {console.log(`${greeting}, ${this.name}${punctuation}`); } const person = { name: "Alice" }; greet.call(person, "Hello", "!"); // 输出: Hello, Alice!
-
apply()
参数以数组形式传递:function greet(greeting, punctuation) {console.log(`${greeting}, ${this.name}${punctuation}`); } const person = { name: "Alice" }; greet.apply(person, ["Hello", "!"]); // 输出: Hello, Alice!
-
bind()
参数可以部分传递(称为 参数预置),剩余参数在调用时继续传递:function greet(greeting, punctuation) {console.log(`${greeting}, ${this.name}${punctuation}`); } const person = { name: "Alice" }; const boundGreet = greet.bind(person, "Hello"); boundGreet("!"); // 输出: Hello, Alice!
- 主要用途
方法 | 调用时机 | 参数传递形式 | 返回值 | 主要用途 |
---|---|---|---|---|
call | 立即执行 | 参数逐个传递 | 函数执行的结果 | 临时改变 this 并立即调用函数 |
apply | 立即执行 | 参数以数组形式传递 | 函数执行的结果 | 适用于参数以数组形式存储的情况 |
bind | 不立即执行 | 参数可部分预置 | 返回绑定后的新函数 | 创建绑定了特定 this 的新函数 |
- 示例:共同场景
场景 1:借用其他对象的方法
const array = [1, 2, 3];
const max = Math.max.call(null, ...array); // 使用 call
console.log(max); // 输出: 3const maxWithApply = Math.max.apply(null, array); // 使用 apply
console.log(maxWithApply); // 输出: 3
场景 2:函数柯里化
function multiply(a, b) {return a * b;
}
const double = multiply.bind(null, 2); // 使用 bind 预置第一个参数
console.log(double(5)); // 输出: 10
场景 3:事件监听
const button = document.querySelector("button");
function handleClick() {console.log(this.textContent);
}
button.addEventListener("click", handleClick.bind(button)); // 使用 bind 绑定 this
- 总结
call()
和apply()
是即时调用,区别仅在于参数的传递形式。bind()
返回一个绑定了指定this
的新函数,可延迟执行且支持参数预置。
3、实现call、apply 及 bind 函数
1. 实现 call
方法
步骤:
- 检查
context
是否为null
或undefined
,如果是,则将context
设置为全局对象。 - 将调用的函数临时挂载到
context
上,确保this
指向context
。 - 使用展开操作符将参数传递给函数并执行。
- 删除临时挂载的属性。
- 返回函数执行结果。
Function.prototype.myCall = function (context, ...args) {// Step 1: 设置 context 的默认值为全局对象context = context || globalThis;// Step 2: 将当前函数挂载到 context 上const fnKey = Symbol("fn"); // 避免属性冲突context[fnKey] = this;// Step 3: 执行函数const result = context[fnKey](...args);// Step 4: 删除临时属性delete context[fnKey];// Step 5: 返回结果return result;
};
使用示例:
function sayHello(greeting) {console.log(`${greeting}, my name is ${this.name}`);
}const person = { name: "Alice" };
sayHello.myCall(person, "Hello"); // 输出: Hello, my name is Alice
2. 实现 apply
方法
步骤:
- 检查
context
是否为null
或undefined
,如果是,则将context
设置为全局对象。 - 将调用的函数临时挂载到
context
上。 - 检查参数是否为数组或类数组对象,并使用展开操作符传递参数。
- 删除临时挂载的属性。
- 返回函数执行结果。
Function.prototype.myApply = function (context, args) {// Step 1: 设置 context 的默认值为全局对象context = context || globalThis;// Step 2: 将当前函数挂载到 context 上const fnKey = Symbol("fn");context[fnKey] = this;// Step 3: 执行函数并传递参数const result = context[fnKey](...(args || []));// Step 4: 删除临时属性delete context[fnKey];// Step 5: 返回结果return result;
};
使用示例:
function introduce(greeting, punctuation) {console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}const person = { name: "Bob" };
introduce.myApply(person, ["Hi", "!"]); // 输出: Hi, my name is Bob!
3. 实现 bind
方法
步骤:
- 保存当前函数的引用(即
this
)。 - 接受
context
和一组参数,用于预置绑定。 - 返回一个新函数,执行时:
- 如果以普通函数调用,新函数的
this
被绑定到指定的context
。 - 如果以构造函数调用,忽略绑定的
context
,this
指向新创建的实例。
- 如果以普通函数调用,新函数的
- 支持传递剩余参数。
Function.prototype.myBind = function (context, ...args) {const fn = this; // Step 1: 保存当前函数引用// Step 2: 返回一个新函数return function (...newArgs) {// Step 3: 如果是构造函数调用if (this instanceof fn) {return new fn(...args, ...newArgs);}// Step 4: 普通函数调用return fn.call(context, ...args, ...newArgs);};
};
使用示例:
function greet(greeting, punctuation) {console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}const person = { name: "Charlie" };
const boundGreet = greet.myBind(person, "Hello");
boundGreet("!"); // 输出: Hello, my name is Charlie!
异步编程
1. 异步编程的实现方式?
异步编程是现代编程中的一种重要模式,尤其在处理耗时操作(如 I/O、网络请求或定时任务)时。
1. 回调函数(Callback)
最早的异步编程方式,通过将回调函数作为参数传递给另一个函数,任务完成后调用回调函数。
优点
- 简单直接,易于理解。
- 适用于简单的异步任务。
缺点
- 回调地狱(Callback Hell):嵌套过多时代码变得难以维护。
- 不易处理错误和异常。
示例
function fetchData(callback) {setTimeout(() => {callback("Data fetched");}, 1000);
}fetchData((data) => {console.log(data); // 输出: Data fetched
});
2. Promise
Promise
是 ES6 引入的一种异步编程方式,用于解决回调地狱问题。它将异步操作封装为一个对象,并支持链式调用。
优点
- 解决了回调地狱问题。
- 更清晰的错误处理(通过
catch
捕获异常)。
缺点
- 相比回调函数稍显复杂。
- 如果链式操作太多,代码仍可能变得难以维护。
示例
function fetchData() {return new Promise((resolve, reject) => {setTimeout(() => {resolve("Data fetched");}, 1000);});
}fetchData().then((data) => {console.log(data); // 输出: Data fetched}).catch((error) => {console.error(error);});
3. async/await
async/await
是基于 Promise
的语法糖,引入于 ES8。它通过异步函数和 await
关键字使代码看起来像同步代码,从而提高了可读性。
优点
- 代码更加简洁,易读性强。
- 天然支持错误处理(通过
try-catch
)。
缺点
- 与
Promise
一样,需理解其原理。 - 异步任务之间如果没有依赖,可能导致性能降低(因为
await
会阻塞后续代码执行)。
示例
function fetchData() {return new Promise((resolve) => {setTimeout(() => {resolve("Data fetched");}, 1000);});
}async function fetchAndLogData() {try {const data = await fetchData();console.log(data); // 输出: Data fetched} catch (error) {console.error(error);}
}fetchAndLogData();
4. Generator + yield
Generator 是一种迭代器,配合 yield
可以实现异步操作,通过手动或使用库(如 co
)驱动。
优点
- 提供了控制执行流程的能力。
- 在
async/await
之前是异步编程的主要解决方案。
缺点
- 需要配合库(如
co
)使用才能高效管理异步流程。 - 代码复杂度较高,逐渐被
async/await
替代。
示例
function* fetchData() {const data = yield new Promise((resolve) => {setTimeout(() => {resolve("Data fetched");}, 1000);});console.log(data); // 输出: Data fetched
}const generator = fetchData();
const promise = generator.next().value;promise.then((data) => {generator.next(data);
});
5. 基于事件的异步
通过事件触发机制来实现异步操作。例如,Node.js 中的事件监听模式。
优点
- 适用于处理多事件和广播场景。
- 高度灵活。
缺点
- 需要理解事件机制。
- 可能导致代码逻辑分散。
示例
const EventEmitter = require("events");
const emitter = new EventEmitter();emitter.on("dataFetched", (data) => {console.log(data); // 输出: Data fetched
});setTimeout(() => {emitter.emit("dataFetched", "Data fetched");
}, 1000);
6. 基于 Web APIs(如 setTimeout
, setInterval
, fetch
等)
浏览器和 Node.js 提供了一些原生的异步 APIs,例如 setTimeout
、fetch
等,它们是异步编程的底层支持。
示例
console.log("Start");setTimeout(() => {console.log("Async task");
}, 1000);console.log("End");// 输出:
// Start
// End
// Async task
总结
方式 | 特点 | 适用场景 |
---|---|---|
回调函数 | 简单直接,但容易导致回调地狱。 | 简单的异步任务。 |
Promise | 链式调用,解决回调地狱。 | 复杂的异步逻辑,错误处理。 |
async/await | 语法更优雅,易读性强。 | 大多数现代异步编程。 |
Generator | 可控流程,但复杂度较高,需库支持。 | 控制异步流程或对性能要求高的场景 |
事件驱动 | 灵活,适合广播或多事件触发场景。 | Node.js 事件流。 |
Web APIs | 底层支持,例如定时器、网络请求等。 | 浏览器或 Node.js 原生支持。 |
2. setTimeout
、Promise
、Async/Await
的区别
setTimeout
、Promise
和 Async/Await
是 JavaScript 中处理异步操作的三种不同方式,它们的功能和使用场景有所不同。下面我将详细解释它们的区别:
setTimeout
作用
setTimeout
是一个全局函数,用来设置定时器,在指定的延时后执行某个回调函数。
特点
- 非阻塞:
setTimeout
不会阻塞后面的代码,它会在指定的延时后执行指定的回调。 - 仅一次:
setTimeout
是单次定时操作,延时之后执行回调一次。如果需要多次延时,可以使用setInterval
。 - 回调函数:
setTimeout
传入的是一个回调函数,无法直接返回值,且无法链式调用。
示例
console.log('Start');
setTimeout(() => {console.log('Timeout');
}, 1000);
console.log('End');
输出:
Start
End
Timeout
上面代码中,setTimeout
会在 1 秒后打印 "Timeout"
,但是不会阻塞后面的代码执行。
Promise
作用
Promise
是一种用于表示异步操作最终完成或失败的机制,它表示一个异步操作的最终完成(或失败)及其结果值。
特点
-
可以链式调用:
Promise
支持.then()
和.catch()
方法,可以连续处理多个异步操作。 -
三种状态
:
- Pending(待定):初始状态,既不是成功,也不是失败。
- Resolved(已解决):操作成功完成,返回结果。
- Rejected(已拒绝):操作失败,返回错误信息。
示例
console.log('Start');const myPromise = new Promise((resolve, reject) => {setTimeout(() => {resolve('Success');}, 1000);
});myPromise.then(result => {console.log(result); // "Success"
}).catch(error => {console.log(error);
});console.log('End');
输出
Start
End
Success
- 在上面的代码中,
Promise
被用来模拟一个异步操作,并在 1 秒后通过resolve()
返回"Success"
。
Async/Await
作用
async
/await
是基于Promise
的语法糖,用来简化异步代码,使其更像同步代码,易于阅读和维护。
特点
- 简化异步代码:
await
可以使代码停在Promise
的等待状态,直到Promise
完成。 - 更接近同步代码的写法:比
then
/catch
更加简洁,避免回调地狱。 - 只在
async
函数内使用:await
只能在async
函数内使用。
示例
console.log('Start');async function example() {const result = await new Promise((resolve, reject) => {setTimeout(() => {resolve('Success');}, 1000);});console.log(result); // "Success"
}example();console.log('End');
输出
Start
End
Success
- 在这个例子中,
await
会让程序等待Promise
完成后再继续执行。
区别总结
特性 | setTimeout | Promise | Async/Await |
---|---|---|---|
类型 | 函数 | 对象(表示异步操作的最终结果) | 基于 Promise 的语法糖 |
用途 | 设置延时操作 | 管理异步操作的成功/失败,支持链式调用 | 使异步操作代码更简洁、像同步操作一样易读 |
阻塞/非阻塞 | 非阻塞 | 非阻塞 | 非阻塞,和 Promise 配合使用 |
使用场景 | 延时任务、定时器 | 异步操作(例如 HTTP 请求、读取文件等) | 处理复杂的异步操作,简化代码结构和处理错误机制 |
语法 | setTimeout(callback, delay) | new Promise((resolve, reject) => {...}) | async function { await promise } |
返回值 | 无返回值 | 返回一个 Promise 对象 | 返回一个 Promise 对象,简化了 then /catch 用法 |
处理错误 | 使用回调函数中处理错误 | 使用 .catch() 方法处理错误 | 使用 try /catch 语法来捕获异常 |
总结
setTimeout
用于设置定时操作,不涉及复杂的异步管理。Promise
是异步操作的核心机制,适用于异步任务管理、链式调用等场景。Async/Await
是Promise
的语法糖,使得异步代码的写法更加接近同步代码,增强了可读性和可维护性。
3、对Promise的理解
Promise
是 JavaScript 中用于处理异步操作的一种机制,它能够更好地组织和管理异步代码,避免传统回调函数带来的回调地狱(callback hell)。Promise
通过链式调用的方式,使得异步代码变得更加清晰和易于管理。
基本概念
Promise
代表了一个 异步操作的最终完成(或失败) 及其结果值的表示。简而言之,Promise
是一个承诺,它表示某个值可能在将来某个时间点可用,或者可能永远不会可用(如果操作失败)。
Promise 的三种状态
- Pending(等待态):初始状态,表示异步操作尚未完成。
- Fulfilled(已兑现态):异步操作完成,且成功返回了结果。
- Rejected(已拒绝态):异步操作失败,且返回了错误信息。
Promise 的构造函数
Promise
是通过 new Promise()
创建的,构造函数接受一个 executor
函数,这个函数接受两个参数:resolve
和 reject
,分别用于处理成功和失败的情况。
let promise = new Promise((resolve, reject) => {// 异步操作let success = true;if (success) {resolve("操作成功!");} else {reject("操作失败!");}
});
Promise 的方法
then()
then()
方法用于定义 Promise 成功或失败后的回调函数,它返回一个新的 Promise,从而可以进行链式调用。
promise.then((result) => {console.log(result); // "操作成功!"
}).catch((error) => {console.log(error); // "操作失败!"
});
then()
:接收两个函数,第一个函数在resolve
时执行,第二个函数在reject
时执行。catch()
:是对.then(null, rejection)
的简写,用于处理Promise
的失败情况。
catch()
catch()
用于捕获 Promise
的失败情况,相当于 .then(null, rejection)
的简写。
promise.catch((error) => {console.log(error); // 处理拒绝情况
});
finally()
finally()
在 Promise
状态最终决定(无论是成功还是失败)时执行,用于清理操作。它总是会执行,且不管结果是成功还是失败。
promise.finally(() => {console.log("无论成功还是失败,都会执行此回调");
});
Promise.all()
Promise.all()
用于并行处理多个 Promise
,并返回一个新的 Promise
,该 Promise
只有在所有输入的 Promise
都成功时才会成功;如果有一个失败,它就会立即失败。
Promise.all([promise1, promise2, promise3]).then((results) => {console.log(results); // 所有 Promise 都成功时执行}).catch((error) => {console.log(error); // 任何一个 Promise 失败时执行});
Promise.race()
Promise.race()
用于并行处理多个 Promise
,并返回第一个完成的 Promise
的结果。
Promise.race([promise1, promise2, promise3]).then((result) => {console.log(result); // 返回第一个完成的 Promise 的结果}).catch((error) => {console.log(error);});
Promise.allSettled()
Promise.allSettled()
会等待所有的 Promise
执行完毕,不管它们是成功还是失败,最终都会执行。
Promise.allSettled([promise1, promise2, promise3]).then((results) => {console.log(results); // 包含每个 Promise 的状态及结果});
Promise.any()
Promise.any()
返回第一个成功的 Promise
,如果所有 Promise
都失败,返回一个失败的 Promise
。
Promise.any([promise1, promise2, promise3]).then((result) => {console.log(result); // 返回第一个成功的 Promise}).catch((error) => {console.log(error); // 所有 Promise 都失败时执行});
示例
假设你需要执行两个异步操作,一个模拟获取数据,另一个模拟保存数据:
function getData() {return new Promise((resolve, reject) => {setTimeout(() => {resolve("数据获取成功");}, 1000);});
}function saveData(data) {return new Promise((resolve, reject) => {setTimeout(() => {resolve("数据保存成功:" + data);}, 1000);});
}getData().then((data) => {console.log(data); // 数据获取成功return saveData(data); // 将获取的数据传给保存函数}).then((result) => {console.log(result); // 数据保存成功:数据获取成功}).catch((error) => {console.log("发生错误:" + error);});
Promise 的优势
- 更好的错误处理:通过
catch()
可以统一捕获所有异步操作中的错误,不必担心遗漏回调函数中的错误处理。 - 链式调用:可以通过链式调用
then()
来依次处理多个异步操作,使代码更加清晰。 - 并发控制:通过
Promise.all()
、Promise.race()
等方法可以更好地控制多个异步操作的并发执行。
总结
Promise
是现代 JavaScript 异步编程的基石,它提供了一个统一的 API 来管理异步操作。通过 Promise
,我们可以更方便地管理异步代码,并且避免了回调地狱的问题。then()
、catch()
、finally()
和 Promise.all()
等方法使得异步操作变得更加可读和可维护。
4、async/await的优势
async/await
是基于 Promise
的一种语法糖,旨在使异步代码更加简洁、直观,并且接近同步代码的写法。它是现代 JavaScript 异步编程的重要特性,极大地提升了代码的可读性和可维护性。
async/await
的基本概念
async
:表示函数是一个异步函数,返回值是一个Promise
。如果函数中有return
语句,返回的值会被包装成一个Promise
,即使没有return
,也会返回一个已解决的Promise
。await
:只能在async
函数内部使用,用于等待一个Promise
的完成(无论是成功还是失败)。await
会暂停当前函数的执行,直到Promise
被解决,然后继续执行后续代码。
async/await
的优势
-
代码可读性强,接近同步代码
- 在传统的回调函数中,异步操作通常嵌套得很深,导致代码难以阅读和理解。而
async/await
的语法使异步代码的编写更像同步代码,避免了回调地狱。 - 使用
await
可以让异步操作按顺序执行,从而使代码更加直观。
async function fetchData() {try {const user = await fetch('/user'); // 等待异步操作完成const profile = await fetch(`/profile/${user.id}`); // 等待第二个异步操作return profile.json();} catch (error) {console.error('Error fetching data:', error);} }
- 在传统的回调函数中,异步操作通常嵌套得很深,导致代码难以阅读和理解。而
-
异常处理简洁
- 在使用
Promise
时,我们通常使用.then()
和.catch()
来处理成功和失败的情况,这样往往导致链式调用,异常捕获比较复杂。 async/await
使用try/catch
语句来捕获异步操作中的错误,就像同步代码中的错误处理一样。异常捕获的方式更为简洁直观。
async function fetchData() {try {const response = await fetch('/data');const data = await response.json();return data;} catch (error) {console.error('Failed to fetch data:', error); // 错误处理更加简洁} }
- 在使用
-
避免回调地狱
- 在回调函数的传统做法中,异步操作的回调函数通常嵌套在一起,导致代码变得混乱和难以维护。
async/await
能使得异步操作更直观,避免了层层嵌套。
// 回调地狱的代码 fetch('/user', (err, user) => {if (err) return console.error(err);fetch(`/profile/${user.id}`, (err, profile) => {if (err) return console.error(err);console.log(profile);}); });// 使用 async/await 后 async function fetchUserProfile() {try {const user = await fetch('/user');const profile = await fetch(`/profile/${user.id}`);console.log(profile);} catch (error) {console.error('Error:', error);} }
- 在回调函数的传统做法中,异步操作的回调函数通常嵌套在一起,导致代码变得混乱和难以维护。
-
提高错误追踪和调试的效率
- 当代码有问题时,
async/await
让你可以使用同步的调试方式,方便单步执行和错误追踪。相比之下,传统的回调函数中由于异步性质,错误往往出现在回调中,调试起来不容易定位。
- 当代码有问题时,
-
可以与现有的
Promise
兼容async/await
是建立在Promise
基础之上的,因此可以与现有的Promise
代码兼容。这意味着你可以使用async/await
来代替回调函数,或者与Promise
配合使用。
async function fetchData() {const response = await fetch('/data');return await response.json(); }fetchData().then(data => console.log(data)).catch(err => console.error('Error:', err));
-
更好的控制流程
async/await
可以更方便地处理依赖顺序的异步操作。通过await
可以确保异步操作按顺序完成,而不需要像传统Promise
或回调那样手动控制执行顺序。
async function loadData() {const user = await getUserData(); // 确保获取用户数据const posts = await getUserPosts(user.id); // 确保获取与该用户相关的帖子return { user, posts }; }
-
支持并发操作
- 虽然
async/await
使异步代码看起来像同步代码,但它并不意味着它会阻塞其他操作。你可以同时发起多个异步请求,并通过Promise.all()
等方式控制多个异步操作的并发执行。
async function fetchMultipleData() {const userPromise = fetch('/user');const profilePromise = fetch('/profile');const [user, profile] = await Promise.all([userPromise, profilePromise]);console.log(user, profile); }
- 虽然
总结
async/await
是一种更现代和优雅的异步编程方式,它将异步操作的编写方式简化为接近同步代码的形式,提高了代码的可读性和可维护性。相比传统的回调函数和 Promise
,async/await
的优势体现在:
- 代码更加简洁和直观;
- 异常处理更简单;
- 避免回调地狱;
- 更容易调试和追踪错误;
- 可以方便地与现有的
Promise
代码结合使用。
5、对async/await 的理解
async/await其实是Generator
的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await则为等待,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return
一个直接量,async 会把这个直接量通过 Promise.resolve()
封装成 Promise 对象。
async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:then()
链来处理这个 Promise 对象。
6、await 到底在等啥?
await 在等待什么呢? 一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。
因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。
await 表达式的运算结果取决于它等的是什么。
- 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
- 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
面向对象
1、 对象继承的方式有哪些?
(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。
(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
垃圾回收与内存泄漏
1. 浏览器的垃圾回收机制
浏览器的垃圾回收机制主要用于自动管理内存,确保开发者无需手动分配和释放内存。浏览器通过检测内存中的对象是否仍然可访问来判断哪些对象可以被回收。这一机制通常基于垃圾回收算法,以优化性能并避免内存泄漏。
1. 什么是垃圾回收?
垃圾回收(Garbage Collection, GC)是一种自动化内存管理机制。它会回收不再使用或无法访问的内存,以释放资源并提高性能。
垃圾数据的定义
- 无引用的对象:对象不再被引用,无法访问。
- 不可达对象:对象间虽有引用,但从根对象出发无法到达它们。
2. 浏览器垃圾回收的原理
浏览器主要采用两种常见的垃圾回收算法:
(1)标记清除(Mark-and-Sweep)
这是现代浏览器垃圾回收的核心算法。
- 工作原理:
- 垃圾回收器从“根”(如全局对象
window
或当前执行的函数作用域)开始,标记所有可达的对象。 - 如果某对象未被标记,则认为它不可达,可被视为垃圾并清除。
- 清除的内存空间会被回收,供新对象使用。
- 垃圾回收器从“根”(如全局对象
- 示例:
function createObject() {const obj = { key: 'value' };return obj;
}
const a = createObject(); // 'obj' 被引用
a = null; // 'obj' 无法访问,等待回收
(2)引用计数(Reference Counting)
此方法较少单独使用,但在某些场景中仍有效。
-
工作原理:
- 每个对象维护一个引用计数,当引用增加时计数加一,引用减少时计数减一。
- 当引用计数为 0 时,该对象会被回收。
-
问题:循环引用: 引用计数无法处理循环引用的情况。
function circular() {const obj1 = {};const obj2 = {};obj1.ref = obj2;obj2.ref = obj1; } circular(); // 两对象互相引用,引用计数永远大于 0,导致内存泄漏
3. 垃圾回收的触发时机
浏览器不会实时执行垃圾回收,而是在以下时机触发:
- 内存使用达到阈值:当分配的内存超过一定限制时,触发 GC。
- 空闲时回收:浏览器发现空闲时间(如任务完成后),会执行垃圾回收。
- 主动触发:开发者可以通过性能工具或某些浏览器 API 主动触发垃圾回收。
4. 如何避免内存泄漏?
(1)常见内存泄漏场景
-
意外的全局变量: 未声明的变量会被挂载到全局对象上,无法回收。
function example() {leakedVar = 'I am global'; // 未用 `let` 或 `const` 声明 }
-
被遗忘的定时器或事件监听器: 未清除的
setInterval
或未移除的事件监听器会占用内存。const btn = document.getElementById('button'); btn.addEventListener('click', () => console.log('clicked')); btn = null; // btn 仍在事件监听器中被引用,无法回收
-
闭包造成的引用: 闭包可能意外持有对父作用域变量的引用。
function closure() {let obj = { key: 'value' };return () => console.log(obj); } const retainedClosure = closure();
-
DOM 元素未正确清除: 删除 DOM 元素时,仍有脚本对象引用它们。
const div = document.createElement('div'); document.body.appendChild(div); // div 被删除时,未解除其他地方的引用
(2)优化策略
-
手动清理引用: 在适当时机将无用变量置为
null
,显式解除引用。let obj = { key: 'value' }; obj = null; // 提示垃圾回收器回收
-
移除不必要的事件监听器:
const button = document.getElementById('button'); const handleClick = () => console.log('clicked'); button.addEventListener('click', handleClick); button.removeEventListener('click', handleClick); // 解除监听器
-
清理定时器:
const timer = setInterval(() => console.log('running'), 1000); clearInterval(timer); // 停止定时器
-
避免无意的全局变量: 始终使用
let
、const
或var
声明变量。 -
使用弱引用(
WeakMap
或WeakSet
): 如果对象的引用关系是非必要的,弱引用不会阻止垃圾回收。const weakMap = new WeakMap();
5. 垃圾回收的局限性
- 无法实时回收:垃圾回收是一个低优先级任务,可能会延迟执行。
- 高性能损耗:垃圾回收会消耗一定的系统资源,导致短暂的性能下降。
- 无法完全避免内存泄漏:一些不规范的代码仍可能导致内存泄漏。
总结
浏览器的垃圾回收机制通过自动检测并清理不可达的对象,简化了内存管理,减少了开发者的负担。然而,为了提升应用性能,开发者仍需注意合理管理内存,避免内存泄漏和过度占用内存。
相关文章:
JavaScript宝典下
小哆啦闭关修炼已久,潜心攻读专业秘技,方才下山考研本欲大展宏图,怎奈山河虽壮志难酬,终是觉察考研无望。思来想去,不若弃考研之念,重拾敲代码之道,复盘前端奇术,以备闯荡职场江湖。…...
浅谈云计算12 | KVM虚拟化技术
KVM虚拟化技术 一、KVM虚拟化技术基础1.1 KVM虚拟化技术简介1.2 KVM虚拟化技术架构1.2.1 KVM内核模块1.2.2 用户空间工具(QEMU、Libvirt等) 二、KVM虚拟化技术原理2.1 硬件辅助虚拟化2.2 VMCS结构与工作机制 三、KVM虚拟化技术面临的挑战与应对策略3.1 性…...
Spring Boot 动态表操作服务实现
Spring Boot 动态表操作服务实现 Spring Boot 动态表操作服务实现1. 环境配置2. JdbcTemplate 的使用2.1 创建动态表2.2 动态添加字段2.3 动态删除字段2.4 动态修改字段类型2.5 删除表的方法实现 3. 小结3.1 可能的优化 Spring Boot 动态表操作服务实现 在现代的应用开发中&am…...
62_Redis服务器集群优化
Redis集群虽然具备高可用特性,且能实现自动故障恢复,但是如果使用不当,也会存在一些问题,总结如下。 集群完整性问题集群带宽问题数据倾斜问题客户端性能问题命令的集群兼容性问题Lua和事务问题1.集群完整性问题 在 Redis 集群的默认配置下,当节点检测到存在至少一个哈希…...
晨辉面试抽签和评分管理系统之九:随机编排考生的分组(以教师资格考试面试为例)
晨辉面试抽签和评分管理系统(下载地址:www.chenhuisoft.cn)是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…...
Linux Top 命令 load average 指标解读
前言 作为平台开发的同学,维护平台稳定性是我们最基本的工作职责,下面主要介绍下top 命令里 ,load average 这个指标如何去衡量机器负载程度。 概念介绍 load average 是系统在过去 1 分钟、5 分钟、15 分钟 的平均负载,它表示运…...
Nacos: 一个动态服务发现与配置管理平台
Nacos: 一个动态服务发现与配置管理平台 引言 在微服务架构日益普及的今天,服务之间的调用和配置管理变得越来越复杂。为了简化这一过程并提高开发效率,阿里巴巴推出了Nacos——一个易于使用的动态服务发现、配置管理和服务管理平台。 Nacos是什么&am…...
SpringBoot + 事务钩子函数
一、案例背景 拿支付系统相关的业务来举例。在支付系统中,我们需要记录每个账户的资金流水(记录用户A因为哪个操作扣了钱,因为哪个操作加了钱),这样我们才能对每个账户的账做到心中有数,对于支付系统而言&…...
OpenCV相机标定与3D重建(56)估计物体姿态(即旋转和平移)的函数solvePnPRansac()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 使用RANSAC方案从3D-2D点对应关系中找到物体的姿态。 cv::solvePnPRansac 是 OpenCV 中用于估计物体姿态(即旋转和平移)的…...
【JVM中的三色标记法是什么?】
JVM中的三色标记法是什么? 一、基本概念二、标记过程三、优势与问题四、漏标与多标的解决方案三色标记法(Tri-color Marking Algorithm)是Java虚拟机(JVM)中一种用于追踪对象存活状态的垃圾回收算法。 它基于William D. Hana和Mark S. McCulleghan在1976年提出的两色标记法…...
从0开始学习搭网站第二天
前言:今天比较惭愧,中午打铲吃了一把,看着也到钻二了,干脆顺手把这个赛季的大师上了,于是乎一直到网上才开始工作,同样,今天的学习内容大多来自mdn社区mdn 目录 怎么把文件上传到web服务器采用S…...
43.Textbox的数据绑定 C#例子 WPF例子
固定最简步骤,包括 XAML: 题头里引入命名空间 标题下面引入类 box和block绑定属性 C#: 通知的类,及对应固定的任务 引入字段 引入属性 属性双触发,其中一个更新block的属性 block>指向box的属性 从Textbo…...
钉钉实现第三方登录示例(重复回调问题解析)
钉钉作为专门为企业打造的沟通协助平台,包含的功能很多,考勤打卡,审批,日记,钉盘,钉邮等。基本满足了一些中小企业的大部分工作需求。因此对接钉钉的一些功能模块业务需求在开发中也是比较常见的。钉钉的开…...
Vue2+OpenLayers添加/删除点、点击事件功能实现(提供Gitee源码)
目录 一、案例截图 二、安装OpenLayers库 三、安装Element-UI 四、代码实现 4.1、添加一个点 4.2、删除所有点 4.3、根据经纬度删除点 4.4、给点添加点击事件 4.5、完整代码 五、Gitee源码 一、案例截图 可以新增/删除标记点,点击标记点可以获取到当前标…...
算法妙妙屋-------2..回溯的奇妙律动
回溯算法是一种用于系统性地搜索和解决问题的算法,它以深度优先搜索(DFS)为基础,用来探索所有可能的解决方案。通过递归地尝试候选解并在必要时回退(即“回溯”),它能够高效地解决许多涉及组合、…...
pytest-instafail:让测试失败信息即时反馈
pytest-instafail:让测试失败信息即时反馈 前言一、简介二、优势三、安装与使用3.1 未安装时运行情况3.2 安装3.3 已安装时运行情况3.3 pytest.ini 配置选项 四、对比 总结 前言 当测试用例数量庞大时,定位测试失败的原因往往耗时费力。此时,…...
K8S--配置存活、就绪和启动探针
目录 1 本人基础环境2 目的3 存活、就绪和启动探针介绍3.1 存活探针3.2 就绪探针3.3 启动探针 4 探针使用场景4.1 存活探针4.2 就绪探针4.3 启动探针 5 配置存活、就绪和启动探针5.1 定义存活探针5.2 定义一个存活态 HTTP 请求接口5.3 定义 TCP 的就绪探针、存活探测5.4 定义 g…...
solidity基础 -- 枚举
在智能合约开发领域,Solidity语言因其简洁高效而被广泛使用。其中,枚举(enum)作为一种特殊的数据类型,为合约的状态管理提供了极大的便利。本文将通过一个具体的Solidity合约示例,深入探讨枚举的定义、使用…...
重回C语言之老兵重装上阵(六)枚举
1. 什么是枚举 (enum)? 枚举(enum)是 C 语言中的一种数据类型,用于定义一组具名的整数常量。它可以使代码更加可读,帮助程序员更容易理解程序中的常量值。通过枚举,程序员可以使用有意义的名称来代替数字&…...
python+playwright自动化测试(一):安装及简单使用,截图录屏
目录 基本使用 浏览器调用 启用浏览器 创建窗口对象 访问URL 页面的刷新、返回、前进 关闭 截图、录屏、保存pdf 截图 录屏 保存为pdf 设置窗口大小 调试模式 手机模式及new_context的更多参数 手机模式 new_context的其他参数 设置语言和时区 设置和修改位置…...
Mysql--架构篇--体系结构(连接层,SQL层,存储引擎层,文件存储层)
MySQL是一种广泛使用的关系型数据库管理系统(RDBMS),其体系结构设计旨在提供高效的数据存储、查询处理和事务管理。MySQL的体系结构可以分为多个层次,每个层次负责不同的功能模块。 MySQL的体系结构主要由以下几个部分组成&#…...
git merge 压缩提交
在 Git 中,执行 git merge 时可以通过一些操作来“压缩”提交,通常是指将合并过程中的多个提交压缩成一个单一的提交。这可以通过使用 --squash 选项来完成,或者在合并后进行交互式 rebase。以下是两种常见的方法: 方法 1&#x…...
Python脚本自动发送电子邮件
要编写一个Python脚本来自动发送电子邮件,你可以使用smtplib库来处理SMTP协议,以及email库来构建邮件内容。 安装必要的库 通常情况下,smtplib和email库是Python标准库的一部分,因此不需要额外安装。如果你使用的是较旧的Python版…...
uniapp中rpx和upx的区别
在 UniApp 中,rpx 和 upx 是两种不同的单位,它们的主要区别在于适用的场景和计算方式。 ### rpx(Responsive Pixel) - **适用场景**:rpx 是一种响应式单位,主要用于小程序和移动端的布局。 - **计算方式**…...
CentOS 9 Stream 中查看 Python 版本并升级 Python
CentOS 9 Stream 中查看 Python 版本并升级 Python 1. 查看当前 Python 版本2. 升级 Python 版本(1)安装开发工具(2)安装必要的依赖包(3)下载和安装新版本的 Python(4)验证安装 3. …...
可以用于分割字符串的方法(python)
一、str.split(sep,maxsplit)函数(返回列表) sep:分隔符 maxsplit:分割次数 a"Hello world" list1a.split(" ",1) print(list1) 结果: [Hello, world] 二、str.rsplit(sep,maxsplit)函数&…...
【Vue】全局/局部组件使用流程(Vue2为例)
全局组件和局部组件区别 如何使用 全局组件:全局注册后,可以在任意页面中直接使用。局部组件:在页面中需要先导入子组件路径,注册组件才能使用。 适用场景 全局组件:适用于高频使用的组件,如导航栏、业…...
virtual box虚拟机误删Python3.6后导致UBUNTU18.04开机无UI界面(进不了desktop)的解决方法
最近在解决一个python引起的问题的时候,作者心一狠,删了系统自带的python3.6, 顺便还删了python3。导致重启后ubuntu的virtual box虚拟机无法看到UI登录界面,只给我了孤零零的命令行。装了很多东西不可能重装,无奈只能…...
虚拟线程JDK与Spring Core Reactor
两种虚拟线程对比:JDK vs. Spring Core Reactor性能对比 1、基于 JDK 的虚拟线程实现: 摘自实际代码: public static void withFlatMapUsingJDK() { ... var virtualThreadExecutor Executors.newThreadPerTaskExecutor( Thread .ofVirtual…...
纯 Python、Django、FastAPI、Flask、Pyramid、Jupyter、dbt 解析和差异分析
一、纯 Python 1.1 基础概念 Python 是一种高级、通用、解释型的编程语言,以其简洁易读的语法和丰富的标准库而闻名。“纯 Python” 在这里指的是不依赖特定的 Web 框架或数据分析工具,仅使用 Python 原生的功能和标准库来开发应用程序或执行任务。 1.…...
C++ NULL和nullptr
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码: #ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif 如上是条件编译的宏定义 确保在不同编程环境下正确处理NULL的定义 C中NULL可能被定义…...
算法日记1:洛谷p2678跳石头(二分答案)
1、题目 二、题解: 2.1解题思路: 1.题目要求求出最小值最大,明显的二分答案题目,所以我们可以二分可以跳跃距离int l-1,rL1; 2.此时我们思考lmid和rmid的处理,当我们的check(mid)为true时候 表明我们此时的mid是符合要求的, 那么…...
PID控制器 (Proportional-Integral-Derivative Controller) 算法详解及案例分析
PID控制器 (Proportional-Integral-Derivative Controller) 算法详解及案例分析 目录 PID控制器 (Proportional-Integral-Derivative Controller) 算法详解及案例分析1. 引言2. PID控制器的基本概念2.1 PID控制器的定义2.2 PID控制器的核心思想2.3 PID控制器的应用领域 3. PID控…...
Vue中nextTick实现原理
源码实现思路(面试高分回答) 面试官问我 Vue 的 nextTick 原理是怎么实现的,我这样回答: 在调用 this.$nextTick(cb) 之前: 存在一个 callbacks 数组,用于存放所有的 cb 回调函数。存在一个 flushCallbac…...
【MATLAB】subplot如何增加title
在 Matlab 中,使用 subplot 函数将图形窗口划分为多个子图,并使用 title 函数为每个子图添加标题。以下是一个示例: matlab % 生成示例数据 x 0:0.1:10; y1 sin(x); y2 cos(x); % 创建一个 2 行 1 列的子图布局,并选…...
vue3+ts的<img :src=““ >写法
vue3ts的<img :src"" >写法<img :src"datasetImage" alt"数据分布示意图" /><script setup lang"ts">const datasetImage ref();datasetImage.value new URL(../../../assets/images/login-background.jpg, impo…...
Vue+Echarts+百度地图 实现 路径规划
实现功能: 通过选择 相关调拨,系统自动规划 路径,并且以地图的形式呈现最佳路径 技术难点: 1. vue 结合使用 echarts 2.echarts 在 vue嵌入百度地图,并且做出路径 曲线 最终结果:...
uniapp 小程序 textarea 层级穿透,聚焦光标位置错误怎么办?
前言 在开发微信小程序时,使用 textarea 组件可能会遇到一些棘手的问题。最近我在使用 uniapp 开发微信小程序时,就遇到了两个非常令人头疼的问题: 层级穿透:由于 textarea 是原生组件,任何元素都无法遮盖住它。当其…...
IGP协议的双点双向注入(路由引入)
一、拓扑环境 二、单向注入 以上拓扑为例,单点注入存在路由回包问题 在AR5上注入直连路由 55.5.5.5 需求:AR1上的10.1.1.1 需访问AR5上的55.5.5.5 1、在AR2和AR3上查看注入的55.5.5.5的路由信息 2、现在边界设备以学习到目的网段的路由信息࿰…...
【React】新建React项目
目录 create-react-app基础运用React核心依赖React 核心思想:数据驱动React 采用 MVC体系package.jsonindex.html好书推荐 官方提供了快速构建React 项目的脚手架: create-react-app ,目前使用它安装默认是19版本,我们这里降为18…...
“AI 自动化效能评估系统:开启企业高效发展新征程
在当今数字化飞速发展的时代,企业面临着日益激烈的市场竞争,如何提升效率、降低成本成为了企业生存与发展的关键。AI 自动化效能评估系统应运而生,它如同一把智能钥匙,为企业开启了高效发展的新征程。 AI 自动化效能评估系统&…...
免 root 开启 Pixel 手机 VoLTE 功能
部分运营商需要开启 VoLTE 功能才可以正常通话和接收短信,但是默认情况下,Pixel 是无法开启的,需要我们手动开启一下。经过网友的确认,这种方法还适用于荣耀 MAGIC 等其他品牌的手机。 具体流程如下: 1.打开开发者选…...
华为2024嵌入式研发面试题
01 你认为最好的排序算法是什么? 在实际的编程中,最好的排序算法要根据实际需求和数据规模来选择,因为每种排序算法都有其优势和劣势。以下是一些常见排序算法及其优缺点: 冒泡排序 冒泡排序是一种简单直观的排序算法࿰…...
Android Room 报错:too many SQL variables (code 1 SQLITE_ERROR) 原因及解决方法
报错信息: android.database.sqlite.SQLiteException: too many SQL variables (code 1 SQLITE_ERROR): while compiling: SELECT * FROM points WHERE id IN (?,?,?,...,?,?,?)SQLiteException: too many SQL variables 通常是由于一次查询或插入的 SQL 语句…...
PHP 字符串
PHP 字符串 引言 在 PHP 中,字符串是一种非常基础且重要的数据类型。字符串可以包含字母、数字、标点符号以及特殊字符。PHP 提供了丰富的字符串函数,使得字符串操作变得简单而高效。本文将详细介绍 PHP 中字符串的常用操作,包括字符串的创…...
Android15源码编译问题处理
最近想在Raspberry Pi5上面运行自己编译的Android15镜像,参考如下链接来处理: GitHub - raspberry-vanilla/android_local_manifest GitHub - raspberry-vanilla/android_kernel_manifest 代码同步完后,编译就出问题了,总是提示: FAILED: analyzing Android.bp files and…...
Transformer架构和Transformers 库和Hugging Face
Transformer架构 和 Hugging Face 之间的关系非常紧密,Hugging Face 是推动 Transformer 架构普及和应用的重要力量。以下是两者的关系及其具体联系: 1. Transformer 架构 背景: Transformer 是由 Google 在 2017 年提出的革命性架构,基于自…...
【机器学习 | 数据挖掘】离群点检测
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘,以提取有价值的信息和洞察。它结合了大数据技术、人工智能(AI)、机器学习(ML&a…...
【极速版 -- 大模型入门到进阶】除了 Prompting, 大模型还能如何被应用?
文章目录 大模型应用 -- Generative AI Projects🌊 大模型应用的时效优势🌊 大模型应用的方式 - Technology Options应用方式一 🐟 Prompting:最简单快速应用方式二🐟 Retrieval augmented generation (RAG)࿱…...
[Unity]发包前遇到的坑之GridLayoutGroup
发包前禁用了UI上面一个调试页面A后,发现无法正确获取某一个用了GridLayoutGroup组件的所有子物体的世界坐标。 一顿研究之后发现,在Start的时候想要正确获取其坐标,需要强制刷新一次布局,方法如下: UnityEngine.U…...