web前端面试题
1、垂直居中方式
/* 垂直居中方式 */
.aaaaa {
/* 直接定位 */
position: absolute; // 子绝父相
top: 45px;
left: 45px
margin: 0 auto; // 块级
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center; // 文本居中
vertical-align: middle; // 图片
display: flex; // 自由布局
justify-content: center;
align-items: center;
height: 100px; // 行高等于高度
line-height: 100px;
}
2、清除float影响
/* 清除float影响
1.尾部额外标签,添加块元素的css样式clear:both
2.单伪元素清除法
.clearfix::after{
content:'';
display:blok;
clear:both;
//低版本浏览器添加
height:0;
visibility:hidden;
}
3.双伪元素解决塌陷法
.clearfix::before,
.clearfix::after{
content:'';
display:table;
}
//真正清除浮动部分
.clearfix::after{
clear:both;
}
4.父元素添加overflow:hidden;
*/
3、css3超出部分省略
适用于input标签和普通标签文本内容超出部分省略号显示。
(1)哪三个属性
overflow:hidden;
white-space:nowrap;
text-overflow:ellipsis;
(2)具体属性用法
overflow属性的使用:
特点:必须给块级容器指定高度,或者使用不换行属性让内容变宽
属性值:
- visible:默认值。
- hidden:内容会被修剪,浏览器会显示滚动条。
- scroll:由浏览器定夺,如果内容被修剪,会显示滚动条。
- auto:规定从父元素集成overflow属性的值。
white-space属性的使用 :处理元素中的空白
属性值:常用
- normal:文字换行。
- nowrap:文字不换行。
text-overflow属性的使用:
特点:该属性不会强制发生溢出,要是文本溢出其容器,必须设置overflow和white-space
属性值:
- clip:默认值,换行。
- ellipsis:显示一个‘…’来表示剪切文本
4、css实现三角形
三角形原理:边框的均分原理
通过设置 border 的 top、left、right、bottom 属性实现,transparent 表示透明色
.box1 {
width: 0;
height: 0;
border-top: 100px solid red;
border-left: 100px solid blue;
border-right: 100px solid green;
border-bottom: 100px solid yellow;
}
.box2 {
width: 0;
height: 0;
/* transparent 透明色 */
border-top: 200px solid transparent;
border-left: 0px solid blue;
border-right: 100px solid green;
border-bottom: 0 solid yellow;
}
.box3 {
width: 0;
height: 0;
border-color: transparent green transparent transparent;
border-style: solid;
border-width: 200px 100px 0 0;
}
5、Session、Cookie、sessionStorage、localStorage区别
特别:
cookie 数据存放在客户的浏览器上,session 数据放在服务器上;
cookie 不安全,可能被别人分析欺骗
① 传递方式不同
-
cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。
-
sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
② 数据大小不同
Cookie 4KB、sessionStorage 约 5M、localStorage 约 20M
③ 数据有效期不同
-
sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;
-
localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;
-
cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
④ 作用域(生命周期)不同
sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;
localStorage 在所有同源窗口中都是共享的;
cookie也是在所有同源窗口中都是共享的。
6、iframe 是什么?有什么优缺点?
iframe 元素会创建包含另一个文档的内联框架。
优点:
- 解决加载缓慢的第三方内容如图标和广告等的加载问题
- Security sandbox
- 并行加载脚本
缺点:
- iframe会阻塞主页面的Onload事件
- 即时内容为空,加载也需要时间
- 没有语意
-
搜索引擎无法解读这种页面,不利于 SEO
-
iframe 和主页面共享连接池,而浏览器对相同区域有限制所以会影响性能。
7、对 HTML 标签语义化的理解
HTML 语义化标签是指正确的标签包含了正确的内容,结构良好,便于阅读,比如 nav 表示导航条,类似的还有 article、header、footer 等等标签。
8、你对web标准以及W3C的理解?
web标准:web标准主要分为结构、表现、行为3部分结构:指我们平时在body里面写的标签,主要是由HTML标签组成表现:指更加丰富HTML标签样式,主要由CSS样式组成行为:指页面和用户的交互,主要由JS部分组成
W3C:W3C对web标准提出了规范化的要求,即代码规范对结构的要求1、标签字母要小写2、标签要闭合3、标签不允许随意嵌套
对表现和行为的要求1、建议使用外链CSS和js脚本,实现结构与表现分离、结构与行为分离,
能提高页面的渲染效率,更快地显示网页内容
9、回流和重绘
回流:当一个元素自身的宽高,布局,显示或隐藏,或元素内部的文字结构发生变化
,导致需要重新构建页面的时候,就产生了回流复制
重绘:当一个元素自身的宽高,布局,及显示或隐藏没有改变,
而只是改变了元素的外观风格的时候,就产生了重绘
10、搭建网页都需要用到哪些布局方式
① 标准流(标签自带布局)
② 浮动(float)
③ 定位(position)
层级关系
标准流<浮动<定位(默认情况,定位的盒子,后来者居上;z-index:整数;取值越大,显示顺序越在上;默认为0,必须配合定位。)
11、防抖和节流
1. 防抖(debounce):
防抖触发高频率事件时n秒后只会执行一次,如果n秒内再次触发,则会重新计算。
简单概括:每次触发时都会取消之前的延时调用。(将多次执行变为最后一次执行)
2. 节流(thorttle):
高频事件触发,每次触发事件时设置一个延迟调用方法,并且取消之前延时
调用的方法。
简单概括:每次触发事件时都会判断是否等待执行的延时函数。(将多次连续的执行变成每隔一段时间执行)
**区别:**降低回调执行频率,节省计算资源。
防抖和节流本质是不一样的。防抖是将多次执行变为最后一次执行,节流是将
多次执行变成每隔一段事件执行
函数防抖一定连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行
一次。
12、判断js的数据类型
通过 typeof 和 instanceof
typeof 可以用来区分除了 Null 类型(可以用全等===
判断)以外的原始数据类型
instanceof 不能用于判断原始数据类型的数据
13、 var、let和const的区别
var、let 区别:
1.使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象;
2.使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升;
3.let不允许在相同作用域内,重复声明同一个变量
const
(1) const是设置常量,也就是不能改变。const定义的数值字符串的时候值不能改变。
(2) const 定义的对象的时候,对象不能改变,但是对象的属性值可以改变。
var | let | const |
---|---|---|
只有全局作用域和函数作用域 | 有块级作用域 | 有块级作用域 |
存在变量提升 | 不存在变量提升 | 不存在变量提升 |
可以重复声明 | 不能重复声明 | 不能重复声明 |
没有死区 | 存在暂时性死区 | 存在暂时性死区 |
可以先声明后赋值 | 可以先声明 后赋值 | 声明即赋值 |
14、函数提升与变量提升
变量提升
简单说就是在 JavaScript 代码执行前引擎会先进行预编译,预编译期间会将变量声明与函数声明提 升至其对应作用域的最顶端,函数内声明的变量只会提升至该函数作用域最顶层当函数内部定义的一个变量与外部相同时,那么函数体内的这个变量就会被升到最顶端
函数提升
函数提升只会提升函数声明式写法,函数表达式的写法不存在函数提升
函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上
15、什么是作用域?全局作用域和局部作用域?
概念:代码(变量)可以使用的范围就是作用域。主要是为了提高程序的可靠性,也是为了减少命名冲突
全局作用域和局部作用域
全局作用域:指的是整个js文件,定义在全局中的全局变量,可以在局部作用域中使用,函数内部没有声明直接赋值的变量也叫全局变量
局部作用域:主要指的是函数作用域,函数内部也就是局部作用域
在函数内部var定义的变量-,叫做局部变量,局部变量无法被外部获取
16、什么是事件冒泡?怎么阻止事件冒泡?
概念:当我们点击子元素触发父元素的事件,这种现象,我们叫做事件冒泡,即由子元素向祖先元素传播,就像气泡从水底上浮
event.stopPropagation();阻止事件冒泡
17、什么是事件委托?
通俗的讲,子代有事,让父级执行,当我们点击li时,子元素会通过事件的冒泡来触发父元素的事件,主要是通过事件冒泡
原理: 不要给每个子节点单独设置事件监听器,而是事件监听设置在其父节点上,然后利用冒泡原理影响设置每个子节点
18、闭包
闭包就是能够读取其他函数内部变量的函数
闭包基本上就是一个函数内部返回一个函数
特点:
-
函数嵌套函数
-
内部函数可以访问外部变量
-
参数和变量不会被回收(回收牵扯出全局变量和局部变量,因为全局变量在页面关闭后销毁,比较占内存资源。局部变量在函数执行完毕后销毁,闭包保护局部变量不会在函数执行完被回收也就是销毁)
19、深拷贝与浅拷贝
浅拷贝:基本数据类型拷贝的是值,引用数据类型拷贝的是地址
深拷贝:引用数据类型拷贝的是开辟新地址中的值
① JS 中可以使用 node.cloneNode()
复制;参数为空或者false,浅拷贝,只复制标签不复制内容;可以添加true,深复制
② JQuery 中可以使用 $.extend([deep], target, object, [objectN])
,把 deep 参数设为 true
③ json暴力转化
对象和字符串的相转化
var a = JSON.stringify(one);//先转字符串再转对象
var b = JSON.parse(a);
封装开辟新地址的函数
function deepCopy(obj){
var objs = JSON.stringify(obj);
var newObj = JSON.parse(objs);
return newObj;
}
这种简单粗暴的方式有局限性,当值为undefined、function、symbol会在转换过程中被忽略。
④ ES6 新语法扩展运算符
var obj = {name:'123',age:13};
var obj2 = {...obj}
只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
⑤ for in 循环遍历对象
var obj = {
name: "小明",
age: 20
}
var obj1 = {}
for (var key in obj) {
//遍历属性值,深拷贝
obj1[key] = obj[key]
}
console.log(obj1);
同样的,只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
⑥ Object.assign() 对象的合并
利用Object.assign(), 第一个参数必须是空对象
var obj = {name:'123',age:13};
var obj2 = Object.assign({},obj1);
只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
⑦ 利用循环和递归的方式
function deepClone(obj, newObj) {
var newObj = newObj || {};
for (let key in obj) {
if (typeof obj[key] == 'object') {
newObj[key] = (obj[key].constructor === Array) ? [] : {}
deepClone(obj[key], newObj[key]);
} else {
newObj[key] = obj[key]
}
}
return newObj;
}
在循环递归中需要注意设置临界值(typeof obj[key] == ‘object’),否则会造成死循环。
循环递归可以处理对象中嵌套数组或对象的问题。相当于第三种方法的优化。
20、javascript 的内存(垃圾)回收机制?
含义:垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存
一般使用标记清除方法(mark and sweep), 当变量进入环境标记为进入环境,离开环境标记为离开环境
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
还有引用计数方法(reference counting), 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。
21、JavaScript 的同源策略
概念: 同源策略是客户端脚本(尤其是Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。
这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。
指一段脚本只能读取来自同一来源的窗口和文档的属性。
22、解决跨域?
- jsonp跨域
- document.domain + iframe 跨域
- nodejs中间件代理跨域
- vue 的 proxy 代理
23、内存泄漏
定义:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题。
js中可能出现的内存泄漏情况:结果:变慢,崩溃,延迟大等
js中可能出现的内存泄漏原因:
- 全局变量
- dom 清空时,还存在引用
- 定时器未清除
- 子元素存在引起的内存泄露
24、JavaScript 的执行策略
1. EventLoop
JavaScript 单线程语言
问题:前一个任务耗时,程序假死
(1) 同步任务和异步任务
① 同步任务(synchronous)
- 非耗时任务,主线程上排队执行
- 前一个执行完,才执行后一个
② 异步任务 (asynchronous)
- 好耗时任务,js 委托给宿主环境执行
- 异步任务执行完成后,会通知 js主线程执行异步任务的回调函数
(2) 同步任务和异步任务的执行过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e9HxW9hs-1692283578768)(D:\PerfectCode\前端学习\VUE\Vue3\images\同步异步执行过程.PNG)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AWbEgfTQ-1692283578769)(D:\PerfectCode\前端学习\VUE\Vue3\images\同步异步面试题.PNG)]
2. 宏任务和微任务
① 宏任务(macrotask)
- 异步 Ajax 请求
- setTimeout、setinterval
- 文件操作
- 其它宏任务
② 微任务 (microtask)
- Promise.then、.catch 和 .finally
- process.nextTick
- 其它微任务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82hiO7Rz-1692283578770)(D:\PerfectCode\前端学习\VUE\Vue3\images\Js任务.PNG)]
(1) 宏任务和微任务的执行顺序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GqoVc2PS-1692283578770)(D:\PerfectCode\前端学习\VUE\Vue3\images\宏任务微任务执行顺序.PNG)]
(2) 经典面试题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ugffWqz-1692283578771)(D:\PerfectCode\前端学习\VUE\Vue3\images\宏任务微任务面试题.PNG)]
25、get与post的区别
- get 通过地址栏拼接参数,但是因为地址栏长度有限,所以get传输数据也有限
- post 没有数据传输大小的限制
- get不需要设置请求头post需要设置请求头
- get请求数据可能被缓存post不会
- get只能URL编码,post支持的编码格式众多
26、web性能优化
-
降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。
-
加快请求速度:预解析 DNS,减少域名数,并行加载,CDN 分发。
-
缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage。
-
渲染:JS/CSS 优化,加载顺序,服务端渲染,pipeline。
27、为什么vue组件中的data是一个函数
确保组件间数据相互不被影响,防止变量污染
28、computed和watch的区别
1、含义
computed:计算属性,并且有缓存,只有她所依赖的属性值发生变化computed才会重新计算,并且支持同步
watch:监听器,不支持缓存(可以在data中直接使用),监听的数据发生变化才会触发,watch支持异步,监听的函数可以接收两个参数,一个是最新的值,另一个是改变之前的值
2、特点
计算属性 computed :
支持缓存,只有依赖数据发生改变,才会重新进行计算
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
侦听属性watch:
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
3、使用场景
-
computed
- 当一个属性受多个属性影响的时候就需要用到computed
- 最典型的例子: 购物车商品结算的时候
-
watch
- 当一条数据影响多条数据的时候就需要用watch,搜索数据
- 深度监听:
handle()
deep:true; immediate:true
29、v-if和v-show的区别
v-if:是真正的条件渲染,同时他也是惰性的,如果在初始渲染时条件为假,则什么也不做,直到第一次变为真时,才会开始渲染,条件切换过程会发生销毁和重建,动态创建和删除元素
具有更高的切换开销,如果在运行是条件很少改变,使用v-if比较好
v-show:不管初始条件是什么,元素总是会被选二胺,并且知识简单的基于css(display:none/block)进行切换
具有更高的初始开销,如果切换非常频繁,则使用v-show比较好
30、vue的生命周期
概念:vue实例从创建到销毁的过程就是生命周期,也就是从开始创建,出啊石化数据,编译模板,挂载dom->渲染,更新->渲染,卸载等一系列过程
beforeCreate():vue实例创建之前调用
created():vue实例创建完成
beforeMount():视图编译之前
mounted():视图编译完成,vuedom加载之后
beforeUpdate():数据改变之前
updated():数据改变之后
beforeDestory():vue2销毁之前 || beforeUnmount():vue3销毁之前
destory():vue2销毁之后 || Unmounted():vue3销毁之后
特别的:
keep-alive 对应的生命周期函数
被缓存:deactivated
被激活:activated
31、vue组件如何进行传值的
-
父组件向子组件传递数据
- 父组件内设置要传的数据,在父组件中引用的子组件上绑定
:(v-bind)
一个自定义属性并把数据绑定在自定义属性上,在子组件添加props
接收即可(自定义属性)
- 父组件内设置要传的数据,在父组件中引用的子组件上绑定
-
子组件向父组件传递数据
- 子组件通过vue实例方法
$emit
进行触发并且可以携带参数,父组件监听使用@(v-on)
绑定的事件进行监听,然后方法处理(自定义方法)
- 子组件通过vue实例方法
-
兄弟组件之间传递数据
-
引入第三方 new vue 定义为 eventBus
-
在组件中 created 中订阅方法 eventBus.$on(“自定义事件名”,methods 中的方法名)
-
在另一个兄弟组件中的 methods 中写函数,在函数中发布 eventBus 订阅的方法
-
eventBus.$emit("自定义事件名”)
-
在组件的 template 中绑定事件(比如 click)
-
-
祖孙之间的传值
-
provide
定义的对象会加载到子组件或者孙子组件中 -
inject
接收祖先组件中provide函数return出的内容
-
-
父子之间的双向数组同步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gryi6CFQ-1692283578772)(D:\PerfectCode\前端学习\VUE\Vue3\images\组件v-model作用.PNG)]
<button @click="count++">+1</button>
APP-----count: {{count}}
<MyCounter v-model:number="count"></MyCounter>
----------------------------------------------
<template>
<div>
<p>count 值:{{ number }}</p>
<button @click="add">+1</button>
</div>
</template>
<script>
export default {
name: 'MyCounter',
props: ['number'],
emits: ['update:number'],
methods: {
add() {
this.$emit('update:number', this.number + 1)
}
},
}
</script>
-
可以通过 vuex 传值
-
可以使用本地存储进行传值(cookie、localstorge、sessionStorage)
32、常见的修饰符及其作用
修饰符 | 作用 |
---|---|
.stop | 等同于js中event.stopPropagation(),防止事件冒泡 |
.prevent | 等同于js中event.preventDefault(),阻止默认行为 |
.capture | 当元素发生冒泡时,先触发带有修饰符的元素,若有多个修饰符,则由内而外触发。如 div1中嵌套div2中嵌套div3.capture中嵌套div4,那么执行顺序为:div3=》div4=》div2=》div1 |
.slef | 只触发自己范围内的事件,不包含子元素 |
.once | 只触发一次 |
.lazy | 延迟执行,一般用于表单输入框 |
.trim | 自动去除首位空格 |
.number | 自动转化为数值类型 |
33、vue-router路由模式有几种?
- hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
- history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
- abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
34、对 nextTick 的理解
-
当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,你需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。
-
在使用某个第三方插件时,希望在vue生成的某些DOM动态发生变化时重新应用该插件,也会用到该方法,这时候就需要$nextTick的回调函数中执行重新应用插件的方法,例如:应用滚动插件better-scroll时
35、vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
主要包括以下几个模块:
- State => 基本数据,定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter => 从基本数据派生的数据,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
- Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。
- Action => 像一个装饰器,包裹mutations,使之可以异步。用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
- Module => 模块化Vuex,允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
import { mapGetters } from 'vuex' // 导入
computed: { // 计算属性
...mapGetters([
'属性名'
])
}
this.$store.commit('set属性名', 属性值) // 设置
// 直接调用
36、对keep-alive的理解
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
- 一般结合路由和动态组件一起使用,用于缓存组件;
- 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
- 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
37、请说出 vue-cli 工程中每个文件夹和文件的用处?
- build 文件夹是保存一些 webpack 的初始化配置。
- config 文件夹保存一些项目初始化的配置
- node_modules 是 npm 加载的项目依赖的模块
- src 目录是我们要开发的目录:
- assets 用来放置图片
- components 用来放组件文件
- app.vue 是项目入口文件
- main.js 项目的核心文件
38、Vuex 页面刷新数据丢失怎么解决?
需要做 vuex 数据持久化,一般使用本地储存的方案来保存数据,可以自己设计存储方案,也可以使用第三方插件。
推荐使用 vuex-persist 插件,它是为 Vuex 持久化储存而生的一个插件。不需要你手动存取 storage,
而是直接将状态保存至 cookie 或者 localStorage中。
39、vue 中使用了哪些设计模式?
1、工厂模式 - 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode。
2、单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉。
3、发布-订阅模式。(vue 事件机制)
4、观察者模式。(响应式数据原理)
5、装饰器模式(@装饰器的用法)
6、策略模式,策略模式指对象有某个行为,但是在不同的场景中,
该行为有不同的实现方案 - 比如选项的合并策略。
40、你都做过哪些 Vue 的性能优化?
- 这里只列举针对 Vue 的性能优化,整个项目的性能优化是一个大工程。
- 对象层级不要过深,否则性能就会差。
- 不需要响应式的数据不要放在 data 中(可以使用 Object.freeze() 冻结数据)
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分场景使用
- v-for 遍历必须加 key,key最好是id值,且避免同时使用 v-if
- 大数据列表和表格性能优化 - 虚拟列表 / 虚拟表格
- 防止内部泄露,组件销毁后把全局变量和时间销毁
- 图片懒加载
- 路由懒加载
- 异步路由
- 第三方插件的按需加载
- 适当采用 keep-alive 缓存组件防抖、节流的运用
- 服务端渲染 SSR or 预渲染
41、Vue路由懒加载
1、vue异步组件实现路由懒加载
component:resolve=>(['需要加载的路由的地址',resolve])
2、es提出的import(推荐使用这种方式)
const HelloWorld = ()=>import('需要加载的模块地址')
42、其他内容
1、性能优化的几个方面?
- 资源压缩合并,减少HTTP请求
- 非核心代码异步加载
- 利用浏览器缓存
- 使用CDN
- 预解析DNS
2、异步加载?
- 动态脚本加载
- defer
- async
3、加载方式
- defer是在html解析完毕才执行,如果有多个则按加载顺序执行
- async是加载完毕后立即执行,如果是多个,执行顺序与加载顺序无关
4、预加载
在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。
预加载其实是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载
link rel=“preload” href=“http://example.com”
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。
5、DNS预解析
DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
在https协议中默认a标签不会开启预解析,因此需要手动设置meta6、懒执行
懒执行就是将某些逻辑延迟到使用时再计算。
该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,
就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。
7、懒加载
懒加载就是将不关键的资源延后加载。懒加载的原理就是只加载自定义区域(通常是可视区域,
但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,
先设置图片标签的 src 属性为一张占位图,
将真实的图片资源放入一个自定义属性中,当进入自定义区域时,
就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。懒加载不仅可以用于图片
43、案例数组去重
//数组去重['x', 's', 'a', 's', 'g', 'a', 'h', 'm', 'd', 'l']
//1、目标:把旧数组中里面不重复的元素取出来放到新数组里,重复元素只保留一个,放到新数组里去重
//2、核心算法:遍历旧数组,拿旧数组元素去查新数组,如果该元素在新数组里没有出现过就添加,否则不添加
//3、利用 新数组.indexOf(数组元素) 如果返回是-1就说明新数组没有该元素,那就添加上
//封装一个 去重函数 unique
function unique(arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i])
}
}
return newArr;
}
console.log(['x', 's', 'a', 's', 'g', 'a', 'h', 'm', 'd', 'l']);
console.log(unique(['x', 's', 'a', 's', 'g', 'a', 'h', 'm', 'd', 'l']));
44、查询字符串所有特定元素出现位置及次数
// 查找字符串'ascascasdsacsfea'中所有a出现的位置以及次数
// 核心算法:先查找第一个a出现的位置
// 然后,只要indexOf返回的结果不是-1,就继续往后查找
// 因为indexOf只能查找到第一个元素,所以后边的查找,一定是当前索引加1,从而继续查找
var str = 'ascascasdsacsfea';
var index = str.indexOf('a');
var num = 0;
while (index !== -1) {
num++;
addr = index + 1;
console.log('第' + num + '个的位置是:' + addr);
index = str.indexOf('a', index + 1);
}
console.log('a出现的次数是:' + num);
45、常用的 JS 库
-
tocbot.js(目录生成)
-
waypoints.js(平滑滚动)
-
swiper.js(轮播图)
-
qrcode.js(生成二维码)
-
Day.js(时间日期处理)
-
lodash.js(鲁大师,高效处理array、number、objects、string等对象,降低JS操作难度)
-
zplayer.js(音乐)
-
gallery.js(画廊,图片管理)
-
distance.js(实现计算两点经纬度之间的距离)
-
Dropzone.js(文件拖拽上传)
46、特别的:forEach和map的区别
1、相同点
(1) 都是循环遍历数组中的每一项。
(2) 每次执行匿名函数都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)。
(3) 匿名函数中的this都是指向window。
(4) 只能遍历数组。
2、不同点
(1) map()会分配内存空间存储新数组并返回,forEach()不会返回数据。
(2) forEach()允许callback更改原始数组的元素。map()返回新的数组
47、vue2 和 vue3 的区别
-
双向数据绑定原理不同;
-
是否支持碎片;
-
API类型不同;
-
定义数据变量和方法不同;
-
生命周期钩子函数不同;
-
父子传参不同;
-
指令与插槽不同;
-
main.js文件不同。
48、把树转化数组
// 树状结构数据
const data = [
{
id: 1,
pid: null,
title: '研发部',
children: [
{ id: 5, title: '前端研发部', pid: 1 },
{ id: 6, title: '后端研发部', pid: 1 },
{ id: 7, title: '算法研发部', pid: 1 },
],
},
{
id: 2,
pid: null,
title: '开发部',
children: [
{ id: 8, title: '前端开发部', pid: 2 },
{ id: 9, title: '后端开发部', pid: 2 },
{ id: 10, title: '算法开发部', pid: 2 },
],
},
{ id: 3, title: '市场部', pid: null },
{ id: 4, title: '销售部', pid: null },
];
// 方法一
function treeToArray1(array) {
const result = [];
array.forEach((item) => {
if (item.children) {
// 得到push进去最后一位成员的位置 从1开始,同时直接返回数组长度index
let index = result.push(item, ...item.children);
// 删除children属性
delete result[index - item.children.length - 1].children;
} else {
result.push(item);
}
});
return result;
}
// 方法二
function treeToArray2(array) {
return array.reduce((prev, cur) => {
if (cur.children) {
// 删除chidren项 因为push得到的是push进去的成员在第几个位置 从1开始 因此要减1
delete prev[prev.push(cur, ...cur.children) - cur.children.length - 1].children;
} else {
prev.push(cur);
}
return prev;
}, []);
}
// 数组结果数据
const data = [
{
id: 1,
title: '研发部',
pid: null,
},
{
id: 2,
title: '开发部',
pid: null,
},
{
id: 3,
title: '市场部',
pid: null,
},
{
id: 4,
title: '销售部',
pid: null,
},
{
id: 5,
title: '前端研发部',
pid: 1,
},
{
id: 6,
title: '后端研发部',
pid: 1,
},
{
id: 7,
title: '算法研发部',
pid: 1,
},
{
id: 8,
title: '前端开发部',
pid: 2,
},
{
id: 9,
title: '后端开发部',
pid: 2,
},
{
id: 10,
title: '算法开发部',
pid: 2,
},
];
49、用 js 实现将一个二维数组转换成树结构。
比如,将以下二维数组:
const arr = [
["a", "aa", "aaa", "aaaa"],
["b", "bb", "bbb"],
["a", "ab", "aba"],
["a", "aa", "aab"]
]
转为树结构的对象数组:
[{
"name" : "a",
"child" : [
{
"name" : "aa",
"child" : [
{
"name" : "aaa",
"child" : [
{
"name" : "aaaa",
"child" : []
}
]
},
{
"name" : "aab",
"child" : []
}
]
},
{
"name" : "ab",
"child" : [
{
"name": "aba",
"child" : []
}
]
}
]
},
{
"name": "b",
"child" : [
{
"name" : "bb",
"child" : [
{
"name" : "bbb",
"child" : []
}
]
}
]
}]
实现思路基本是将一维数组转为链表,再进行合并去重。算法如下:
// 实现思路基本是将一维数组转为链表,再进行合并去重。算法如下:
function arr2List(arr) {
const list = [];
const head = {
name: arr[0],
child: [],
};
let prev = head;
list.push(prev);
for (let i = 1; i < arr.length; i++) {
const current = {
name: arr[i],
child: [],
};
// 将当前节点挂载到链表尾节点上
prev.child.push(current);
prev = current;
}
// 返回链表头节点, 已经挂载了所有节点
return list;
}
function mergeList(tree, list) {
// 递归终止条件,因为可能有多个链表头节点挂载到树上
if (!tree) {
return;
}
// 遍历树,查找是否有共同的父节点
const hasCommonParent = tree.some(treeItem => {
// 有共同的父节点
if (treeItem.name === list[0].name) {
// 将链表头节点挂载到树上
mergeList(treeItem.child, list[0].child); // 确保链表头节点的子节点不会丢失
// 递归终止条件
return true;
}
// 递归继续
return false;
})
// 没有共同的父节点,将链表头节点挂载到树上
if (!tree.length || !hasCommonParent) {
tree.push(list[0]);
}
}
function arr2Tree(arr) {
const tree = [];
// 遍历二维数组,将每个一维数组转为链表,再将链表合并到树中
arr.forEach((item) => {
const list = arr2List(item);
mergeList(tree, list);
});
return tree;
}
const arr = [
['a', 'aa', 'aaa', 'aaaa'],
['b', 'bb', 'bbb'],
['a', 'ab', 'aba'],
['a', 'aa', 'aab'],
['b', 'ba', 'aabb'],
['b', 'ba', 'aabbaa'],
];
arr2Tree(arr);
50、Date对象
Date() : Date()日期对象 是一个构造函数,必须用new来调用;
无参数时 为当前时间;
字符串格式 ‘2023-4-9 12:48:12’;
数字格式 2023, 4, 9 返回的是5月;
//Date()日期对象 是一个构造函数,必须用new来调用
var date1 = new Date();
var date2 = new Date('2023-4-9 12:48:12');
var date3 = new Date(2023, 4, 9);
console.log(date1); //返回当前时间
console.log(date2); //正常
console.log(date3); //返回的是5月,不是4月
var date4 = new Date();
console.log(date4.getFullYear()); //返回当前年份
console.log(date4.getMonth() + 1); //返回当前月份,返回月份默认从0开始,记得加1
console.log(date4.getDate()); //返回当前几号
console.log(date4.getDay()); //返回当前星期,从 星期日到星期六 依次是 0,1,2,3,4,5,6
console.log(date4.getHours()); //时
console.log(date4.getMinutes()); //分
console.log(date4.getSeconds()); //秒
var year = date4.getFullYear();
var month = date4.getMonth() + 1; //加1
var dates = date4.getDate();
var arr = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
var day = date4.getDay();
console.log('当前日期是:' + year + '年' + month + '月' + dates + '日 ' + arr[day]);
function getTime() {
var time = new Date();
var h = time.getHours(); //时
h = h < 10 ? '0' + h : h; //小于10,前边补0
var m = time.getMinutes(); //分
m = m < 10 ? '0' + m : m; //小于10,前边补0
var s = time.getSeconds(); //秒
s = s < 10 ? '0' + s : s; //小于10,前边补0
return h + '时' + m + '分' + s + '秒';
}
console.log(getTime()); //00时00分00秒
时间戳(总的毫秒数)
//获取总的毫秒数(时间戳),距离1970年1月1日过了多少毫秒
//1、通过valueOf() 或者 getTime() 获取
var date5 = new Date();
console.log(date5.valueOf());
console.log(date5.getTime());
//2、简单写法(常用)
var date6 = +new Date();
console.log(date6);
//3、H5新增的方法
console.log(Date.now());
倒计时
//1、核心算法:输入的时间减去现在的时间就是剩余时间,即倒计时,但是不能拿着时分秒直接相减
//2、用时间戳来做,用户输入的时间的总毫秒数减去现在时间的总毫秒数,得到剩余时间的总毫秒数
//3、把剩余时间总毫秒数转化为天、时、分、秒(时间戳转化为时分秒)
//转化公式如下:
// d = parseInt(总秒数/ 60/60 /24); //计算天数
// h = parseInt(总秒数/ 60/60 %24); //计算小时
// m = parseInt(总秒数/ 60 % 60); //计算分数
// s = parseInt(总秒数/ %60); //计算当前秒数
function countDown(time) {
var nowTime = +new Date(); //当前时间的总毫秒数
var inputTime = +new Date(time); //用户输入时间的总毫秒数
var times = (inputTime - nowTime) / 1000; //剩余时间总秒数
var d = parseInt(times / 60 / 60 / 24); //计算天数
d = d < 10 ? '0' + d : d;
var h = parseInt(times/ 60 / 60 % 24); //计算小时
h = h < 10 ? '0' + h : h;
var m = parseInt(times/ 60 % 60); //计算分数
m = m < 10 ? '0' + m : m;
var s = parseInt(times % 60); //计算当前秒数
s = s < 10 ? '0' + s : s;
return d + '天' + h + '时' + m +'分' + s + '秒';
}
console.log(countDown('2023-4-11 18:00:00'));
var date = new Date();
console.log(date);
51、数组去重
//数组去重['x', 's', 'a', 's', 'g', 'a', 'h', 'm', 'd', 'l']
//1、目标:把旧数组中里面不重复的元素取出来放到新数组里,重复元素只保留一个,放到新数组里去重
//2、核心算法:遍历旧数组,拿旧数组元素去查新数组,如果该元素在新数组里没有出现过就添加,否则不添加
//3、利用 新数组.indexOf(数组元素) 如果返回是-1就说明新数组没有该元素,那就添加上
//封装一个 去重函数 unique
function unique(arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i])
}
}
return newArr;
}
console.log(['x', 's', 'a', 's', 'g', 'a', 'h', 'm', 'd', 'l']);
console.log(unique(['x', 's', 'a', 's', 'g', 'a', 'h', 'm', 'd', 'l']));
52、什么是盒子模型
css 盒模型(Box Model),在html中任何一个元素都可以看作盒子,这里说的盒子与现实不太一样,css中盒子是二维的,盒子由内到外分四个部分:margin(外边距)、border(边框)、padding(内边距)、content(内容)。css通过margin、border、padding三个属性来控制盒子,content是html元素的内容。
53、css 的 @import 和 link 的区别
1、link属于html标签,而@import是css提供的,只能加载CSS
2、加载顺序,link在页面加载时被加载,@import在页面加载完之后再加载
3、link是html标签,因此没有兼容性,而@import只有IE5以上才能识别
4、link是可以通过 JS 操作 DOM ,插入link标签来改变样式;由于 DOM 方法是基于文档的,无法使用@import
54、git 常用命令
创建仓库:1.创建一个仓库,git init 2.git clone一个远程仓库
git日常工作流程:1.git pull拉取最新的代码 2.git add将修改的代码文件提交到暂存区 3.git commit -m 将暂存区的代码提交到本地仓 4.git push将代码推送到远程仓
分支相关的命令:git branch 查看分支,git checkout分支名 切换分支,git checkout -b分支名 新建并切换到新的分支,git merge合并分支,其实合并分支还有另一种,是git rebase,但是很少去用,这两个的区别就是merge保留了完整的历史记录,它是将当前的commit和公共分支合并在一起,形成一个新的commit,rebase不会保留完整的历史记录 ,它是将当前分支的commit放到公共分支的最后面,形成一条新的分支