JS核心知识点之箭头函数与this详细解析
作者:SnowJasmine
一.数组去重的方式有那些?
数组去重的核心是筛选出数组中唯一不重复的元素,推荐优先使用ES6,代码简洁,性能优异
1.Set配合扩展运算符
Set 是 ES6 提供的不允许重复值的集合,配合扩展运算符 /Array.from 可快速去重。

2.利用indexOf/includes 遍历判断
遍历数组,用 indexOf(返回 - 1 表示不存在)或 includes(返回布尔值)判断元素是否已存在于新数组,不存在加入新数组,存在则不加入

还可以使用forEach+includes实现

3.利用filter + indexOf/Set(筛选式去重)

二.讲述一下手写深拷贝的逻辑
核心就 3 步,抓住本质即可:
- 判断类型:如果是基本类型 /
null,直接返回(无需拷贝); - 创建容器:区分数组和普通对象,创建对应的空新对象 / 新数组;
- 递归拷贝:遍历原数据的属性 / 元素,递归调用深拷贝,赋值给新容器。
满足绝大多数简单场景简化版手写代码

以下是测试

总结:
- 简化版深拷贝的核心是「类型判断 + 递归拷贝」,3 步即可实现普通场景的深拷贝需求。
- 基础简化版足以应对日常大部分简单场景(基本类型、普通对象、数组)。
三.this指向的理解
1.this的核心本质:this 是 JavaScript 中的关键字,它的指向不是在定义时确定的,而是在函数调用时确定的(即 “调用时绑定”)。简单说:this 指向的是「调用函数的那个对象」,谁调用函数,this 就指向谁。
2.this 的 4 种核心绑定规则(从常用到特殊):
1.默认绑定(独立函数调用)
当函数独立调用(没有明确的调用对象)时,this 指向全局对象
浏览器环境:全局对象是 window。
Node.js 环境:全局对象是 global。
严格模式(use strict):默认绑定的 this 是 undefined

2.隐式绑定
当函数作为对象的方法被调用时,this 指向这个调用函数的对象(即 “点前面的对象”)

3. 显式绑定(手动指定 this)
通过 call、apply、bind 方法,可以手动指定 this 的指向,不受调用方式影响。
call/apply:立即执行函数,第一个参数是 this 指向的对象
bind:返回一个新函数(不立即执行),新函数的 this 永久绑定为第一个参数

4.new绑定
当函数作为构造函数(用 new 关键字调用)时,this 指向新建的实例对象。

5.特殊场景 箭头函数
箭头函数没有自己的 this,它的 this 是继承自外层作用域的 this(定义时确定,而非调用时),不受绑定规则影响。

总结:
this指向是「调用时确定」的,核心原则是 “谁调用,指向谁”(箭头函数除外)。- 核心绑定规则:默认绑定(全局 /undefined)、隐式绑定(对象方法)、显式绑定(call/apply/bind)、new 绑定(构造函数实例)。
- 箭头函数无自身
this,继承外层作用域的this(定义时确定)。 - 绑定优先级:
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
四. 如何使用promise封装原生ajax?
封装思路
- 创建 Promise 实例:用 Promise 包裹 AJAX 逻辑,成功时调用
resolve,失败时调用reject。 - 创建 XHR 对象:通过
new XMLHttpRequest()生成 AJAX 核心对象。 - 配置请求:调用
open()方法设置请求方式、URL、是否异步。 - 处理响应:监听
onload事件(请求完成),判断响应状态码,成功则传递响应数据,失败则传递错误信息。 - 处理网络错误:监听
onerror事件(网络异常),触发reject。 - 发送请求:调用
send()方法,携带请求体(POST 请求时使用)。 - 可选配置:设置请求头(如 POST 请求的
Content-Type)。
注意事项
- 跨域问题:原生 AJAX 受同源策略限制,跨域需后端配置 CORS 或使用 JSONP(本封装不支持 JSONP)。
- 请求体格式:如果需要提交表单数据(
application/x-www-form-urlencoded),需将data转为key=value格式,并对应设置请求头。 - 兼容性:
XMLHttpRequest支持 IE7 及以上浏览器,如需兼容更低版本,需使用ActiveXObject(极少场景需要)。
总结
- 核心是用 Promise 包裹原生 AJAX 流程,成功调用
resolve、失败调用reject,实现异步流程的优雅管理。 - 封装关键步骤:创建 XHR 对象 → 配置请求 → 设置请求头 → 处理响应 / 错误 → 发送请求。
- 使用时通过
then接收成功数据、catch捕获错误,支持 GET/POST 等常见请求方式,可灵活配置参数。 - 该封装支持 JSON 格式请求 / 响应,自动拼接 GET 参数,具备默认配置,可直接用于日常简单接口请求。
五. 箭头函数和普通函数的区别?
箭头函数是ES6新增的函数语法,与普通函数(ES5函数)相比,核心区别体现在6个方面,具体如下:
this指向不同(核心区别):普通函数的this指向动态变化,取决于调用方式(全局调用指向window/global,对象方法调用指向该对象,构造函数调用指向实例,call/apply/bind可手动改变this);箭头函数没有自己的this,其this继承自外层作用域的this,且一旦确定无法改变(call/apply/bind对其this无效,仅能传递参数)。
构造函数特性:普通函数可作为构造函数使用new关键字创建实例(此时this指向实例);箭头函数不能作为构造函数,使用new调用会抛出TypeError错误(因箭头函数没有prototype属性)。
arguments对象:普通函数内部有arguments对象,用于存储实参列表(类数组,可通过Array.from转换为数组);箭头函数没有arguments对象,若需获取实参,可使用剩余参数(...args)。
prototype属性:普通函数有prototype属性,其原型上的方法可被实例继承;箭头函数没有prototype属性。
函数体简化:箭头函数支持简洁语法,若函数体只有一条return语句,可省略大括号和return关键字(如(a,b) => a+b);若返回对象,需用小括号包裹(如(a,b) => ({name: a, age: b}));普通函数无此简化语法。
不能用作Generator函数:箭头函数不能使用yield关键字,无法作为Generator函数;普通函数可以。
六. 浏览器环境中事件循环的理解?
浏览器环境的事件循环(Event Loop)是JavaScript解决单线程执行阻塞问题的核心机制,其本质是协调调用栈、宏任务队列(Macro Task Queue)和微任务队列(Micro Task Queue)的执行顺序,确保异步任务有序执行,具体逻辑和流程如下:
核心前提:JavaScript是单线程语言,同一时间只能执行一个任务,若直接执行异步任务(如定时器、网络请求、DOM事件)会导致阻塞,因此需要事件循环机制区分同步/异步任务的执行时机。
关键组件:
调用栈:用于执行同步任务的栈结构,遵循“先进后出”原则,函数调用时入栈,执行完毕后出栈;
宏任务队列:存储宏任务(异步任务的一种),常见类型有:script(整体代码)、setTimeout/setInterval、I/O操作(如文件读取)、UI渲染、requestAnimationFrame;
微任务队列:存储微任务(异步任务的一种),常见类型有:Promise.then/catch/finally、MutationObserver、process.nextTick(Node环境特有,浏览器无)、queueMicrotask。
执行流程(核心规则):
1. 先执行调用栈中的同步任务,直到调用栈为空;
2. 执行微任务队列中的所有微任务(按队列顺序依次执行),直到微任务队列为空;
3. 执行一次宏任务队列中的第一个宏任务,将其推入调用栈执行,执行完毕后调用栈为空;
4. 重复步骤2-3,形成循环(即“事件循环”)。
注意点:
整体script代码属于宏任务,优先执行;
微任务的执行优先级高于宏任务,每次执行完一个宏任务后,必须清空所有微任务再执行下一个宏任务;
UI渲染任务在微任务队列清空后、下一个宏任务执行前进行(不同浏览器可能有细微差异,但核心逻辑一致)。
七. === 和 Object.is() 的区别?
===(严格相等运算符)和Object.is()都是JavaScript中用于判断两个值是否“严格相等”的方式,但二者在处理特殊值(如NaN、0和-0)时存在差异,核心区别如下:
核心一致点:均不进行类型转换,直接比较值的类型和具体值,若类型不同则直接返回false(这一点区别于==,==会先进行类型转换再比较值)。
关键差异点(特殊值处理):
处理NaN:===认为NaN !== NaN(这是JavaScript的设计缺陷,NaN与任何值都不相等,包括自身);而Object.is(NaN, NaN)返回true(修复了NaN的比较问题)。
处理0和-0:===认为+0 === -0(因为二者在数值上等价);而Object.is(+0, -0)返回false(因为+0和-0在底层存储和某些运算场景中存在差异,如1/+0=+Infinity,1/-0=-Infinity)。
普通值比较一致性:对于除NaN、+0、-0之外的普通值,二者结果完全一致。例如:
5 === 5 → true;Object.is(5,5) → true;
'5' === 5 → false;Object.is('5',5) → false;
null === undefined → false;Object.is(null, undefined) → false。
总结:Object.is()可以理解为“更严格的===”,它修正了===在NaN和+0/-0比较上的不合理之处,更符合直觉上的“值相等”判断。
八. promise的理解?
Promise是ES6引入的异步编程解决方案,用于解决传统回调函数(回调地狱)的问题,其本质是一个状态容器,存储着异步操作的结果(未完成/已完成/已失败),并提供统一的API供开发者处理异步结果,核心特性和用法如下:
核心定义:Promise是一个构造函数,可通过new Promise((resolve, reject) => { ... })创建实例,接收一个“执行器函数”作为参数,执行器函数立即执行,内部包含异步操作逻辑。
三个状态(不可逆):注意:状态一旦从pending转为fulfilled或rejected,就无法再改变。
pending(等待态):初始状态,异步操作未完成;
fulfilled(成功态):异步操作完成,调用resolve()后从pending转为fulfilled,状态固定;
rejected(失败态):异步操作失败,调用reject()后从pending转为rejected,状态固定。
核心方法(原型方法):
then():接收两个可选参数(成功回调、失败回调),用于处理fulfilled和rejected状态的结果;then()方法返回一个新的Promise实例,因此支持链式调用(解决回调地狱的核心);
catch():专门处理rejected状态的结果,等价于then(null, 失败回调);通常用于链式调用的末尾,统一捕获前面所有异步操作的错误;
finally():无论Promise状态是fulfilled还是rejected,都会执行的回调;finally()返回一个新的Promise,且回调函数不接收任何参数(无法获取异步结果),主要用于执行清理操作(如关闭加载动画)。
静态方法(工具方法):
Promise.resolve(value):快速创建一个fulfilled状态的Promise,value为成功结果;若value本身是Promise,则直接返回该Promise;
Promise.reject(reason):快速创建一个rejected状态的Promise,reason为失败原因;
Promise.all(iterable):接收一个可迭代对象(如数组),包含多个Promise;只有所有Promise都变为fulfilled,才返回fulfilled状态的Promise(结果为所有Promise成功结果的数组);只要有一个Promise变为rejected,立即返回rejected状态的Promise(结果为第一个失败的原因);
Promise.race(iterable):接收可迭代对象,返回一个新Promise;只要有一个Promise状态改变(fulfilled或rejected),就立即返回该状态的结果(“竞速”机制);
Promise.allSettled(iterable):接收可迭代对象,等待所有Promise都完成(无论成功或失败)后,返回fulfilled状态的Promise,结果为每个Promise的状态和结果组成的数组(解决了Promise.all()一个失败就整体失败的问题);
Promise.any(iterable):接收可迭代对象,只要有一个Promise变为fulfilled,就返回fulfilled状态的结果;只有所有Promise都变为rejected,才返回rejected状态的Promise(与Promise.all()相反)。
核心优势:解决回调地狱(回调嵌套过深导致的代码可读性差、维护困难),通过链式调用将异步操作线性化;统一异步操作的错误处理机制(catch()统一捕获)。
注意点:Promise一旦创建,执行器函数立即执行,无法中途取消;若未添加catch()捕获错误,失败状态的Promise会抛出未捕获错误,影响程序执行。
九. 递归函数的理解?
递归函数是指在函数内部直接或间接调用自身的函数,是一种常用的编程思想,核心用于将复杂的大问题拆解为与原问题结构相似的小问题,通过解决小问题最终解决大问题,其核心要素和注意事项如下:
核心组成(两个必要条件):
递归终止条件(基线条件):函数必须有一个明确的终止条件,当满足该条件时停止调用自身,否则会导致无限递归(栈溢出错误,如Maximum call stack size exceeded);
递归递推条件(递归步骤):函数在内部调用自身时,必须将问题规模缩小(即传递的参数更接近终止条件),确保最终能触发终止条件。
执行原理:递归函数的执行依赖调用栈,每次调用自身时,会将当前函数的执行上下文(参数、局部变量、执行位置)压入调用栈;当触发终止条件后,函数开始逐步返回,同时从调用栈中弹出执行上下文,依次执行剩余逻辑。
典型应用场景:
数学问题:计算阶乘(n! = n × (n-1) × ... × 1)、斐波那契数列、幂运算等;
数据结构操作:遍历树(二叉树前序/中序/后序遍历)、图的深度优先搜索(DFS)、链表反转等;
复杂逻辑拆解:如文件目录遍历(多层嵌套目录的读取)。
示例(计算n的阶乘):
// 递归函数:计算n的阶乘 function factorial(n) { // 终止条件:n=1时返回1 (1! = 1) if (n === 1) return 1; // 递推条件:n! = n × (n-1)!,缩小问题规模 (n-1) return n * factorial(n - 1); } factorial(5); // 结果:120(5×4×3×2×1) }优缺点:
优点:代码简洁、逻辑清晰,能直观体现问题的递归结构;
缺点:递归调用会占用额外的栈空间,若递归深度过深易导致栈溢出;重复计算问题(如斐波那契数列的简单递归实现,会重复计算大量相同子问题),效率较低。
优化方案:
尾递归优化:将递归调用放在函数的最后一步(尾调用),部分语言(如ES6规范中的JavaScript)支持尾递归优化,可避免栈溢出(但实际浏览器支持度有限);
记忆化缓存:将已计算的子问题结果缓存起来,避免重复计算(如用对象存储斐波那契数列的已计算值);
迭代改写:将递归逻辑改为循环迭代(如用for循环计算阶乘),完全避免递归调用的栈空间占用。
十. 快速排序的逻辑是什么?
快速排序是一种高效的排序算法,基于分治思想,核心逻辑是通过“选基准、分区、递归排序”三步,将无序数组逐步拆解为有序子数组,最终合并为完整的有序数组,其时间复杂度平均为O(n log n),最坏为O(n²)(可通过合理选基准优化),具体逻辑步骤如下:
步骤1:选择基准元素(pivot):从待排序数组中选择一个元素作为“基准”,基准的选择直接影响排序效率,常见选择方式有:
选数组第一个元素或最后一个元素(简单但易导致最坏情况,如有序数组);
选数组中间元素(折中方案);
随机选择元素(推荐,可降低最坏情况概率)。
步骤2:分区操作(partition):以基准元素为标准,将待排序数组划分为两个子数组: 分区实现逻辑(双指针法):
左子数组:所有元素都小于基准元素;
右子数组:所有元素都大于或等于基准元素;
基准元素最终会落在其“正确的排序位置”(即最终有序数组中该元素的位置)。
初始化左指针(left)指向数组起始位置,右指针(right)指向数组末尾;
右指针向左移动,找到第一个小于基准的元素,停止移动;
左指针向右移动,找到第一个大于或等于基准的元素,停止移动;
交换左、右指针指向的元素;
重复步骤2-4,直到左指针 >= 右指针;
交换基准元素与左指针(或右指针)指向的元素,完成分区,此时基准元素位于正确位置。
步骤3:递归排序子数组:对分区后的左子数组和右子数组,分别重复步骤1-2(选基准、分区),直到子数组的长度为0或1(此时子数组已天然有序,无需继续排序)。
步骤4:合并结果:由于左子数组、基准元素、右子数组已分别有序,将三者直接拼接即可得到完整的有序数组(递归过程中无需额外合并操作,分区后自然有序)。
示例(以数组[5,2,9,3,7,6,1]为例):
选基准:假设选中间元素3;
分区:通过双指针交换,最终得到左子数组[2,1]、基准3、右子数组[5,9,7,6];
递归排序左子数组[2,1]:选基准2,分区得到[1]、2、空数组,左子数组排序完成;
递归排序右子数组[5,9,7,6]:选基准7,分区得到[5,6]、7、[9];再递归排序[5,6],得到[5,6];
拼接结果:[1,2] + [3] + [5,6,7,9] → 最终有序数组[1,2,3,5,6,7,9]。
核心特点:
原地排序:不需要额外的大量存储空间(仅递归调用占用栈空间),空间复杂度为O(log n)(递归深度);
不稳定排序:相等元素的相对位置可能在排序过程中改变(如数组[3,2,3,1],排序后两个3的位置可能颠倒);
高效性:平均时间复杂度O(n log n),是实际开发中最常用的排序算法之一(如V8引擎的Array.sort()在数组长度大于22时使用快速排序的变体)。
总结
到此这篇关于JS核心知识点之箭头函数与this详细解析的文章就介绍到这了,更多相关JS箭头函数与this内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
