九号
1·页面想渲染的几个流程
-
解析HTML生成DOM树。
-
解析CSS生成CSSOM规则树。
-
将DOM树与CSSOM规则树合并在一起生成渲染树。
-
遍历渲染树开始布局,计算每个节点的位置大小信息。
-
将渲染树每个节点绘制到屏幕。
2· SPA首页加载慢怎么解决
- 减小入口文件体积,常见的手段就是进行路由的懒加载,动态加载路由
- 合理利用静态资源本地缓存
- 按需加载UI组件
- 合理使用webpack等打包工具进行前端性能优化,常见的包括(treeshaking)等
- 骨架屏的引用
3· 实现深拷贝的几种方式
- JSON.stringify();(深拷贝普通对象时推荐使用) 但是属性类型为undefined、null、Date、RegExp、function时,使用该方式进行深拷贝会出问题。在代码里会报错
- 递归方式(推荐使用)
- 第三方库lodash的cloneDeep()方法
4·深拷贝与浅拷贝的区别
-
浅拷贝是创建一个新对象,该对象的内容是原始对象的引用。换句话说,新对象与原始对象共享内存中的某些部分。当对其中一个对象进行更改时,另一个对象也会受到影响。
-
深拷贝会创建一个新对象,并且递归地复制原始对象及其内容,而不仅仅是引用。深拷贝不共享任何内存地址,因此对其中一个对象的更改不会影响另一个对象。
4·闭包
示例:
function outerFunction() {
let count = 0;
return function innerFunction() {
count++;
console.log(count);
};
}
const counter = outerFunction();
counter(); // 输出 1
counter(); // 输出 2
counter(); // 输出 3
const anotherCounter = outerFunction();
anotherCounter(); // 输出 1
闭包的缺点:
- 内存泄漏: 如果闭包持有的对外部变量的引用没有被正确释放,可能会导致内存泄漏。尤其是在使用闭包保存DOM元素引用时,需要特别注意在不需要时解除引用,避免循环引用导致的内存泄漏。
- 调试困难: 闭包的嵌套结构有时会使代码难以理解和调试。由于闭包可以访问外部作用域的变量,追踪变量的变化可能会比较复杂。
- 过度使用导致代码复杂: 虽然闭包很有用,但过度使用可能会使代码变得难以理解和维护。应该谨慎使用闭包,并在必要时才使用。
- 性能问题(潜在): 虽然在现代浏览器中闭包的性能问题已经得到很大改善,但在某些情况下,闭包的性能仍然可能比普通函数略低。这主要是因为闭包需要维护其词法环境,这会带来一些额外的开销。
5·前端js中引起内存泄漏的常见原因
1. 意外的全局变量#
由于 js 对未声明变量的处理方式是在全局对象上创建该变量的引用。如果在浏览器中,全局对象就是 window 对象。变量在窗口关闭或重新刷新页面之前都不会被释放,如果未声明的变量缓存大量的数据,就会导致内存泄露。
1.1 未声明变量#
function fn() {
a = 'hello'
}
fn()
1.2 使用 this 创建的变量(this 的指向是 window)。#
function fn() {
this.a = 'hello'
}
fn()
解决方法:
- 避免创建全局变量
- 使用严格模式,在 JavaScript 文件头部或者函数的顶部加上 use strict
2. 闭包引起的内存泄漏#
由于闭包可以读取函数内部的变量,然后让这些变量始终保存在内存中。如果在使用结束后没有将局部变量清除,就可能导致内存泄露。
3. 没有清理的DOM元素引用#
虽然在某个地方删除了元素,但是对象中还存在对dom的引用。
// 在对象中引用DOM
var elements = {
btn: document.getElementById('btn'),
}
function doSomeThing() {
elements.btn.click()
}
function removeBtn() {
// 将body中的btn移除, 也就是移除 DOM树中的btn
document.body.removeChild(document.getElementById('btn'))
// 但是此时全局变量elements还是保留了对btn的引用, btn还是存在于内存中,不能被回收
}
4. 被遗忘的定时器或者回调#
定时器中有 dom 的引用,即使 dom 删除了,但是定时器还在,所以内存中还是有这个 dom。
// 定时器 loadData例为请求数据函数
var serverData = loadData()
setInterval(function () {
var renderer = document.getElementById('renderer')
if (renderer) {
renderer.innerHTML = JSON.stringify(serverData)
}
}, 5000)
// 观察者模式
var btn = document.getElementById('btn')
function onClick(element) {
element.innerHTMl = "innerHTML"
}
btn.addEventListener('click', onClick)
6·前端vue中引起内存泄漏的常见原因
1.全局变量造成的内存泄露#
声明的全局变量在切换页面的时候没有清空
<template>
<div id="home">这里是首页</div>
</template>
<script>
export default {
mounted() {
window.test = {
// 此处在全局window对象中引用了本页面的dom对象
name: 'home',
node: document.getElementById('home'),
}
},
}
</script>
解决方法: 在页面卸载的时候顺便处理掉该引用
destroyed () {
window.test = null // 页面卸载的时候解除引用
}
2. 监听在 window/body 等事件没有解绑#
特别注意 window.addEventListener 之类的时间监听
<template>
<div id="home">这里是首页</div>
</template>
<script>
export default {
mounted () {
window.addEventListener('resize', this.func) // window对象引用了home页面的方法
}
}
</script>
解决方法: 在页面销毁的时候,顺便解除引用,释放内存
beforeDestroy () {
window.removeEventListener('resize', this.func)
}
3. 绑在 EventBus 的事件没有解绑#
<template>
<div id="home">这里是首页</div>
</template>
<script>
export default {
mounted () {
this.$EventBus.$on('homeTask', res => this.func(res))
}
}
</script>
解决方法: 在页面卸载的时候也可以考虑解除引用
mounted () {
this.$EventBus.$on('homeTask', res => this.func(res))
},
destroyed () {
this.$EventBus.$off()
}
7·js中的宏任务和微任务
JavaScript 是一种单线程的编程语言,这意味着在同一时间只能执行一个任务。为了有效地处理并发操作,JavaScript 引入了事件循环(Event Loop)机制,其中宏任务(Macro Task)和微任务(Micro Task)在其中扮演着关键角色。
1. 什么是宏任务和微任务?
-
宏任务(Macro Task) 是 JavaScript 中执行的大块任务或代码块,它包括了一些常见的操作,如:
setTimeoutsetIntervalsetImmediate(仅在 Node.js 中)- I/O 操作
- UI 渲染
- 事件处理
-
微任务(Micro Task) 是一个需要在当前宏任务完成后、下一个宏任务开始前立即执行的小任务。常见的微任务有:
Promise.then、Promise.catch、Promise.finallyMutationObserverprocess.nextTick(仅在 Node.js 中)
宏任务与微任务的执行顺序
了解宏任务与微任务的执行顺序对于掌握 JavaScript 异步操作非常重要。以下是一些关键点:
- 微任务总是在当前宏任务结束后立即执行,优先级高于下一个宏任务。
- 如果在微任务中再次添加微任务,这些新添加的微任务会在当前微任务队列完成后立即执行。
示例代码:
console.log('Start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
}).then(() => {
console.log('Promise 2');
});
console.log('End');
执行顺序解释:
- 'Start' 和 'End' 是同步代码,首先执行。
setTimeout是一个宏任务,它会在宏任务队列中排队,等待当前宏任务完成后执行。Promise.then是一个微任务,它会在当前宏任务(即同步代码执行完毕后)立即执行。- 因此,微任务 'Promise 1' 和 'Promise 2' 会先于
setTimeout的回调执行。 - 最后,
setTimeout的回调会被执行。
输出顺序:
Start
End
Promise 1
Promise 2
setTimeout
8·vuerouuter有哪几种导航钩子
- 全局钩子
- 某个路由的钩子
- 组件内钩子
两种函数:
Vue.beforeEach(function(to,form,next){}) /*在跳转之前执行*/Vue.afterEach(function(to,form))/*在跳转之后判断*/
代码语言:javascript
**复制
router.beforeEach((to, from, next) => {
let token = router.app.$storage.fetch("token");
let needAuth = to.matched.some(item => item.meta.login);
if(!token && needAuth) return next({
path: "/login"});
next();
});
beforeEach函数有三个参数:
to:router即将进入的路由对象;from:当前导航即将离开的路由;next:Function,进行管道中的一个钩子,如果执行完了,则导航的状态就是confirmed(确认的);否则为false,终止导航。
注:afterEach()不用传next()函数。
可以在路由组件内直接定义以下路由导航钩子:
beforeRouteEnterbeforeRouteUpdate(2.2 新增)beforeRouteLeave
这里简单说下钩子函数用法:它是和data,methods平级的。
代码语言:javascript
beforeRouteLeave(to, from, next) {
next()
},
beforeRouteEnter(to, from, next) {
next()
},
beforeRouteUpdate(to, from, next) {
next()
},
data:{
},
method: {
}
9·get和post有什么区别
-
参数位置:GET请求的参数是直接放在URL中的,而POST请求的参数则是放在请求体中。
-
缓存能力:GET请求可以被浏览器缓存,而POST请求则不能被缓存。
-
长度限制:由于GET参数在URL中,所以有长度限制;而POST请求的参数在请求体中,因此没有长度限制。
-
安全性:GET请求的安全性相对较差,因为参数明文显示在URL中;而POST请求的安全性较好,参数在请求体中传输。
-
访问方式:GET请求可以通过浏览器直接访问,支持刷新和后退操作;而POST请求不能直接通过浏览器访问。
10·什么是渐进增强和优雅降级
11·v-bind和v-model的区别
定义和功能
-
v-bind:
- 单向绑定:v-bind用于将Vue实例的数据绑定到DOM元素的属性上,数据只能从Vue实例流向DOM元素,不能反向流动。12
- 适用范围:可以绑定任何属性,如class、style等。35
-
v-model:
- 双向绑定:v-model用于在表单控件元素(如input、select、textarea等)和Vue实例之间建立双向数据绑定,数据可以从Vue实例流向DOM元素,也可以从DOM元素流向Vue实例。
- 适用范围:主要用于表单控件,如input、radio、checkbox等。12
使用场景和示例
- v-bind:适用于需要动态更新DOM属性但不涉及用户输入的场景。例如,可以使用v-bind动态绑定class或style属性,根据数据变化更新元素的样式或类。4
- v-model:适用于需要实时同步用户输入和Vue实例数据的表单控件。例如,在input框中使用v-model绑定一个变量,用户输入的内容会实时更新该变量的值,反之亦然。
总结
- v-bind主要用于单向数据绑定,适用于需要动态更新DOM属性的场景。
- v-model主要用于双向数据绑定,适用于需要实时同步用户输入和Vue实例数据的表单控件。
12·原型链
每个对象都有一个原型对象(prototype),通过这个原型对象,可以访问它继承的属性和方法。如果在一个对象上找不到某个属性或方法,就会沿着原型链向上查找,直到找到或者到达原型链的顶端(null)。
13·rem和em的区别
rem和em的主要区别在于它们所依赖的基准不同:
- em:em是一个相对长度单位,其值会继承父级元素的字体大小。例如,如果父元素的字体大小设置为16像素(px),那么1em就等于16px,2em就等于32px。em的优点是可以根据父元素的字体大小进行缩放,具有一定的灵活性,但缺点是如果嵌套层级较多,em的值会根据所有祖先元素的字体大小进行复合计算,容易导致计算复杂和难以预测最终大小。12
- rem:rem是CSS3中新增的一个相对单位,它相对于HTML根元素(即html元素)的字体大小。例如,如果根元素的字体大小设置为16像素(px),那么1rem就等于16px。rem的优点是可以避免em的复合计算问题,只需修改根元素的字体大小即可成比例地调整所有字体大小,具有更好的灵活性和可预测性。缺点是需要设置根元素的字体大小,并且如果用户修改了浏览器的默认字体大小,可能需要通过JavaScript动态调整根元素的字体大小以保持一致性。
14.vue中的插槽
什么是插槽?
插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制
插槽的分类:
- 默认插槽
- 具名插槽
- 动态插槽
默认插槽的名字:default
15.vue中常见的指令
-
v-bind或:- 用于动态地绑定一个或多个属性,或组件 prop 到表达式。
- 例如:
<a v-bind:href="url">Link</a>或<a :href="url">Link</a>
-
v-model- 创建一个双向数据绑定在表单输入和应用状态之间。
- 例如:
<input v-model="message">
-
v-on或@- 用于绑定事件监听器。
- 例如:
<button v-on:click="doSomething">Click me</button>或<button @click="doSomething">Click me</button>
-
v-if和v-else-if/v-else- 根据表达式的真假条件来渲染元素。
- 例如:
<p v-if="seen">Now you see me</p>
-
v-show- 根据表达式的真假值切换元素的 CSS 属性
display。 - 例如:
<p v-show="ok">Am I visible?</p>
- 根据表达式的真假值切换元素的 CSS 属性
-
v-for- 基于源数据多次渲染元素或模板块。
- 例如:
<li v-for="item in items">{{ item.text }}</li>
-
v-pre- 跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。
- 例如:
<span v-pre>{{ This will not be compiled }}</span>
-
v-cloak- 这个指令保持在元素上直到 Vue 实例结束编译。常与 CSS 规则一起使用,可以隐藏未编译的 Mustache 标签直到 Vue 实例准备就绪。
- 例如:在 CSS 中使用
[v-cloak] { display: none },然后在元素上添加v-cloak。
-
v-html- 更新元素的
innerHTML。注意这样做非常危险,因为它容易导致 XSS 攻击,除非你非常确定内容是安全的。 - 例如:
<div v-html="rawHtml"></div>
- 更新元素的
-
v-text- 更新元素的
textContent。 - 例如:
<span v-text="msg"></span>
- 更新元素的
-
v-once- 执行一次性地插值,当数据改变时,插值处的内容不会更新。
- 例如:
<span v-once>This will never update</span>