2024前段高频面试题
数据类型
1.js基本数据类型以及它们的区别
javascript共有8种数据类型
- 原始数据类型
- undefined
- null
- boolean
- number
- string
- symbol
- bigint
- 引用数据类型(对象、数组、函数)
- object
symbol和bigint
symbol和bigint都是es6新增的数据类型
- symbol代表创建后独一无二且不可变的数据类型
- 主要为了解决可能出现的全局变量冲突问题
- bigint可以用来表示任意精度格式的整数
- 可以安全的存储和操作大整数
原始数据类型和引用数据类型的区别是什么?
- 区别是:存放的位置不同
- 原始数据类型直接存放在栈(stack)中
- 占据空间小,大小稳定,需要频繁使用
- 引用数据类型存放在堆(heap)中
- 占据空间大,大小不稳定
- 在栈中存储指针,指针指向堆中该实体的起始地址
- 解释器首先寻找栈中存放的地址,再从堆中获得实体
栈和堆的区别是什么?
- 操作系统中,内存被分为堆区和栈区
- 栈中数据为先进后出
- 内存由编译器自动分配释放
- 堆是一个优先队列,按照优先级进行排序,优先级可以按照大小来规定
- 一般由开发者分配释放
2.数据类型检测的方式有哪些
- typeof
- 判断null/数组/对象类型时,结果都会是object
- instanceof
- 机制:判断在原型链中是否能找到该类型的原型
- 可以用来测试对象原型链中是否存在指定的prototype属性
- 仅对引用类型有效,无法判断原始数据类型
- constructor
- 可以使用constructor来判断数据类型,但是如果修改对象的原型就行不通了
- Object.prototype.toString.call()
- 可以获取属性类型名称字符串
问:为什么obj.toString()和Object.prototype.toString()的返回值不一样
答:因为后者调用的时Object的原型方法,而使用实例进行调用,toString方法都被重写了
数组
3.判断数组的方式有哪些
- Array.isArray(obj)
- obj.proto === Array.prototype
- Object.prototype.toString().call(obj).slice(8,-1) === ‘Array’
- obj instanceof Array
- Array.prototype.isPrototypeOf(obj)
9.数组有哪些原生方法
- toString
- join,将数组元素连接成字符串
- push/pop/shift/unshift
- reverse
- sort 回调函数返回值为整数时,交换两个参数的位置
- concat,不影响原数组
- slice,不影响原数组
- splice,影响原数组
- indexOf、lastIndexOf
- every/some/filter/forEach
- reduce/reduceRight
11.数组的遍历方法有哪些
- forEach
- map
- filter
- for…of
- 返回数组元素/对象的属性值
- every/some
- find/findIndex
- reduce/reduceRight
12.forEach和map的区别
都是用来遍历数组的
- forEach主要是针对每一个元素执行提供的函数,返回值没有意义
- map会根据回调函数的返回值得到一个新的数组
语法特性
4.请简述JavaScript中的this
- this指向当前执行上下文中的对象
- 调用模式
- 函数调用模式:全局定义函数直接被调用,this指向全局对象
- 方法调用模式:函数作为对象的方法被调用,this指向对象
- 构造器调用模式:函数作为构造器被new调用,执行前会新建一个对象,this指向对象
- apply、call、bind:可以指定this的指向
- apply:传入2个参数
- 参数1:this绑定对象
- 参数2:其它参数(数组格式)
- call:
- 参数1:this绑定对象
- 其它参数(必须逐个例句出来)
- bind
- 参数1:this指向
- 返回值:新函数
- apply:传入2个参数
- 调用模式优先级:构造器 > apply/bind/call > 方法 > 函数
箭头函数和普通函数的区别
- 箭头函数的写法更加简洁
- 箭头函数的this指向函数定义的上下文
- 箭头函数继承的this指向不会改变
- 箭头函数不能作为构造函数使用
- 箭头函数没有自己arguments对象
- 箭头函数没有prototype
- 箭头函数不能用作generator函数,不能使用yield关键字
5.AMD和CommonJs的区别
都是实现模块化的方式
- Commonjs
- 同步加载,由于运行在服务器端,从本地磁盘读取数据,速度很快
- 更适合服务器,nodejs使用commonjs
- module/exports/require/global
- module.exports输出,require加载
- AMD/require.js
- 异步方式加载模块,模块加载不影响后面语句运行
- 更适合浏览器
- 使用require.config()指定引用路径
- define()定义模块,require()加载模块
6.ES6模块与CommonJs模块有什么异同
两者都能对引入的对象的内部属性值进行改变
- ES6模块
- 语法简单,适用浏览器和服务器
- export导出,import导入
- 是对模块的引用,模块本身指向不能改变
- commonjs
- 是对模块的浅拷贝
- 模块本身可以被重新赋值
7.let、const、var的区别
- 块级作用域
- let、const具有块级作用域,var没有
- 块级作用域使用花括号包裹,用于解决2个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
- 变量提升
- 变量只能在声明后使用,否则会报错
- var存在变量提升
- let/const不存在变量提升
- 全局添加属性
- 浏览器全局变量:window,Node全局变量global
- var声明变量会成为全局对象的属性
- let和const不会
- 重复声明
- var可以重复声明,后声明的变量会覆盖前声明的
- const、let不允许重复声明
- 暂时性死区
- let/const声明之前无法使用
- 初始值设置
- var/let可以不设置初始值
- const必须设置初始值
- 指针指向
- let/var创建的变量都可以更改指针指向
- const不能
10.for in 和 for of的区别
- for_of
- 遍历对象的键值,遍历数组元素值
- 只遍历当前对象上的属性
- for_in
- 遍历对象的键名,遍历数组索引
- 会遍历对象的整个原型链,性能比较差
23、ES6有哪些新特性
- 箭头函数
- 解构赋值
- 模版字符串
- promise
- symbol,用于创建一个独一无二的值,不能与其他数据类型进行运算
- let/const
- 模块化(import/export)
- for..of循环刻碟带对象
- 扩展运算符…
- Map/Set
- Proxy,允许在对象和函数调用等操作前后添加自定义的行为
- Class类
- 函数默认参数
24、匿名函数的典型应用场景是什么?
- 需要立即调用执行的函数,封装局部作用域的代码,避免声明的变量暴露到全局作用域
1 | (function(){})() |
- 用于一些函数作为参数的情况,比如setTimeout或者forEach
引用类型
8.new操作符的实现原理
- 首先创建了一个新的空对象
- 将对象的原型设置为函数的prototype对象
- 让函数的this指向这个对象,执行构造函数的代码
- 判断函数返回类型,如果是值类型,返回创建的对象,如果是引用类型,返回引用类型的对象
13.原型和原型链
- js中的构造函数内部存在一个prototype属性
- 新建的对象内部存在一个指针,指向构造函数的prototype属性的对应值,即原型
- 浏览器中能够通过proto属性访问这个值
- 当访问一个对象的属性时,对象内部不存在这个属性,那么就会沿着原型链向上寻找该属性/方法
- 原型链的终点:
- Object.prototype.proto,也就是null
- 可以通过hasOwnProperty方法判断属性到底是原型链上的还是本身定义的
全局变量&&作用域
14.对执行上下文、作用域链、闭包的理解
闭包
- 指的是有权访问另一个函数作用域中变量的函数
- 最常见的方式:在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量
- 比如A函数内有B函数,B函数能访问A函数中的变量,B函数就是闭包
- 用途:
- 在函数外部访问函数内部的变量(间接访问函数内部的变量)
- 使运行已经结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用
作用域
- 全局作用域
- window对象的属性拥有全局作用域
- 最外层函数和变量都拥有全局作用域
- 函数作用域
- 函数内部的变量
- 块级作用域
- es6的新特性
- let/const有效
- 循环比较适合块级作用域
- 全局作用域
作用域链
- 在当前作用域查找不到变量时,就去父级作用域查找,直到全局作用域为止
- 保证对环境中的变量和函数进行有序访问
15.实现call、apply及bind函数
16.call、apply函数的区别
区别在于传参不同:
- apply:第二个参数为数组或类数组,作为函数的参数
- call,第一个参数往后全是函数的参数
闭包
- 什么是闭包(内层函数中访问到外层函数的作用域)
- 一个函数与其内部创建的函数的作用域的连接
- 阻止了函数调用结束时对函数内部变量的销毁,因为变量被闭包函数引用
- 解决的问题
- 内部函数可以访问到外部函数的内容
- 使用场景
- 防抖节流
- 创建私有变量/方法
- 延长变量的生命周期
- 闭包的缺点:
- 变量驻留在内存中,造成内存损耗问题
- 解决方法:手动清空闭包函数
异步模式
17.异步编程的实现方式
- 回调函数
- Promise方式
- generator(不熟)
- async,promise的语法糖
18、setTimeout、Promise、Async/Await的区别
19、Promise.all和Promise.race的区别以及使用场景
- Promise.all 将多个promise实例包装成一个新的promise实例
- 成功:返回结果数组
- 失败:返回最先被reject失败状态的值
- 使用场景:需要多个ajax请求全部回来之后才能正常显示,且返回数组顺序和传入时的顺序一样
- Promise.race
- 传入的请求中,返回请求响应最快的结果
20、对async/await的理解
- 是promise的一种语法糖,能够解决回调地狱
- async修饰的函数,返回值是promise对象
- await用于修饰一步操作
- async函数中,会等待await代码执行结束后,才会继续执行下面的语句
宏任务和微任务
- js是单线程语言
- js代码会先执行完同步任务,再进入事件循环
- 事件循环:请求、定时器、事件
- 检测是否有可执行的微任务
- 有可执行的微任务,执行微任务
- 微任务清空后执行宏任务
- 检测是否有可执行的微任务
- 任务类型
- 宏任务
- setTimout、setInterval
- 执行前提:清空了所有的微任务
- 微任务
- promise
- 宏任务
性能优化
21、浏览器的垃圾回收机制
22、哪些情况会导致内存泄漏
原理相关
25、你能举出一个柯里化函数的例子吗?它有哪些好处
26、什么是事件循环?调用堆栈和任务队列之间有什么区别
- 事件循环是一个单线程循环
- 用于监视调用堆栈并检查是否有工作即将在任务队列中完成
- 如果调用堆栈为空并且任务队列中有回调函数,则将回调函数出队并推送到调用堆栈中执行
js设计模式有哪些
几个比较要命的问题:
- 如何解决跨域问题
- http握手
- 如何区分null和空数组
- Map的定义方式
- Object哪些方法用的比较多
- 防抖节流
- 同源策略