文章目录
- 一、数据类型
-
- [1. JavaScript有哪些数据类型,它们的区别?](#1. JavaScript有哪些数据类型,它们的区别?)
-
- 1.1.null和undefined区别
- [1.2. typeof null 的结果是什么,为什么?](#1.2. typeof null 的结果是什么,为什么?)
- 2.NaN
-
- [2.1.typeof NaN 的结果是什么?](#2.1.typeof NaN 的结果是什么?)
- [2.2.isNaN 和 Number.isNaN 函数的区别?](#2.2.isNaN 和 Number.isNaN 函数的区别?)
- 3.判断数据类型方式
-
- [3.1.intanceof 操作符的实现原理及实现](#3.1.intanceof 操作符的实现原理及实现)
- [4. 判断数组的方式有哪些](#4. 判断数组的方式有哪些)
- [4. 为什么0.1+0.2 ! == 0.3,如何让其相等](#4. 为什么0.1+0.2 ! == 0.3,如何让其相等)
- [5. 如何获取安全的 undefined 值?](#5. 如何获取安全的 undefined 值?)
- 6.js类型转换规则
-
- [6.1. `==` 操作符的强制类型转换规则?及与 `===`、`Object.is()`的区别?](#6.1.
==操作符的强制类型转换规则?及与===、Object.is()的区别?)
- [6.1. `==` 操作符的强制类型转换规则?及与 `===`、`Object.is()`的区别?](#6.1.
- [7. || 和 && 操作符的返回值?](#7. || 和 && 操作符的返回值?)
- [9. 什么是 JavaScript 中的包装类型?](#9. 什么是 JavaScript 中的包装类型?)
- [10. 为什么会有BigInt的提案?](#10. 为什么会有BigInt的提案?)
- [11. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别](#11. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别)
- [12. 如何判断一个对象是空对象](#12. 如何判断一个对象是空对象)
- 二、ES6
-
- [1. ES6常用新特性](#1. ES6常用新特性)
- 1.let、const、var的区别
- 2、箭头函数及和普通函数的区别
- 3.模板字符串
- [4.函数参数增强:默认参数 + 剩余参数](#4.函数参数增强:默认参数 + 剩余参数)
- 5.对象、数组的解构
- [6.数组 / 对象扩展:扩展运算符(...)(浅拷贝)](#6.数组 / 对象扩展:扩展运算符(...)(浅拷贝))
- [7. 面向对象:Class](#7. 面向对象:Class)
- [8. 模块化:import /export](#8. 模块化:import /export)
- [9. 新数据结构:Set / Map](#9. 新数据结构:Set / Map)
-
- 9.1.Map和Object的区别
- [9.2. weakSet和Set区别](#9.2. weakSet和Set区别)
- 9.3.weakMap和Map区别
- [10. 数组、字符串方法](#10. 数组、字符串方法)
-
- [10.1. 字符串](#10.1. 字符串)
- [10.2. 数组](#10.2. 数组)
- [11. Symbol](#11. Symbol)
- 12、for...in(es5)、for...of(es6)
- [13. Proxy 可以实现什么功能?](#13. Proxy 可以实现什么功能?)
- [14. 如何提取高度嵌套的对象里的指定属性?](#14. 如何提取高度嵌套的对象里的指定属性?)
- 三、异步编程
-
- [1. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?](#1. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?)
- 2、Promise(解决回调地狱)
- 3.async/await(es8)
- [4. async/await对比Promise的区别](#4. async/await对比Promise的区别)
- [5、generator. ??](#5、generator. ??)
- [6. 并发与并行的区别?](#6. 并发与并行的区别?)
- [7. setTimeout、setInterval、requestAnimationFrame 各有什么特点?](#7. setTimeout、setInterval、requestAnimationFrame 各有什么特点?)
- [8、事件循环 EventLoop ?](#8、事件循环 EventLoop ?)
- 四、JavaScript基础
-
- [1. new操作符的实现原理](#1. new操作符的实现原理)
- [2. js有哪些内置对象](#2. js有哪些内置对象)
- [3. 对JSON的理解](#3. 对JSON的理解)
- [4. js脚本`<script>`延迟加载的方式有哪些?](#4. js脚本
<script>延迟加载的方式有哪些?) - [5. js 类数组对象的定义?](#5. js 类数组对象的定义?)
-
- [5.1.为什么函数的 arguments 参数是类数组而不是数组?](#5.1.为什么函数的 arguments 参数是类数组而不是数组?)
- 5.2.如何转化为数组?
- 5.3.如何遍历类数组?
- [6. Unicode、UTF-8、UTF-16、UTF-32的区别?](#6. Unicode、UTF-8、UTF-16、UTF-32的区别?)
- [7. 常见的位运算符有哪些?其计算规则是什么?](#7. 常见的位运算符有哪些?其计算规则是什么?)
- [8. 什么是 DOM 和 BOM?](#8. 什么是 DOM 和 BOM?)
- [9. escape、encodeURI、encodeURIComponent 的区别](#9. escape、encodeURI、encodeURIComponent 的区别)
- [10. JavaScript为什么要进行变量提升,它导致了什么问题?](#10. JavaScript为什么要进行变量提升,它导致了什么问题?)
- [11. 什么是尾调用,使用尾调用有什么好处?](#11. 什么是尾调用,使用尾调用有什么好处?)
- [12. use strict是什么意思 ? 使用它区别是什么?](#12. use strict是什么意思 ? 使用它区别是什么?)
- [13. 如何判断一个对象是否属于某个类?](#13. 如何判断一个对象是否属于某个类?)
- [14. 对AJAX的理解,实现一个AJAX请求](#14. 对AJAX的理解,实现一个AJAX请求)
- [15. ajax、axios、fetch的区别](#15. ajax、axios、fetch的区别)
- [五、原型与原型链 ??](#五、原型与原型链 ??)
-
- [41. 对原型、原型链的理解](#41. 对原型、原型链的理解)
- [42. 原型修改、重写](#42. 原型修改、重写)
- [43. 原型链指向](#43. 原型链指向)
- [44. 原型链的终点是什么?如何打印出原型链的终点?](#44. 原型链的终点是什么?如何打印出原型链的终点?)
- [45. 如何获得对象非原型链上的属性?](#45. 如何获得对象非原型链上的属性?)
- 六、执行上下文/作用域链/闭包
-
- [1. 对闭包的理解](#1. 对闭包的理解)
-
- [1.1 .使用场景四、防抖与节流](#1.1 .使用场景四、防抖与节流)
- [2. 对作用域、作用域链的理解](#2. 对作用域、作用域链的理解)
- [3. 对执行上下文的理解](#3. 对执行上下文的理解)
- 七、this/call/apply/bind
-
- [1. 对this对象的理解](#1. 对this对象的理解)
- [2. call() 和 apply() 的区别?](#2. call() 和 apply() 的区别?)
- [3. 实现call、apply 及 bind 函数](#3. 实现call、apply 及 bind 函数)
- [连续多个 bind,最后this指向是什么?](#连续多个 bind,最后this指向是什么?)
- 八、面向对象
-
- [1. 对象创建的方式有哪些?](#1. 对象创建的方式有哪些?)
- [2. 对象继承的方式有哪些?](#2. 对象继承的方式有哪些?)
- 九、垃圾回收与内存泄漏
-
- [1. 浏览器的垃圾回收机制](#1. 浏览器的垃圾回收机制)
- [2. 哪些情况会导致内存泄漏](#2. 哪些情况会导致内存泄漏)
一、数据类型
1. JavaScript有哪些数据类型,它们的区别?
总结: 7种基本数据类型 (Undefined、Null、Boolean、Number、String、Symbol、BigInt)+ 1种引用数据类型(Object)
区别:
- 存储位置不同: 基本 (值本身存储在栈中);引用(地址存储在栈中,值存储在堆中)
- 可变性: 基本 (不可变。修改,本质是创建了新的值,而非修改原有的值);引用(可变)
- 比较方式: 基本 (按值比较);引用(按引用比较,比较地址)
- 复制方式: 基本 (复制后会创建一个新的独立值);引用(复制的是地址引用,指向同一个对象)
其中Symbol 和 BigInt 是ES6 中新增的数据类型:
- Symbol(ES6):唯一且不可变的标识符,用于对象属性名。
- BigInt(ES2020):任意精度整数,后缀需加 n,如 9007199254740993n。
1.1.null和undefined区别
总结: 都是基本数据类型 ;undefined表示未定义 ,如声明了还没定义会返回它;null表示空对象 ,通常用来初始化,赋值给会返回对象的变量。
相同点: undefined 和 null 都是基本数据类型
不同点
undefined代表的含义是未定义 ;null代表的含义是空对象- 变量声明了但还没有定义 的时候会返回
undefined,null主要用于赋值 给一些可能会返回对象的变量,作为初始化。 - 使用 typeof 进行判断时,Null 类型化会返回 "object"
1.2. typeof null 的结果是什么,为什么?
总结: null使用typeof判断时,会返回object,因为对象类型的二进制标识符是000,而null被认为是一个空指针,即零地址(值全是0),所以会被错误的标识为object
结果是"object"
javascript的值是二进制数据 ,用低位表示数据类型 。
对象的类型 标识符 是000 ,null被认为是一个空指针,即零地址,值全是0 ,所以被错误的标识为object
javascript
000: object - 当前存储的数据指向一个对象。
1: int - 当前存储的数据是一个 31 位的有符号整数。
010: double - 当前存储的数据指向一个双精度的浮点数。
100: string - 当前存储的数据指向一个字符串。
110: boolean - 当前存储的数据是布尔值。
2.NaN
NaN(Not-a-Number)是 js 中被视为数字类型的一个特殊值 ,表示无法计算出合法的数值
特点:
typeof NaN仍然是number,尽管它表示"不是数字"。NaN与任何值都不相等,包括它自身 ,即 NaN !== NaN。
-isNaN(NaN)返回true
2.1.typeof NaN 的结果是什么?
总结: NaN意思不是一个数字 ,用来标识数学运算的结果 是非法 的或者未定义 的。在js中它被视为数字类型里 的一个特殊值 。所以typeof NaN 的结果是number。
NaN 表示 "Not a Number",用于表示非法的或未定义的数学运算结果。
typeof NaN 的结果是 "number" ,因为 NaN 在 js 中被视为数字类型的一个特殊值。
javascript
typeof NaN; // "number"
//不用typeof判断,用isNaN判断
console.log(isNaN(123)) // 输出 false,因为 123 可以转换成数值
console.log(isNaN(NaN)) // 输出 true,因为 NaN 是 NaN
2.2.isNaN 和 Number.isNaN 函数的区别?
总结: 两者都是用来判断数据是否是NaN数据类型 的。区别在isNaN 判断之前会先将值转成数值 再去判断,这就导致一些不是NaN的值也被误判为NaN ;而Number.isNaN不转类型 ,只有参数本身是NaN 时才会返回 true。
isNaN(value)是 js 提供的全局方法。- 作用:将传入的值转换为数值,然后检查该数值是否为 NaN
- 注意点:它会在检查前 尝试将参数转换为数值 ,导致一些非 NaN 值也被误判为 NaN
Number.isNaN(value)ES6 引入的方法,它不会进行类型转换 ,只有当参数本身是 NaN 时才会返回 true
javascript
console.log(isNaN(NaN)); // true
console.log(isNaN('hello')); // true ("hello" 无法转换成数字,最终变成 NaN)
console.log(isNaN('123')); // false (字符串 "123" 可转换为数字 123)
console.log(isNaN(undefined)); // true (undefined 转换成 NaN)
console.log(isNaN(null)); // false (null 转换为 0)
//
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('hello')); // false (不会进行类型转换)
3.判断数据类型方式
总结: 判断基本数据类型 用typeof;判断引用 数据类型用instanceof(判断数组还可以用Array.isArray);更复杂的用Object.prototype.toString.call()
typeof:- 判断基本数据 类型(除了null),返回字符串
- 注意点:数组、对象、null都会被判断为object
instanceof:只能判断引用数据 类型,而不能判断基本数据类型Object.prototype.toString.call():Object 对象的原型方法 toString 来判断数据类型constructor:不能判断null,undefined。其它的都可以。由于类的constructor可以随意更改,此时会存在判断不准确的问题Array.isArray():判断是否是数组
javascript
console.log(typeof true); // boolean
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
var a = Object.prototype.toString;
console.log(a.call(function(){}));
console.log(a.call(null));
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
3.1.intanceof 操作符的实现原理及实现
原理:通过检查对象的原型链 ,看对象的原型链上是否存在指定类型的原型
实现 :当你使用 object instanceof Type 时,js 引擎会沿着 object 的原型链向上查找 ,看是否能在原型链中找到 Type.prototype 。如果找到,就返回 true;否则返回 false。
javascript
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
4. 判断数组的方式有哪些
Object.prototype.toString.call()- 原型链
ES6的Array.isArray()(常用)instanceof(常用)Array.prototype.isPrototypeOf
javascript
Object.prototype.toString.call(obj) === 'Array';
obj.__proto__ === Array.prototype;
Array.isArrray(obj);
obj instanceof Array
Array.prototype.isPrototypeOf(obj)
4. 为什么0.1+0.2 ! == 0.3,如何让其相等
总结: 浮点数问题的本质是二进制存储精度误差 ,像0.1这样的小数无法被二进制精确表示,所以计算时会出现误差。
解决方法: toFixed(控制误差范围);转成整数计算
javascript
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2) // 0.30000000000000004
console.log(n1*10+n2*10)
//让其相等。toFixed(num)方法可把 Number 四舍五入为指定小数位数的数字
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
5. 如何获取安全的 undefined 值?
总结: es5之前 它是个可写属性,可以修改它的值会出现问题,所以使用void标识 符,后面放任意表达式,永远返回undefined;es6之后,它被定义为不可写了。
es5 之前undefined是全局对象的一个可写属性 ,开发者可以修改它的值,会出现问题
es5 之后,全局的 undefined 被定义为不可写
void 运算符的特性
- void 会执行其后的表达式 ,但无论表达式结果是什么 ,永远返回 undefined。
void 0是最简洁的写法(0 是合法且最短的表达式)
javascript
undefined = 123; // 在旧环境中可能成功
console.log(undefined); // 可能输出 123,而不是预期的 undefined
//void
console.log(void 0); // undefined
console.log(void "hello"); // undefined
console.log(void (1+1)); // undefined
6.js类型转换规则
总结: 分为显式和隐式类型转换 ,显式 主要用到String()、Number()、Boolean();Boolean()主要是用运算符,如比较运算符、算术运算 符。其中><优先转数字;+有字符串转字符串,没转数字;其他-、*、/都转数字;==按优先级转。
- 显式类型转换
- 转为字符串:
toString()、String()boolean->"true"/"false"undefined、null->"undefined"/"null"{}->"[object Object]"[1,2,3]->"1,2,3"
- 转为数字:
Number()、parseInt()(没Number严格,逐个解析字符,遇到不能转换的字符就停下来)、parseFloat()string->空字符串0;仅数字的字符串数字;非纯数字字符串NaNboolean->0/1undefined、null->NaN/0Object->仅包含单个数字的数组数字;其他NaN
- 转为布尔:
Boolean()- 转为
false的情况:undefined、null、0、-0、""、NaN - 转为
true的情况:{}、[]、其他非空值
- 转为
- 转为字符串:
- 隐式类型转换
- 比较运算(
==、>、<)、if、while需要布尔值地方>、<:优先转数字if、while:自动转布尔值==:按优先级转换类型
- 算术运算 (
+、-、*、/、%)+: 有一边是string,就拼接;没有string,转number- 其他运算符:转成Number
- 比较运算(
显式类型转换
javascript
//转为字符串
String(true) // "true"/"false"
String(undefined) // "undefined"
String(null) // "null"
//对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
//转数字
Number('') // 0
Number('324') // 324
Number('324abc') // NaN
Number(true) // 1/0
Number(undefined) // NaN
Number(null) // 0
// 对象:通常转换成NaN(除了只包含单个数值的数组)
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
隐式类型转换
javascript
"3" + 4; // "34"(数字转字符串)
[] + {}; // "[object Object]"([] 转 "",{} 转 "[object Object]")
true + 1; // 2(true → 1)
null + 1; // 1(null → 0)
"5" - 2; // 3
"10" / "2"; // 5
"abc" - 1; // NaN
"10" > 2; // true("10" → 10)
"abc" > 1; // false("abc" → NaN,比较返回 false)
if ("hello") { ... } // true(非空字符串)
//对象先调用 valueOf()(默认返回对象自身),再调用 toString()
[] + 1; // "1"([] → "" → 字符串拼接)
{} + []; // 0({} 被解析为空代码块,实际计算 +[] → 0)
6.1. == 操作符的强制类型转换规则?及与 ===、Object.is()的区别?
==:抽象相等,比较前先类型转换,再比值===:严格相等,比较两个值的数据和类型是否全相等Object.is():行为与 === 基本一致,除**+0不等于-0;NaN等于自身**
== 比较规则
NaN不等于任何值,包括自己null和undefined不等于其它,除了null和undefined- 字符串 vs 数字 → 字符串转数字
- 布尔值 vs 其他类型 → 布尔值转数字
- 对象 vs 原始类型 → 对象转原始值(先使用valueOf,再使用toString)
对象转原始值 (valueOf、toString)
调用 valueOf():尝试获取原始值。
- 如果返回原始值,则使用它。
- 如果返回对象 ,继续调用 toString():将对象转为字符串
内置对象使用valueof()返回
- 对象: 返回 对象本身
- 数组: 返回 数组本身
- Date: 返回 时间戳(数字)
- 包装对象: 返回 原始值
javascript
//对象 valueOf() 返回对象本身
{} == "[object Object]"; // true
{} == 0; // "[object Object]" → NaN → false
//数组 valueOf() 返回数组本身
[] == 0; // [] → "" → 0 → true
[1] == "1"; // [1] → "1" → true
[1, 2] == 0; // [1,2] → "1,2" → NaN → false
//Date valueOf() 返回时间戳(数字)
const date = new Date();
date == date.toString(); // date → 时间戳 → 与字符串比较 → false
date == date.valueOf(); // 时间戳 == 时间戳 → true
//包装对象(Number、String、Boolean)valueOf() 返回原始值
new Number(1) == 1; // true(Number对象 → 1)
new String("a") == "a";// true(String对象 → "a")
7. || 和 && 操作符的返回值?
||:寻找第一个真值,常用于默认值。&&:寻找第一个假值,常用于条件执行或安全访问&&的优先级高于||
9. 什么是 JavaScript 中的包装类型?
总结: 把基本数据类型 (String、Number、Boolean)包装成有属性和方法的对象 。如string.length、number.toFixed等。js在内部自动包装 ,所以可以直接使用,就等同于new实例化。
js中,原始值没有方法或属性 ,但为了能够 使用方法 和属性。
js提供了包装类型 ,允许基本类型(如String、Number、Boolean)临时"包装"为对象,从而调用对象方法。,比如字符串和数字的 length 属性、toUpperCase() 方法等。
三种基本包装类型
- String(字符串包装类型): 对原始字符串类型 string 的包装(如 toUpperCase()、toLowerCase() 等)
- **Number(数字包装类型):**对原始 number 类型的包装,它为数字提供一些方法(如toFixed() 或 toPrecision())
- **Boolean(布尔值包装类型):**原始布尔类型 boolean 的包装
javascript
//js在内部自动包装为 String 对象
let greeting = "hello";
console.log(greeting.toUpperCase()); // 输出: "HELLO"
//等同于
let wrappedStr = new String("hello");
console.log(wrappedStr.toUpperCase()); // 输出: "HELLO"
console.log((3.213445).toFixed(2)); // 输出: "3.14"
//等同于
let wrappedNum = new Number(num);
console.log(wrappedNum.toFixed(2)); // 输出: "42.00"
let isActive = false;
if (new Boolean(isActive)) {
console.log("This is active");
} else {
console.log("This is not active"); // 这行代码会被执行
}
10. 为什么会有BigInt的提案?
总结: 因为js中number类型 有一个最大安全整数限制 ,在这个范围内,精度不会丢失;超过这个范围,计算就会不准确了。因此用BigInt处理大数计算时精度问题。
BigInt的提案主要是由于js中Number类型在处理大数时的精度问题。
js中的Number类型 有一个最大安全整数限制 ,即Number.MAX_SAFE_INTEGER。
在这个范围内,计算结果不会出现精度丢失
但一旦超过这个范围,就会出现计算不准确的情况。
这在需要进行大数计算时,不得不依靠一些第三方库来解决,因此官方提出了BigInt来解决此问题。
11. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别
- 浅拷贝 :
- 只复制对象的第一层属性 ,嵌套对象是引用复制。修改嵌套对象的属性会影响原对象。
- 实现方式:
扩展运算符...、Object.assign()
- 深拷贝 :
- 递归复制对象及其所有嵌套对象 ,确保每个层级的内容都被独立复制,不再共享引用地址。
- 实现方式:
JSON.parse(JSON.strigify())
JSON.parse(JSON.strigify())缺点
- 无法 处理函数和undefined
- 无法 处理循环饮用(对象某属性引用了对象本身)
- 无法 处理一些特殊对象 ,如Error、Date,会被转为空对象
- 对象的原型链上的属性会丢失(如果属性是继承下来的)
12. 如何判断一个对象是空对象
JSON.strigify(obj) == '{}'Object.keys(obj).length === 0
二、ES6
1. ES6常用新特性
- let、const(解决 var声明的变量存在变量提升、重复声明覆盖、块级无隔离三大问题)
- 箭头函数(解决了「this 绑定混乱」的核心问题,简化写法)
- 模板字符串(优化字符串)
- 函数参数增强:默认参数 + 剩余参数(rest)
...rest(在键那边,用了存储数据) - 对象、数组的解构(简化变量赋值)
- 数组 / 对象扩展:扩展运算符(...)
...[1,2](在值那边,用来解构值) - 面向对象:Class
- 模块化:import /export
- 新数据结构:Set / Map
- 异步编程:Promise(解决回调地狱)
- 数组、字符串新方法
- Symbol
1.let、const、var的区别
总结: 从5个方面来说,
1、作用域上 ,var有函数作用域(全局变量和局部变量);let、const有块级作用域({})。
2、变量提升 上,var可以变量提升,在声明前访问;let、const不行。
3、重复声明 ,var可以;let、const不可以
4、值的可变性 ,var、let可以,const不可以
5、全局对象属性,var全局变量会挂载到window对象上;而let、const没有
let 声明变量;const声明常量
区别
- 作用域:
var:有函数作用域。有全局变量和函数级变量let、const:有块级作用域,仅在声明的代码块(由 {} 界定)内有效
- 变量提升:
var:存在变量提升 。可以在声明之前被访问,其初始值为 undefined。let、const:不存在变量提升 。在声明之前访问会引发引用错误,此区域称为"暂时性死区"。
- 重复声明:
var:允许 在同一作用域内重复声明 同一个变量,后者会覆盖前者。let、const:不允许在 同一作用域内重复声明 同一个变量,否则会引发语法错误。
- 值的可变性:
var、let:变量在初始化后,其值可以被重新赋值修改。const:声明常量时必须立即初始化 ,且基本类型的值不可更改 。引用类型 (如对象、数组),其指向的内存地址不可变 ,但对象属性或数组元素可以修改。
- 全局对象属性:
var:全局作用域下声明变量时,该变量会挂载到window对象上let、const:没有全局对象属性
2、箭头函数及和普通函数的区别
总结: 箭头函数简化 了普通函数的写法 ;箭头函数没有自己的this ,它的this来自外层包裹函数(声明时的this);没有arguments对象,也不能new调用。
简化了函数的声明语法,更解决了「this 绑定混乱」的核心问题
特性:(和普通函数的区别)
- **语法简化:**省略function关键字,写法:
()=>{} - this 绑定固定: 箭头函数没有自己的this ,它的this继承自「外层作用域的 this」 (即声明时的 this,而非调用时的 this);
- **无 arguments 对象:**无法通过arguments获取参数列表,需用「剩余参数」替代
- **不能作为构造函数:**不能用new关键字调用,否则报错
javascript
const add = (a, b) => a + b;
console.log(add(3, 4)); // 7
const calculate = (a, b) => {
const sum = a + b;
const product = a * b;
return sum + product; // 必须显式return
};
console.log(calculate(2, 3)); // 2+3 + 2*3 = 11
const user = {
name: "张三",
// 传统函数:this指向调用者(user)
sayName1: function() {
setTimeout(function() {
console.log(this.name); // undefined(this指向window/global)
}, 100);
},
// 箭头函数:this继承外层作用域(user)
sayName2: function() {
setTimeout(() => {
console.log(this.name); // 张三(正确)
}, 100);
}
};
user.sayName1(); // undefined
user.sayName2(); // 张三
3.模板字符串
ES6 之前,拼接字符串需要用**+连接**,遇到多行字符串或变量插值时,代码冗长且易出错。
ES6之后,使用模板字符串 用 **反引号()** 包裹,用${}`来插入变量
特性:
- 多行字符串: 直接换行即可,无需\n或+
- 变量 / 表达式插值: 用
${}包裹变量或表达式,自动解析并拼接 - **标签模板:**可通过函数自定义模板字符串的解析逻辑
js
// 1. 多行字符串:直接换行
const html = `
<div class="user">
<h1>用户信息</h1>
<p>姓名:张三</p>
</div>
`;
console.log(html); // 输出带换行的完整HTML字符串
// 2. 变量插值:${}包裹变量
const name = "张三";
const age = 25;
const info = `我叫${name},今年${age}岁`;
console.log(info); // 我叫张三,今年25岁
// 3. 表达式插值:${}内可写任意表达式
const a = 10;
const b = 20;
const calc = `${a} + ${b} = ${a + b}`;
console.log(calc); // 10 + 20 = 30
// 4. 标签模板(进阶):自定义解析逻辑
function format(strings, ...values) {
// strings:模板字符串的静态部分数组
// values:插值部分的数组
return strings[0] + values[0].toUpperCase() + strings[1];
}
const result = format`Hello, ${name}!`;
console.log(result); // Hello, 张三!(若name是小写,会转为大写)
4.函数参数增强:默认参数 + 剩余参数
默认参数: 在函数声明时直接为参数设置默认值,当参数未传递或为undefined时生效
剩余参数: 用...rest表示,收集函数的「剩余参数」并组成一个数组,替代arguments(arguments是类数组,需转换为数组才能用数组方法)
javascript
//默认参数
function add(a, b = 0) { // 正确:非默认参数在前
return a + b;
}
//剩余参数
function printFirstAndRest(first, ...rest) {
console.log("第一个参数:", first); //1
console.log("剩余参数:", rest); // [2,3,4]
}
printFirstAndRest(1, 2, 3, 4);
// 3. 对比arguments:剩余参数是数组,arguments是类数组
function compareArgs(...rest) {
console.log(rest instanceof Array); // true(数组)
console.log(arguments instanceof Array); // false(类数组)
// arguments需转换为数组才能用reduce:Array.from(arguments).reduce(...)
}
compareArgs(1, 2, 3);
5.对象、数组的解构
总结: 对象按属性名匹配;数组按下标匹配
数组解构:按位置匹配 (根据数组的「索引位置 」匹配变量,支持默认值、嵌套解构)
对象解构:按属性名匹配 (根据对象的「属性名」匹配变量,支持别名、默认值、嵌套解构)
javascript
// 1. 基本数组解构
const [a, b, c] = [10, 20, 30];
// 2. 跳过元素:用空逗号占位
const [x, , y] = [1, 2, 3]; //x=1;y=3
// 3. 默认值:当解构的值为undefined时使用默认值
const [m, n = 5] = [3]; // m=3;n=5
// 4. 嵌套数组解构
const [p, [q, r]] = [1, [2, 3]]; //p=1;q=2;r=3
// 5. 与扩展运算符结合:获取剩余元素
const [first, ...rest] = [1, 2, 3, 4]; //first=1;rest=[2,3,4]
6.数组 / 对象扩展:扩展运算符(...)(浅拷贝)
扩展运算符(...)可以将「可迭代对象 」(数组、Set、字符串等)或「对象」展开为单个元素 / 属性,常用于数组拷贝、 合并 ,对象拷贝、合并等场景
**数组扩展:**将数组展开为逗号分隔的元素列表,支持拷贝、合并、函数传参
**对象扩展:**将对象展开为键值对列表,支持对象拷贝、合并、添加新属性
javascript
// 1. 数组拷贝:创建新数组(浅拷贝)
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // 等价于arr1.slice()
arr2.push(4);
console.log(arr2); // [1, 2, 3, 4]
// 2. 数组合并:替代concat方法
const arr3 = [4, 5, 6];
const mergedArr = [...arr1, ...arr3]; // 合并arr1和arr3, 结果[1, 2, 3, 4, 5, 6]
// 3. 函数传参:将数组展开为函数参数
function add(a, b, c) {
return a + b + c;
}
const nums = [1, 2, 3];
console.log(add(...nums)); // 6(等价于add(1, 2, 3))
//对象用法同数组
const obj1 = { a: 1, b: 2 };
const newObj = { ...obj1, c: 3, a: 10 }; // 新增c,修改a。结果{ a: 10, b: 2, c: 3 }
7. 面向对象:Class
总结: 使用class定义类 ,里面可以定义属性、方法、constructor(构造函数)。使用它的好处是,可以使用extends继承 这个类,还可以继承后再扩展 ,增加了代码的复用性。(子类使用父类的构造函数 需要用super调用)
特性
- 类定义 :用
class关键字定义类 ,内部包含constructor(构造函数)和方法; - 继承 :用
extends关键字实现类的继承 ,super()调用父类构造函数; - 静态方法 :用
static关键字定义静态方法 ,只能通过类调用,不能通过实例调用; - 实例方法:类内部定义的方法,只能通过实例调用
javascript
// 1. 定义基础类
class Person {
// 构造函数:实例化时执行,初始化属性
constructor(name, age) {
this.name = name; // 实例属性
this.age = age;
}
// 实例方法:通过实例调用
sayHi() {
console.log(`我叫${this.name},今年${this.age}岁`);
}
// 静态方法:通过类调用,this指向类本身
static createPerson(name, age) {
return new Person(name, age); // 工厂方法,创建Person实例
}
}
// 实例化类
const person1 = new Person("张三", 25);
person1.sayHi(); // 我叫张三,今年25岁(调用实例方法)
// 调用静态方法
const person2 = Person.createPerson("李四", 30);
person2.sayHi(); // 我叫李四,今年30岁
// 2. 继承:定义子类Student继承自Person
class Student extends Person {
// 子类构造函数:必须调用super()
constructor(name, age, studentId) {
super(name, age); // 调用父类构造函数,初始化name和age
this.studentId = studentId; // 子类新增属性
}
// 子类重写父类方法
sayHi() {
console.log(`我是学生${this.name},学号${this.studentId}`);
}
// 子类新增方法
study() {
console.log(`${this.name}正在学习`);
}
}
// 实例化子类
const student1 = new Student("王五", 20, "2024001");
student1.sayHi(); // 我是学生王五,学号2024001(重写后的方法)
student1.study(); // 王五正在学习(子类新增方法)
console.log(student1 instanceof Student); // true
console.log(student1 instanceof Person); // true(子类实例也是父类实例)
8. 模块化:import /export
核心概念:
- 模块(Module):一个 JS 文件就是一个模块,内部变量 / 函数默认对外不可见;
- 导出(Export) :将模块内的变量 / 函数暴露给外部,分为「命名导出」和「默认导出」;
- 导入(Import) :在其他模块中引入已导出的变量 / 函数
javascript
// 方式1:声明时直接导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
// 导出时重命名:用"原名称 as 新名称"
export { add as addFn,funA,... };
// 默认导出
export default greet = (name) => `Hello, ${name}`;
//导入其他模块
import { PI, add, funA as testFn } from './moduleA.js';
//导入所有
import * as moduleA from './moduleA.js';
console.log(moduleA.PI);
9. 新数据结构:Set / Map
总结: 都是es6新增的数据类型,Set类似于数组 ,存值(且无重复,自动去重),可以用for...of遍历 ,常用方法add、delete、has;Map类似对象 ,它的键可以是任意类型 ,可以遍历 (forEach、for...of、keys(所有键名)、values ( 所有键值)等),常用方法get、set、delete、has
- Set:无重复值的集合
- 存储唯一值:自动去重,不允许重复元素
- 可迭代:支持for...of遍历,可转换为数组
- 常用方法 :
add()(添加)、delete()(删除)、has()(判断是否存在)、size(获取长度)、clear()(清空)
- Map:键值对集合(键可任意类型)
- 键可以是任意类型:如 Number、String、Object、Symbol 等(对象的键只能是字符串 / Symbol);
- 可迭代 :按插入顺序排列,遍历:
forEach、for...of、keys()(所有键名)、values()( 所有键值)、entries()(所有键值对); - 常用方法 :
set(key, value)(添加键值对)、get(key)(获取值)、delete(key)(删除)、has(key)(判断)、size(长度)、clear()(清空)
9.1.Map和Object的区别
总结: 从5个方面来说,
1、键的数据类型 :map是任意值 ;object必须是string或Symbol
2、键值对顺序 :map有序;object无序
3、方法 :map有增删改方法;object没有
4、遍历 :map可以遍历;object不行
5、性能:map在增删改场景下更好
区别:
- 键的数据类型: Map的键可以是任意值,而Object的键必须是一个String或是Symbol。
- 键值对的顺序: Map中的key是有序的,而Object的键是无序的。
- 功能和方法: Map提供
set(), get(), has(), delete(), size等方法,而Object没有内置的方法支持快速增删查改,需手动实现或用工具。 - 迭代: Map可以直接被迭代,而Object不可以直接被迭代。
- 性能: Map在频繁增删键值对的场景下表现更好,而Object的效率比较差。
9.2. weakSet和Set区别
- WeakSet中只能存放对象类型
- WeakSet对元素的引用是弱引用,若没有对某个对象进行引用,那么垃圾回收机制可以对该对象进行回收;
- 常见方法:
add、delete、has - WeakSet不能遍历
9.3.weakMap和Map区别
- WeakMap的key只能使用对象
- WeakMap的key对 对象的引用是弱引用,如果没有引用这个对象,那么垃圾回收机制可以回收该对象
- 常见方法:
set、get、delete、has - WeakSet不能遍历
10. 数组、字符串方法
- 字符串方法总结(不修改原字符串)
- ES5 是基础,重点掌握:
indexOf、slice(截取)、split(切割)、trim、replace、toUpperCase/toLowerCase - ES6+ 是升级,优先使用:
includes(替代 indexOf)、startsWith/endsWith、模板字符串
- ES5 是基础,重点掌握:
- 数组方法总结
- 修改原数组的方法:
push(后加)、pop(后删)、unshift(前加)、shift(前删)、splice(增删改)、reverse(翻转)、sort(排序)、fill(填充) - 不改原数组的方法:
includes、indexOf、lastIndexOf、slice(截取)、concat(拼接) - 遍历:
forEach、map、filter、every、some、find、findIndex、reduce(累积) - 创建数组/类数组转数组:
Array.of (创建)、Array.from(转)
- 修改原数组的方法:
10.1. 字符串
- ES5 原生方法(所有浏览器兼容)
- 字符 / 索引 查找类(获取字符 / 查找位置)
charAt(下标):获取指定下标的单个字符charCodeAt(下标):获取指定下标 的单个字符的 Unicode 编码值indexOf(指定字串, 起始下标):查找指定的子串 / 字符 ,返回首次出现的索引值lastIndexOf(指定字串):末尾向前查找指定的子串 / 字符,返回最后一次出现的索引值
- 字符串 截取 / 切割类(提取指定片段)
slice(起始下标, 截止下标):截取字符串中指定区间的内容,返回新字符串substring(起始下标, 截止下标):同上
- 大小写 转换类
toUpperCase() / toLowerCase():全部转为大写/小写,返回新字符串
- 内容 替换 / 分割/拼接类
split(分隔符, 限制返回的长度):按照指定分隔符切割 ,转为一个数组返回replace(替换前字串, 替换后字串):第一个匹配到 的 替换前字串 替换成 替换后字串,返回新字符串concat(str1, str2, ...):拼接 字符串,返回拼接后的新字符串trim():去除首尾的所有空白符(空格、换行符、制表符)(注:部分早期浏览器如 IE8 不支持,但通常视为 ES5 基础方法)
- 字符 / 索引 查找类(获取字符 / 查找位置)
- ES6 及之后新增方法
- 包含判断类
includes(查找字串, 起始下标):是否包含指定的子串 / 字符,返回 true / falsestartsWith(查找字串, 起始下标):是否以指定的子串开头endsWith(查找字串, 检测的长度):是否以指定的子串结尾
- 重复拼接类
repeat(数字):将原字符串 重复拼接 n 次,返回拼接后的新字符串
- 补全填充类(字符串补位,实用)
padStart(补全后长度, 填充字串):从字符串的开头 填充指定内容,直到字符串的长度达到设置的补全后长度,返回补全后的新字符串padEnd(补全后长度, 填充字串):从字符串的末尾 填充指定内容,直到字符串的长度达到 设置的补全后长度,返回补全后的新字符串
- 模板字符串(反引号
${var})
- 包含判断类
10.2. 数组
- ES5 原生方法(不会修改原数组)
slice(起始下标, 截止下标):数组截取(切片)concat(arr1, arr2, ...):数组 合并join(分隔符):数组转字符串indexOf(查找元素,起始下标):查找元素索引(前--->后查找)lastIndexOf(查找元素,起始下标):查找元素索引(后--->前查找)includes (查找元素):判断数组是否包含指定元素arr.flat(深度):数组扁平化,将「多维数组」转换为「一维数组」。arr.flat(1/Infinity)扁平化1层/所有层flatMap (callback):先 map ,再 flat (1) 扁平化,相当于 arr.map().flat(1)- 数组遍历
forEach(callback):数组遍历。(无返回值)arr.forEach((item, index, arr) => { 执行逻辑 })find(callback)/findIndex(callback):→ 遍历数组,返回第一个满足条件的元素 / 索引map(callback):数组(格式化、数据转换 ),对每个元素做处理 / 转换。(返回新数组)arr.map((item, index, arr) => { return 处理后的元素 })filter(callback):数组过滤,根据条件筛选 出符合要求。(返回新数组)arr.filter((item, index, arr) => { return 筛选条件 })some(callback):数组是否有元素 满足条件。arr.some((item, index, arr) => { return 判断条件 })every(callback):数组所有元素是否 满足条件arr.every((item, index, arr) => { return 判断条件 })reduce(callback, 初始值):数组累积(用于数组求和、求平均值、去重、扁平化、分组统计),每个元素的处理结果累积到一个变量中,返回这个累积值。`arr.reduce((prev, item) => prev + item, 0)
- ES5 原生方法(会修改原数组)
push(...元素):末尾追加元素pop():末尾删除元素(只删除一次最后一个元素)unshift(...items):开头追加元素shift():开头删除元素sort(compareFunction):数组排序。(不传参:字符串排序;传参a-b升序、b-a降序)sort((a,b) => a - b)reverse():数组反转splice(起始下标, 删除的个数0, ...添加 / 替换的元素):万能增删改。splice(2,0,3,4)增34;splice(2,1,99)替换下标为2的元素fill(填充的值, 起始下标, 截止下标):数组填充。fill(0)全部填0;fill(99,1,3)下标1到下标3填99
- ES6 及之后新增方法
Array.from(伪数组): 伪数组转真数组Array.of(...items/数字):传单个数字:数组长度;传多个值:数组的参数。Array.of(3)长度为3的数组;Array.of(2,3)数组为[2,3]
javascript
//reduce
// 场景1:求和(最基础)
const sum = arr.reduce((prev, item) => prev + item, 0)
// 场景2:求数组中最大值
const max = arr.reduce((prev, item) => prev > item ? prev : item, 0)
// 场景3:数组去重(进阶)
const uniqueArr = arr.reduce((prev, item) => {
if(!prev.includes(item)) prev.push(item)
return prev
}, [])
11. Symbol
总结: 主要特点是独一无二,适合用来做对象的唯一属性值,避免属性名冲突;也常用来定义一些不希望被遍历到的私有属性;或者是定义常量集合。
通过 Symbol() 函数创建,调用都会生成一个完全唯一的值 ,即使传入相同的描述文本,生成的 Symbol 也不相等
使用 Symbol.for() 创建全局可复用的 Symbol
使用场景
- 作为对象的唯一属性键,避免属性名冲突
- 定义对象的 "私有" 属性(模拟私有性): Symbol 键名不会出现在遍历 中,只能通过**
Object.getOwnPropertySymbols()访问**,起到隐藏属性的作用 - 定义常量集合,避免魔法字符串:
javascript
console.log(Symbol('foo') !== Symbol('foo')); // true
//对象的唯一属性键
const user = {};
const nameA = Symbol('name');
const nameB = Symbol('name');
user[nameA] = 'A';
user[nameB] = 'B';
console.log(user[nameA] !== user[nameB]) //true
//对象的 "私有" 属性
const secretKey = Symbol('secret');
const obj = {
public: '公开信息',
[secretKey]: '敏感信息' // 模拟私有属性
};
//常量集合
const STATUS = {
PENDING: Symbol('pending'),
SUCCESS: Symbol('success'),
ERROR: Symbol('error')
};
12、for...in(es5)、for...of(es6)
总结
for...in循环只遍历对象,获取键;for...of循环可以用来遍历数组、 类数组对象, 字符串、 Set、 Map 以及 Generator 对象,获取值。
详解
for...of概念 :允许遍历一个含有 迭代器 接口的数据结构 。(数组、对象等)并且返回各项的值
for...in 和 for...of 的区别
for...of遍历获取 的是对象的键值for (let key in obj);for...in获取 的是对象的键名 ;for (let val in arr)for... in会遍历对象 的整个原型链 , 性能非常差 不推荐使用; 而for ... of只遍历 当前对象 不会遍历 原型链;- 遍历数组 ,
for...in会返回数组中所有可枚举的属性 (包括原型链上 可枚举的属性);for...of只返回数组的下标对应的属性值;
13. Proxy 可以实现什么功能?
总结: 意思是代理。用来拦截对象的操作,使用方法get、set、has等自定义逻辑处理
定义: Proxy 是 ES6 引入的一个强大功能,意思是"代理"。它可以用来拦截对象的操作 ,并在操作时进行自定义的逻辑处理 。new Proxy(target, handler)
常用的拦截操作(handler )
get------ 拦截对象属性的读取set------ 拦截对象属性的 赋值has------ 拦截 in 操作符deleteProperty------ 拦截 delete 操作apply------ 拦截函数调用construct------ 拦截 new 操作
javascript
const user = {name: "Tom",age: 18};
const proxy = new Proxy(user, {
get(target, prop) {
if (prop === "age") {
return target[prop] + " 岁";
}
return target[prop];
},
set(target, prop, value) {
if (prop === "age") {
if (typeof value !== "number") {
throw new TypeError("年龄必须是数字!");
}
}
target[prop] = value;
return true; // 必须返回 true 表示赋值成功
},
has(target, prop) {
if (prop === "password") {
return false; // 隐藏 password 属性
}
return prop in target;
},
deleteProperty(target, prop) {
if (prop === "name") {
console.log("name 属性不能被删除!");
return false;
}
return true;
}
})
console.log("password" in proxy); // false
javascript
//函数
function sum(a, b) {
return a + b;
}
const proxy = new Proxy(sum, {
apply(target, thisArg, args) {
console.log("拦截函数调用,参数是:", args);
return target(...args) * 2; // 返回值改为原函数结果的两倍
}
});
console.log(proxy(1, 2)); // 6
// new
class Person {
constructor(name) {
this.name = name;
}
}
const ProxyPerson = new Proxy(Person, {
construct(target, args) {
console.log("正在创建实例,参数:", args);
return new target(...args);
}
});
const p = new ProxyPerson("Tom"); // 正在创建实例,参数: [ 'Tom' ]
14. 如何提取高度嵌套的对象里的指定属性?
- 已知结构:使用解构赋值
- 结构不确定:使用可选链操作符
?.
javascript
const school = {
classes: {
stu: {
name: 'Bob',
age: 24
}
}
};
// 一行解构提取深层属性
const { classes: { stu: { name } } } = school;
console.log(name); // 'Bob'
//可选链操作符
const phone = school?.classes?.stu?.phone ?? '未提供';
三、异步编程
1. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?
概念: 回调函数(Callback)是指将一个函数作为参数传递给另一个函数 ,并在特定时刻执行它 。在 JavaScript 中,回调函数常用于处理异步操作,如读取文件、请求数据、 定时器 等。
javascript
function fetchData(callback) {
setTimeout(() => {
callback("Data loaded");
}, 1000);
}
fetchData((result) => {
console.log(result); // 输出:Data loaded
});
回调地狱产生的原因: 当需要执行多个异步操作时,每个操作都依赖前一个操作的结果,这时就会出现"回调嵌套"的情况。导致代码层级过深,形成"金字塔"结构,称为回调地狱
javascript
doTask1((result1) => {
doTask2(result1, (result2) => {
doTask3(result2, (result3) => {
console.log("All tasks completed:", result3);
});
});
});
缺点:
- 可读性差:嵌套层级过深,代码逻辑混乱。
- 维护困难:修改或调试时容易出错。
- 错误处理复杂:多层嵌套导致异常捕获困难。
解决方案:(异步编程的实现方式)
Promiseasync/awaitGenerator 函数
2、Promise(解决回调地狱)
总结: promise解决了回调地狱问题,使用.then链条式调用,避免嵌套。有三个状态pending、fulfilled、rejected;Promise对象方法.all(全成功才返回)、.race(跑的快的决定结果);实例方法.then(成功)、.catch(失败)、.finally(无论成功还是失败)
优势:
- 链式调用 :通过
.then()避免嵌套。 - 统一错误处理 :通过
.catch()捕获异常。
核心概念
- 三个状态 (状态的改变 是通过在异步操作结束后调用
resolve() 和 reject()函数来实现的)pending:初始状态,既未成功也未失败;fulfilled:操作成功,状态不可逆;rejected:操作失败,状态不可逆。
- Promise对象-静态方法
Promise.resolve():将一个普通的值 转换 成 Promise 类型的数据。- 非 Promise 对象,将返回一个 Promise 对象,状态为 fulfilled,结果为参数值
- Promise 对象,将返回的 Promise 对象的状态和参数,与参数相同
Promise.reject():始终返回一个失败的 Promise 对象,并且不受参数的影响。Promise.all([promise1, promise2]):同时执行多个Promise,全部成功才返回,有一个失败则立即失败Promise.race([promise1, promise2]):同时执行多个Promise,第一个改变状态的Promise决定结果
- 实例方法
then:状态变为fulfilled时执行,返回新的 Promise,支持链式调用(resolve())catch:状态变为rejected时执行,捕获错误(reject())finally:无论成功或失败,都会执行
写法结构
javascript
let p1 = Promise.resolve(123);
let p1 = Promise.reject(456);
//等同于
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(123);
} else {
reject(456);
}
});
//Promise.resolve方法 参数及结果
let p1 = Promise.resolve(123);
console.log(p1); // Promise {<fulfilled>: 123}
let p2 = Promise.resolve(new Promise((resolve, reject) => { resolve(456); }));
console.log(p2); // Promise {<fulfilled>: 456}
let p3 = Promise.resolve(new Promise((resolve, reject) => { reject("error"); }));
console.log(p3); // Promise {<rejected>: "error"}
详细使用
javascript
// 1. 创建Promise
function fetchData() {
return new Promise((resolve, reject) => {
// 模拟异步操作(如AJAX请求)
setTimeout(() => {
const success = true; // 模拟成功/失败
if (success) {
const data = { name: "张三", age: 25 };
resolve(data); // 成功:调用resolve,状态变为fulfilled
} else {
const error = new Error("数据获取失败");
reject(error); // 失败:调用reject,状态变为rejected
}
}, 1000);
});
}
// 2. 使用Promise:链式调用
fetchData()
.then((data) => {
console.log("获取数据成功:", data);
// 返回新的Promise,继续链式调用
return new Promise((resolve) => {
setTimeout(() => {
resolve(data.name); // 将name传递给下一个then
}, 500);
});
})
.then((name) => {
console.log("处理后的数据:", name); // 张三
})
.catch((error) => {
console.log("错误:", error.message); // 捕获所有前面的错误
})
.finally(() => {
console.log("无论成功或失败,都会执行"); // 可选,用于清理操作(如关闭加载动画)
});
// 3. Promise常用静态方法
// 3.1 Promise.all():同时执行多个Promise,全部成功才返回,有一个失败则立即失败
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
Promise.all([promise1, promise2])
.then((values) => {
console.log(values); // [1, 2](所有成功,返回结果数组)
})
.catch((error) => {
console.log(error);
});
// 3.2 Promise.race():同时执行多个Promise,第一个改变状态的Promise决定结果
const promise3 = new Promise((resolve) => setTimeout(resolve, 100, "快的Promise"));
const promise4 = new Promise((resolve) => setTimeout(resolve, 200, "慢的Promise"));
Promise.race([promise3, promise4])
.then((value) => {
console.log(value); // "快的Promise"(第一个成功的结果)
});
3.async/await(es8)
总结: 用同步的方式写异步代码,
async 是声明函数是异步 的,返回值是Promise对象 (return的值 会被封装成Promise对象 ,根据值的类型自动改变状态 ;没有return值,返回undefined值的Promise对象)
await 是用来等待后面的表达式返回结果 ,若后面是Promise对象 ,会阻塞代码中断执行 ,若不想中断用try...catch包裹多个await Promise 捕获异常
概念: 是一种基于 Promise 实现的异步编程方式 。async/await 也是一种语法糖 。
优势: async/await 实现了用同步方式 来写异步代码 (promise是链式调用形式写异步代码)
缺点: 性能问题,await会阻塞代码
- async : 用来声明一个function是异步的,返回Promise对象
- 使用 return 语句返回的值 会被自动封装成 Promise 对象 , 该对象的状态 会根据 return 语句返回的值 类 型自动变为 resolved 或 rejected。
- 没有return ,则该函数返回一个 undefined 值的 Promise 对象
- await : 在async 函数内部,它会暂停 async 函数的执行,等待 Promise 对象的状态改变,然后再继续执行 async 函数。
- await不仅仅用于等Promise对象,可以等任意表达式的结果
- await后面的 Promise 对象如果变为 reject 状态 ,则 reject 的参数 会被 catch 方法的回调函数接收到
- 任何一个 await 语句后面的 Promise 对象变为 reject 状态 ,那么整个 async 函数都会中断执行 。(不想中断:
try...catch)
async代码写法
javascript
// 成功
async function foo() {
return 'Hello World!'
}
foo().then(value => console.log(value)) // 'Hello World!'
//失败
async function bar() {
throw new Error('Something went wrong!')
}
bar().catch(error => console.error(error)) // 'Error: Something went wrong!'
//async 函数中没有 return 语句,则该函数返回一个 undefined 值的 Promise 对象
async function foo() {
console.log('Hello World!')
// 等同于 return Promise.resolve(undefined)
}
foo().then(value => console.log(value)) // 'Hello World!' // undefined
await代码写法
javascript
async function foo() {
await Promise.resolve('xx')
}
async function f() {
await Promise.reject('出错了')
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 有一个await就会中断函数
async function f() {
await Promise.reject('出错了')
await Promise.resolve('hello world') // 不会执行
}
// 不想中断
async function f() {
try {
await Promise.reject('出错了')
} catch(e) {
}
return await Promise.resolve('hello world')
}
f().then(v => console.log(v)) // hello world
//多个await
async function main() {
try {
const val1 = await firstStep()
const val2 = await secondStep(val1)
const val3 = await thirdStep(val1, val2)
console.log('Final: ', val3)
} catch (err) {
console.error(err)
}
}
4. async/await对比Promise的区别
总结: 分为4个方面
1、语法差异: Promise 基于回调函数的异步处理方式 ;async/await是一种的语法糖,基于generator和promise (async声明异步,await等待结果)
2、可读性: Promise 链式调用 书写异步;async/await是同步 书写异步
3、异步操作的顺序: Promise 处理多个异步,要嵌套then的方式 ,或者all、race;async/await 同步代码
4、错误捕获和处理: Promise 用catch方法 ;async/await用try...catch
区别:
- 语法差异
- promise 是基于回调函数的异步处理方式,使用 then 和 catch 方法来处理异步操作的结果和错误。
- async/await 是一种基于 生成器 函数(Generator)和 promise 的语法糖,通过在函数前面加上 async 关键字,将函数转换为返回 promise 对象的异步函数,并使用 await 关键字等待异步操作的结果。
- 可读性
- promise 以链式调用的形式书写异步代码。
- async/await 以同步形式书写异步代码 。更加直观和易读,代码结构更加清晰。
- 错误处理
- promise 中,通常使用 catch 方法来捕获和处理错误。
- async/await 中,通常使用 try...catch 语句来捕获和处理错误。
- 异步操作的顺序
- promise 中,如果有多个异步操作需要按照特定的顺序执行 ,我们需要通过嵌套的 then 方法或使用 Promise.all、Promise.race 等组合方法来实现。
- async/await 中,可以使用同步的代码风格来编写异步操作的顺序,使代码更加简洁和可读。
- 错误堆栈
- promise 中,当发生错误时,错误 信息 中会包含详细的堆栈信息,可以追踪到错误发生的位置。
- async/await 中,由于使用了 try...catch 语句捕获错误 ,堆栈信息可能会被截断,不够详细。
5、generator. ??
6. 并发与并行的区别?
- 并发 :宏观 上同一时间段内处理多个任务 ,这些任务交替执行 (强调任务在时间段上的重叠)。
- 并行 :微观 上同一时刻执行多个任务 ,这些任务真正同时进行 (强调任务在时刻上的同步)。
7. setTimeout、setInterval、requestAnimationFrame 各有什么特点?
setTimeout:指定延迟后执行一次回调函数。适用场景:一次性延迟任务(如倒计时、防抖、延迟加载)setInterval:每隔固定时间间隔重复执行回调函数。适用场景:周期性轮询、定时更新(需手动取消以避免内存泄漏)requestAnimationFrame:专为动画优化,在浏览器下一次重绘前执行回调。适用场景:所有视觉动画(Canvas、CSS 变换、游戏、滚动效果)
javascript
// setTimeout
setTimeout(() => console.log("1秒后执行"), 1000);
//setInterval
const id = setInterval(() => console.log("每秒执行"), 1000);
clearInterval(id); // 手动停止
//requestAnimationFrame
function animate(timestamp) {
// 动画逻辑
requestAnimationFrame(animate); // 递归调用保持循环
}
requestAnimationFrame(animate);
8、事件循环 EventLoop ?
四、JavaScript基础
1. new操作符的实现原理
- 创建新对象: {}
- 设置原型链,将对象的原型设置为constructor构造函数的 prototype 对象:
- 让函数的 this 指向这个对象,执行构造函数的代码: 为这个新对象添加属性
- 返回新对象
javascript
function createObject(constructor, ...args) {
const obj = {}; // 步骤1: 创建新对象
obj.__proto__ = constructor.prototype; // 步骤2: 设置原型链
const result = constructor.apply(obj, args); // 步骤3: 绑定this并执行构造函数
return (typeof result === 'object' && result !== null) ? result : obj; // 步骤5: 返回结果
}
2. js有哪些内置对象
总结: 常用的有对象、数组、函数、字符串、Date、JSON、正则、Promise等;
Object:所有对象的基类,提供如 keys()、values()、assign() 等方法。-
Function:函数构造器,支持 .call()、.apply()、.bind() 等方法。 -
Array:用于处理有序集合,提供 push()、pop()、map()、filter() 等丰富方法。 -
String:字符串操作,如 substring()、split()、trim()、includes()。 Number:数值处理,包括 toFixed()、toPrecision()、isInteger()、isNaN()。JSON:用于 JSON 序列化与反序列化,如 stringify() 和 parse()。Date:处理日期和时间,支持创建、格式化、时间戳计算等。-
Map:键值对集合,键可为任意类型。 -
Set:存储唯一值的集合。 Error及其子类型:如 TypeError、ReferenceError、SyntaxError、RangeError 等,用于异常处理。RegExp:用于模式匹配和文本搜索,支持字面量 /.../ 或构造函数方式。Promise:异步操作处理(ES6 引入)。Proxy 和 Reflect:用于元编程,拦截对象操作(ES6 引入)。
3. 对JSON的理解
JSON的数据结构 主要由两种形式组成:对象(Object)和数组(Array) 。对象 是一个无序的键值对集合 ,用花括号{}表示;数组 是一个有序的值的集合 ,用方括号[]表示。
两个原生方法实现:
JSON.stringify():将 js 对象或值转换为 JSON 字符串。JSON.parse():将 JSON 字符串解析为 js 对象或值。
javascript
const obj = { name: "Alice", age: 25 };
const jsonString = JSON.stringify(obj);
console.log(jsonString); // '{"name":"Alice","age":25}'
const obj = JSON.parse(jsonString);
console.log(obj.name); // "Alice"
4. js脚本<script>延迟加载的方式有哪些?
- defer 属性: 脚本在文档解析完成后 、DOMContentLoaded 事件触发前按顺序执行 。
<script defer src="script.js">(按顺序执行且操作 DOM ) - async 属性: 脚本异步下载,下载完成后立即执行,可能中断 HTML 解析 。
<script async src="script.js">(完全独立且需尽快执行) - 动态创建 DOM 方式: 通过 js 动态创建 script 元素并插入 DOM,实现按需加载。
- 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
- 将
<script>放在 HTML 底部: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
5. js 类数组对象的定义?
类数组: 具有一个 length 属性和若干索引属性 的任意对象 。这样的对象可以用数字索引来访问其元素 ,但它们不是真正的数组。
javascript
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
5.1.为什么函数的 arguments 参数是类数组而不是数组?
历史原因 :在早期js版本 中,arguments 被设计为类数组对象 ,主要是为了性能考虑 。减少内存占用和提高访问速度 。
兼容性 :早期的浏览器和js引擎不支持完整的数组方法 ,因此提供一个类似数组的对象可以保证基本的兼容性
5.2.如何转化为数组?
Array.prototype.sliceArray.from使用扩展运算符:仅适用于可迭代对象Array.prototype.map:如果需要映射每个元素
javascript
const trueArray = Array.prototype.slice.call(arrayLike);
const trueArray = Array.from(arrayLike);
const trueArray = [...arrayLike];
const trueArray = Array.prototype.map.call(arrayLike, (item, index) => item);
5.3.如何遍历类数组?
转成数组后再遍历
6. Unicode、UTF-8、UTF-16、UTF-32的区别?
Unicode 是字符集,定义了"有哪些字符";UTF-8、UTF-16、UTF-32 是编码方式,定义了"如何用字节存储这些字符"
假设:Unicode 是字典,UTF-8/16/32 是三种不同的"抄写方式" ------ 你选哪种,取决于你用的是纸笔(UTF-8)、钢笔(UTF-16)还是金笔(UTF-32)。
7. 常见的位运算符有哪些?其计算规则是什么?
常见的位运算符共有 6 种:按位与&、按位或|(表格写不出来)、按位异或^、按位取反~、左移<<和右移>>。
| 运算符 | 符号 | 计算规则 | 示例(8位二进制) | 结果(十进制) |
|---|---|---|---|---|
| 按位与 | & | 两数对应位均为1时,结果为1,否则为0 | 12 & 10 → 1100 & 1010 | 8 |
| 按位或 | 两数对应位任一为1,结果为1 | `12 | ||
| 按位异或 | ^ | 两数对应位不同时为1,相同时为0 | 12 ^ 10 → 1100 ^ 1010 | 6 |
| 按位取反 | ~ | 所有位取反(0变1,1变0) | ~12 → ~00001100 | -13(补码表示) |
| 左移 | << | 所有位左移n位,右侧补0 | 12 << 2 → 1100 << 2 | 48 |
| 右移 | >> | 所有位右移n位,左侧补符号位(有符号数) | 12 >> 2 → 1100 >> 2 | 3 |
8. 什么是 DOM 和 BOM?
- DOM(文档对象模型) :由W3C制定标准 ,它将网页文档解析为树形结构(节点树) ,使js能够动态访问和修改页面中的元素、内容、结构和样式。
- 顶层对象:
document(隶属于window对象)。 - 主要功能: 获取、修改、添加或删除页面元素;事件
- 顶层对象:
- BOM(浏览器对象模型):由浏览器厂商提供。它通过一系列对象(核心是window对象)让js能够控制浏览器窗口、导航、屏幕信息等与网页内容无关的功能。
- 顶层对象:
window - 主要对象与功能 :
location:控制URL跳转与刷新。history:操作浏览器历史记录。navigator:获取浏览器信息。screen:获取屏幕尺寸。alert/confirm:弹出对话框
- 顶层对象:
9. escape、encodeURI、encodeURIComponent 的区别
escape是对字符串(string)进行编码
encodeURI、encodeURIComponent 对URL编码
10. JavaScript为什么要进行变量提升,它导致了什么问题?
总结
变量提升 是JavaScript在代码执行之前对变量和函数声明进行预处理的机制 。它提高了性能和容错性 ,
但也可能导致在变量声明之前使用变量时出现undefined 的情况。
为避免潜在问题,建议在代码中始终先声明变量再使用 ,并使用ES6的let和const来避免变量提升带来的一些隐患。
11. 什么是尾调用,使用尾调用有什么好处?
尾调用 是指一个函数 的最后一步操作 是调用另一个函数 。它可以减少内存消耗,提高性能,并且可以优化 编译器
12. use strict是什么意思 ? 使用它区别是什么?
"use strict" :指定代码在严格条件下执行;
目的:
- 消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
- 消除代码运行的不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 Javascript 做好铺垫
区别
- 禁止使用 with 语句。
- 禁止 this 关键字指向全局对象。
- 对象不能有重名的属性
13. 如何判断一个对象是否属于某个类?
总结: instanceof运算符、通过constructor属性、Object.prototype.toString()。instanceof和constructor属性提供了更准确的判断 ,而toString()主要用于获取对象类型,不适用于自定义类。
14. 对AJAX的理解,实现一个AJAX请求
AJAX是一种用于在网页上进行异步数据交互 的技术。它允许网页通过后台与服务器 进行数据交换 ,而不需要刷新整个页面。
Ajax的原理 简单来说通过XmlHttpRequest对象 来向服务器发异步请求 ,从服务器获得数据 ,然后用JavaScript来操作DOM而更新页面
实现一个基本的AJAX请求可以分为以下几个步骤:
- 创建一个XMLHttpRequest对象 :使用
new XMLHttpRequest()创建一个新的XMLHttpRequest对象,该对象用于发送HTTP请求。 - 设置请求参数和回调函数 :使用open()方法设置请求的类型(GET或POST)和URL 。如果有需要,还可以设置其他的请求参数,例如请求头信息等。然后,使用
onreadystatechange属性指定一个回调函数 ,该函数将在请求状态发生变化时被调用。 - 发送请求 :使用send()方法发送请求 。对于GET 请求,可以将请求参数附加到URL 中;对于POST 请求,可以将请求参数作为send()方法的参数传递。
- 处理响应 :在回调函数中,通过检查readyState和status属性来确定请求的状态 。
readyState表示请求的当前状态 ,其中4表示请求已完成并且响应已就绪,status表示服务器返回的HTTP状态码 ,200表示请求成功。可以使用responseText或responseXML属性来获取服务器返回的数据。
readyState表示请求的当前状态,包括0(未初始化)、1(已打开)、2(已发送)、3(接收中)和4(已完成)。
javascript
// 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest()
// 指定请求的方法、URL和是否为异步请求
xhr.open('POST', 'https://api.example.com/data', true)
// 设置接收到响应时的处理函数
xhr.onload = function () {
if (xhr.status === 200) {
// 在这里处理从服务器返回的数据
var response = JSON.parse(xhr.responseText)
console.log(response)
} else {
console.error('请求失败:' + xhr.status)
}
}
// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json')
// 发送请求到服务器
var data = {
username: 'John',
password: 'secret'
}
xhr.send(JSON.stringify(data))
15. ajax、axios、fetch的区别
- Ajax: 是一种较为早期的技术,基于 XMLHttpRequest 对象发起 HTTP 请求,提供了很大的灵活性,但也带来了复杂性。
- Fetch: 作为现代 API,它基于 Promise ,提供更简洁和易于理解的接口,使得处理异步请求 变得更加方便。不过在错误处理上有些不足。
- Axios: 基于 Promise 的 HTTP 客户端,适用于 浏览器 和 Node.js。它封装了 XMLHttpRequest 和 fetch API,提供了简单的 API 进行网络请求。尤其适合需要处理大量 HTTP 请求的项目,但需要依赖库的额外引入。
五、原型与原型链 ??
41. 对原型、原型链的理解
总结:
- 原型: 每个函数在创建时 都会自动生成一个 prototype 属性 ,它指向一个对象 (函数.prototype得到的这个对象 ),这个对象称为原型对象 。通过这个原型对象,所有由该函数构造出来的实例对象可以共享方法和属性。
- 原型链:
原型详解
javascript
var a=function (){}
console.log(a.prototype)
打印结果如下:从结果来看,函数.prototype得到的是一个对象,包含了constructor和_proto_。有constructor说明是一个构造函数。

构造函数创建实例时,把通用方法写到 prototype 上,让所有实例共享。
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
return 'Hi, I am ' + this.name;
};
const p1 = new Person('Tom');
const p2 = new Person('Jerry');
console.log(p1.sayHi === p2.sayHi); // true,共享方法
42. 原型修改、重写
43. 原型链指向
44. 原型链的终点是什么?如何打印出原型链的终点?
45. 如何获得对象非原型链上的属性?
六、执行上下文/作用域链/闭包
1. 对闭包的理解
闭包:当一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量时,就形成了闭包 。这些变量不会因为外部函数执行结束而被垃圾回收机制回收,而是被保留下来。
闭包的原理: "从内存角度 看,正常情况下函数执行完毕后,其执行上下文会被销毁,局部变量会被垃圾回收。但闭包 会让被引用的变量保留在内存 中,因为内部函数持有对这些变量的引用。"
闭包的形成条件:
- 必须有外层函数和内层函数的嵌套关系
- 内层函数必须访问外层函数作用域中的变量
- 内层函数被外部使用,也就是说内层函数必须以某种方式被保留(返回、赋值、传递等)
闭包的核心使用场景
- 数据私有化(模拟私有变量) 外层函数实例拿不到它定义的变量
counter.count 结果是 undefined - 函数工厂(柯里化)
- 防抖与节流
- 循环中的异步问题
javascript
function createCounter() {
let count = 0; // 这个变量本应在函数执行完后被销毁
let inner = function() {
count++; // 但闭包让它"活"了下来
return count;
}
inner() //只是内部调用,没有返回inner,不形成闭包
return inner //内层函数被返回到外部,形成闭包
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
使用场景一、数据封装和私有变量
javascript
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() { count++; },
getCount: function() { return count; }
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
console.log(counter.count); // undefined (无法直接访问)
使用场景二、函数工厂(柯里化)
javascript
function makeAdder(x) {
return function(y) {
return x + y;
};
}
const add5 = makeAdder(5);
console.log(add5(2)); // 7
使用场景三、循环中的异步问题
javascript
// ❌ 错误示例
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 输出 5 个 5
}, 1000);
}
// ✅ 使用闭包解决
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => {
console.log(j); // 输出 0, 1, 2, 3, 4
}, 1000);
})(i);
}
// ✅ 使用let解决
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i); // 1,2,3,4,5
}, 1000);
}
1.1 .使用场景四、防抖与节流
javascript
// 防抖
function debounce(fn, delay) {
let timer = null; // 闭包保存 timer
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用
const handleInput = debounce(() => {
console.log("搜索请求");
}, 500);