JavaScript---八股

JavaScript---八股
寻觅~流光JavaScript
一.数据结构
1.JavaScript 有哪些数据类型
这些数据类型可以分为原始数据类型
与引用数据类型(复杂数据类型),
他们在内存中的存储方式不同
其中 **Symbol**
和 **BigInt\*\*
是 ES6 中新增的数据类型:
- **
Symbol
**代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。 - **
BigInt**
是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
堆: 存放引用数据类型,引用数据类型占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,如Object
、Array
、Function
。栈: 存放原始数据类型,栈中的简单数据段,占据空间小,属于被频繁使用的数据,如String
、Number
、Null
、Boolean
。
- 堆: 存放引用数据类型,引用数据类型占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,如
Object
、Array
、Function
。 - 栈: 存放原始数据类型,栈中的简单数据段,占据空间小,属于被频繁使用的数据,如
String
、Number
、Null
、Boolean
。
2.Undefined 与 Null 的区别
Undefined
和 Null
都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined
和 null
。
- undefined 代表的含义是未定义,一般变量声明了但还没有定义的时候会返回
undefined
,typeof
为undefined
- null 代表的含义是空对象,null 主要用于赋值给一些可能会返回对象的变量,作为初始化,
typeof
为object
3.typeof null 的结果是什么,为什么?
4.为什么 0.1 + 0.2 ≠ 0.3,如何让其相等
5.typeof NaN
会返回什么?
会返回Number
,他表示一个不能表示的数字
6.for…in…
与for…of…
的区别
for...in
和for...of
都是JavaScript
中的循环语句,而for…of
是 ES6 新增的遍历方式,允许遍历一个含有iterator
接口的数据结构(数组、对象等)并且返回各项的值,和ES3
中的for…in
的区别如下
for…of
遍历获取的是对象的键值,for…in
获取的是对象的键名;for… in
会遍历对象的整个原型链,性能非常差不推荐使用,而for … of
只遍历当前对象不会遍历原型链;- 对于数组的遍历,
for…in
会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of
只返回数组的下标对应的属性值;
总结:for...in
循环主要是为了遍历对象而生,不适用于遍历数组;for...of
循环可以用来遍历数组、类数组对象,字符串、Set
、Map
以及 Generator
对象。
7.对 AJAX 的理解,实现一个 AJAX 请求
AJAX
是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML
文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。 创建AJAX
请求的步骤:
- 创建一个
XMLHttpRequest
对象。 - 在这个对象上使用
open
方法创建一个HTTP
请求,open
方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。 - 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过
setRequestHeader
方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个XMLHttpRequest
对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange
事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的readyState
变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过response
中的数据来对页面进行更新了。 - 当对象的属性和监听函数设置完成后,最后调用
send
方法来向服务器发起请求,可以传入参数作为发送的数据体。
1 | const serve_url = '/serve_url' |
8.ajax,axios,fetch 的区别
- 基于原生
XHR
开发,XHR
本身架构不清晰。 - 针对MVC编程,不符合现在前端 MVVM 的浪潮。
- 多个请求之间如果有先后关系的话,就会出现回调地狱
- 配置和调用方式非常混乱,而且基于事件的异步模型不友好。
- 支持
Promise
API - 从浏览器中创建
XMLHttpRequest
- 从
node.js
创建http
请求 - 支持请求拦截和响应拦截
- 自动转换
JSON
数据 - 客服端支持防止
CSRF/XSRF
浏览器原生实现的请求方式,ajax 的替代品基于标准 Promise
实现,支持async/awaitfetchtch
只对网络请求报错,对 400,500 都当做成功的请求,需要封装去处理默认不会带cookie
,需要添加配置项fetch
没有办法原生监测请求的进度,而XHR
可以。
- 浏览器原生实现的请求方式,ajax 的替代品
- 基于标准
Promise
实现,支持async/await
fetchtch
只对网络请求报错,对 400,500 都当做成功的请求,需要封装去处理- 默认不会带
cookie
,需要添加配置项 fetch
没有办法原生监测请求的进度,而XHR
可以。
9.forEach 和 map 的区别
两个方法都是用来遍历数组,区别如下:
forEach()
对数据的操作会改变原来的数据,这个方法没有返回值map()
方法不会改变原来数组的值,会返回一个新的数组,新的数组中的值是原来的数组进行处理后的值
10.什么是尾调用,使用尾调用有什么好处
这样做的好处就是:
在一个函数里调用另外一个函数会保留当前执行的上下文,如果在函数尾部调用,因为已经是函数最后一步,所以这时可以不用保留当前的执行上下文,从而节省内存。但是 ES6 的尾调用只能在严格模式下开启,正常模式是无效的。
11.如何实现深浅拷贝
使用JSON.stringify()
将 js 对象序列化,再通过 JSON.parse 反序列
- 如果对象中有函数、
undefined
、symbol
时,都会丢失 - 如果有正则表达式、
Error
对象等,会得到空对象
Objec.assign()
拷贝对象- 扩展运算符(
…
)
12.什么是深拷贝,浅拷贝,他们的区别是什么?
它们的区别总结:
特征 | 浅拷贝 (Shallow Copy) | 深拷贝 (Deep Copy) |
---|---|---|
拷贝内容 | 仅拷贝顶层属性 | 拷贝所有属性,包括嵌套的引用类型 |
引用类型属性 | 拷贝的是引用地址 | 创建全新的对象或数组,拷贝的是值 |
独立性 | 新对象和原对象共享嵌套引用类型的内存空间,互相影响 | 新对象和原对象完全独立,互不影响 |
实现方式 | Object.assign() ,扩展运算符 (... ) (针对对象和数组) | 需要特殊方法实现,如递归拷贝、JSON.parse(JSON.stringify(obj)) 等 |
咋么获取一个元素的位置
element.getBoundingClientRect()
返回一个DOMRect 对象,里面有这些属性:
属性 | 含义 |
---|---|
top | 元素上边缘到视口顶部的距离(单位:px) |
left | 元素左边缘到视口左边的距离 |
bottom | 元素下边缘到视口顶部的距离(= top + height) |
right | 元素右边缘到视口左边的距离(= left + width) |
width | 元素宽度 |
height | 元素高度 |
x | 等同于 left (某些浏览器里还有这个字段) |
y | 等同于 top |
这些值都是相对于视口(viewport)左上角(0,0)点来计算的。
二.ES6
1.let,const,var 的区别
可以从六个方向来说明这个问题:
块级作用域:
块作用域由
1
{ }
包裹,
1
let
和
1
const
具有块级作用域,
1
var
不存在块级作用域。块级作用域解决了 ES5 中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
变量提升:
var
存在变量提升,let
和const
不存在变量提升,即变量只能在声明之后使用,否则会报错。给全局添加属性: 浏览器的全局对象是
window
,Node
的全局对象是global
。var
声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let
和const
不会。重复声明:
var
声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const
和let
不允许重复声明变量。初始值设置: 在变量声明时,
var
和let
可以不用设置初始值。而const
声明变量必须设置初始值。暂时性死区:在使用
let
、const
命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var
声明的变量不存在暂时性死区。
2.箭头函数与普通函数的区别
箭头函数是匿名函数,不能作为构造函数,使用new
关键字。箭头函数没有arguments
箭头函数没有自己的this
,会获取所在的上下文作为自己的thiscall()
、applay()
、bind()
方法不能改变箭头函数中的this
指向箭头函数没有prototype
箭头函数不能用作Generator
函数,不能使用yeild
关键字
- 箭头函数是匿名函数,不能作为构造函数,使用
new
关键字。 - 箭头函数没有
arguments
- 箭头函数没有自己的
this
,会获取所在的上下文作为自己的this
call()
、applay()
、bind()
方法不能改变箭头函数中的this
指向- 箭头函数没有
prototype
- 箭头函数不能用作
Generator
函数,不能使用yeild
关键字
总结:
特性 | 普通函数 (Regular Function) | 箭头函数 (Arrow Function) |
---|---|---|
this | 动态绑定,取决于调用方式 | 词法作用域,继承自定义时所在上下文 |
arguments | 拥有 arguments 对象 | 没有 arguments 对象,使用剩余参数 (... ) |
构造函数 | 可以用 new 调用 | 不能用 new 调用 (不是构造函数) |
prototype | 拥有 prototype 属性 | 没有 prototype 属性 |
yield | 可以作为生成器函数使用 (function* ) | 不能作为生成器函数使用 |
重复命名参数 | 非严格模式下允许 (已废弃),严格模式下不允许 | 不允许 |
语法 | 相对较长 | 更简洁 |
3.Set、Map 的区别
Set
- 创建:
new Set([1, 1, 2, 3, 3, 4, 2])
add(value)
:添加某个值,返回 Set 结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为 Set 的成员。clear()
:清除所有成员,没有返回值。
Map
set(key, val):
向Map
中添加新元素get(key):
通过键值查找特定的数值并返回has(key):
判断Map
对象中是否有Key
所对应的值,有返回true
,否则返回false
delete(key):
通过键值从Map
中移除对应的数据clear():
将这个Map
中的所有元素删除
区别
Map
是一种键值对的集合,和对象不同的是,键可以是任意值Map
可以遍历,可以和各种数据格式转换Set
是类似数组的一种的数据结构,类似数组的一种集合,但在 Set 中没有重复的值
4.map 和 Object 的区别
map
和Object
都是用键值对来存储数据,区别如下:
- 键的类型:
Map
的键可以是任意数据类型(包括对象、函数、NaN
等),而Object
的键只能是字符串或者Symbol
类型。 - 键值对的顺序:
Map
中的键值对是按照插入的顺序存储的,而对象中的键值对则没有顺序。 - 键值对的遍例:
Map
的键值对可以使用for...of
进行遍历,而Object
的键值对需要手动遍历键值对。 - 继承关系:
Map
没有继承关系,而Object
是所有对象的基类。
5.说说你对 Promise 的理解
Promise
是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了地狱回调。
Promise
的实例有三个状态:
Pending
(初始状态)Fulfilled
(成功状态)Rejected
(失败状态)
Promise
的实例有两个过程:
pending
->fulfilled
: Resolved(已完成)pending
->rejected
:Rejected(已拒绝)注意:一旦从进行状态变成为其他状态就永远不能更改状态了,其过程是不可逆的。
Promise
构造函数接收一个带有resolve
和reject
参数的回调函数。
resolve
的作用是将Promise
状态从pending
变为fulfilled
,在异步操作成功时调用,并将异步结果返回,作为参数传递出去reject
的作用是将Promise
状态从pending
变为rejected
,在异步操作失败后,将异步操作错误的结果,作为参数传递出去
Promise
的缺点:
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
6.Promise 方法
promise.then()
对应resolve
成功的处理promise.catch()
对应reject
失败的处理promise.all()
可以完成并行任务,将多个Promise
实例数组,包装成一个新的Promise
实例,返回的实例就是普通的Promise
。有一个失败,代表该Primise
失败。当所有的子Promise
完成,返回值时全部值的数组promise.race()
类似promise.all()
,区别在于有任意一个完成就算完成promise.allSettled()
返回一个在所有给定的 promise
都已经 fulfilled
或 rejected
后的 promise
,并带有一个对象数组,每个对象表示对应的promise
结果。
promise.then()
对应resolve
成功的处理promise.catch()
对应reject
失败的处理promise.all()
可以完成并行任务,将多个Promise
实例数组,包装成一个新的Promise
实例,返回的实例就是普通的Promise
。有一个失败,代表该Primise
失败。当所有的子Promise
完成,返回值时全部值的数组promise.race()
类似promise.all()
,区别在于有任意一个完成就算完成promise.allSettled()
返回一个在所有给定的promise
都已经fulfilled
或rejected
后的promise
,并带有一个对象数组,每个对象表示对应的promise
结果。
7.promise.all 和 promise.allsettled 区别
Promise.all()
和 Promise.allSettled()
都是用来处理多个 Promise
实例的方法,它们的区别在于以下几点:
- all: 只有当所有
Promise
实例都resolve
后,才会resolve
返回一个由所有Promise
返回值组成的数组。如果有一个Promise
实例reject
,就会立即被拒绝,并返回拒绝原因。all
是团队的成功才算,如果有一个人失败就算失败。 - allSettled: 等所有
Promise
执行完毕后,不管成功或失败, 都会吧每个Promise
状态信息放到一个数组里面返回。
8.async/await 对比 Promise 的优势
- 代码可读性高,
Promise
虽然摆脱了回掉地狱,但自身的链式调用会影响可读性。 - 相对
Promise
更优雅,传值更方便。 - 对错误处理友好,可以通过
try/catch
捕获,Promise
的错误捕获⾮常冗余
9.谈谈你对 ES6 的理解
- 解构赋值
- 扩展运算符
- 箭头函数
- 模版字符串
Set
、Map
集合- 新增
class
类 Proxy
Promise
- …
10.ES6 模块和 CommonJS 模块有什么区别
- 语法不同:
ES6
模块使用import
和export
关键字来导入和导出模块,而CommonJS
模块使用require
和module.exports
或exports
来导入和导出模块。 - 异步加载:
ES6
模块支持动态导入(dynamic import),可以异步加载模块。这使得在需要时按需加载模块成为可能,从而提高了性能。CommonJS
模块在设计时没有考虑异步加载的需求,通常在模块的顶部进行同步加载。
三.性能优化
1.图片懒加载
1.概念:
仅在图片进入用户视口(viewport)时才加载该图片。
在页面初始加载时,未在视口中的图片不会被加载,只有当用户滚动页面,使图片进入视口时,才会触发加载,, 从而提升页面的加载速度和用户体验。
2.实现方式:
1.采用原生loading="lazy”
属性
在 h5 中引入了loading
属性,浏览器会自动延迟加载该图片
1 | <img src="" loading="lazy" alt=""> |
这种方法简单易用,但需要注意浏览器的兼容性。
2.使用 JavaScript 和IntersectionObserver
IntersectionObserver
是一个浏览器 API,用于异步观察目标元素与其祖先元素或视口的交叉状态。
通过该 API,可以检测图片是否进入视口,从而动态加载图片。
1 | // 获取全部的属性位data-src的img标签 |
在 HTML 中,图片的 src
属性可以先留空,实际的图片路径存放在 data-src
属性中。
这种方法性能较好,适用于现代浏览器。
需要图片懒加载的就可以使用data-src
属性
3.使用滚动事件和 getBoundingClientRect
对于不支持 IntersectionObserver
的浏览器,可以使用 getBoundingClientRect
方法结合滚动事件来判断图片是否进入视口。
1 | function LazyLoad() { |
节流与防抖
1.节流:
节流(throttle)是指在一定时间间隔内,无论事件被触发多少次,只执行一次事件处理数。 常用于高频触发但不需要每次都响应的场景,比如页面滚动、按钮点击防止连击、窗口缩放等。
核心实现原理
- 记录上一次执行的时间(
lastTime
) - 每次触发事件时,比较当前时间
now
和lastTime
- 如果时间间隔大于或等于设定的
delay
,就执行一次处理函数fn
- 更新
lastTime
为当前时间
1 | // 节流代码 |
2.防抖
防抖是指短时间内多次触发同一事件时,只有最后一次生效。常用于搜索框输入、窗口大小调整等场景。
核心实现原理
- 每次触发事件时,取消之前的定时器。
- 重新启动一个新的定时器。
- 只有最后一次触发后的设定时间内没有再次触发,才会真正执行函数。
1 | // 防抖代码 |
网络优化
四.跨域、同源策略和 CORS
同源策略
同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
同源指的是两个 URL 的协议
、域名
、端口
一致,反之,则是跨域。
出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。
最主要的三种解决方案,分别是 JSONP 和 CORS 和 Nginx 反向代理。
- JSONP:出现的早,兼容性好(兼容低版本 IE)。是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。缺点是只支持
GET
请求,不支持POST
请求。 - CORS:出现的较晚,它是 W3C 标准,属于跨域
AJAX
请求的根本解决方案。支持GET
和POST
请求。缺点是不兼容某些低版本的浏览器。 - Nginx 反向代理:同源策略对服务器不加限制,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持
session
,不需要修改任何代码,并且不会影响服务器性能。
方法一:CORS(跨域资源共享,推荐)
服务器(后端)在 HTTP 响应头 添加:
1 | Access-Control-Allow-Origin: * |
*指允许所有的来源请求,或者可以指定相应的域名