CSS
1. 盒模型
组成:margin,border,padding,content
分类:标准盒模型,IE 盒模型
- 标准盒模型的 width 和 height 属性的范围只包含了 content
- IE盒模型的 width 和 height 属性的范围包含了 border,padding 和 content
2. flex 常用属性
flex 布局是什么?
- flex 是 flexible box 的缩写,译为 “弹性布局”,用来为盒模型提供最大的灵活性。
容器&项目
- 采用 flex 布局的元素,称为 flex 容器(flex container),简称 “容器”。它的所有子元素自动成为容器成员,称为 flex 项目(flex item),简称 “项目”。
六个属性
- flex-direction,flex-wrap,flex-flow,justify-content,align-items,align-content
flex-direction
- 该属性决定主轴的方向,即项目的排列方向。
flex-wrap
- 默认情况下,项目都排在一条线上,不换行。该属性定义,如果一条线排不下,如何换行。
flex-flow
- 该属性是 flex-direction 和 flex-wrap 的简写形式,默认为 row nowarp.
justify-content
- 该属性定义了项目在主轴上的对齐方式。
align-items
- 该属性定义了项目在交叉轴上的对齐方式。
stretch
(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
align-content
该属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
参考链接:flex 布局教程-语法篇
3. 盒子水平垂直居中
水平垂直居中的方法
- 定位 + calc(固定宽高)
- 定位 + margin(固定宽高)
- 定位 + margin: auto; (不定宽高)
- 定位 + transform(不定宽高)
- flex 布局(不定宽高)
- grid 布局(不定宽高)
- table-cell 布局(不定宽高)
参考链接:七种垂直水平居中的方法
JavaScript
1. js 原型和原型链
原型
- 官方解释:Object that provides shared properties for other objects.
- 通俗来说:当某个对象为其他对象提供共享属性的访问,就被称为其他对象的原型。
原型链
- 在创建对象时,每个对象都包含一个隐式引用指向它的原型或者null。原型也是对象,因此它也有自己的原型,这样就构成了一个原型链。
原型链有什么作用?
- 在访问一个对象的属性时,实际上是在查询原型链。这个对象是原型链的第一个元素,先检查它是否包含属性名,如果是则返回属性值,否则检查原型链上的第二个元素,以此类推。
如何实现原型继承?
- 显式继承:Object.create 或者 Object.setPrototypeOf
- 隐式继承:class,在使用 new 关键字实例化时,会自动继承 constructor 对象的原型,作为实例的原型。
2. js 的类型检测
typeof
:其中数组、对象、null都会被判断为 object,其他判断都正确。instanceof
:只能正确判断引用数据类型,而不能判断基本数据类型;其内部运行机制是判断在其原型链中能否找到该类型的原型。constructor
:有两个作用,一是判断数据的类型,二是对象实例通过 constructor 对象访问它的构造函数。Object.prototype.toString.call()
:使用 Object 对象的原型方法 toString 来判断数据类型。
3. 防抖和节流
一个比喻
- 把大楼中的电梯完成一次运送看作为一次函数的执行和响应。假如电梯有两种运行策略 debounce 和 throttle,超时设定为 15 秒,不考虑容量限制。
- 电梯第一个人进来后,15 秒后准时运送一次,这是节流。
- 电梯第一个人进来后,等待 15 秒,如果过程中又有人进来,则重新计时,直到 15 秒后开始运送,这是防抖。
抽象表示
- 当设置时间频率为 500ms,在 2s 时间内,频繁触发函数:
- 节流:每隔 500ms 就执行一次
- 防抖:500ms 内,该函数不会被回调第二次,则执行一次。
实现和目的
- 二者都可以通过使用 setTimeout 实现,其目的都是降低回调执行频率,节省计算资源。
function throttle(fn, delay) {
let timer = null
let starttime = Date.now()
return function() {
let currTime = Date.now()
let remaining = delay - (currTime - starttime)
let context = this
let args = arguments
clearTimeout(timer)
if(remaing <= 0) {
fn.apply(context, args)
starttime = Date.now()
} else {
timer = setTimeout(fn, remaining)
}
}
}
function debounce(func, wait) {
let timeout;
return function () {
let context = this; // 保存this指向
let args = argument; // 拿到event对象
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait)
}
}
应用场景
- 节流在间隔一段时间执行一次回调的场景:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
- 防抖在连续的事件中,只需触发一次回调的场景:
- 搜索框搜索输入,用户最后一次输入完,才发送网络请求
- 手机号、邮箱验证输入检测
- 窗口大小 resize,只需窗口调整完成后,计算窗口大小,防止重复渲染
参考链接:节流防抖
4. 深、浅拷贝区别,实现一个深拷贝?
浅拷贝
定义
- 如果是基本类型,拷贝的是值;如果是引用类型,拷贝的是内存地址。即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址。
常见的浅拷贝方式
- Object.assign()
- Array.prototype.slice(), Array.prototype.concat()
- 使用拓展运算符实现的复制
let fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
let citrus = fruits.slice(1,3);
// output
Orange,Lemon
let sedan = ["S60", "S90"];
let SUV = ["XC40", "XC60", "XC90"];
let Volvo = sedan.concat(SUV);
// concat() 方法连接两个或多个数组。
// output
S60,S90,XC40,XC60,XC90
深拷贝
定义
- 深拷贝会开辟一个新的栈,两个对象完全相同,但对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
常见的深拷贝方式
- _.cloneDeep()
- jQuery.extend()(将一个或多个对象的内容合并到目标对象)
- JSON.stringify()(将 js 对象转换为字符串)
- 手写循环递归
区别
- 浅拷贝和深拷贝都创建出一个新的对象
- 浅拷贝只复制某个对象的指针,新旧对象共享一块内存,修改对象属性会影响原对象
- 深拷贝会另外创造一个一模一样的对象,新旧对象不共享内存,修改新对象不会改变原对象
5. 事件循环(Event loop)
- js 是单线程,为了防止代码阻塞,我们把代码(任务):同步任务和异步任务
- 同步代码给 js 引擎执行,异步代码交给宿主环境
- 同步代码放入执行栈,异步代码等待时机成熟送入任务队列中排队
- 执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程就是事件循环(Eventloop)
宏任&微任务
宏任务:script 脚本的执行,setTimeout,setInterval,setImmediate,I/O 操作等(宿主环境发起)
微任务:promise 的回调(promise 本身同步,then/catch 的回调函数是异步的),async/await,process.nextTick(node)等(js 引擎发起)
微任务执行完成后,再执行宏任务。
6. 闭包
定义
- 闭包是指有权访问另一个函数作用域中的变量的函数。
闭包形成的条件
- 函数的嵌套
- 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
闭包的用途
- 模拟块级作用域
- 创建模块
- 私有化变量
- 能够访问外部函数的局部变量
闭包缺点
- 会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏。
js 基础
1. 数组有哪些原生方法
增
- 首部操作方法:shift() 和 unshift()
- 尾部操作方法:pop() 和 push()
- 连接方法:concat()
删
- 截取方法:slice(),浅拷贝,用于截取数组中的一部分返回,不影响原数组。
- 删除、替换和插入方法:splice()
查
- 查找方法:indexOf()、includes()、find() 和 findIndex()
- 遍历方法:forEach()、map()、filter()、every() 和 some()
另
- 归并方法:reduce()
- 排序方法:sort()、reverse()
参考链接:js数组的原生方法
2. 什么是DOM和BOM?
- DOM是文档对象模型,即把文档当作一个对象,这个对象主要定义了处理网页内容的方法和接口。
- BOM是浏览器对象模型,即把浏览器当作一个对象,这个对象主要定义了与浏览器进行交互的方法和接口。BOM的核心是 window,而window对象既是通过js访问浏览器窗口的一个接口,也是一个全局对象。这意味着在网页中定义的任何对象、变量和函数,都作为全局对象的一个属性或者方法存在。window对象含有location对象、navigator对象、screen对象等子对象。
3. 对类数组对象的理解,如何转化为数组
类数组不是数组,而是一个类似数组的对象。一个类数组对象应当符合以下两点:
- 使用数字作为属性名称
- 需要具备 length 属性
const arrayLike = {
0: '张三',
1: '李四',
length: 2
}
常见的类数组对象:arguments、字符串和DOM 方法的返回结果。
const str ="abc"
console.log(str.length) // 3
console.log(str[0]) // a
常见的类数组转为数组的方法有以下几种:
// 通过 call 调用数组的 slice 方法
Array.prototype.slice.call(arrayLike);
// 通过 call 调用数组的 splice 方法
Array.prototype.splice.call(arrayLike, 0);
// 通过 call 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
// 通过 Array.from 方法来转换
Array.from(arrayLike);
类数组的意义?
- 当你需要设计一个方法,这个方法需要返回一个数组,但是又不想让这个数组有 push 等可能会修改这个数组的方法,这时候就可以考虑返回一个类数组对象了。
参考链接:JavaScript 为什么需要类数组
ES6 系列
1. let、const、var 的区别
块级作用域
:let、const具有块级作用域,var不存在块级作用域。给全局对象添加属性
:var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但let、const不会。变量提升
:var存在变量提升,let、const不存在变量提升,即变量只能在声明之后使用,否则会报错。重复声明
:var可以重复声明变量,但let、const不允许重复声明变量。暂时性死区
:在使用let、const命令声明变量之前,该变量都是不可用的,这被称为暂时性死区。初始值设置
:在声明变量时,const必须设置初始值,但var、let没有这个要求。指针指向
:var、let可以被重新赋值,但const不可以,即const声明的变量是不允许改变指针的指向。
2. 箭头函数与普通函数的区别
更简洁
:箭头函数比普通函数更加简洁没有自己的this
:箭头函数没有自己的this,箭头函数不会创建自己的this,只会在自己作用域的上一层继承this。因此其this指向永远不会改变。没有自己的arguments
:在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。不能作为构造函数使用
:其原因是箭头函数没有自己的this.
Vue2
1. 虚拟DOM中的diff算法
虚拟DOM
- 虚拟DOM是在内存中维护一个通过数据结构描述出来的一个虚拟DOM树,当数据发生改变的时候,会先对虚拟DOM进行模拟修改,然后对比新旧虚拟DOM树,而这个对比就是通过 diff 算法来进行的。
diff 算法
- 步骤一:用 js 对象模拟 DOM 树
- 步骤二:比较两棵虚拟 DOM 树的差异
- 步骤三:把差异应用到真正的 DOM 树上
diff 算法对比过程
- 根元素改变,删除当前 DOM 树重新创建
- 根元素未变,属性改变:更新属性
- 根元素未变,子元素/内容改变
- 无 key 就地更新,有 key 按 key 比较
2. 组件通信
props/$emit
:父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信。eventBus($emit/$on)
:可用于兄弟组件之间通信,事件总线充当桥梁的作用。provide/inject
(依赖注入):可用于祖孙之间之间通信。ref/$refs
:可用于父子组件之间通信。$parent/$children
:$parent
可以让组件访问父组件的实例,$children
可以让组件访问子组件的实例。$attrs/$listeners
:爷爷组件和孙子组件通信,可以使用这种方式。
3. computed 和 watch 的区别
注:从名字、是否有缓存性、什么时候执行三个方面回答。
- computed 计算属性:依赖其他属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。如:实时获取 vuex 中的网络数据。
- watch 侦听器:更多的是观察的作用,无缓存性,每当监听的数据变化时都会执行。如:监听表单的输入。
4. data 是一个函数而不是对象?
- Vue 组件可能存在多个实例,如果使用对象形式定义 data,则会导致它们共用一个 data 对象,产生数据污染;采用函数的形式,initData 调用时会将其作为工厂函数都会返回全新的 data 对象。
- 根实例是单例,可以是对象也可以是函数,不会产生数据污染的情况。
5. v-model 的实现原理?
Vue2.0
- v-model 是用来在表单控件或者组件上创建双向绑定的,它的本质是 v-bind 和 v-on 的语法糖。
- v-bind 绑定一个 value 属性
- v-on 指令给当前元素绑定 input 事件
<input type="text" v-model="message">
// 等价于
<input type="text" v-bind:value="messagee" v-on:input"message = $event.target.value">
Vue3.0
- 在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop,并接收抛出的 update:modelValue 事件。
参考链接:说明v-model的实现原理?,v-model 如何实现双向绑定的?
6. v-if 和 v-show 的区别
- 控制手段不同:v-show 隐藏是为元素添加 display: none; DOM 元素依旧还在。v-if 显示隐藏是将 DOM 元素整个添加或删除。
- 编译过程不同:v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show 只是简单的基于 CSS 切换。
- 编译条件不同:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译;v-show 是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且 DOM 元素保留。
- 性能消耗不同:v-if 有更高的切换消耗;v-show 有更高的初始渲染消耗。
- 使用场景不同:v-if 适合运营条件下很少改变;v-show 适合频繁切换。
参考链接:v-if和v-show有什么区别
7. 双向数据绑定的原理
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
参考链接:Vue双向数据绑定的原理
8. Object.defineProperty() 数据劫持的缺点?
- 在对一些属性进行操作时,使用这种方法无法拦截,比如通过修改下标方式修改数组数据或者给对象新增属性,都不能触发组件的重新渲染。
- 在 Vue3.0 中通过使用 Proxy 对对象进行代理,从而实现数据劫持,其优点是可以完美监听到任何方式的数据改变。
9. 常见的事件修饰符及其作用
- .stop:等同于 js 中的 event.stopPropagation(),防止事件冒泡。
- .prevent:等同于 js 中的 event.preventDefault(),防止执行预设的行为。
- .capture:与事件冒泡的方向相反,事件捕获由外到内。
- .self:只会触发自己范围内的事件,不包含子元素。
- .once:只会触发一次。
<form @submit.prevent="SomeFunction"></form>
// 单独 submit 点击后会自动进行提交等一系列操作,prevent 就可以阻止这些操作,
// 让上面这段代码乖乖执行我们分配给它的 SomeFunction
Vue3
1. vue2 和 vue3 的区别
- 监测机制的改变
- vue3 使用了 es6 的 proxy api 对数据代理,监测的是整个对象,而不再是某个属性。从而让 vue3 可以监测到对象属性的添加和删除,也可以监听数组的变化。
- vue3 支持 Map、Set、WeakMap 和 WeakSet
- API 模式不同
- vue2 使用 选项式 API(Options API),vue3 使用 组合式 API(Composition API)
- 生命周期不同
- 在 vue2 生命周期函数名基础上加了 on
- beforeDestroy 和 destroyed 更名为 onBeforeUnmount 和 onUnmounted
- 用 setup 代替了 beforeCreate 和 created
- 新增了两个开发环境用于调试的钩子
- vue3支持碎片(fragments)
- vue2 在组件中只有一个根节点
- vue3 在组件中可以拥有多个根节点
- 建立数据的方式不同
- vue2 把数据放入 data 属性中
- vue3 需要使用一个新的 setup 方法,此方法在组件初始化构造的时候触发
小结:vue3 性能跟高,体积更小,更利于复用,代码维护更方便。
参考链接:2022面试万字总结
2. composition API 与 options API 的区别
- options API 易于学习和使用,但相似的逻辑不易复用(mixins 不是特别好维护),在大项目中尤为明显。
- composition API 是基于逻辑功能组织代码的,一个功能 API 相关放到一起,大大提升了代码可读性和可维护性。
- composition API 向下兼容 options API
3. pinia
核心概念
- state:状态
- actions:修改状态(包括同步和异步,pinia中没有mutations)
- getters:计算属性
其他
- vuex 只能有一个根级别的状态,pinia 可以定义多个根级别状态
- 相比 vuex4,pinia 对 vue3 兼容性更好,具备更完善的类型推导(即对ts支持更好)
- pinia 同样支持 vue 开发者工具
4. vite 和 webpack 的区别
- 启动方式不同:vite 在启动的时候不需要打包,不用进行编译,这种方式类似于使用某个 UI 框架的时候,可以对其进行按需加载。按需动态编译可以缩减编译时间。
- 热更新方面,效率更高。当改动了某个模块的时候,也只用让浏览器重新请求该模块,而无需像 webpack 那样将模块以及模块依赖的模块全部编译一次。
- 但 vite 相关生态没有 webpack 完善。
TypeScript
1. 对 ts 的理解?与 js 的区别?
定义
- ts 是 js 的类型的超集,支持 es6 语法,支持面向对象编程的概念,如类,接口,继承,泛型等。
- ts 也是一种静态类型检查的语言,提供了类型注解,在代码编译阶段就可以检查出数据类型的错误。
特性
- 类型批注和编译时类型检查
- 类型推断
- 类型擦除
- 接口:ts 中用接口来定义对象类型
- 枚举
- mixin
- 名字空间
- 元组:相当于一个可以装不同类型数据的数组
- ...
2. ts 的数据类型
和 js 基本一致,也分成:基本类型,引用类型;在基本类型上,ts 新增了 void,any,enum 等原始类型
3. ts 的枚举类型
定义
- 枚举就是一个对象的所有可能取值的集合,例如表示星期的字符串可以看作是一个枚举。
应用场景
- 后端返回 0,1 等状态的时候,用枚举去定义,可提高代码的可读性。
- 后端返回的字段使用 0 - 6 标记对应的日期。
enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
4. ts 的接口
定义
- 一个接口所描述的是一个对象相关的属性和方法,但不提供具体创建此对象实例的方法。
export interface IAccount {
name: string
password: string
}
应用
- 多人开发用到同一个函数,为了避免运行时的错误,可以使用接口定义参数变量。
5. ts 的函数
与 js 函数的区别
- 从定义的方式而言,ts 声明函数需要定义参数类型或者返回值类型
- ts 在参数中,添加可选参数供使用者选择
- ts 增添函数重载功能,使用者只需要通过查看函数声明的方式,即可知道函数传递的参数个数以及类型。
项目
1. 登录-cookie-session
认证流程
- 用户输入登录信息并请求登录
- 服务端收到请求,验证登录信息
- 验证成功后,服务端自动创建 session 对象,存储用户信息,并生成 sessionId,并在响应头 Set-Cookie 中设置这个唯一标识符
- 浏览器将 sessionId 存入本地 cookie 中
- 在后续的 HTTP 请求中,请求头会自动携带该域名下的 cookie 信息
- 服务端收到请求后,校验请求中的 sessionId,如果验证成功,则返回请求资源,否则返回 401
优缺点
优点
- 优点一:cookie 易用,在不受用户干预或过期处理的情况下,cookie 通常是客户端上持续时间最长的数据保留形式
- 优点二:当用户登录和主动注销,只需要添加删除对于的 session 就可以了,方便管理
缺点
- 缺点一:sessionId 存放在 cookie 中,无法避免 CSRF 攻击
- 缺点二:当服务器对接大量的客户端,就需要存放大量的 sessionId,这样会导致服务器压力过大
- 缺点三:如果服务端是一个集群,为了同步登录态,需要将 sessionId 同步到每一台机器上,增加了服务端维护成本
参考链接:前端登录,这一篇就够了,前端登录鉴权方案,30s 看懂最基础的认证方式: Session-Cookie 认证
2. 登录-token认证
认证流程
- 用户输入登录信息并请求登录
- 服务端收到请求,验证登录信息
- 验证成功后,服务端颁发 token,并返回给客户端
- 客户端将 token 保存到 localStorage 或 sessionStorage 中
- 后续客户端向服务器请求资源的时候,将 token 附带于 HTTP 请求头 Authorization 字段中发送请求
- 服务端收到请求后,校验请求中的 token,如果验证成功,则返回请求资源,否则返回 401
优缺点
优点
- 优点一:服务端不需要存放 token,所以不会对服务器造成压力,也利于多个服务间共享用户状态
- 优点二:token 不用保存在 cookie 中,提升了页面的安全性(避免CSRF攻击),而且支持跨域跨程序调用,cookie 是不允许跨域访问的
- 优点三:支持移动设备
缺点
- 缺点一:占带宽,正常情况下比 sessionId 更大
- 缺点二:token 下发之后,只要在生效时间之内,就一直有效,如果服务端想收回此 token 的权限,并不容易
参考链接:前端登录-这一篇就够了,前端登录鉴权方案
3. 权限控制
前端权限的意义
- 降低非法操作的可能性:当手动输入URL、控制台发送请求、开发者工具改数据这种级别的入侵可以防范掉。
- 尽可能排除不必要请求,减轻服务器压力:不具备权限的请求,应该压根就不需要发送。
- 提高用户体验:根据用户具备的权限为该用户展现自己权限范围内的内容。
前端权限控制思路
- 菜单的控制
- 在登录请求中,会得到权限数据,前端根据权限数据,展示对应的菜单。
- 界面的控制
- 如果用户没有登录,手动在地址栏敲入管理界面的地址,则需要跳转到登录界面。
- 如果用户已经登录,手动敲入非权限内的地址,则需要跳转到 404 界面。
- 实现方式:导航守卫,动态路由
- 按钮的控制
- 在某个菜单的界面中,还得根据权限数据,展示出可进行操作的按钮,比如删除、修改、增加。
- 实现方式:自定义指令,路由规则中增加 meta 数据,对比当前的路由规则和 meta 数据
- 请求和响应的控制
- 如果用户通过非常规操作,比如通过调试工具讲某些禁用的按钮变成启用状态,此时发送的请求,应该被前端所拦截。
- 实现方式:请求方式的约定 restful:GET(view),POST(add)
参考链接:后台系统的权限控制与管理,前端鉴权控制,vue权限校验
- 菜单控制:rights 该⽤户具备的权限数据,⼀级权限就对应⼀级菜单,⼆级权限就对应⼆级菜单。
- 界面控制
- 情况一:正常的逻辑是通过登录页面,登录成功之后跳转到管理平台页面。但如果用户直接敲入管理平台的地址,也是可以跳过登录的步骤。所以应该在某个时机判断用户是否登录。而这个时机就是路由导航守卫。
- 情况二:由于路由信息还是完整的存在于浏览器,则当用户登录之后,在地址栏敲入不具备权限的菜单,仍然可以访问。解决的办法是动态加载路由信息。
- 请求和响应的控制
- 请求控制:除了当登录请求都得要带上token,服务器才可以鉴别你的身份外。如果发出了非权限内的请求,前端应该直接拦截。注意:需遵照 RESTFUL 风格(查看 GET请求,增加 POST 请求,修改 PUT 请求,删除 DELETE 请求).
- 响应控制:得到了服务器返回的状态码 401,代表 token 超时或者被篡改了,此时应该强制跳转到登录界面。
axios.interceptors.response.use( function(res) {
if (res.data.meta.status === 401) {
router.push('/login')
sessionStorage.clear()
window.location.reload()
}
return res
})
浏览器
1. 浏览器渲染机制,重绘,重排
布局(layout):计算出每个节点在屏幕中的位置
绘制(painting):即遍历 render 树,并绘制每个节点
render-tree 不包含 display: none; 和尺寸为 0px 的元素;包含 visibility: hiddenl 或 opacity: 0
浏览器渲染机制
浏览器渲染展示网页的过程,大致分为以下五个步骤:
- HTML 被 HTML 解析器解析成 DOM 树
- CSS 则被 CSS 解析器解析成 CSSOM 树
- 结合 DOM 树和 CSSOM 树,生成一棵渲染树(render tree)
- 生成布局(flow/layout),即将渲染树的所有节点进行平面合成
- 将布局绘制(paint)在屏幕上
参考链接:浏览器渲染机制、重绘、重排,
重绘
- 某些元素的外观被改变,例如:color,background,background-image,background-position,border-style,border-radius 等
重排
- 重新生成布局,重新排列元素。例如:添加或者删除可见的 DOM 元素,浏览器窗口尺寸改变(resize事件),元素尺寸改变(边距、边框、宽度和高度),内容变化(用户在 input 框中输入文字)等
2. Cookie、LocalStorage、SessionStorage 异同
提示:既然这三个都是存储数据的,可以从存储大小、数据的有效期、数据的作用域和服务器之间的传递几个方面回答。
共同点:都是保存在浏览器端,且同源。
区别:
- 存储大小不同:cookie 数据不超过 4k,sessionStorage 和 localStorage 可达到 5M 或更大。
- 数据有效期不同:cookie 在过期时间之前都有效;sessionStorage 在当前浏览器窗口关闭前有效;localStorage 窗口或者浏览器关闭也一直保存,持久有效。
- 作用域不同:cookie 在所有同源窗口中都是共享的;sessionStorage 在不同的浏览器窗口不共享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的。
- 与服务器间传递不同:cookie 在同源的 HTTP 请求中携带,即 cookie 在浏览器和服务器间来回传递;而 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。
参考链接:Cookie、LocalStorage、SessionStorage
3. 什么是同源策略
跨域问题其实就是浏览器的同源策略造成的。
- 同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。同源指的是:协议、端口号、域名必须一致。
下表给出了与 URL store.company.com/dir/page.ht… 的源进⾏对⽐的示例:
同源策略限制了三个方面
- 当前域下的 js 脚本不能访问其他域下的 cookie、localStorage 和 indexDB.
- 当前域下的 js 脚本不能够操作访问其他域下的 DOM.
- 当前域下 ajax 无法发送跨域请求。
4. 如何解决跨域问题?
- cors:其基本思想是使用自定义的 HTTP 头部运行浏览器和服务器相互了解对方,从而决定请求或响应成功与否。
- jsonp:其原理是利用
<script>
标签没有跨域限制。 - postMessage 跨域:解决了页面和其打开的新窗口,多窗口之间消息传递等跨域数据传递问题。
- nginx 反向代理。
参考链接:解决跨域问题
5. 浏览器的垃圾回收机制
垃圾回收
- js代码运行时,需要分配内存空间来存储变量和值。当变量不参与运行时,就需要系统收回占用的内存空间,这就是垃圾回收。
回收机制
- js 会定期对那些不再使用的变量、对象所占用的内存进行释放。
- js 中存在两种变量:局部变量和全局变量,全局变量的生命周期会持续到页面卸载,而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束。当然,存在闭包的情况除外。
垃圾回收方式
- 浏览器通常使用的垃圾回收方式有两种:标记清除,引用计数
-
标记清除
- 当变量进入执行环境时,就标记这个变量 “进入环境”,此时变量不会被回收。当变量离开环境时,就会被标记为 “离开环境”,此时变量会被内存释放。
-
引用计数
- 跟踪记录每个值被引用的次数,当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1,相反,如果包含对这个值引用的变量又取得了另一个值(被另一个引用类型赋值),则这个值的引用次数就减1,当这个引用次数变为0时,说明这个变量已经没有价值。因此,在下次垃圾回收时,这个变量所占有的内存空间会被释放。
- 但这种方法会引起循环引用的问题。
- 引用计数法,最重要的就是计数器,记录有多少引用该对象。
减少垃圾回收
- 虽然浏览器可以进行垃圾自动回收,但当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。
对数组进行优化
:在清空一个数组时,最简单的方法是给其赋值为[],但与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。对object进行优化
:对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。对函数进行优化
:在循环中的函数表达式,如果可以复用,尽量放在函数的外面。
6. 哪些情况会导致内存泄漏
意外的全局变量
:由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。被遗忘的计时器或回调函数
:设置了 setInterval 定时器,而忘记取消它;如果循环函数有对外部变量引用的话,那么这个变量会被一直留在内存中,无法被回收。脱离DOM的引用
:获取一个DOM元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。闭包
:不合理的使用闭包,从而导致某些变量一直被留在内存中。
webpack
webpack 是一个用于现代 js 应用程序的静态模块打包工具。
1. 打包原理
- 识别入口文件
- 逐层识别模块依赖(CommonJs、AMD、ES6 等)
- 分析代码、转换代码、编译代码、输出代码
- 最终形成打包后的代码
2. 什么是 loader(加载)
loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,如编译、压缩等,最终一起打包到指定的文件中
3. 什么是 plugin(生成)
在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。
4. loader 和 plugin 的区别
loader 是一个转换器,操作的对象是文件,比如将 A.scss 转换为 A.css.
plugin 是一个扩展器,基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。
5. 常见的 loader
- file-loader:将文件输出到一个文件夹中。
- image-loader:加载并压缩图片文件。
- json-loader:加载 json 文件.
- babel-loader:将 ES6 转换为 ES5.
- ts-loader:将 ts 转换为 js.
- sass-loader/less-loader:将 sass/less 转换为 css.
- style-loader:将 css 代码注入到 js 中。
- vue-loader:加载 vue.js 单文件组件。
6. 常见的 plugin
- define-plugin:定义环境变量
- ignore-plugin:忽略部分文件
- clean-webpack-plugin:目录清理
- mini-css-extract-plugin:分离样式文件,css 提取为独立文件
- serviceworker-webpack-plugin:为网页应用增加离线缓存功能
HTTP
1. 状态码的类别
- 1xx:information(信息性状态码),接受的请求正在处理。
- 2xx:success(成功状态码),请求正常处理完毕。
- 200 响应成功
- 3xx:redirection(重定向状态码),需要进行附加操作才能完成请求。
- 301 永久重定向,302 临时重定向
- 4xx:client error(客户端错误状态码),服务器无法处理请求。
- 401 认证失败,403 服务器禁止访问,404 Not Found
- 5xx:server error(服务器错误状态码),服务器处理请求出错。
- 500 502 服务器内部错误,504 服务器繁忙
参考链接:HTTP 的重定向
2. GET 和 POST 区别
- 应用场景不同:GET一般用于查询操作,POST一般用于修改操作
- 浏览器是否缓存:浏览器一般会对GET请求缓存,但很少对POST请求缓存
- 传送的参数不同:GET参数通过URL传递(GET有长度限制且不安全,POST无长度限制),POST则放在Request Body中
- 参数类型:POST的参数传递支持更多的数据类型
Nodejs
1. 是什么?优缺点?应用场景?
是什么
- nodejs 是一个服务器端的、非阻塞式I/O的、事件驱动的 js 运行环境。
优点
- 处理高并发场景性能极佳
- 适合 I/O 密集型应用
缺点:因为 nodejs 是单线程,带来的缺点有:
- 不适合 CPU 密集型应用
- 只支持单核 CPU,不能充分利用 CPU
- 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃
应用场景
- 善于 I/O,不善于计算,因为 nodejs 是一个单线程,如果计算(同步)太多,则会阻塞这个线程
- 与 websocket 配合,开发长连接的实时交互应用程序
2. nodejs 有哪些全局对象?
真正的全局对象
- class:buffer,process,console,clearInterval,setInterval,clearTimeout,setTimeout,global
模块级别的全局对象
- __dirname,__filename,exports,module,require