八股简答汇总
约 10681 字大约 36 分钟
2025-08-29
一、三大件篇
1. Js闭包
简单理解:闭包 = 内层函数 + 外层函数的变量
产生原因: 作用域链,当前作用域可以访问上级作用域中的变量。
js中,每个函数会创建一个执行上下文,其中包含了函数内部的变量和局部作用域。函数执行完成后其上下文会被摧毁,作用域也会消失。但是如果函数内部定义了一个新函数,并且新函数内部引用了外部函数的变量/参数,那么外部函数执行并销毁时,内部函数引用的变量/参数就不会随之消失,这样就形成了闭合的作用域。
使用问题: 内存泄漏,定时器/事件监听器未被清除/dom未被正确移除
垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露
使用场景: 模仿块级作用域,能够实现柯里化,实现变量私有化,延长变量生命周期
怎么解决内存泄漏: 移除事件监听器/定时器
2. 怎么判断元素是否到达可视区域
3. 讲一讲 js 的原型链
比如说现在存在一个构造函数A,那么A.prototype就是A的原型对象。原型对象上可以挂载所有对象实例的共享方法
现在令 a = new A()
, 那么:
a._proto_= A.prototype
A.prototype.constructor = A
A.prototype._proto_= Object.prototype
Object._proto_=Function.prototype
Object.prototype._proto_=null
基于原型对象的继承使得不同构造函数的原型对象关联在一起的链状结构称为原型链
4. CSS 中 BFC 是什么
1.定义:块级格式化上下文,独立的渲染区域,不会影响边界外的元素
2.形成条件:①浮动 ②非静态定位static ③overflow不为visible ④display:inline-block、table-cell、table-caption、flex、inline-flex
3.布局规则:①垂直方向上从上到下排列 ②同一个BFC内 box的margin会重叠 ④BFC不会与float元素重叠 ⑤BFC计算高度也会计算float元素
4.解决的问题:①创建自适应两栏布局 ②解决父元素高度塌陷问题 ③解决margin重叠问题
5. JS 数据类型有哪两类,这两类有什么区别
JS数据类型分为两类:一类是基本数据类型,也叫简单数据类型,包含7种类型,分别是Number 、String、Boolean、BigInt、Symbol、Null、Undefined。另一类是引用数据类型也叫复杂数据类型,通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object。
基本数据类型和引用数据类型在内存中的存储方式不同。基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。引用数据类型是存储在堆内存中,占据空间大。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址
拓展:
Symbol是ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以作为object的key。数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。由于Symbol()创建数据具有唯一性,所以 Symbol() !== Symbol(), 同时使用Symbol数据作为key不能使用for获取到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个obj对象中key类型是Symbol的key值。
let key = Symbol('key'); let obj = { [key]: 'symbol'}; let keyArray = Object.getOwnPropertySymbols(obj); // 返回一个数组[Symbol('key')] obj[keyArray[0]] // 'symbol'
BigInt也是ES6新出的一种数据类型,这种数据类型的特点就是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。
使用方法:
- 整数末尾直接+n:647326483767797n
- 调用BigInt()构造函数:BigInt("647326483767797")
注意:BigInt和Number之间不能进行混合操作
6. hash 路由和 history 路由的区别
- url结构:Hash 是域名后紧接的# 符号部分,例如 example.com/#section1。而 history 没有。他是使用 HTML5 的 History API 来修改 URL 的路径,例如 example.com/section1。
- 对服务器的请求:Hash 部分的变化不会触发浏览器向服务器发送请求,因为 hash 部分的变化只会触发页面内的锚点跳转。而 history 的变化可能会触发浏览器向服务器发送请求,因为它可以修改 URL 的路径
- 浏览器历史记录:使用 history API 修改 URL 的路径时,浏览器会添加一条新的历史记录,因此用户可以通过浏览器的后退和前进按钮来导航页面。而修改 URL 的 hash 部分不会添加新的历史记录,只会修改当前的 URL
- 前端路由:基于 hash 的 URL 可以用于实现前端路由,因为 hash 的变化可以被浏览器检测到,并触发onhashchange()事件,并执行相应代码,例如单页应用中的路由切换。而基于 history API 的 URL 可以更加自然地模拟传统的 URL 结构,但需要服务器端的支持
详细说明:
7. JS 为什么是单线程的
作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作。当然,我们可以为浏览器引入“锁”的机制来解决这些冲突,但这会大大提高复杂性,所以 JavaScript 从诞生开始就选择了单线程执行。
另外,因为 JavaScript 是单线程的,在某一时刻内只能执行特定的一个任务,并且会阻塞其它任务执行。那么对于类似 I/O 等耗时的任务,就没必要等待他们执行完后才继续后面的操作。在这些任务完成前,JavaScript 完全可以往下执行其他操作,当这些耗时的任务完成后则以回调的方式执行相应处理。这些就是 JavaScript 与生俱来的特性:异步与回调
8. map 和 weakMap 的区别
weakmap的键会检查变量的引用,只要其中任意一个引用被释放,该键值对就会被删除
- map对象的键可以是任何类型,但是weakmap对象中的键只能是对象引用
- weakmap不能包含无引用的对象,否则会被自动清除出集合
- weakmap对象是不可枚举的,无法获取大小
9. 静态语言和动态语言的区别
类型检查: 静态语言在编译时进行检查,动态语言在运行时进行代码检查
类型声明:
静态语言:变量类型需在编译时显式声明或推断(如
int x = 10;
),类型错误在编译阶段暴露(如Java类型不匹配报错)。动态语言:变量无需声明类型,可随时赋值为不同类型(如Python中
x = 10
后x = "text"
合法),类型错误仅在运行时发现(如JavaScript调用未定义方法)编译与解释:静态语言通常需要先将代码编译成机器语言或中间代码,然后才能执行。而动态语言在运行时通过解释器逐行解释执行代码。
10. 讲一讲浏览器的事件循环机制
任务分为同步任务和异步任务,再细分可以分为宏任务和微任务。
先执行宏任务,在此期间遇到微任务则将其加入微任务队列,遇到宏任务则加入宏任务队列。执行完宏任务后检查微任务队列有没有待执行的代码,有则执行,执行期间依然是 “遇到微任务则将其加入微任务队列,遇到宏任务则加入宏任务队列” 。执行完微任务队列则再检查宏任务队列有无待执行的代码,如此循环。
11. promise 有哪些 API
12. let const 和 var 的区别是什么
(1)块级作用域: 块作用域由 { }
包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
13. 图片懒加载的三种实现方式
12.Vue中图片懒加载,下载插件,在main.js中引入。在组件中使用:对于img,v-lazy=”‘/static/img/product/″ 对于背景图片:v-lazy:backgroundImage = “showMessage.imageUrl”
14. null 和 undefined 的区别
null表示一个值被定义了,但是是空值,但是undefined表示未被定义。null和undefined在if判断中都会被解析为false,但是在用Number运算时,null的结果为0,undefined的结果为NaN,让属性变为null就需要先定义,再赋空值。使用typeof进行判断时,null类型返回"object"。使用双等号比较这两个时返回true,因为双等号会进行隐式类型转换;三等号返回false,因为三等号是严格相等
15. 几种通信方式的区别
16. 讲一讲 ESM 和 CJS 的区别
- 用法不同:
- ES module 是原生支持的 Javascript 模块系统,使用 import/export 关键字实现模块的导入和导出。
- 而 CommonJs 是 Node 最早引入的模块化方案,采用 require 和 module.exports 实现模块的导入和导出
- 加载方式不同:
- 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
- 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”
- 导入和导出特性不同:
- ES module支持异步导入,动态导入和命名导入等特性,可以根据需要动态导入导出,模块里面的变量绑定其所在的模块。
- 而 CommonJs 只支持同步导入导出。
- 循环依赖处理方式不同:
- ES module 采用在编译阶段解决并处理:ES Module 通过使用一张模块间的依赖地图来解决死循环问题,标记进入过的模块为“获取中”,所以循环引用时不会再次进入
- 而 cjs 则通过在第一次被
require
时就会执行并缓存其exports
对象。这样在循环引用中,CommonJS 就会提供一个“部分导出对象”(partial exports),从而打破无限循环,如下,若 a 文件中引用了 b,b 文件中引用了 a:
main.js
└──> a.js
└──> b.js
└──> a.js (cached partial exports)
- 兼容性不同
- ESmodule 需要在支持 ES6 的浏览器或者 Nodejs 的版本才能使用,
- 而CommonJS 的兼容性会更好
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块输出的是值的拷贝(浅拷贝),也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
- ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
17. 原型链关系如何继承
- 每个对象都有一个 proto 属性,它指向该对象的原型
- 当我们访问一个对象的属性或方法时,如果对象自身没有该属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的末端
- 如果一个对象的原型也是一个对象,那么它也有自己的 proto 属性,指向它的原型,这样就形成了一个原型链
- 原型链的终点是 null,它是所有对象的原型链的最后一个链接
- 当我们使用 new 关键字创建一个对象时,该对象的原型会自动指向构造函数的 prototype 属性
18. 浏览器一帧都干了什么
- 接受输入事件
- 执行事件回调
- 开始一帧
- 执行 RAF (RequestAnimationFrame)
- 页面布局,样式计算
- 绘制渲染
- 执行 RIC (RequestIdelCallback) 第七步的 RIC 事件不是每一帧结束都会执行,只有在一帧的 16.6ms 中做完了前面 6 件事,且还有剩余时间,才会执行。
19. 怎么解决循环引用
- 闭包:是无法避免的,要了解闭包是何时创建的以及它保留了哪些对象
- 未处理监听事件和定时器:正确的进行移除
- DOM元素未正确移除
- 循环引用
解决问题的关键是:不进行死循环
- 模块循环引用
- cjs: 每一个模块都先加入缓存再执行
- esm:ES Module来处理循环使用一张模块间的依赖地图来解决死循环问题,标记进入过的模块为“获取中”,所以循环引用时不会再次进入
- 对象循环引用
- 会造成JSON数据的序列化错误
- 对象的深拷贝无法正确处理循环引用
解决办法:
- JSON.decycle
- 深拷贝
JSON.decycle
和JSON.retrocycle
来自 Douglas Crockford 的 cycle.js 库,用于解决 JavaScript 对象循环引用导致的序列化问题。
JSON.decycle
:它的作用是打破循环引用。它会在序列化时,将循环引用替换为一个特殊的路径指针(例如{"$ref": "$"}
表示指向根节点,{"$ref": "2.path.to.object"}
表示指向特定路径),从而让一个包含循环引用的对象能够被JSON.stringify
成功序列化,不再报错
20. 深浅拷贝的地址
浅拷贝会创建一个新的对象或数组,但它们与原始对象共享相同的地址;而深拷贝会创建一个完全独立的新对象或数组,拥有新的地址
21. Json.stringfy有什么弊端
- 无法处理循环引用
- 不支持特定的数据类型
- 丢失对象的原型链和方法
- 性能问题:在处理大量对象或者深层嵌套对象的时候,可能会消耗大量的时间和内存
详细讲解:
22. 内存泄露的原因,怎么解决内存泄露
- 闭包:是无法避免的,要了解闭包是何时创建的以及它保留了哪些对象
- 未处理监听事件和定时器:正确的进行移除
- DOM元素未正确移除
- 循环引用导致,如何解决:怎么解决循环引用
23. css加载会影响dom加载吗
详细信息可以参考文章:原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的
大体的结论: CSS 不会阻塞 DOM 的解析但会阻塞 DOM 的渲染
当浏览器遇到 link或者 style 标签时,会开始下载和解析 CSS 文件。但是如果 CSS 文件包含在了 head 标签中,并且这个标签中没有 defer 或 async 标志时,浏览器在遇到 CSS 文件时会阻塞后续 DOM 的解析和构建,直到 CSS 文件完全下载和解析完成:
- defer 属性被添加时,脚本会被异步执行,会在整个页面解析完毕后按顺序执行
- 使用 async 属性时,脚本不会等待页面解析完成,会在下载完毕后立即执行,执行顺序不确定,可能会影响到依赖脚本的执行
- 如果没有添加这两个标签时,脚本会立即加载并执行,这会阻塞页面的渲染
23. script标签中defer和async的区别
如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。
下图可以直观的看出三者之间的区别:
其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。
defer 和 async属性都是去异步加载外部的JS脚本文件,它们都不会阻塞页面的解析,其区别如下:
- **执行顺序:**多个带async属性的标签,不能保证加载的顺序;多个带defer属性的标签,按照加载顺序执行;
- 在script标签中写入defer或者async时,就会使JS文件异步加载,即html执行到script标签时,JS加载和文档解析同时进行,而async是在JS加载完成后立即执行JS脚本,阻塞文档解析,而defer则是JS加载完成后,在文档解析完成后执行JS脚本
24. new操作符执行的过程
- 创建一个新对象
- 将新对象的__proto__(原型)指向构造函数的prototype(原型对象)
- 构造函数绑定新对象的this并执行返回结果
- 判断返回结果是否为null,如果为null,返回新对象,否则直接返回执行结果。
24. LocalStorage 和 SessionStorage 和 cookie 的区别
详细说明:LocalStorage 和 SessionStorage 的区别
生命周期:
sessionStorage:
存储的数据仅在当前会话期间有效。会话期间指的是浏览器窗口或标签页处于打开状态。
关闭窗口或标签页时,sessionStorage 中的数据将被清除。
localStorage:
存储的数据在浏览器关闭后仍然有效,并且在同一个浏览器窗口或标签页重新打开时仍然可以访问。
数据只能通过 JavaScript 删除或由用户清除浏览器缓存来清除。
存储大小:
sessionStorage:
通常在 5MB 到 10MB 之间。
存储数据仅对当前会话有效,因此相对较小的容量通常足够使用。
localStorage:
通常在 5MB 到 10MB 之间。
存储数据会一直存在,除非用户清除浏览器缓存或通过 JavaScript 删除。
数据共享:
sessionStorage:
存储在 sessionStorage 中的数据只能在同一窗口或标签页之间共享。
localStorage:
存储在 localStorage 中的数据可以在同一浏览器的不同窗口或标签页之间共享。
cookie和session的区别
25. JS 的设计模式,说几个常用的
- 发布订阅模式:发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在
- 观察者模式:观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。例如生活中,我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸
- 策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
- 策略模式利用组合,委托等技术和思想,有效的避免很多 if 条件语句
- 策略模式提供了开放-封闭原则,使代码更容易理解和扩展
- 策略模式中的代码可以复用
- 发布订阅模式与观察者模式的不同:
- 在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
- 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
- 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)
26. e.target 和 e.currTarget 的区别
- target:表示的是当前触发事件的 DOM 元素(事件触发的目标)
- currentTarget:表示引用事件侦听器正在侦听的 DOM 元素(事件注册的目标)
27. foreach和map的区别
- 返回值:foreach没有返回值
- 数组的改变:foreach在遍历的时候可以改变原始数组的元素。而map方法不会改变原始数组,它会创建并返回一个新的数组
28. promise异常穿透实现原理
指发生异常但没有被catch到,异常就会传递到下一个promise链中的错误处理方法。
如果异常一直传递到promise链的结尾却仍然没有被处理,就会触发全局的unhandledrejection事件
29. localstorage如何像cookie一样有过期时间
一种是惰性删除:惰性删除是指获取数据的时候,拿到存储的时间戳和当前时间做对比,如果超过过期时间就清除Cookie。 另一种是定时删除:每隔一段时间执行一次删除操作,并通过限制删除操作执行的次数和频率,来减少删除操作对CPU的长期占用。 LocalStorage清空应用场景:token存储在LocalStorage中,要清空
30. foreach里面为什么不能放promise
- foreach 是一个同步任务,他不支持处理异步函数。如果在 foreach 中执行了异步任务,他无法等待异步函数完成,会继续执行下一项。这意味着在 foreach 中使用异步任务,无法保证执行顺序
- 无法捕获异步函数中的错误,即使出现错误,仍会继续执行
- this 指向问题:在对象里面的调用 foreach 方法,假如参数是普通函数,那么这个函数的this指向为全局作用域,需使用 bind 绑定 this 或者使用箭头函数
31. 伪类和伪元素的区别
32. link标签的preload属性
用来指示浏览器在页面加载过程中预加载指定的资源
- 使用方法:preload需要与as一起使用,以指定要预加载资源的类型
<link rel="preload" href="styles.css" as="style">
// 如果要预加载一个脚本文件,可以这样使用:
<link rel="preload" href="script.js" as="script">
33. flex的属性是哪三个的简写,默认值是什么
flex-grow
: 默认值为0,决定了项目在父容器中剩余空间分配时的权重flex-shrink
:默认值为1,指定项目在空间不足时的缩小比例flex-basis
:指定项目在主轴方向上的初始大小,默认值为 auto
34. 高度塌陷的原因
- 绝对定位元素
- 浮动元素:当父元素包含浮动(float)的子元素时,父元素的高度不会自动包裹浮动元素,导致高度塌陷。这是因为浮动元素从文档流中脱离,不会撑开父元素的高度
- 固定定位/粘性定位,这些脱离标准流的元素
35. display:none 与 visibility:hidden 的区别
36. px、em、rem 的区别以及使用场景
三者的区别:
- px是固定的像素,一旦设置了就无法因为适应页面大小而改变。
- em和rem相对于px更具有灵活性,他们是相对长度单位,其长度不是固定的,更适用于响应式布局。
- em是相对于其父元素来设置字体大小,这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小。而rem是相对于根元素,这样就意味着,只需要在根元素确定一个参考值。
使用场景:
- 对于只需要适配少部分移动设备,且分辨率对页面影响不大的,使用px即可 。
- 对于需要适配各种移动设备,使用rem,例如需要适配iPhone和iPad等分辨率差别比较挺大的设备。
37. position 的属性有哪些,区别是什么
38. z-index 在属性在什么情况下会失效
三者的区别:
- px是固定的像素,一旦设置了就无法因为适应页面大小而改变。
- em和rem相对于px更具有灵活性,他们是相对长度单位,其长度不是固定的,更适用于响应式布局。
- em是相对于其父元素来设置字体大小,这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小。而rem是相对于根元素,这样就意味着,只需要在根元素确定一个参考值。
使用场景:
- 对于只需要适配少部分移动设备,且分辨率对页面影响不大的,使用px即可 。
- 对于需要适配各种移动设备,使用rem,例如需要适配iPhone和iPad等分辨率差别比较挺大的设备。
39. 常见的 css 布局单位
常用的布局单位包括像素(px
),百分比(%
),em
,rem
,vw/vh
。
(1)像素(px
)是页面布局的基础,一个像素表示终端(电脑、手机、平板等)屏幕所能显示的最小的区域,像素分为两种类型:CSS像素和物理像素:
- CSS像素:为web开发者提供,在CSS中使用的一个抽象单位;
- 物理像素:只与设备的硬件密度有关,任何设备的物理像素都是固定的。
(2)百分比(%
),当浏览器的宽度或者高度发生变化时,通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。一般认为子元素的百分比相对于直接父元素。
(3)em和rem相对于px更具灵活性,它们都是相对长度单位,它们之间的区别:em相对于父元素,rem相对于根元素。
- em: 文本相对长度单位。相对于当前对象内文本的字体尺寸。如果当前行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(默认16px)。(相对父元素的字体大小倍数)。
- rem: rem是CSS3新增的一个相对单位,相对于根元素(html元素)的font-size的倍数。作用:利用rem可以实现简单的响应式布局,可以利用html元素中字体的大小与屏幕间的比值来设置font-size的值,以此实现当屏幕分辨率变化时让元素也随之变化。
(4)vw/vh是与视图窗口有关的单位,vw表示相对于视图窗口的宽度,vh表示相对于视图窗口高度,除了vw和vh外,还有vmin和vmax两个相关的单位。
- vw:相对于视窗的宽度,视窗宽度是100vw;
- vh:相对于视窗的高度,视窗高度是100vh;
- vmin:vw和vh中的较小值;
- vmax:vw和vh中的较大值;
vw/vh 和百分比很类似,两者的区别:
- 百分比(
%
):大部分相对于祖先元素,也有相对于自身的情况比如(border-radius、translate等) - vw/vm:相对于视窗的尺寸
39. display 的属性值及其作用
属性值 | 作用 |
---|---|
none | 元素不显示,并且会从文档流中移除。 |
block | 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。 |
inline | 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。 |
inline-block | 默认宽度为内容宽度,可以设置宽高,同行显示。 |
list-item | 像块类型元素一样显示,并添加样式列表标记。 |
table | 此元素会作为块级表格来显示。 |
inherit | 规定应该从父元素继承display属性的值。 |
40. Sass、Less 是什么,为什么要使用他们
都是css预处理器,css的一层抽象层。是一种特殊的语言/语法编译成css。例如less赋予了css动态语言的特性,包括变量/继承/运算/函数
使用他们可以封装重复处理的代码;轻松实现多重继承,兼容css代码,方便应用到老项目中
41. 对媒体查询的理解
使用 @media 查询,可以针对不同的媒体类型定义不同的样式。@media 可以针对不同的屏幕尺寸设置不同的样式,特别是需要设置设计响应式的页面,@media 是非常有用的。当重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。
详细说明:
42. 对Flex布局的理解及其使用场景
以下6个属性设置在容器上:
- flex-direction属性决定主轴的方向(即项目的排列方向)。
- flex-wrap属性定义,如果一条轴线排不下,如何换行。
- flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
- justify-content属性定义了项目在主轴上的对齐方式。
- align-items属性定义项目在交叉轴上如何对齐。
- align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
以下6个属性设置在项目上:
- order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
- flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
- flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
- flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
- flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。
- align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
43. MVVM、MVC、MCP 的区别
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。
(2)MVVM
MVVM 分为 Model、View、ViewModel:
- Model代表数据模型,数据和业务逻辑都在Model层中定义;
- View代表UI视图,负责数据的展示;
- ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
这种模式实现了 Model和View的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM。
(3)MVP
MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。这样 View 层和 Model 层耦合在一起,当项目逻辑变得复杂的时候,可能会造成代码的混乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只知道 Model 的接口,因此它没有办法控制 View 层的更新,MVP 模式中,View 层的接口暴露给了 Presenter 因此可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还包含了其他的响应逻辑。
44. 对 SSR 的理解
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
SSR的优势:
- 更好的SEO
- 首屏加载速度更快
SSR的缺点:
- 开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
- 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
- 更多的服务端负载。
45. 对 SPA 单页面的理解,它的优缺点分别是什么
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
- 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
- 基于上面一点,SPA 相对对服务器压力小;
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
- 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
- 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
- SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
46. params和query的区别
用法:query要用path来引入,params要用name来引入,接收参数都是类似的,分别是 this.$route.query.name
和 this.$route.params.name
。
url地址显示:query更加类似于ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示
注意:query刷新不会丢失query里面的数据 params刷新会丢失 params里面的数据。
47. 对虚拟 DOM 的理解
从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构,使跨平台渲染成为可能。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。
虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对 DOM的描述。它设计的最初目的,就是更好的跨平台,比如Node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟DOM,因为虚拟DOM本身是js对象。 在代码渲染到页面之前,vue会把代码转换成一个对象(虚拟 DOM)。以对象的形式来描述真实DOM结构,最终渲染到页面。在每次数据发生变化前,虚拟DOM都会缓存一份,变化之时,现在的虚拟DOM会与缓存的虚拟DOM进行比较。在vue内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。
另外现代前端框架的一个基本要求就是无须手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大提高开发效率。
48. 虚拟DOM的解析过程
虚拟DOM的解析过程:
- 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
- 当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
- 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。
49. 为什么要用虚拟DOM
(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能
看一下页面渲染的流程:解析HTML -> 生成DOM -> 生成 CSSOM -> Layout -> Paint -> Compiler
下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶
- 真实DOM∶ 生成HTML字符串+重建所有的DOM元素
- 虚拟DOM∶ 生成vNode+ DOMDiff+必要的dom更新
Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,依然可以给你提供过得去的性能。
(2)跨平台
Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。
50. 虚拟DOM真的比真实DOM性能好吗
- 首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
- 正如它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。
51. 柯里化的作用是什么
柯里化是一种将“多个参数的函数”转换成“一系列只有一个参数的函数”的方法
能够实现:参数复用,函数组合,延迟执行和定制化
52. 怎么判断数据类型
- typeof:常用于判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object'
- instanceof:主要用于区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined, null, symbol检测不出来。a instanceof A
- Array.isArray
- Object.prototype.tostring.call():用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串