javascript面试题
解释下JavaScript中this是如何工作的?
全局作用域或函数调用: 在全局作用域或者普通函数调用中,this指向全局对象,也就是window
作为对象方法调用: 当函数作为对象的一个方法被调用时,this指向这个对象。
作为构造函数调用: 当使用new关键字调用函数时,this指向新创建的对象。
在事件处理函数中: 在 DOM 事件处理函数中,this通常指向触发事件的元素。
箭头函数: 箭头函数没有自己的this,它会捕获其所在(即定义的位置)上下文的this值。
使用call,apply,bind调用: 使用call,apply或bind方法,可以设置函数运行时的this值。
this的值是在函数被调用时确定的,而不是在函数被定义时确定。这就是 JavaScript 中的动态作用域。
简述异步线程,轮询机制,宏任务微任务?
同步任务: 指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
异步任务: 指的是不进入主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
1. 同步和异步任务在不同的执行"场所",同步的进入主线程,异步的进入Event Table执行并注册函数。
2. 当指定的异步事情完成时,Event Table会将这个函数移入Event Queue。
3. 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,推入主线程执行。
4. js引擎的monitoring process进程会持续不断的检查主线程执行栈是否为空,一旦为空,就会去
EventQueue那里检查是否有等待被调用的函数。上述过程会不断重复,也就是常说的
Event Loop(事件循环也可以叫事件轮询)
宏任务(macrotask)和微任务(microtask)
macrotask 和 microtask 表示异步任务的两种分类。
在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做
task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask
任务,周而复始,直至两个队列的任务都取完。
JavaScript 执行机制:
主线程任务——微任务——宏任务——宏任务里的微任务——宏任务里的微任务中的宏任务——知道任务全部完成
Javascript 怎样判断array 和 object ?
可以使用Array.isArray()方法来判断一个变量是否是数组,
使用typeof运算符结合Object.prototype.toString.call()来判断一个变量是否是对象。
var isArray = Array.isArray(value);
var isObject = value !== null && typeof value === 'object' &&
Object.prototype.toString.call(value) === '[object Object]';
Javascipt中async await 和promise和generator有什么区别
Promise
基本概念:Promise是一种处理异步操作的结果(成功或失败)的标准化对象,它可以链接多个回调函数,并且支持链
式调用。Promise有两种状态:fulfilled(已完成)和rejected(已拒绝),并且状态一旦确定就不可更改。
优点:解决了回调地狱问题,使得异步代码更加有序和易于理解。
缺点:虽然比传统的回调函数更易于理解和维护,但编写时仍然需要链式调用.then()和.catch(),
在某些情况下代码可能依然显得繁琐。
Generator
基本概念:Generator是一种特殊的函数,可以暂停执行并在之后恢复。通过使用yield关键字,可以在函数执行过程中
暂停,并在需要时通过迭代器恢复执行。与异步操作结合时,常与co这样的库一起使用,以自动处理Promise的迭代。
优点:使得异步代码看起来更像同步代码,提高了可读性。
缺点:需要额外的库来配合处理异步流程,不如async/await原生支持。
async/await
基本概念:async是声明异步函数的关键字,而await则用于等待Promise的解决。
async函数内部可以使用await来等待Promise的结果,使得异步代码读起来更像同步代码。
优点:
简洁易读:极大地简化了异步代码的编写,几乎和同步代码一样直观。
内置支持:不需要额外的库,大多数现代浏览器和Node.js环境都支持。
错误处理:可以使用try/catch进行错误处理,与同步代码的错误处理方式一致。
缺点:仅支持在async函数内部使用await。
Promise是基础,提供了异步编程的基本结构。
Generator通过暂停和恢复执行的能力,使得异步控制流更加灵活,但需要额外的库来完全发挥潜力。
async/await则是基于Promise的语法糖,进一步简化了异步编程模型,使得异步代码的编写和阅读更加接近同步代
码,是目前处理异步操作的首选方案。
Javascript解释什么是工厂模式,有什么优缺点?
工厂模式是一种创建型设计模式,在JavaScript中广泛使用,用于提供创建对象的接口,而无需指定所要创建的
对象的具体类。简单来说,它的核心目的是将对象的创建过程与使用过程分离,以提高代码的可维护性和灵活性。
优点
代码解耦:通过将对象的创建逻辑封装在工厂函数中,减少了客户端代码与具体对象创建过程之间的依赖,
提高了代码的可维护性和扩展性。
易于扩展:当需要添加新的对象类型时,只需修改工厂函数,无需改动已有的使用这些对象的代码。
隐藏创建细节:客户端不需要知道对象是如何被创建或初始化的,只需知道如何使用工厂函数获取所需对象即可。
缺点
难以识别对象类型:由于所有对象都是通过同一个工厂函数创建,很难直接通过构造函数名称等方式判断
对象的具体类型,可能需要额外的方法来识别对象类型。
过多的条件判断:如果需要支持大量不同类型对象的创建,工厂函数内部可能会包含大量的条件分支语句,
这会使得代码变得复杂且难以管理。
不支持抽象类的构造:与某些面向对象语言中的工厂方法模式相比,简单的工厂模式在JavaScript中
不能直接支持抽象类的构造,因为JavaScript本身不支持抽象类的概念。
简述页面从发送http请求到渲染页面的全过程?
地址解析:用户在浏览器地址栏输入URL后,浏览器首先对URL进行解析,确定协议、主机名、端口和路径等信息。
DNS查询:浏览器通过DNS(域名系统)将网址的域名转换成服务器的IP地址。这一过程可能包括查询本地DNS缓存、
操作系统缓存、路由器缓存以及向DNS服务器递归查询。
建立TCP连接:使用解析到的IP地址,浏览器与服务器建立TCP连接,这通常涉及三次握手过程,
以确保双方都准备好进行可靠的数据传输。
发送HTTP请求:建立连接后,浏览器构造一个HTTP请求报文,包含请求方法(如GET或POST)、请求URI、HTTP版本、
请求头(如User-Agent、Accept-Language等)以及可能的请求体(如果是POST请求),然后将其发送给服务器。
服务器处理请求:服务器接收到请求后,根据请求的内容进行处理,这可能包括查询数据库、执行服务器端脚本
(如PHP、Node.js等)、生成响应内容等。
生成响应:服务器完成请求处理后,构造一个HTTP响应报文,包含状态码(如200 OK表示成功)、响应头
(如Content-Type、Server等)以及响应体(即实际要传输的数据,如HTML、CSS、JavaScript文件等)。
下载响应内容:浏览器接收到来自服务器的响应后,开始下载响应体。如果响应是一个HTML文档,浏览器会继续解析这个文档。
解析HTML:浏览器解析HTML文档,构建DOM(文档对象模型)树,这棵树代表了文档的结构。
加载CSS和JavaScript:在解析HTML过程中遇到CSS和JavaScript引用时,浏览器会发送额外的HTTP请求获取这些资
源。CSS用于样式渲染,而JavaScript可能会修改DOM结构、执行逻辑或请求额外数据。
渲染页面:当DOM树构建完成且CSSOM(CSS对象模型)可用时,浏览器开始渲染页面。它将DOM和CSSOM合并为一个渲染
树,计算每个节点的具体布局(布局阶段),然后将布局结果绘制到屏幕上(绘制阶段)。
执行JavaScript:JavaScript可以在这整个过程中不同阶段执行,初期执行可能会影响DOM构建,后期执行则可能修改
已渲染的页面,触发重排(reflow)或重绘(repaint)。
页面交互:页面完全渲染后,用户可以与页面交互,此时JavaScript监听事件并作出相应处理,可能再次触发网络请
求、更新DOM等操作。
Javascript 闭包是什么,闭包形成的原因和闭包的用途 ?
闭包: 它指的是有权访问另一个函数作用域中变量的函数,即使外部函数已经执行完毕,只要内部函数还被引用着,
其词法作用域就会保存下来;
函数嵌套:当一个函数内部定义了另一个函数时,内部函数(称为闭包)可以访问其外部函数的变量。
作用域链:在JavaScript中,每个函数在创建时都会生成一个作用域链,用于保存在其外部环境中声明的所有变量。
当内部函数引用了外部函数的变量时,这个变量及其作用域就会被闭包所保留,即使外部函数已经执行结束。
用途:
数据封装:闭包可以用来创建私有变量,实现数据隐藏,避免全局变量的污染。例如,可以在外部无法直接访问的环境中保存状态信息。
函数记忆(Memoization):利用闭包存储计算结果,当相同的输入再次出现时,可以直接从闭包中返回之前计算的结果,提高效率。
异步编程中的状态保持:在处理异步操作时,闭包可以帮助维持函数调用之间的状态,确保回调函数能够访问到正确的变量值。
模块化设计:通过闭包,可以模拟实现模块化,将相关的函数和数据组织在一起,减少全局变量的使用,提高代码的组织性和可维护性。
Javascript垃圾回收方法?
垃圾回收主要依赖于两种基本算法:标记-清除(Mark-and-Sweep)和引用计数(Reference Counting)
标记-清除(Mark-and-Sweep)
标记阶段:垃圾回收器从根对象(如全局变量、执行上下文栈中的变量)开始,遍历所有可达的对象,将它们标记
为“活着”。这个过程是递归进行的,所有可从根对象通过引用链访问到的对象都会被标记。
清除阶段:遍历完所有可达对象后,垃圾回收器会遍历堆中的所有对象,那些未被标记的对象被视为不再使用,
即“垃圾”,会被回收。之后,内存空间被释放,可供新对象使用。
引用计数
这种方法相对简单,每个对象都有一个引用计数器,当一个对象被创建时,其引用计数初始化为1。每当有一个新的
引用指向该对象时,计数器加1;当引用被删除或离开作用域时,计数器减1。当对象的引用计数降为0时,表明没
有变量再引用该对象,该对象即可被回收
尽管引用计数直观易懂,但它存在循环引用的问题,可能导致内存泄漏。因此,现代JavaScript引擎更多依赖于
标记-清除或其改进版——分代收集算法。
分代收集:将内存分为年轻代和老年代。新分配的对象通常放在年轻代,频繁的垃圾回收会在此进行,
因为大部分小对象很快就会变为不可达。如果一个对象经过多次垃圾回收仍然存活,它会被移动到老年代,
老年代的垃圾回收频率较低,因为长期存活的对象更可能持续存活。
增量标记与并发标记:为了避免在标记阶段导致长时间的暂停,现代JavaScript引擎采用增量标记
和/或并发标记技术,允许标记过程与JavaScript代码执行交替进行,减少垃圾回收带来的卡顿感。
空闲时间收集:一些引擎会选择在浏览器空闲时执行垃圾回收,以减少对用户体验的影响。
综上所述,JavaScript的垃圾回收是一个复杂且不断优化的过程,旨在自动管理内存,提高应用性能和响应性。
简述JavaScript中什么是柯里化?
柯里化(Currying)是函数式编程中的一个重要概念,它是指将原来接受多个参数的函数转换成一系列只接受单个参数
的函数的过程。换句话说,柯里化是将一个多参数函数转换为一系列嵌套的单参数函数,每次调用只处理一个参数,
直至所有参数都被处理完毕。
函数的柯里化,需要依赖参数以及递归,通过拆分参数的方式,来调用一个多参数的函数方法,以达到减少代码冗余,
增加可读性的目的。
柯里化的用处:延迟计算, 参数复用, 动态生成函数
应用场景:
减少重复传递不变的部分参数;
将柯里化后的callback参数传递给其他函数。
简述 JavaScript 中的高阶函数是什么?
JavaScript 中的高阶函数是一种接受一个或多个函数作为参数或返回一个新函数作为结果。
常用高阶函数的一个常见用法是对数组进行操作,例如使用 map()、reduce() 和 filter()。
这些函数允许您在数组上应用自定义的函数,并对数组的每个元素执行操作。
优点:代码复用,代码简化,实现函数组合、柯里化
简述JavaScript什么是构造函数?它与普通函数有什么区别?
构造函数是一种特殊的函数,主要用于创建并初始化一个新的对象实例。构造函数主要用于定义对象的属性和方法。
实例化构造函数时:
1、创建一个新的空对象:new操作符首先会在内存中创建一个新的空对象。
2、设置原型链:新创建的对象的[[Prototype]](或可以使用__proto__访问)属性会被设置为构造函数的
prototype属性所指向的对象,从而继承构造函数原型上的方法和属性。
3、绑定this值:构造函数内的this关键字会被绑定到新创建的对象上。
4、执行构造函数体:接着执行构造函数的代码,通常用于给新对象添加属性和方法。
5、返回对象:如果构造函数没有显式返回一个对象(或者返回null或undefined),那么new操作符会自动
返回刚创建的那个对象。
构造函数与普通函数的区别:
1、调用方式:构造函数通常与new操作符一起使用,用于创建并初始化对象;而普通函数可以直接调用,
不使用new关键字。
2、默认返回值:普通函数可以根据需要返回任何类型的值;而构造函数默认不需手动返回值,new操作符
会自动返回新创建的对象实例,除非构造函数显式返回了一个对象(非基本类型值),这时返回的是该对象。
3、this的绑定:在普通函数中,this的值取决于函数的调用方式(全局对象、严格模式下的
undefined、对象方法中的调用对象等);而在构造函数中,this默认绑定到新创建的对象实例上。
4、用途:构造函数主要用于创建特定类型的对象,并初始化其属性;普通函数的用途更为广泛,可以用
于执行任何逻辑操作,不仅仅是创建对象。
解释什么是JavaScript时间死区?
JavaScript中的"时间死区"特指在使用let和const声明变量时,在变量声明之前,该变量是不可访问的区域。
这个概念是在ES6(ECMAScript 2015)中引入的,旨在解决变量提升(hoisting)可能引起的一些问题,
特别是对于var声明的变量在声明前可能默认为undefined的情况。
在代码块内,使用let或const命令声明变量之前,该变量都是不可用的,在变量声明之前属于该变量的“死区”,
这在语法上被称为“暂时性死区”。
时间死区的意义
时间死区的设计旨在增强JavaScript的代码可读性和避免运行时错误,比如意外地使用了未初始化的变量。
它强制开发者在使用变量前明确地声明它们,从而提高了代码的清晰度。
JavaScript 中有多少个线程?
在传统的JavaScript执行环境中,如浏览器和Node.js的主线程中,默认情况下JavaScript是单线程的。
这意味着在给定的时间点,JavaScript只能执行一个任务。
为了实现异步处理和非阻塞操作,JavaScript采用了事件循环(Event Loop)和回调队列(Callback Queue)机制,
并且近年来引入了Promise、async/await等高级异步编程模型,以提高执行效率和用户体验。
简述Set、Map、WeakSet 和 WeakMap 的区别 ?
Set
特点:Set是一个不包含重复元素的集合,用于存储唯一的值(无论是基本类型还是引用类型)。
用途:适用于去重、成员检测等场景。
键值特点:只有值,没有键。
内存管理:强引用,垃圾回收不会回收Set中对象的引用。
Map
特点:Map是一种键值对的集合,允许使用任何类型的值(包括对象)作为键或值。
用途:适用于需要使用任意类型作为键的场景,比如对象映射。
键值特点:每个条目包含一个键和一个值。
内存管理:强引用,垃圾回收不会回收Map中对象的引用。
WeakSet
特点:WeakSet类似于Set,但成员必须是对象,且是弱引用。
用途:适合用于跟踪对象,而不用担心阻止垃圾回收。
键值特点:只有值(对象),没有键,且值只能是对象,不允许重复。
内存管理:弱引用,当WeakSet中的对象没有任何其他引用时,垃圾回收器可以回收这些对象,不会造成内存泄漏。
WeakMap
特点:类似于Map,但其键必须是对象,且是弱引用。
用途:适合关联附加信息到对象上,而不会影响这些对象的垃圾回收。
键值特点:键必须是对象,值可以是任意类型,键是弱引用。
内存管理:弱引用,当WeakMap中的键对象没有任何其他引用时,整个键值对可以被垃圾回收。
区别:
强引用 vs 弱引用:Set和Map使用强引用,可能导致内存泄漏,如果忘记清理集合中的对象;WeakSet和WeakMap
使用弱引用,不会阻止垃圾回收,更适合于临时关联数据或跟踪对象而不影响其生命周期。
键的类型:Map和WeakMap允许任何类型的键,而Set和WeakSet仅能存储值(Set可以是任意类型,
WeakSet必须是对象)。
应用场景:根据是否需要唯一性、是否关心键的生命周期、以及是否需要任意类型作为键,选择合适的集合类型。
async/await 对比Promise的优势
解决问题的角度:Promise 解决了多个回调函数嵌套的时候会造成回调地狱问题,不利于代码维护。async/await
解决 Promise的多个 then 的链式调用问题。
错误处理:在 Promise 中,使用 catch 方法来捕获和处理错误。而在 async/await 中,可以使用 try/catch
语句来捕获异步函数中的错误。
可读性:相对于 Promise 的链式调用,async/await 更接近传统的同步编程风格,使得异步代码更易于理解和维护。
异步操作的顺序控制:使用 Promise 时,你可以使用 .then 方法将多个异步操作串联起来,或者使用
Promise.all 来等待多个异步操作都完成。而使用 async/await,则可以使用 await 关键字按照顺序依次执行异步操作。
Promise 使用链式调用的方式处理异步操作,通过 then 和 catch 方法来注册回调函数。
async/await 使用更直观的同步语法,使用 async 声明一个 function 是异步函数,然后通过 await 关键字等待
一个异步方法执行完成,并且会阻塞当前函数体内后面的代码,等await等待的 promise对象执行完毕后,再执行阻塞
的代码。规定await只能出现在async函数内。async 函数返回的是一个 Promise 对象。
ES6简述module、export、import的作用 ?
module(模块): 模块是包含一组功能(变量、函数、类等)的JavaScript文件。每个模块都是独立的,它可以导出自
己的功能供其他模块使用,也可以导入其他模块提供的功能。
export(导出): export关键字用于将模块内部定义的变量、函数、类等导出,使得其他模块可以通过import语句使用
它们。一个模块可以导出多个功能,也可以导出一个默认功能。
import(导入): import关键字用于导入另一个模块导出的功能。你可以导入一个模块的特定功能,也可以导入模块的
全,import语句必须位于模块的顶层,不能在条件语句或循环中使用。
部功能。
作用:
代码重用和封装:模块化允许开发者编写可重用且封装良好的代码块。
命名空间:使用模块可以避免全局命名空间污染,因为模块内定义的变量、函数、类等不会自动成为全局作用域的一部分。
依赖管理:通过模块化,可以清晰地定义模块间的依赖关系。
提高维护性:模块化使得代码更易于维护和更新,因为功能被划分到独立的模块中。
简述你对ES6中新增的set,map两种数据结构的理解?
Set是一种新的集合类型,用于存储唯一值,不允许重复。它类似于数组,但每个元素只能出现一次,
Set自动排除任何重复的值。
主要特性:
唯一性:集合中的值都是唯一的,尝试添加重复的值不会有任何效果。
迭代:Set是可迭代的,可以使用for...of循环遍历。
操作方法:提供了.add(value)、.delete(value)、.has(value)和.clear()等方法来操作集合。
大小:可以通过.size属性获取集合的大小(元素数量)
Map是一种新的键值对集合结构,它允许使用任意类型的值作为键,这与传统的对象(只能使用字符串或Symbol作为键)不同。
主要特性:
键的多样性:键可以是任何类型的值,包括对象、函数或任何原始类型。
保持键值对的插入顺序:遍历Map时,键值对会按照插入的顺序返回。
操作方法:提供了.set(key, value)、.get(key)、.has(key)、.delete(key)和.clear()等方法来操作键值对。
大小:可以通过.size属性获取键值对的数量。
应用场景:
当需要使用非字符串作为键时。
存储额外的键值对信息,且需要高效查询。
实现复杂的键值对逻辑,比如缓存机制。
总结:Set和Map提供了更专业的集合类型来存储唯一值和键值对,它们解决了传统对象的一些限制(如键的类型限制和
唯一性保证),使得数据存储和管理更加灵活和高效。
如何怎么理解ES6中的Promise?
Promise是一个代表了异步操作最终完成或失败的对象。它允许你为异步操作的成功结果或失败原因关联处理程序
(handlers)。通过使用Promise,可以避免更深层次的嵌套回调,即所谓的“回调地狱”,并且可以编写出更清晰和易
于维护的异步代码。
Promise有三种状态:
pending(等待中):初始状态,既不是fulfilled也不是rejected。
fulfilled(已成功):异步操作成功完成。
rejected(已失败):异步操作失败。
Promise一旦从pending变为fulfilled或rejected,就不会再变,这称为Promise的不可变性。
Promise的优势
改善代码可读性:提供了一种更好的方式来组织和处理异步操作的结果。
错误处理:通过.catch()方法,可以集中处理异步操作链中的错误。
同步化异步流程:通过链式调用和async/await(ES7引入)可以以同步的方式写异步代码,提高代码的清晰度。
简述ES6 Symbol的作用?
Symbol的主要作用和特性包括:
唯一性: 每个通过Symbol()函数创建的symbol值都是唯一的,即使是用相同的参数创建的symbol也不相等。
这保证了使用symbol作为对象属性名时,不会与其他属性名发生冲突。
不可变性: symbol一旦被创建,就不能被修改。它们是不可变的,确保了属性名的稳定性。
使用场景:
私有属性: Symbol常被用来作为对象的私有成员,因为symbol类型的属性不会出现在常规的对象属性枚举中,
例如for...in循环或Object.keys()方法中,这使得symbol属性可以被视为对象的私有属性。
防止命名冲突: 在大型项目或者是多人协作的项目中,使用symbol可以防止属性名的冲突,特别是在扩展
第三方库的对象时尤其有用。