JavaScript
1.js基本数据类型有哪些及它们的区别
Details
基本数据类型:
Undefined、Null、Boolean、Number、String、Symbol、BigInt
引入数据类型:
Object
其中
Symbol
和BigInt
是ES6
中新增的数据类型:Symbol
代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。BigInt
是一种数字类型的数据,它可以表示任意精度格式的整数,使用BigInt
可以安全地存储和操作大整数,即使这个数已经超出了Number
能够表示的安全整数范围。
2. 数据类型检测的方式有哪些
Details
typeof
instanceof
Object.prototype.toString.call
Array.isArray
=== null / === undefined
Number.isNaN()
3. 判断数组的方式有哪些
Details
obj.__proto__ === Array.prototype
Array.isArrray(obj)
Object.prototype.toString.call
obj instanceof Array
Array.prototype.isPrototypeOf
4.请简述JavaScript中的this
Details
this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
- 第一种是
函数调用模式
,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。 - 第二种是
方法调用模式
,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。 - 第三种是
构造器调用模式
,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。 - 第四种是
apply 、 call 和 bind 调用模式
,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。
5. let、const、var的区别
Details
块级作用域
: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
变量提升
: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。给全局添加属性
: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。重复声明
: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。暂时性死区
: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。初始值设置
: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。指针指向
: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
6.原型和原型链
Details
在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype
属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype
属性对应的值,在 ES5
中这个指针被称为对象的原型。一般来说不应该能够获取到这个值的,但是现在浏览器中都实现了 proto 属性来访问这个属性,但是最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf()
方法,可以通过这个方法来获取对象的原型。
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype
所以这就是新建的对象为什么能够使用 toString()
等方法的原因。 特点: JavaScript
对象是通过引用来传递的,创建的每个新对象实体中并没有一份属于自己的原型副本。当修改原型时,与之相关的对象也会继承这一改变。
7. 对闭包的理解
Details
闭包是指有权访问另一个函数作用域中变量的函数
,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包有两个常用的用途
- 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
PS: 闭包容易引起内存泄漏,谨慎使用
8. 浏览器的垃圾回收机制
Details
垃圾回收:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。
回收机制:
- Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
- JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
- 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
(2)垃圾回收的方式
浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数。
1)标记清除
- 标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。
- 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
2)引用计数
- 另外一种垃圾回收机制就是引用计数,这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。
- 这种方法会引起循环引用的问题:例如:
obj1
和obj2
通过属性进行相互引用,两个对象的引用次数都是2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,obj1
和obj2
还将会继续存在,因此它们的引用次数永远不会是0,就会引起循环引用。
9. 哪些情况会导致内存泄漏
前端内存泄漏指的是 JavaScript 中已分配的内存,由于代码逻辑或引用关系的问题,不再需要却无法被垃圾回收机制回收的情况。随着时间推移,泄漏的内存不断累积,会导致页面性能下降、卡顿、崩溃,甚至影响整个浏览器或设备的稳定性
Details
- 未清理的定时器 (Timers) 和回调 (Callbacks)
- 未移除的事件监听器 (Event Listeners)
- 闭包 (Closures) 引用
- 游离的 DOM 引用 (Dangling DOM References)
- 全局变量 (Global Variables)
- console.log 持有引用
- 第三方库管理不当,如echarts 提供了销毁方法
- 未关闭的连接和订阅 (WebSockets, Observables, Event Emitters)
- 缓存管理不当
10. ES6有哪些新特性
Details
箭头函数
解构赋值
模板字符串
promise
symbol
Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值,不能与其他数据类型进行运算- 新的变量声明方式-
let
和const
- 模块化-es6新增了
模块化
,根据功能封装模块,通过import导入
,然后通过export导出
也可以使用export default导出
for…of
循环,用于遍历可迭代对象(如数组、Map 和 Set)中的元素扩展运算符
:使用...
可以将数组或对象展开成多个参数,或者将多个参数合并成一个数组展开运算符:
在ES6中用...
来表示展开运算符,它可以将数组或者对象进行展开Map 和 Set
,引入了两种新的数据结构,分别用于存储键值对和唯一值Proxy
,允许在对象和函数调用等操作前后添加自定义的行为- 类(
Class
),引入了面向对象编程中类的概念 默认参数
(Default Parameter),在定义函数时可以给参数设置默认值
11. 延迟加载js的方法
async 和 defer 属性(推荐)
async:异步加载,不阻塞渲染,加载完成后立即执行(无序)
<script src="script.js" async></script>
defer:异步加载,延迟到HTML解析完成后执行(按顺序)
<script src="script.js" defer></script>
方法 | 是否阻塞渲染 | 执行顺序 | 适用场景 |
---|---|---|---|
async | ❌ 非阻塞 | ❌ 无序 | 独立脚本(如分析代码) |
defer | ❌ 非阻塞 | ✅ 顺序 | 依赖 DOM/其他脚本的代码 |
动态注入 | ❌ 非阻塞 | 可控 | 精准控制加载时机 |
IntersectionObserver | ❌ 非阻塞 | 可控 图片/组件懒加载 | |
import() | ❌ 非阻塞 | ✅ 顺序 | 现代框架路由懒加载(React/Vue) |
12. ES6 的箭头函数 (=>) 和 ES5 的普通函数 (function) 区别
特性 | 箭头函数 (=>) | ES5 普通函数 (function) |
---|---|---|
语法 | 更简洁(可省略 return、{}、()) | 标准语法 (function() {}) |
this 绑定 | 继承自外层作用域(词法作用域) | 动态绑定(取决于调用方式) |
构造函数 | ❌ 不可用作构造函数(new 会报错) | ✅ 可用作构造函数 |
arguments 对象 | ❌ 不存在 | ✅ 存在 |
prototype 属性 | ❌ 不存在 | ✅ 存在 |
方法定义 | 适合无独立 this 需求的场景 | 适合需要动态 this 的方法 |
13.前端性能优化
- 路由懒加载
SPA 项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验
- 组件懒加载
- 骨架屏
- 长列表虚拟滚动
- 图片优化
- 图片懒加载
- 字体图标
- 转base64
14.前端资源加载方式
async、defer 是 script 标签的专属属性,对于网页中的其他资源,可以通过 link 的 preload、prefetch 属性来预加载 如今现代框架已经将 preload、prefetch 添加到打包流程中了,通过灵活的配置,去使用这些预加载功能,同时我们也可以审时度势地向 script 标签添加 async、defer 属性去处理资源,这样可以显著提升性能