> 文章列表 > 2023 面试题js、es6篇

2023 面试题js、es6篇

2023 面试题js、es6篇

什么是闭包?

闭包的定义

闭包是指能够访问另一个函数作用域中的变量的一个函数。 在js中,只有函数内部的子函数才能访问局部变量, 所以闭包可以理解成 “定义在一个函数内部的函数”。

应用场景

将内部的函数返到外部去,让外部函数可以访问到内部函数的变量和方法

闭包的优点:

延长外部函数局部变量的生命周期

闭包的缺点:

优点也是缺点,本应被销毁的变量,因为闭包的原因没有被销毁,长期存在的话,容易造成内存泄漏

call apply bind 的作用与区别?

作用

改变函数内部 this 的指向

区别

1、call 和 apply 会调用函数,而 bind 不会调用
2、call 和 bind 的参数是 参数列表逐个传入,而 apply 的参数必须为数组形式

应用场景

1、call 经常做继承
2、apply 经常跟数组有关系,比如借用 Math 对象对数组进行处理
3、bind 不调用函数,但是想改变 this 的指向,比如改变定时器内部的 this 指向

原始值和引用值有什么区别?

1、原始值大小固定,保存在栈内存中从一个变量到另一个变量复制原始值,会创建该值得第二个副本
2、引用值是对象,存储在堆内存中包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身
3、从一个变量到另一个变量复制引用值,只会复制指针,因此结果是两个变量都指向同一个对象
4、typeof操作费可以确定值的原始类型,instanceof操作符用于确保值得引用类型

什么是作用域,什么是作用域链?

任何变量都存在于某个执行上下文中(也称为作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。
1、执行上下文分为:全局上下文、 函数上下文、 块级上下文
2、代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数
3、函数或块的局部上下文不仅可以访问自己作用域内的变量,也可以访问任何包含上下文乃至全局上下文的变量
4、全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据
5、每个函数都有一个作用域链,查找变量或方法时,会先从函数作用域逐层查找最后到全局作用域查找,这些作用域的集合称为作用域链。

解释一下JavaScript垃圾回收?

垃圾回收基本思路:确定那个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行。
JavaScript是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收。
1、离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除
2、主流的垃圾回收算法是标记算法,即先给当前不使用的值加上标记,再回来回收它们的内存
3、引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript引擎不再使用这种算法,但某些旧版本的IE仍然会受这种算法的影响,原因是JavaScript会访问非原生JavaScript对象(如DOM对象)。
4、引用计数在代码中循环引用时会出现问题
5、解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。

Object和Map到底有什么区别?

  • 给定固定大小内存的情况下,Map一般会比Object多存储50%的键值对。
  • 插入Map一般会稍微快一点。
  • Map的删除性能完胜Object。

JavaScript的继承是通过什么方式实现的?(问法2:什么是原型链?问法3:构造函数、原型、实例三者的关系?)

原型:

  • 所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。
  • 所有的引用类型都有一个’_ _ proto_ _'属性(也叫隐式原型,它是一个普通的对象)。
  • 所有的函数都有一个’prototype’属性(这也叫显式原型,它也是一个普通的对象)。
  • 所有引用类型,它的’_ _ proto_ _'属性指向它的构造函数的’prototype’属性。
  • 当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找。

原型链:

  • 每个对象都有 proto 属性,这个属性指向原型对象,当想访问对象的一个属性时,如果这个对象本身没有这个属性就会通过 __proto__属性 查找,原型对象也是对象,每个对象又有自己的 proto 属性,所以就会一直这样查找上去,直到找到这个属性,这就是原型链的概念。
  • 原型链就是对象沿着 proto 这条链逐步向上搜索,最顶层是 Object,Object 的 proto 是 null。

总结

ECMA-262把原型链定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数,这样就在实例和原型之间构造了一条原型链。

对内存泄漏的了解

理解:

无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃。

原因:

  • 意外的全局变量
  • DOM元素清空时,还存在引用
  • 闭包
  • 遗忘的定时器

new 操作符具体干了什么?

  • 创建一个空对象,并且把 this 指向这个对象,同时还继承了该对象的原型
  • 属性和方法被加入到 this 引用的对象中

说说对浅拷贝 和 深拷贝的理解

浅拷贝:

如果拷贝的是基本数据类型相当于直接拷贝它的值,修改值互不影响
如果拷贝的是引用数据类型,拷贝的就是指向堆内存里面这个对象的内存地址,如果修改了其中一个对象的数据,那么另一个对象也会受到影响,因为内存地址指向堆里面同一块内存
常用 es6 0bject.assign() 实现

深拷贝:

深拷贝是将一个对象完整的独立拷贝一份出来,然后在堆内存中开辟一块新的内存块存储,所以不会互相影响
常用 es6扩展运算符 递归 Array.concat() 实现
或者转为字符串再转为数组

你了解构造函数吗 ? class 是什么 ? 两者有什么区别 ?

在es5中构造函数其实就是在定义一个类,可以实例化对象,es6中class其实是构造函数的语法糖。但还是有点区别的:

  • 在class内部和class的方法内部,默认使用严格模式
  • class类不存在预解析,也就是不能先调用class生成实例,再定义class类,但是构造函数可以。
  • class中定义的方法默认不能被枚举,也就是不能被遍历。
  • class必须使用new执行,但是构造函数没有new也可以执行。
  • class中的所有方法都没有原型,也就不能被new
  • class中继承可以继承静态方法,但是构造函数的继承不能。

Promise是什么?

  • Promise 是 es6 引入解决异步编程问题的解决方案
  • Promise 有三种状态:pending(进行中)、resolve(已完成)、reject(已失败)
  • 当 Promise 的状态由 pending 转变为 resolved 或 reject 时,会执行相应的回调, 一旦从 pending 状态变成为其他状态就不能再更改状态了
  • 可以链式调用,解决回调地狱的问题

Promsie 和 async/await 的区别和使用 ?

  • 函数前面多了一个async关键字。await关键字只能用在async定义的函数内。async函数会隐式地返回一个promise,该promise的reosolve值就是函数return的值。
  • 第1点暗示我们不能在 外层代码中使用await,因为不在async函数内。使用:
    1.async和await是配对使用的,await存在于async的内部。否则会报错 。
    2.await表示在这里等待一个promise返回,再接下来执行。
    3.await后面跟着的应该是一个promise对象,(也可以不是,如果不是接下来也没什么意义了…)

什么是宏任务,什么是微任务?

同步任务

同步任务是指在主线程上排队执行的任务,
只有前一个任务执行完毕,才能继续执行下一个任务。

异步任务

异步任务指的是,不进入主线程、而进入"任务队列"的任务,只有等主线程任务执行完毕,"任务队列"的任务才会进入主线程执行。
异步任务分为 宏任务 和 微任务

宏任务

  • 常见的宏任务:settimeout setInterval script(最外层的script标签)
  • 会压入到调用栈中,宏任务会等到调用栈清空之后再执行

微任务

  • 常见的微任务:promise (async await)
  • 会在调用栈清空时立即执行(优先级大于宏任务), 调用栈中加入的微任务会立马执行

总结

  • 同一作用域微任务队列优先于宏任务队列执行
  • 微任务队列上创建的宏任务会被后添加到当前宏任务队列的尾端,微任务队列中创建的微任务会被添加到微任务队列的尾端
  • 只要微任务队列中还有任务,宏任务队列就只会等待微任务队列执行完毕后再执行

如何判断数据类型 ?他们的优缺点是什么?

  • typeof 用来检测数据类型的运算符
    检测的不管是数组还是正则都返回的是"object",所以typeof不能判断一个值是否为数组

  • instanceof/constructor。检测某一个实例是否属于某一个类使用instanceof/constructor可以检测数组和正则
    用instanceof检测的时候,只要当前的这个类在实例的原型链上(可以通过原型链__proto__找到它),检测出来的结果都是true。
    基本数据类型的值是不能用instanceof来检测的
    在类的原型继承中,instanceof检测出来的结果其实是不准确的

  • Object.prototype.toString.call(value) ->找到Object原型上的toString方法,让方法执行,并且让方法中的this变为value(value->就是我们要检测数据类型的值)。检测的类型比较多,也比较精准。

数组常用方法有那些

concat() 连接两个或更多的数组,并返回结果。join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。pop() 删除并返回数组的最后一个元素。  shift() 删除并返回数组的第一个元素push() 向数组的末尾添加一个或更多元素,并返回新的长度。unshift() 向数组的开头添加一个或更多元素,并返回新的长度。reverse() 颠倒数组中元素的顺序。slice() 从某个已有的数组返回选定的元素sort() 对数组的元素进行排序splice() 删除元素,并向数组添加新元素。toSource() 返回该对象的源代码。toString() 把数组转换为字符串,并返回结果。toLocaleString() 把数组转换为本地数组,并返回结果。valueOf() 返回数组对象的原始值

常用的es6语法有哪些

let、const
解构赋值
模板字符串
箭头函数
函数默认值
promise
set、map结构
class类
symbol
Iterator 和 for…of 循环.
数值的扩展方法
数组的扩展方法
正则的扩展方法
对象的扩展方法

讲一下 var、let、const 的区别?

var 和 let/const 都是 JavaScript 中声明变量的关键字,但是它们在作用域和变量提升方面有一些不同。

var 声明的变量存在变量提升,在代码块执行前就已经存在,并且值为 undefined。

let 和 const 声明的变量不存在变量提升,在代码块执行前不存在,并且在代码块内部作用域内有效。

let 声明的变量可以被重新赋值,而 const 声明的变量不能被重新赋值。

const对象的属性可以修改吗

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

使用箭头函数应注意什么?

(1)用了箭头函数,this就不是指向window,而是父级(指向是可变的)
(2)不能够使用arguments对象
(3)不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数