javascript面试题

JavaScript

js有哪些内置对象?

JavaScript有许多内置对象,包括但不限于:

  • 基本对象: Object、Boolean、Symbol、Number、String
  • 符合数据结构:Array、Set、Map、WeakSet、WeakMap
  • 日期和时间对象:Date
  • 数学计算对象:Math
  • 正则表达式对象:RegExp
  • 函数对象:Function
  • 错误对象:Error、TypeError、RangeError等
  • 其他对象:Global、JSON等

什么是闭包?

闭包是指一个函数可以访问另一个函数作用域内的变量。当一个函数嵌套在另一个函数中时,内部函数可以访问外部函数的变量,即使外部函数已经返回了。这种情况下,内部函数形成了一个闭包,它保留了外部函数的作用域链并可以继续访问这些变量。闭包常常用于实现函数的封装和私有化,以及在回调和事件处理等场景下的数据共享与传递。

如何理解作用域、作用域链和执行上下文?

在JavaScript中,作用域、作用域链和执行上下文是密切相关的概念,它们与变量和函数的查找、访问以及生命周期有关。

  1. 作用域(Scope):作用域是一个变量或函数的可访问范围。JavaScript中有三种作用域:全局作用域局部(函数)作用域块级作用域。全局作用域中声明的变量和函数可以在整个代码中访问,局部作用域中声明的变量和函数只能在特定的函数内部访问,块级作用域在一对花括号内定义,对letconst关键字声明的变量有效。变量的生命周期受其作用域的限制。全局作用域中的变量在整个程序执行过程中持续存在,局部作用域中的变量在函数执行结束时销毁,块级作用域在代码块执行结束时,块级作用域中的变量将被销毁。

  2. 作用域链(Scope Chain):当代码执行过程中访问一个变量或函数时,JavaScript引擎会沿着作用域链查找该标识符。作用域链是由当前执行上下文的作用域和其所有父级作用域组成的链表。查找过程从当前作用域开始,然后逐级向上查找,直到找到目标标识符或到达全局作用域。如果在全局作用域中仍未找到目标标识符,则返回undefined

  3. 执行上下文(Execution Context):执行上下文是JavaScript代码执行过程中的环境。每当进入一个新的函数执行或全局代码执行时,都会创建一个新的执行上下文。执行上下文包含了当前执行的代码所需的所有信息,如变量、函数、作用域链等。JavaScript引擎使用执行上下文栈(Execution Context Stack)来管理执行上下文。栈顶的执行上下文为当前执行的代码环境。当一个函数被调用时,一个新的执行上下文被压入栈顶;当函数执行结束时,执行上下文从栈顶弹出,返回到调用者的上下文环境。

总结起来,作用域是变量和函数的可访问范围;作用域链是由当前执行上下文的作用域和其父级作用域组成的链表,用于在代码执行过程中查找变量和函数;执行上下文是代码执行过程中的环境,包含了当前执行的代码所需的所有信息。这三者共同决定了代码执行过程中变量和函数的查找、访问以及生命周期。

如何创建一个没有原型的对象?

可以使用 Object.create(null) 方法创建一个没有原型的对象。这个方法创建一个全新的对象并将其原型设置为 null,因此它没有继承任何属性或方法。例如:

const obj = Object.create(null);
console.log(obj.toString); // undefined

如何理解原型链?

原型链是 JavaScript 中实现继承的一种机制,它通过让一个对象的原型指向另一个对象,从而使得一个对象可以访问另一个对象中定义的属性和方法。当我们试图访问一个对象中不存在的属性或方法时,JavaScript 引擎会沿着原型链一直向上查找,直到找到该属性或方法为止,或者最终抵达 Object.prototype(所有对象的祖先)上停止查找。

let const var比较

  1. 作用域:
  • var:声明的变量具有函数作用域。这意味着在函数内部声明的变量只能在该函数内部访问,而在函数外部声明的变量具有全局作用域。
  • letconst:声明的变量具有块级作用域。这意味着变量仅在声明它们的代码块(例如:if语句、for循环、while循环等)内部可访问。
  1. 变量提升(Hoisting):
  • var:声明的变量会被提升到所在作用域的顶部。这意味着在声明之前访问变量不会导致引用错误,但变量的值将是undefined
  • letconst:声明的变量不会被提升。在声明之前访问变量会导致引用错误。
  1. 重复声明:
  • var:允许在同一作用域内多次声明同名变量,后续声明将被忽略。
  • letconst:在同一作用域内不允许重复声明同名变量。尝试这样做会导致语法错误。
  1. 变量的可变性:
  • varlet:声明的变量可被重新赋值。
  • const:声明的变量是不可变的,即一旦赋值,无法更改。这对于声明常量或确保某个变量在整个程序执行过程中保持不变的情况非常有用。

谈谈你对变量提升的理解

变量提升(Hoisting)是 JavaScript 的一个核心概念,理解它对于编写和理解代码非常重要。以下是我对变量提升的理解:

  1. 什么是变量提升:在 JavaScript 中,变量和函数声明(使用var和function)在内部会被“提升”到它们所在作用域(全局或函数)的顶部。这意味着在代码执行之前,JavaScript 引擎已经知道这些变量和函数的存在,即使它们在源代码中的位置可能在后面。
  2. 声明与赋值:需要注意的是,提升只作用于声明,不作用于赋值或初始化。如果一个变量在后面被赋值,那么它在提升时仍被认为是undefined。只有当执行到赋值语句时,它才会被赋予特定的值。
  3. var, let, const:只有用var声明的变量会被提升。用letconst声明的变量也有类似的提升行为,但由于它们存在“暂时性死区”(Temporal Dead Zone,TDZ),在声明前对它们的访问会导致错误。
  4. 函数提升:函数声明也会被提升,并且优先级高于变量。如果一个函数和一个变量同名,且变量未被赋值,那么该名称指向函数。

JSON.stringify有什么缺点?

JSON.stringify()是一个将JavaScript对象转换为JSON字符串的方法。尽管它在许多情况下非常有用,但它确实存在一些限制和缺点:

  1. 循环引用:JSON.stringify()无法处理具有循环引用的对象。如果一个对象的属性直接或间接引用了自身,JSON.stringify()将抛出一个错误,表示存在循环引用。
  2. undefined、函数和Symbol忽略:JSON.stringify()不会序列化对象中的undefined、函数和Symbol类型的属性。这些属性将被忽略,不会出现在生成的JSON字符串中,单独转换则会返回undefined
  3. 丢失原型链:在对象序列化后,原型链上的属性和方法将丢失。只有对象自身的可枚举属性会被序列化。因此,在反序列化(使用JSON.parse())后,原始对象的原型链信息将不复存在。
  4. 日期对象处理:当使用JSON.stringify()序列化日期对象时,日期对象会被转换为它们的ISO字符串表示形式。在反序列化时,这些日期将被视为普通字符串,而不是日期对象。
  5. 非数组和非对象的值:对于不是数组或对象的顶层值(例如:字符串、数字、布尔值等),JSON.stringify()会直接返回其对应的JSON表示,而不会将其包装在对象或数组中。

for…in 和 for…of的区别?

for…in循环用于遍历对象的可枚举属性,返回的是属性名称;for…of循环用于遍历可迭代对象(如数组、字符串、Map、Set等),返回的是元素值。

new操作符都做了什么

  1. 创建一个新对象
  2. 对象的__proto__指向构造函数的prototype
  3. 构造函数将对象绑定到this并调用
  4. 如果构造函数返回对象或函数则直接返回,否则返回这个新对象

类数组和数组的区别,dom 的类数组如何转换成数组

类数组(Array-like)和数组(Array)都是用于存储多个值的数据结构,但它们之间存在一些关键区别:

  1. 类型:数组是JavaScript的内置对象类型,继承自Array.prototype,具有一系列数组方法(如push()pop()map()等)。类数组是普通的对象,其属性名为索引(如012等),具有一个length属性,但不具备数组的方法。
  2. 原型:数组的原型为Array.prototype,因此具有数组的所有方法。类数组的原型通常为Object.prototype,并不包含数组的方法。

要将DOM的类数组(例如,通过document.getElementsByClassName()document.querySelectorAll()获取的元素集合)转换为数组,可以使用以下方法之一:

  1. 使用Array.from()方法:

    let nodeList = document.querySelectorAll(‘div’);
    let array = Array.from(nodeList);

Array.from()方法会创建一个新数组,并将类数组的元素逐个复制到新数组中。

  1. 使用扩展运算符(Spread Operator):

    let nodeList = document.querySelectorAll(‘div’);
    let array = […nodeList];

扩展运算符...可以将类数组直接转换为数组。

  1. 使用Array.prototype.slice.call()

    let nodeList = document.querySelectorAll(‘div’);
    let array = Array.prototype.slice.call(nodeList);

Array.prototype.slice.call()方法会将类数组作为上下文,并创建一个新数组,将类数组的元素逐个复制到新数组中。

这些方法可以将类数组转换为数组,这样就可以在转换后的数组上使用数组的方法了。注意,这些方法不仅适用于DOM类数组,还适用于其他类数组对象。

offsetWidth/offsetHeight,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别

offsetWidth/offsetHeight是元素的可见宽度/高度加上padding、border和滚动条(如果存在)的宽度/高度。

clientWidth/clientHeight是元素的可见宽度/高度,不包括padding和滚动条。

scrollWidth/scrollHeight是元素内容的完整宽度/高度,包括溢出部分。如果元素没有溢出,则scrollWidth/scrollHeight等于clientWidth/clientHeight。如果有溢出,则scrollWidth/scrollHeight大于clientWidth/clientHeight。

mouseover/mouseout 与 mouseenter/mouseleave 的区别与联系

mouseover和mouseout是HTML DOM事件,它们会在鼠标移入或移出元素时触发。它们也会在鼠标指针进入或离开子元素时触发。这也就是说,如果在父元素上有mouseover事件,并且鼠标指针进入子元素,则该元素上仍然会触发mouseover事件。mouseout同理。

mouseenter和mouseleave事件也是在鼠标进入或离开元素时触发。与mouseover和mouseout不同的是,mouseenter和mouseleave事件不会传播到子元素。因此,如果鼠标指针进入或离开元素的子元素,则不会触发mouseenter和mouseleave事件。

event.stopPropagation()与event.stopImmediatePropagation的区别

event.stopPropagation()可以阻止事件冒泡到父元素,但不阻止其他事件处理程序的执行。而event.stopImmediatePropagation()可以立即阻止事件冒泡并取消同一元素上其他事件处理程序的执行。

说一下事件循环机制Event Loop

事件循环(Event Loop)是 JavaScript 运行时环境中的一个核心概念,它负责协调异步操作和同步代码的执行。JavaScript 是单线程的,这意味着它一次只能执行一个任务。事件循环使 JavaScript 能够在执行同步代码的同时,处理异步操作(如定时器、用户交互和网络请求)的回调。

事件循环的工作原理大致如下:

  1. 首先,JavaScript 引擎执行全局同步代码(例如来自

你可能感兴趣的:(前端,javascript,开发语言,ecmascript)