面试宝典
面试宝典
一、css
1.position属性值
- relative:相对定位
- 相对于其正常位置进行定位
- 不会脱离文档流
- absolute:绝对定位
- 相对于 除static 定位以外的第一个父元素进行定位
- 脱离文档流
- fixed:固定定位
- 相对于浏览器窗口定位
- sticky:粘性定位
- 基于用户滚动的位置,当页面滚动超出目标区域时,固定在目标位置
- static:默认值
1.css选择器,以及对应的权值
-
内联样式:权值1000
-
id选择器:100
-
类选择器:10
-
元素选择器:1
-
通配符、子选择器、相邻选择器:0
1.什么叫dom的重绘、回流
重绘
简单来说就是重新绘画,当一个元素颜色、背景、透明度等发生变化后,不会影响页面布局,但是需要重新渲染页面,这就是浏览器的重绘。
回流
当增加或删除dom节点,或者给元素修改宽高时,改变了页面布局,那么就会重新构造dom树然后再次进行渲染,这就是回流,这是非常耗性能的。并且回流的性能比重绘大
所以我们要尽量减少重绘和回流
减少重绘和回流的方法:
- 尽量使用css属性简写,比如用
border代替border-width,border-style,border-color - 批量修改元素样式
elem.className - 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会影响页面布局,引发回流
- 避免写css表达式,可能会引起回流
1.网页是怎么加载出来的?浏览器渲染的过程?
- 解析HTML,构建DOM树
- 解析CSS,生成CSS规则树
- 合并DOM树和CSS规则,生成render树
- 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
- 绘制render树(paint),绘制页面像素信息
- 浏览器会将各层的信息发送给GPU,GPU将各层合成(composite),显示在屏幕上
1.flex布局的所有属性及属性值
-
flex-direction:设置主轴的方向
- row:默认值,x轴方向,从左到右
- row-reverse:从右到左
- column:从上到下
- column-reverse:从下到上
-
justify-content:设置主轴的子元素排列方式
- flex-start:默认值,从头开始,主轴方向从左向右排列
- flex-end:从尾部开始排列
- center:在主轴居中对齐
- space-around:每个子项之间的距离是两边子项到容器间距的2倍
- space-between:两边贴边,然后平分剩余空间
- space-evenly:子项之间的距离和子项到容器的距离相等
-
align-items:设置侧轴上的子元素排列方式(单行),默认y轴
- flex-start:默认值,从上到下
- flex-end:从下到上
- center:垂直居中
- stretch:拉伸
-
flex-wrap:设置子元素是否换行
- nowrap:默认值,不换行
- wrap:换行
-
align-content:设置侧轴上的子元素的排列方式(多行)
-
flex-grow:伸展比例
-
flex-shrink:收缩比例
-
flex-flow:是 flex-direction 和 flex-wrap 两个属性的简写
-
flex:1:简写 默认0 1 auto
flex-grow: 1; flex-shrink: 1; flex-basis: 0%;
1.什么是BFC?BFC的原理是什么?BFC的应用?
通过设置display、overflow、float等属性来生成BFC,从而达到不同的布局效果。同时,BFC也有很多实际应用场景,如清除浮动、解决边距重叠问题、实现两栏布局等
什么是BFC?
BFC是指块级格式化上下文,是一块独立的渲染区域。
BFC的原理是什么?
BFC主要是通过CSS中的一些属性来实现的:(脱离文档流会触发BFC)
- display属性,将元素的display属性设置为block、inline-block、table-cell、flex等
- overflow属性,设置
overflow: hidden/auto;(非visible值) - float属性,left / right (不建议)
- clear属性
- position 为 absolute / fixed
BFC的应用?
-
清除浮动
-
解决边距重叠问题
-
实现两栏布局
可以使用float属性将左栏浮动,然后在右栏中创建一个BFC,以避免左栏的浮动影响到右栏的布局
.left { float: left; width: 200px; } .right { overflow: hidden; }
2.box-sizing的有效值及对应的盒模型规则?
盒子模型包含:内容、内边距、外边距和边框
-
content-box:默认值,指定盒模型为 ① W3C 标准模型,
- 设置 border、padding、margin 会增加元素 width与 height 的尺寸
- css中设置的width/height,实际是conten区域
- 盒模型的width/height = css设置的width/height+ padding + border + margin
-
border-box:指定盒模型为 ② IE模型(怪异盒模型),
- 设置 border、padding 不会影响元素 width 与 height 的尺寸
- 在css中设置的width/height = content + padding + border
- 盒模型的width/height = css设置的width/height + 外边距margin
-
inherit:指定 box-sizing 属性的值从父元素继承
-
③ 弹性盒模型:flexbox
3.px、em、rem的区别?
px:像素,是相对于显示器屏幕分辨率而言的
em:相对于父元素的字体大小
rem:rem是CSS3新增的一个相对单位,相对于根标签html元素的字体大小
4.选择器~和+的区别?
~ 选择器:匹配紧跟当前条件元素后面的多个兄弟元素
+ 选择器:匹配紧跟当前条件元素后面的一个兄弟元素
5.CSS清除浮动的方式?
1.直接给父容器设置height
实际应用中我们不大可能给所有的盒子加高度,不仅麻烦,并且不能适应页面的快速变化;另外一种,父容器的高度可以通过内容撑开(比如img图片),实际当中此方法用的比较多
2.浮动元素后面使用一个空标签,给空标签设置clear: both;
<div class='clear'></div>
.clear{
clear: both;
}
3.给浮动元素的父容器设置overflow: hidden;或overflow: auto;让父容器形成了BFC
我们可以给父元素添加以下属性来触发BFC:
- float 为 left | right
- overflow 为 hidden | auto | scorll
- display 为 flex | table-cell | table-caption | inline-block
- position 为 absolute | fixed
为了兼容IE最好使用overflow:hidden。
4.给浮动元素的父容器添加一个 ::after伪元素
.clearfix::after{
content: '';
display: block;
clear: both;
}
5.给浮动元素后面添加br标签
br标签存在一个属性:clear,就能够清除浮动。在br标签中设置属性clear,并赋值all
<br clear="all" />
6.父元素和子元素宽高不固定,如何实现水平垂直居中?
-
弹性盒模型:
父元素设置: display: flex; justify-content: center; align-items: center;或
父元素设置: display: flex; 子元素设置:margin: auto; 实现垂直水平居中 -
定位属性(position)配合 位移属性(transform)
父元素设置: position: relative; 子元素设置: position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%)
7.html中元素的margin塌陷、margin重叠(合并)如何解决?
margin塌陷(父子元素之间):
给子容器添加margin-top,它并未向下,它实现的效果则是父容器带着子容器一起向下移动,这就是margin塌陷。
- 解决方法:
- 给父元素设置边框或者内边距(不建议,会改变页面布局)
- 激活父元素的BFC,可以设置overflow: hidden等
margin重叠(兄弟元素之间):
两个兄弟结构的元素,他俩垂直方向的 margin 是合并的,这就是margin合并
- 解决方法:
- 只给一个元素设置margin
- 将一个元素放入BFC容器中,可以设置overflow: hidden等
8.:nth-child和:nth-of-type的区别?
:nth-child(n):匹配当前元素的第 n个子元素,不论元素的类型
:nth-of-type(n):匹配属于当前元素类型的第n个子元素
9.简述align-items和align-content的区别?❤
-
align-items:可以应用于所有的flex容器,它的作用是设置flex子项在每个flex行的交叉轴上的默认对齐方式(相对Y轴)
- flex-start:子项对齐到交叉轴的起始位置。
- flex-end:子项对齐到交叉轴的结束位置。
- center:子项在交叉轴上居中对齐。
- baseline:子项基线对齐。
- stretch:子项在交叉轴上拉伸以填满整个容器。
-
align-content:只适用于多行的flex容器,在使用前需在flex容器设置flex-wrap:wrap;表示子元素超出换行;align-content 它的作用是当flex容器在交叉轴上有多余的空间时,将子项作为一个整体进行对齐。
- flex-start:多行子项对齐到交叉轴的起始位置。
- flex-end:多行子项对齐到交叉轴的结束位置。
- center:多行子项在交叉轴上居中对齐。
- space-between:在每行之间均匀分布多行子项,首行对齐到起始位置,末行对齐到结束位置。
- space-around:在每行周围均匀分布多行子项,包括首行和末行。
- stretch:多行子项在交叉轴上拉伸以填满整个容器。
10.H5的新特性有哪些?
-
语义标签:
<header>、<footer>、<nav>等 -
增强型表单:
新增了五个表单元素(meter、output)、表单属性(placeholder、required)、新的input输入特性
-
视频和音频:视频
<video src=" "></video>音频:<audio src=" "></audio> -
Canvas绘图
-
SVG绘图
-
地理定位
-
拖放API
-
WebWorker
-
WebStorage
-
WebSocket
11.CSS3新增了哪些新特性?
-
选择器
:first-of-type
:nth-of-type(n)
:nth-child(n) 等
-
新样式
- 边框:border-radius、box-shadow、border-image
- 背景:background-size、background-break、background-clip、background-origin
- 文字:text-overflow
- clip:修剪文本
- ellipsis:显示省略符号来代表被修剪的文本
-
transition过渡
-
transform转换
-
animation 动画
-
渐变
12.css3新增伪类和伪元素
🌱伪类:体现在 操作 行为
:first-of-type
:nth-child(n)
:nth-of-type(n)
.....
🌱伪元素:体现在页面上
::first-letter 将样式添加到文本的首字母
::first-line 将样式添加到文本首行
::before 在某元素之前插入某些内容
::after 在某元素之后插入某些内容
::selection 用户选中的部分
13.如何实现0.5px的边框?
.border {
border-bottom: 1px solid red;
transform: scaleY(0.5);
transform-origin: bottom;
margin-bottom: -0.5px;
}
使用了scaleY(0.5)来减半元素的高度(从而减半了底部边框的宽度),并将transform-origin设置为bottom,以确保元素的底部成为缩放的基准点。最后,通过将元素的底部外边距设置为-0.5像素来将其位置向上移动0.5像素,以使边框的中心线正好在元素的边缘上
14.<!DOCTYPE html> 有什么作用 ?
声明当前文件 是一个 html 文件类型的,这不是一行正常的代码--- 仅仅是声明作用
15.WEB 前端的三大核心技术?
HTML -- 结构
CSS -- 表现 (CSS 3大特性:继承、优先、层叠)
JavaScript -- 行为
16.alt 和 title 的区别?
- alt:当图片未正常显示时,给用户提示的文本
- title :当鼠标悬停在图片上时,显示的文本
17.简述一下src与href的区别?
-
href:href表示超文本引用,用来建立当前元素和文档之间的链接,常用在link和a等元素上。
注:当浏览器解析到这一句时会识别该文档为css文件,会下载并不会停止对当前文档的处理,所以建议使用link方式而不是@import加载css。
-
src:src表示引用资源,替换当前元素,是页面内容不可缺少的一部分,常用在img,script,iframe上。
src指向外部资源的位置,指向的内部会迁入到文档中当前标签所在的位置;请求src资源时会将其指向的资源下载并应用到当前文档中,
例如js脚本、img图片等。src链接内的地址不会有跨域问题
注:当浏览器解析到这一句时会暂停其他资源的下载和处理,直至将该资源加载、编译、执行完毕。这也是js脚本放在底部而不是头部的问题
18.溢出文本设置
单行文本:
CSS: {
width: 20px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
};
多行文本:
CSS: {
width: 20px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
}
- -webkit-line-clamp 用来限制在一个块元素显示的文本的行数,为了实现该效果,它需要组合其他的WebKit属性
常见结合属性: - display: -webkit-box; 必须结合的属性 ,将对象作为弹性伸缩盒子模型显示
- -webkit-box-orient 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式
19.设置元素隐藏的方案?
1.设置元素height、font-size和line-height为0
2.控制display属性为none,不会再占据空间
3.添加visibility的hidden属性(visible:显示,hidden隐藏),隐藏后会占据空间
4.opacity为0,隐藏后会占据空间
5.负margin值,数值给用负数,只要数值足够大,就可以让一个元素移出视口外
6.使用transform里面的缩放来进行不可见隐藏
20.data-*用法,如何设置、获取?
1.data-*用法:
- 属性名不应该包含任何大写字母,并且在前缀 "data-" 之后必须有至少一个字符
- 属性值可以是任意字符串
- 一个元素可以拥有任意数量的data属性
- data属性无法储存对象,如需储存,可通过对象序列化
2.如何设置
-
setAttribute('data属性名','新内容')即可设置 -
通过该数据类型的(dataset) API设置data值,IE10以上才支持;
var button = document.queryselector('button') button.dataset.data属性名 = '新内容' ; // 这里的data属性名是指data-后面的名字
3.如何获取
-
getAttribute('data属性名')即可获取 -
通过该数据类型的(dataset) API设置data值,IE10以上才支持;
var button = document.queryselector('button') data = button.dataset.data属性名 ; // 这里的data属性名是指data-后面的名字
二、js
1. 介绍一下JS的内置类型有哪些?
1.空类型:null
2.未定义:undefined
3.布尔:boolean
4.数字:number
5.字符串:string
6.符号:symbol(ES6新增)
7.对象:object
8.除了对象之外,其他为基本类型.
2. 介绍一下 typeof 区分类型的原理
-
typeof原理: 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息
-
000: 对象 010: 浮点数 100:字符串 110: 布尔 1: 整数
-
typeof null 为"object"原因:是因为不同的对象在底层都表示为二进制,在js中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object"
3.js检测数据类型的方式
1.typeof运算符
- 注意:可以判断number、string、undefined、booloean、function这些类型,检测对象、Array、Null、Set、Map、RegExp、Date的数据都返回的都是object
console.log(typeof 123); // number
2.instanceof 运算符
- 检测a是否是A的实例,可以判断数组,不可以判断基本数据类型、Object
console.log(arr instanceof Array); // 返回布尔值
3.constructor构造函数
console.log(arr.constructor === Array); // 返回true,代表arr是一个数组
- 注意:
- 任何实例访问constructor属性得到的都是构造函数本身,函数的prototype属性里面的constructor就是函数本身
- 无法判断null, 因为null无法访问constructor
- 可以判断Object、Array
4.万能检测 Object.prototype.toString.call(数据),可以判断null
// 返回true,代表arr是一个数组
Object.prototype.toString.call(arr) === '[object Array]'
// 因为 Object.prototype === {}.__proto__
{}.toString.call(数据) // 简写
// eg:
console.log({}.toString.call(/sm/) === '[object RegExp]') // true
/* 封装一个万能检测数据类型的方法 */
function checkType(data){
let str = {}.toString.call(data)
return str.slice(8, -1).toLowerCase()
}
// 使用
console.log(checkType([1,1,3,4]))
if(checkType([1,1,3,4]) === 'array'){
console.log('是数组')
}else{
console.log('不是数组')
}
5.判断是不是数组
Array.isArray(数据)
3. 说说你对 JavaScript 的作用域的理解?什么是作用域链?
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域
全局作用域:
任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
块级作用域:ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
- 局部作用域(函数作用域)
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
作用域链:
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象Window,找不到为undefined。这样由多个执行上下文的变量对象构成的链就叫做作用域链
4. var let const的区别
1.var声明的变量会挂载在window上,而let和const声明的变量不会
2.var声明变量存在变量提升,let和const不存在变量提升
3.let和const声明的变量形成块级作用域,在当前作用域内存在暂时性死区
4.同一作用域下let和const不能声明同名变量,而var可以
5.let和var用来声明变量,const一般用来声明常量,一旦声明必须立即赋值,不能使用null占位,声明后不能再修改,如果声明的是复合类型数据,可以修改其属性
5. 说说你对执行上下文的理解
执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文(eval一般不会使用)
-
全局执行上下文:
全局执行上下文只有一个,也就是我们熟知的window对象,我们能在全局作用域中通过this直接访问到它
-
函数执行上下文:
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;
需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
-
执行上下文栈也叫调用栈:
调用栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。
6.for循环和forEach的区别
- for循环可以中断循环(利用break语句或return语句,continue跳过本次循环);
forEach是迭代循环,只能按顺序依次遍历完成,不支持中断行为
-
for可以指定循环的起点,forEach循环起点只能从0开始
-
如果只是遍历集合或者数组,用forEach好些,快些;
(如果对集合中的值进行修改,要用for循环了,forEach不能使用下标来访问每个元素所以不能用于增加,删除等复杂的操作。)
for..of与for..in的区别
1.for of不能遍历对象,for in可以遍历对象(for...in既可以遍历对象 也可以遍历数组)
2.for of可以用来遍历map集合,for in不能遍历map集合
3.for in遍历数组得到的是数组的下标,for of遍历数组得到的是数组的元素
4.for in遍历键,for of遍历值
7.JS中 for循环的终止循环 return、break、continue的区别?
return语句的作用是指定函数返回的值。return语句只能出现在函数体内,出现在代码中的其他任何地方都会造成语法错误!用return语句来终止一个函数的执行
1、return:终止循环并且退出循环所在的方法
function add(){
let list = [1,2,3,4]
for(var i=0;i<list.length;i++){
if(i === 2)return;
console.log(list[i]); // 1 2
}
}
add()
2、continue:终止本次循环(终止当前循环,进行下一次循环)
for (let i = 1; i < 5; i++) {
if (i === 2) {
continue;
}
console.log(i) // 1 3 4
}
3、break:完全结束循环(终止循环执行循环体下面的代码)
for (let i = 1; i < 5; i++) {
if (i === 2) {
break;
}
console.log(i) // 1
}
7.Array.forEach() 与 Array.map() 的区别?
Array.slice() 与 Array.splice() 的区别?
- forEach不支持return,对原来的数组也没有影响。但是可以通过数组的索引来修改原来的数组
map支持return返回值,也不影响原数组,但是会返回一个新的数组
// map可以将一个数组里面数据经过计算 得到一个计算后的新数组
var list = [2,3,4,5]
var newList = list.map(function(item, index){
// console.log(item)
// console.log(index)
// 会将这个函数的返回值放到newList里面
return item * 2
})
console.log(newList) // [4,6,8,10]
-
slice():“读取”数组指定的元素,返回符合条件的数组,不会对原数组进行修改
- arr.slice(start, end) 包含start,不包含end,会返回截取的这段数组/字符串
- end 若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。
- 如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置
splice():“操作”数组指定的元素,会修改原数组,返回被删除的元素
arr.splice(index, [count], [insert Elements])
index是操作的起始位置count = 0插入元素,count > 0删除元素- 如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素。
[insert Elements]向数组新插入的元素
- arr.slice(start, end) 包含start,不包含end,会返回截取的这段数组/字符串
遍历数组的方法有哪些?
var arr = [1,3,5,'哈哈',9]
// 1.for循环
for(let i=0;i<arr.length;i++){
console.log(arr[i]);
}
// 2.forEach循环
arr.forEach(item => {
console.log(item);
});
// 3.map
arr.map(item=>{
console.log(item);
})
// 4.for...in
for(let key in arr){
console.log(arr[key]);
}
// 5.for...of
for(let item of arr){
console.log(item);
}
// 6.filter
arr.filter(item=>{
console.log(item);
})
封装实现数组排序的方法
- 冒泡排序,双重for循环
// 从小到大排序
function sortArr(arr){
// 外层循环:代表比较的次数(次数=数组长度-1)
for(let j=0;j<arr.length-1;j++){
// 内层循环:代表每一次的比较,每一次要拿相邻的两项进行比较
// 如果前一项大于后一项,则交换两项的位置
for(let i=0;i<arr.length-1-j;i++){
if(arr[i] > arr[i+1]){
let tmp = null
tmp = arr[i]
arr[i] = arr[i+1]
arr[i+1] = tmp
}
}
}
return arr
}
-
sort排序
① 数组名.sort()
默认按照ASCII码顺序排序, 从小到大,只能排序每项数据为一位数字的数组
② 自定义sort排序
形参a是后一项,b是前一项,若函数返回一个小于0的数,就会交换位置
arr.sort(function(a,b){ return a-b // 从小到大排序 // return b-a // 从大到小排序 }) console.log(arr);
封装数组去重的方法❤
1.使用双重for循环
function unique(arr){
if (!Array.isArray(arr)) {
console.log(`${arr}不是一个数组`)
return
}
for(let i=0;i<arr.length;i++){
for(let j=i+1;j<arr.length;j++){
// 如果前一个和后一个相等
if(arr[i]===arr[j]){
// 则删除后一个
arr.splice(j,1)
// 删除后,j要减1,否则会少检测一个
j--
}
}
}
return arr
}
2.使用for循环 和 includes 方法
function unique(arr){
if (!Array.isArray(arr)) {
console.log(`${arr}不是一个数组`)
return
}
// 新建一个空数组
var newArr = []
for(let i=0;i<arr.length;i++){
if(!newArr.includes(arr[i])){
newArr.push(arr[i])
}
}
return newArr
}
3.for循环 和indexOf(直接比较,对象比较的是地址,)
function unique(arr){
if (!Array.isArray(arr)) {
console.log(`${arr}不是一个数组`)
return
}
// 新建一个空数组
var newArr = []
for(let i=0;i<arr.length;i++){
if(newArr.indexOf(arr[i]) === -1){
newArr.push(arr[i])
}
}
return newArr
}
4.for循环 、indexOf、splice
function unique(arr){
if (!Array.isArray(arr)) {
console.log(`${arr}不是一个数组`)
return
}
for(let i=0;i<arr.length;i++){
if(arr.indexOf(arr[i]) !== i){
arr.splice(i, 1); //删除该元素
i--; //修正索引
}
}
return arr
}
4.filter
function unique(arr){
if (!Array.isArray(arr)) {
console.log(`${arr}不是一个数组`)
return
}
return arr.filter((item,index)=>{
// arr.indexOf(item)返回item的索引值
// 条件:当前元素在原始数组中的第一个索引 = 当前索引值,则返回
// filter方法会返回满足当前条件的新数组
return arr.indexOf(item) === index
})
}
5.Set
function unique5(arr){
// new Set(arr)得到的是Set集合,利用展开运算符将其转为数组
return [...new Set(arr)]
}
封装随机色的方法
// 随机数
function genRandom(start, end){
return parseInt(Math.random() * (end-start)) + start
}
// #000000 - #ffffff
function getColor(){
var color = '#'
// 生成6次 0-15
for(var i=1;i<=6;i++){
color += genRandom(0,16).toString(16)
}
return color
}
8.get 和 post 请求方式的区别
-
get 一般用来进行查询操作,url 地址有长度限制;它发送的数据是以明文的方式发送的,请求的参数都暴露在 url 地址当中,安全性较低,如果传递中文参数,需要自己进行编码操作
-
post 请求方式主要用来提交数据,没有数据长度的限制,提交的数据内容存在于http 请求体中,数据不会暴漏在 url 地址中,相对安全性高
-
get方式默认拿到的数据会被浏览器缓存, post不会被缓存
9.防抖和节流❤
-
防抖:用户多次触发事件,在用户一直触发事件中,事件不会执行,只有在用户停止触发事件一段时间之后再执行这个事件一次
-
实现方式:设置延时定时器执行之前把上一次定时器清除,最终只有用户连续触发这个事件的间隔时间超出我们设置的参数ms毫秒之后,该事件才会触发一次
// jsonp案例的防抖操作 如果用户一直在输入,是不会发送请求 只有用户连续输入时间间隔超过800ms之后才会请求一次数据,也就是用户在800ms内没有输入才会去请求数据function debounce(fn, ms) { let timeout = null return function() { clearTimeout(timeout) timeout = setTimeout(() => { fn.apply(this, arguments) }, ms) } } //scroll方法中的'do somthing'至少间隔500毫秒执行一次 window.addEventListener('scroll',function(){ var timer;//使用闭包,缓存变量 return function(){ // 定时器执行前先判断 如果有定时器,则先清除定时器 if(timer) clearTimeout(timer); timer = setTimeout(function(){ console.log('do somthing') },500) } }()); //此处()作用:立即调用return后面函数,形成闭包
-
-
节流:用户多次触发事件,在用户一直触发事件过程中,事件会每间隔一段时间执行一次,会执行多次
-
加锁
// @fn 是对应请求数据 // @ms 是用户多次触发事件的时间间隔 是一个毫秒数 function throttle(fn, ms){ let flag = true return function(){ if(!flag) return flag = false // 上锁 setTimeout(()=>{ fn.apply(this, arguments) flag = true // 解开锁 }, ms) } } -
实现方式:用户每次触发事件都会设置一个延时定时器,但是如果已经设置了定时器,就会等上一次的定时器执行之后才会开启下一个,我们可以在这之前定义一个变量,初始值设为true, 开启定时器前 给定时器上锁,设置为false,定时器结束后解开,设置为true,并且在定时器前判断 为false的时候,直接return,不执行定时器
//scroll方法中 当间隔时间大于2s时,'do somthing'执行一次 window.addEventListener('scroll',function(){ var timer ;//使用闭包,缓存变量 var startTime = new Date(); return function(){ var curTime = new Date(); if(curTime - startTime >= 2000){ timer = setTimeout(function(){ console.log('do somthing') },500); startTime = curTime; } } }());//此处()作用 - 立即调用return后面函数,形成闭包
-
10.null和undefined的区别?
- null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的 原始值,转为数值时为NaN;
- typeof null 返回的是object,typeof undefined返回的是undefined
11.谈谈你对NaN的理解?
- Not a Number 不是一个数字,但是数值类型
- NaN 与其他数值进行比较的结果总是不相等的,包括它自身在内
12.如何确定 this 指向以及修改this指向?
this指向问题
-
普通函数内部的this看调用的对象,有人调用,this就指向调用者,如果没有找到谁在调用默认全局在调用,this指向window。
-
箭头函数内部的this有两种说法:一种认为箭头函数没有this,一种认为箭头有this和外层的this一样。总之箭头函数的this指向看它定义位置作用域的this
-
构造函数内部的this指向当前创建出来的实例
普通函数修改this的指向(箭头函数无法修改this)
- call/apply/bind都可以修改this的指向
- call和apply在改变函数this指向的同时会调用这个函数,bind改变函数this指向不会执行函数,会返回一个新的函数,需要调用才能修改this指向
- 如果要传参,call和bind放在第二个参数后面,apply第二个参数里面传一个数组
13.值传递和引用传递的区别
- 基础数据类型和复杂的数据类型在赋值的时候因为存储方式不一样,导致赋值的内容也不一样。
- 基础的数据类型之间赋值是直接将一个变量的数据拷贝给另外一个变量,一个变量的变化不会改变另外一个变量的数据,被称为值传递的过程。
- 复杂数据类型之间的赋值是将一个变量的地址拷贝给另外一个变量,因为地址(引用)指向的数据是同一个位置,所以一个变量变化会导致另一个也发生变化,这种赋值过程被称为引用传递。
15.js的内置构造函数有哪些,他们都有哪些共性
ArrayObjectStringDateFunctionRegExpNumberBoolean- 使用的时候都要用new关键字去实例化
16.new关键字做了哪些事情?
- new调用函数,会在函数内部自动创建一个空对象
- new会将构造函数内部this指向到这个创建出来的对象(实例)
- new会将创建出来的对象的
__proto__指向构造函数的prototype - new会返回这个创建出来的对象
new 一个箭头函数会怎样?
箭头函数是ES6中提出的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能new一个箭头函数
构造函数new调用和直接调用的区别?
- 相同点: 直接调用和new调用都会将函数内部的代码执行
- 不同点:
① 返回值不一样,直接调用的函数返回值看有没有return,new调用函数即使没有return也会返回一个空对象
② 函数内部的this,直接调用 函数内部this指向window,new调用 this指向空对象
17.构造函数、普通函数、箭头函数的区别
-
构造函数在定义的时候和普通函数没有区别,只是定义的时候要求名称首字母大写。
-
箭头函数不可以当成构造函数使用,因为箭头函数的this指向问题
-
普通函数this指向看谁在调用这个函数,如果没有找到,一般是window的隐式调用
-
箭头函数this有两种说法:一种认为箭头函数没有this,一种认为箭头有this和外层的this一样
-
构造函数this指向当前的实例对象
-
箭头函数没有arguments,只有不定形参
...args,普通函数有arguments
18.闭包的含义,闭包的使用场景,闭包的优缺点
闭包的含义
-
能够访问其他函数的内部变量的函数
-
写法:在一个函数A里面有一个变量a,在函数A内部返回一个B函数,在A外面有变量引用着这个A函数的返回值, 在函数B内部也可以访问到函数A内部的变量a
function A(){ var a = 1 return function B(){ a++ } } var data = A() data()
闭包的原理:
- 全局作用域(window)随着页面打开就会自动生成,一直到页面关闭才会被销毁
- 局部作用域在函数执行时会生成,等函数执行完成之后就会被销毁,局部作用域销毁之后局部的变量也会被销毁。如果我们想要局部作用域函数执行之后不被销毁,我们可以在函数内部返回一个引用数据类型。闭包使用的就是返回一个函数,这样函数作用域就不会被销毁,局部变量会在内存空间内。
- 只要我们在调用这个函数,该函数内部有一个局部变量,返回的函数内部使用着这个局部变量,此时就可以访问到局部变量。
- 如何创建一个不销毁的执行空间:
- ①当函数返回一个引用数据类型的值
- ②函数外有一个变量使用着函数的返回值
- 此时即使函数调用结束,执行空间也不会被销毁(不会销毁的原因:外部有一个变量接受这个返回值,拿到这个引用地址)
闭包的使用场景
- 一般在我们要公用一个数据,但是又不能放在全局,放在全局会污染全局变量,这时候要用闭包来解决全局变量污染的问题
- 使用单例模式的时候,用一个变量保存实例,不想放在全局,这时候可以用到闭包
闭包的优缺点
- 优点
- 可以访问一个局部变量
- 可以延长一个局部变量的生命周期
- 可以避免全局变量污染问题
- 缺点
- 占用内层空间 大量使用闭包会造成 栈溢出
- 由于闭包会一直占用内存空间,直到页面销毁,我们可以主动将已使用的闭包销毁:
只有我们主动切断链接,将这个链接地址置为null,闭包才会被垃圾回收机制回收内存空间
19.柯里化面试题
- 将一个接收多个参数调用的函数, 变成一个接收少量参数,调用多次的函数的一门技术
/* 完成以下功能
add(1,2,3,4,5)
add(1)(2)(3)(4)(5)
add(1,2,3)(4,5)
add(1,2)(3,4)(5)
*/
function add(...args){
// 定义数组把每次调用传入的参数进行存储
let arr = args
return function _add(...args){
// 判断传入的参数长度
if(args.length === 0){
// 结束调用,计算和
return arr.reduce((prev, cur)=>{
return prev+cur
} ,0)
}else{
// 拼接数组
arr = arr.concat(args)
return _add
}
}
}
// 加上一个小括号,里面不传任何参数就代表调用结束了
console.log(add(1)(2)());
console.log(add(1)(2)(3)());
console.log(add(1,2,3,4)());
21.浏览器缓存的认识
缓存机制是浏览器自带的机制,第一次访问某一个页面资源时,浏览器会自动将我们访问过的资源进行下载,下次再访问这个页面或者刷新访问的时候,浏览器会自动从缓存里取出这个数据返回到页面中,
- 优点:减少白屏等待时间,提高用户的访问速度,节约用户的流量。
- 缺点:有时候服务端文件发生,但是文件名称没有改变,浏览器无法识别这个文件的变化,还会使用缓存中的文件;
- 为了解决这个问题我们有时候需要在请求的过程中告诉浏览器我们不使用缓存,针对于get请求只要每次携带的参数变化浏览器就不会使用缓存文件,认为你要获取新的数据。
22.跨域和跨域的解决方案
什么是跨域?
- 跨域是由浏览器的同源策略造成的,从一个域名的网页去请求另一个域名的资源时,域名、协议、端口号都不相同时,ajax 请求会失败
跨域的解决方案
-
JSONP
-
利用script标签没有跨域限制的漏洞。通过script标签访问需要访问的后端的地址,后端返回一个函数调用的方式,将需要发送给前端的数据当成这个函数的参数,前端只需要定义一个同名的函数接受后端返回的数据即可。
-
JSONP使用简单且兼容性不错,但是只限于get请求,无法发送post请求
-
JSONP使用还需要后端的配合
-
此种记住已经不是ajax请求了,是script请求
-
-
-
配置反向代理
- 因为同源策略是限制浏览器访问别人的服务器,所以我们可以绕过直接请求,先让我们浏览器请求自己的服务器,由自己的服务器去请求别人的,避免浏览器的跨域限制
- 方法:在自己服务器上追加一个路径识别(api),所有本地请求中带有/api路径的,都会识别到这个请求不是真的请求本地而是要请求别人的服务器
target: ' 目标源地址' , // 设置目标源 changeOrigin:true, // 改变源 pathRewrite: {'^/api': ''} // 重写请求路径,去除路径中的‘/api’,^代表开头 -
后端放开CORS策略
- 一般也不建议这样配置。
23.事件委托
什么是事件委托
在开发中,如果我们想要给一个元素绑定事件,需要获取到这个元素进行绑定,但如果元素是动态生成,无法获取从而无法绑定,此时我们就需要利用冒泡原理把这个事件委托给这个元素在页面中存在的父元素来绑定
使用场景
- ajax请求到的数据动态渲染到页面
- js动态创建插入页面的
如何实现事件委托
- 获取页面已存在的父元素,给这个父元素绑定同类型的事件,利用冒泡原理子元素也会触发这个事件,在通过event.target来缩小事件触发的范围
优点:1.可以给页面动态生成的元素绑定事件;2.提高事件绑定的性能
24. 原型和原型链
原型概念
- 每一个函数都有一个prototype属性
- 每一个对象都有一个
__proto__属性 - 构造函数的prototype和实例对象的
__proto__指向的是同一个地址
原型链概念
- 每一个对象访问属性和方法的时候会优先从自身开始查找
- 如果自身没有这个属性和方法,会自动往它的
__proto__上面查找,而这个对象的__proto__就是它构造函数的prototype - 如果自身的
__proto__里面没有,就会去下一层的__proto__上面查找,也就是它父类构造函数的prototype - 这样一直找到最顶层构造函数Object,Object.prototype指向null,所以原型链顶层是null,这时属性会出现undefined,方法会报错
25.cookie的特点❤
- 服务端和客户端公用的空间,一般存放一些前后端公用的数据
- 存储大小有限制,一般是4KB左右,数量有限制,一般是 50 条左右
- 有时效性,也就是有过期时间,默认的过期是session级别
- 有域名限制,只能在域下面存储,无法在本地存储,当前域只能使用自己域下面的cookie,无法访问其他域的cookie(cookie的过期时间是以服务器时间为准的,删除也是服务器删除的,所以必须要在有域的环境下存储)
-
缺点:
-
长度不能超过4kb,否则会被截掉
-
有安全性问题,如果 cookie 被人拦截了,那人就可以取得所有的 session 信息。即使加密也与事无补,因为拦截者并不需要知道 cookie 的意义,他只要原样转发 cookie 就可以达到目的了
-
有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
-
26.cookie和localStorage还有sessionStorage区别❤
-
cookie一般存储比较重要的数据,是前后端公用的数据存储空间,localStorage和sessionStorage是h5本地存储,存储的数据只能前端使用
-
cookie有一定的大小限制(4kb),本地存储相对于比较大(5M)
-
cookie可以设置过期时间,本地存储没有
-
cookie必须在有域的环境下面才能进行存储,本地存储可以在没有域下面进行存储
-
localStorage存储的数据是永久的,除非用户自己手动清除缓存,否则会一直存在;
sessionStorage存储的数据是会话级别的,关闭浏览器就会自动删除
27.js实现继承的方式
- 借助构造函数实现继承
-
在子类的构造函数里面调用父类的构造函数,利用call或者apply改变父类构造函数this的指向,指向子类的实例,
Person.call(this, 参数)
(改变this的指向的原因:如果直接调用父类的构造函数 父类构造函数内部this指向window,就不是给子类添加了)
-
优点:子类实例可以使用父类的属性和方法
-
缺点:子类实例无法使用父类原型上的属性和方法
-
- 使用原型链继承
- 将子类构造函数的原型指向父类的某个实例
- 优点:子类实例可以使用父类原型上的属性和方法
- 缺点:
- 子类实例无法使用父类的属性和方法
- 子类实例访问constructor属性得到的不是子类的构造函数,而是父类的构造函数,需要修正子类constructor属性为原来的子类
- 组合继承
-
借助构造函数实现继承 + 原型链继承
-
修正子类constructor属性为原来的子类:Student.constructor = Student
-
优点:这样子类的实例既可以使用父类的属性和方法也可以使用父类原型上的属性和方法
-
缺点:
-
会调用父类构造函数两次
-
一次是在子类的构造函数内部
-
一次是在将子类构造函数原型指向父类实例
-
虽然会调用两次但是没有太大影响
-
- ES6中使用class和extends关键字实现继承
class Student extends Person{
constructor(username, studentNo, password){
// 子类在使用this之前(添加自己属性之前) 必须调用父类的构造函数
super(username, password) // super代表就是父类构造函数
this.studentNo = studentNo
}
test(){
console.log('考试')
}
}
28.说下对事件轮询(Event Loop)的理解(异步执行的原理)
- 首先加载js的时候,整个的script代码会一行一行的执行
- 每一行代码执行都会进入调用栈(调用栈是一个执行代码的地方)
- 调用栈在运行代码的时候会判断这行代码是同步还是异步,如果是同步直接执行,返回执行之后得到的结果,如果是异步,会将异步的代码放到webAPI里面,直接执行后面的同步代码
- 异步的代码在webAPI里面会等待时机,进入task queue队列里面。(比如说一个定时器3s执行,在3s到达的时候会将对应的回调函数添加到执行的队列(task queue)里面)
- 所有的同步代码执行结束之后,此时会开启事件轮询,首先查找micro task queue微任务队列里面的微任务,这些任务执行完成之后,去查看task queue里面是否有宏任务需要执行,如果有就拿到调用栈执行,一直到所有的task queue任务结束
- (微任务直接进入micro task queue,不进入task queue)
console.log(1)
setTimeout(()=>{
console.log(2)
},0)
setInterval(() => {
console.log(3)
}, 30);
console.log(4)
console.log(5)
new Promise((resolve)=>{
console.log(6)
resolve(7)
}).then(res=>{
console.log(res)
})
// 1,4,5,6,7,2,3
29.微任务和宏任务有什么区别?
在JavaScript中,宏任务和微任务是异步任务的两种类型,它们的执行顺序和触发方式不同。
-
宏任务是由浏览器或用户触发的异步任务,例如setTimeout、setInterval、DOM事件等。宏任务会被放入宏任务队列中,等待事件轮询机制执行时取出执行。
-
微任务是由JavaScript引擎自身触发的异步任务,例如Promise、MutationObserver等。微任务会被放入微任务队列中,等待宏任务执行完毕后立即执行。
在事件轮询的过程中,JavaScript引擎会优先执行微任务队列中的所有任务,直至清空微任务队列后,再从宏任务队列中取出一个宏任务执行。这保证了微任务的优先级高于宏任务,确保了宏任务的执行不会阻塞微任务的执行。
总的来说,宏任务和微任务的区别在于它们的触发方式和执行顺序,理解它们的区别对于编写高效的异步代码非常重要。
| 微任务(microtask) | 宏任务(macrotask) | |
|---|---|---|
| 谁发起的 | JS引擎自身触发 | 浏览器或用户触发的 |
| 具体事件 | 1. Promise 2.async/await | 1. script (可以理解为外层同步代码) 2. 定时器setTimeout/setInterval 3.ajax 4. dom事件 |
| 谁先运行 | 先运行 | 后运行 |
| 会触发新一轮Tick吗 | 不会 | 会 |
30.异步解决方案有哪些?(获取异步数据的方式)
1.回调函数callback
-
被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数。如setTimeOut,ajax请求,readFile等。
-
缺点:形成回调地狱,使代码看起来很混乱,不易于维护
2.事件发布订阅
-
当一个任务执行完成后,会发布一个事件,当这个事件有一个或多个‘订阅者’的时候,会接收到这个事件的发布,执行相应的任务,这种模式叫发布订阅模式。如node的events,dom的事件绑定
-
缺点:消耗内存,过度使用会使代码难以维护和理解
3.Promise:
- Promise是es6提出的异步编程的一种解决方案。
- 优点:解决了回调地狱的问题,将异步操作以同步操作的流程表达出来
- 缺点:无法取消promise。如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。当执行多个Promise时,一堆then形成链式调用结构
4.Generator
- Generator是es6提出的另一种异步编程解决方案,需要在函数名之前加一个*号,函数内部使用yield语句。Generaotr函数会返回一个遍历器,可以进行遍历操作执行每个中断点yield。
- 优点:没有了Promise的一堆then(),异步操作更像同步操作,代码更加清晰
- 缺点:不能自动执行异步操作,需要写多个next()方法,需要配合使用Thunk函数和Co模块才能做到自动执行。
5.async/await
- async是异步修饰符,async 函数返回一个 Promise
- 优点:内置执行器,比Generator操作更简单。async/await 比 yield语义更清晰。返回值是Promise对象,可以用then指定下一步操作。代码更整洁,让异步代码看起来像同步代码。可以捕获同步和异步的错误。
31.如何避免回调地狱?
1.Promise
利用Promise实例的then方法将异步操作的结果,按照顺序执行,catch方法用来接收处理失败时相应的数据。在上一个promise的then方法中,返回下一个Promise对象,但then的链式调⽤阅读起来也不友好
2.ES6的Generator,使用yield语句(不用了)
3.async/await
async修饰的函数返回值是一个promise实例;
await必须写在async函数中, 一般await等待一个promise实例, 等待实例状态成功, 获取到实例的终值
32.简述下 Promise 对象
Promise是异步编程的一种解决方案,可以获取异步操作的消息。
p = new Promise(function(resolve, reject){
});
Promise实例的状态:
-
有三种状态:pending(进行中)、fulfilled(已完成)、rejected(已失败)。
-
Promise刚被创建时,状态为pending,它的状态改变只有两种可能,从pending变为fulfilled;从pending变为rejected。
-
Promise传入的回调函数自带两个参数,resolve和reject,调用resolve()将状态变为fulfilled,调用reject()将状态变为rejected;
Promise的实例方法(原型方法):
- then方法,接收两个回调函数作为参数,第一个参数在状态变为fulfilled时调用,第二个参数在状态为rejected时被调用
- catch方法,在状态为rejected时被调用
- finally方法,无论状态是否成功都会被调用
Promise实例的结果:
- 在调用resolve()的时候可以传递实例成功的终值value,在then的回调函数里面, 可以获取到这个终值value
- 在调用reject()的时候可以传递实例失败的结果,拒因 reason,在catch的回调函数里面, 可以获取到这个结果
Promise的静态方法:
-
Promise.all()
接收一个数组类型的参数, 数组里每一项数据都是一个promise实例,它的返回值是一个新的promise实例,,返回一个直接包裹resolve内容的数组,数组里面如果所有实例状态都变为成功就会调用then方法, 只要有一个实例状态为失败就会调用catch
-
Promise.allSettled()
和all一样,接收一个数组类型的参数, 每一项数据都是一个promise实例,但它返回的是一个包裹着对象的数组,不管是否成功,都会进入then,都会把所有的Promise实例的数据都返回回来
-
Promise.any()
返回的是最快成功的那一个
-
Promise.race()
拿到最快的那一个, 可能是成功可能是失败的
案例: 有三个请求a b c, 现在要等三个请求都成功后再进行d操作, 如何实现?
把3个请求都封装为promise实例对象,
用Promise.all()方法 传入一个数组,
组里的每项数据为这个promise实例对象,
如果都成功,all()返回的promise实例对象会调用then(),在then里面进行d操作
如何判断对象是否为空?
-
可以将对象转换为JSON字符串, 利用字符串比较规则
function isEmptyObject(data){ let jsonData = JSON.stringify(data) if(jsonData === '{}'){ return true }else{ return false } } console.log(isEmptyObject({})); console.log(isEmptyObject({a:1})); -
利用for..in方法(常用)
function isEmptyObj(obj){ // 默认为空 true let bool = true for(let key in obj){ // 如果进入此循环,说明不为空 bool = false break } return bool } -
es6的方法:Object.keys()
-
es5的方法:Object.getOwnPropertyNames(对象)
33.如何判断对象里面是否有某个属性?❤
var data = {
a: 1,
name: 'jack',
dd: undefined
}
1.直接访问,但是值为undefined时不能用
function hasKeyInObject(data, key){
if(data.constructor === Object){
// 如果data[key]不等于undefined,说明对象里有这个属性,则返回true
// 但是判断不了值为undefined的属性(比如dd)
return data[key] !== undefined
}
}
// 调用封装的函数hasKeyInObject,返回值为true则有这个属性
console.log(hasKeyInObject(data, 'a')); // true
2.通过for...in遍历方式
function hasKeyInObject(data, key) {
let bool = false
// 使用for...in遍历data
for (let aa in data) {
if (aa === key) {
bool = true
// 如果找到,循环可以停止了
break
}
}
return bool
}
3.使用对象的in属性
function hasKeyInObject(data, key) {
return key in data
}
4.利用hasOwnProperty方法
function hasKeyInObject(data, key) {
return data.hasOwnProperty(key)
}
34.如何判断一个对象是否为数组,函数
方法一: instanceof:
var arr=[];
console.log(arr instanceof Array) //返回true
方法二: constructor:
console.log(arr.constructor == Array); //返回true
方法三:万能检测法 Object.prototype.toString.call(数据)
Object.prototype.toString.call(arr) === '[object Array]'
// {}.toString.call(数据)
方法四: Array.isArray()
console.log(Array.isArray(arr)); //返回true
35.数组实现扁平化,不用api
- 普通的递归实现
function myFlat(arr){
let res = [];
for(let i=0; i<arr.length; i++){
if(arr[i] instanceof Array){
res = res.concat(myFlat(arr[i]));
}else {
res.push(arr[i]);
}
}
return res;
}
let arr = [1,[2,3,[4,5]]];
console.log(myFlat(arr))
36.谈谈垃圾回收机制方法
-
标记清除法❤
两个阶段:
-
1.标记阶段(mark):
- 默认给所有内存里的数据一开始都标记为 不可达 0
- 从window对象出发,访问这个对象里面的每一个键名,如果value是一个引用数据类型 继续去访问下一级的键值对
- 如果可以访问 标记为可达 1
-
2.清除阶段(sweep):会把所有内存中标记为0的数据全部回收
-
优点:
①这种算法比引用计数要高效一点,不需要消耗额外的性能去计数,只需要标记两种状态0和1
②能够回收循环引用的对象
③是v8引擎使用最多的算法
-
缺点:
①这种算法本身会导致内存不连续 会出现内存碎片
-
-
引用计数法
跟踪记录每个值被引用的次数,一旦没有引用,内存就直接释放了
-
优点:
①引用计数为0时, 发现垃圾会立即回收
②最大限度减少程序暂停
-
缺点:
①无法回收循环引用的对象
// 循环引用: function Person(){ let a = {} let b = {} a.prop = b b.prop = a }②需要额外增加计算次数的算法,做频繁的加减运算,这样会影响性能
垃圾回收机制对写代码的影响:
-
全局变量问题
尽量避免把数据添加到全局
-
闭包问题
不用的时候 要释放内存,赋值为null
-
定时器问题
不用的时候也要清空
-
事件回调函数
在移除事件监听的时候 记得清空回调函数
-
37.哪些操作会造成内存泄漏?
-
setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
-
闭包
-
控制台日志
-
循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
37.浏览器有哪些兼容问题?
1.滚动条到顶端的距离(滚动高度)
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
2.滚动条到左端的距离
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
3.获取事件对象的兼容
function(e){
var evt = e || event
}
4.滚轮
function mousewheel(obj,fn){
var ff = window.navigator.userAgent.index0f('Firefox');
if (ff!=-1){
obj.addEventListener('DOMMouseScroll',wheel,false);//兼容火狐
}else{
obj.onmousewheel = wheel;//非火狐
}
}
5.阻止浏览器默认事件
function prevent(){
if(event.preventDefault){
event.preventDefault()
}else{
event.returnValue = false; // 兼容IE6-8
}
}
6.获取非行内样式
//获取非行间样式,obj是对象,attr是值
function getStyle(obj,attr){
//针对ie获取非行间样式
if(obj.currentStyle){
return obj.currentStyle[attr];
}else{
//针对非ie
return getComputedStyle(obj,false)[attr];
};
};
defineProperty和Proxy的区别
-
相同点:都可以进行数据劫持
-
不同点:
- 1.defineProperty是劫持对象或数组的某一个键值对, Proxy劫持的是整个对象或数组,proxy性能比defineProperty好
- 2.defineProperty是ES5语法, Proxy是ES6语法, defineProperty兼容性比Proxy好,Proxy不兼容IE
- 3.defineProperty无法监听新增属性和删除属性, Proxy都可以(利用deleteProperty属性来监听删除)
38.深浅克隆(拷贝)及实现方法
克隆要处理的就是复杂数据类型之间的赋值,实现将一个数据传递给另一个数据,一个变化另一个不受影响
var person = {
username: 'jack',
age: 18,
};
var list = [1,2,3,4]
1.浅克隆(只处理一层)
-
方法:新建一个数组/对象, 将原数据里面的每一项取出放到新数组或新对象(基础数据类型之间赋值是值传递,引用数据类型之间赋值是引用传递,赋值的是地址)
① 使用展开运算符
// 1.克隆对象
var person2 = {...person}
// 2.克隆数组
var list2 = [...list]
② 创建新数据, 遍历取出老数据放到新数据里
// 1.克隆对象
var person2 = {};
for(let key in person){
person2[key] = person[key]
}
// 2.克隆数组(遍历旧数组)
var list2 = []
for(let i=0;i<list.length;i++){
list2[i] = list[i]
}
console.log(list2);
③ 使用Object.assign() 合并对象
/*
Object.assign()是对象的静态方法,可以用来复制对象的可枚举属性值到目标对象,返回目标对象
Object.assign(target,...sources)
参数:target: 目标对象
sources: 源对象
*/
var person2 = Object.assign({}, person)
④ 数组的concat方法(克隆数组)
// 和空数组拼接 返回一个数据一样, 但是不是同一个对象的数组
var list2 = list.concat([])
2.深克隆(无视层级)
- 对象/数组的深克隆(递归)
思路: 先判断数据是不是对象/数组 如果是对象就遍历这个对象,取出每一项,赋值一个新对象,再返回这个新对象,如果数据不是一个对象 就直接返回
// 以对象举例:
function cloneDeep(data){
if(data.constructor === Object){
let obj = {}
for(let key in data){
obj[key] = cloneDeep(data[key])
}
return obj
}else{
return data
}
}
var person2 = cloneDeep(person)
- 复合数据的深克隆1
- 遍历
var list = [
{id: 1, name: 'haha', hobbies: [1,2,3,{a:1}]},
{id: 2, name: 'haha', hobbies: [1,2,3,{a:1}]},
{id: 3, name: 'haha', hobbies: [1,2,3,{a:1}]}
]
function cloneDeep(data){
// 1. 如果数据是一个对象或是一个数组, 就需要遍历取出每一项
// Object.prototype.toString.call(data) === '[object Object]'
if(data.constructor === Object || data.constructor === Array){
let newData = Array.isArray(data) ? [] : {}
// for..in 既可以遍历数组也可以遍历对象
for(let key in data){
newData[key] = cloneDeep(data[key])
}
return newData
}else{
// 2.如果是 string undefined number null boolean function regexp date 直接返回
return data
}
}
var list2 = cloneDeep(list)
-
复合数据的深克隆2
- 使用lodash方法
<!-- 1. 引入lodash.js文件 --> <script src="./lodash.js"></script>// 2. 调用_.cloneDeep() var list2 = _.cloneDeep(list) console.log(list2); -
复合数据的深克隆3
- 利用JSON转换实现克隆
// 将list转换成json的时候 所有引用数据引用关系就切换了 var list2 = JSON.parse(JSON.stringify(list))- 注意:
- 这种方式当数据为undefined、function时,在做json转换的时候,数据会丢失
- 当数据是Date、RegExp类型时,在做json转换的时候,类型会发生变化
39.阻止默认事件和阻止冒泡事件的区别
1.event.preventDefault()
阻止默认事件,如点击链接不会被打开,但是冒泡会传递给上一层的父元素
2.event.stopPropagation()
阻止事件冒泡到父元素上,但是默认事件仍会执行
3.return false
暴力,同时阻止事件冒泡和默认事件
40.ajax提交和form表单提交的区别
-
页面刷新:form表单提交后,会跳转页面,会刷新整个页面,而Ajax提交则不会刷新整个页面,只会局部刷新。
-
异步请求:Ajax提交是异步的,即用户可以继续在页面上进行其他操作,而form表单提交是同步的,需要等待服务器响应后才能进行其他操作,用户体验差
-
数据格式:Ajax提交数据可以传输各种类型的数据,如JSON、XML等;而form表单提交数据只能传输表单数据
-
关于输入内容的校验:ajax可以在获取到元素内容后用程序判断;form表单的属性中有校验的字段
-
安全性:form表单提交相对安全,可以使用CSRF防护,而Ajax提交需要在请求头中设置CSRF Token,否则会存在安全风险。
- 表单
- 表单提交数据弊端就是在提交数据的同时会跳转到后端地址,这样返回的时候会导致之前填写的数据丢失需要重新填写
- ajax
- 这种提交数据不会发生跳转 在当前页提交数据给后端 在当前页获取后端返回的数据
41.简述 ajax 的过程
-
创建 ajax实例,
var xhr = new XMLHttpRequest() -
使用open配置请求信息,设置三个参数,指定请求的方式(get或post)、URL 及是否同步(false为同步, true为异步)
-
发送 HTTP 请求,
xhr.send()-
get方式:携带的参数需要拼接在地址的后面,格式
?参数名=数据&参数名=数据 -
post方式:
-
在open后 send之前设置请求头参数格式:
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')或者json格式 -
携带参数要放在send函数里面发送, 格式:
参数名=数据&参数名=数据
-
-
-
获取后端返回的数据
console.log(xhr.responseText)-
异步请求时,
-
监听ajax实例状态为4的时候,获取后端返回的数据
xhr.onreadystatechange = function(){ console.log(xhr.readyState) console.log('触发了...') // 在实例状态readyState为4时,获取数据 if(xhr.readyState === 4){ console.log(xhr.responseText) } }或者
-
利用ajax实例的onload方法,等后端数据返回并且可用的时候会触发一次
xhr.onload = function(){ // 等后端数据返回并且可用的时候会触发一次 console.log(xhr.responseText) }
-
-
42.ajax实例的状态
xhr.readyState:
- 0 代表ajax实例创建完成
- 1 代表ajax实例配置完成
- 2 代表ajax实例发送完成
- 3 代表ajax实例响应数据返回,等待浏览器解析
- 4 数据返回了并且解析完成 数据可用
xhr.onreadystatechange:
- 每一次ajax实例状态变化都会触发这个函数
42.http 常见的状态码有那些?分别代表是什么意思?
2开头的表示成功, 3开头表示重定向,4开头表示 客户端出错,5开头的表示服务端出错
200 - OK,请求成功
301 - 资源网页等被永久转移到其它 URL(永久重定向)
401 - 未授权(网站需要授权登录)token不正确或失效
403 - 禁止访问,服务器拒绝了你的请求,没有权限,未登录
404 - 网页走丢了,找不到url,请求的资源(网页等)不存在
500 - 内部服务器错误
43.HTTP和HTTPS有何区别?
- 使用https协议一般需要用到CA机构颁发的证书,免费证书较少,购买证书需要一定的费用。
- HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
- HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。
44.你了解的设计模式有哪些?
1.单例模式:单个实例 (实例是由类或者构造函数new出来的),用来解决构造函数或者类创建出来的实例公用一份的问题
为什么要公用一份实例:面向对象有一个缺点:每一次实现功能都需要创建一个对象,这样会导致大量创建对象,占用内存
单例模式实现原理:提供一个函数 只要调用这个函数就会给你返回一个实例,将实例进行存储,每一次调用函数之前会做一个判断 如果实例存在 就直接返回上一次的实例,如果实例不存在 就创建一个实例并返回
2.观察者模式(发布订阅模式):
发布订阅模式在实际应用中非常常见,例如,我们在微信App上关注了某个公众号,当该公众号有新文章发布时,就会通知我们。
发布订阅模式定义了一种一对多的依赖关系,当“一”发生变化,通知多个依赖。
3.组合模式
三、Vue
1.v-show和v-if的区别,以及它们的使用场景?
- 区别:
- 在值为false时,v-show控制display属性为none,隐藏该元素(值为true时,去除display属性)
- 而v-if直接从dom节点移除该元素,会导致dom树重新加载
- 使用场景:
- v-show具有更高切换性能,适用于 频繁切换状态场景,比如Tab选项卡
- v-if有更高初始加载性能,适用于当初始值为false 不渲染时,且切换频率较低,比如点击弹窗
2.MVVM原理
利用数据劫持, 结合观察者模式, 生成新的虚拟dom,,利用diff进行虚拟dom比较,减少真实dom操作
-
vue2.x 利用Object.defineProperty 做数据劫持,,劫持的是对象的某个属性
-
vue3.x 利用ES6的Proxy代理,对整个对象进行代理
3.对虚拟dom原理的理解?
概念:虚拟dom是利用js对象的结构,,描述真实dom树状结构。
用途:用来解决vue在数据渲染和更新中的性能问题。 vue数据渲染 更新视图 重新渲染, 每一次不会直接操作真实dom, 会根据数据, 生成新的虚拟dom, 和上一次的虚拟dom进行比较, 找到代价较小的方案来更新真实dom。
结构:tag 标签名、attrs 属性名、children 内容。
// 真实dom:
<div id="box" class="a">
<p class="op">这是p</p>
<span>这是span</span>
这是文本
</div>
// 虚拟dom:
{
tag: 'div', // 标签名
attrs: { //标签属性
id: 'box',
className: 'a'
},
children: [ //标签内容
{
tag:'p',
attrs: {
className: 'op'
},
children: ['这是p']
},
{
tag:'span',
attrs: {
},
children: ['这是span']
},
'这是文本'
]
}
4.diff算法的用途及它的比较规则?
diff算法用来优化虚拟dom比较。
比较规则:
- 同级比较, 不同级不比较,
- 同级 如果没有key, 按照顺序比较
- 同级 如果有key属性, 同级同key比较
5.循环的key属性, 用id和index下标有什么区别,推荐用哪个?
推荐用id,因为数组改变后,下标会自动变化,导致虚拟dom比较错误,而id是不会变化的,且id是独一无二的
6.模板闪烁问题及解决方法?
-
问题:
html 加载顺序是从上往下执行, 加载到模板时, vue app实例 还未渲染到视图上, 此时html按照字符串渲染模板, 等待实例渲染到视图上, 编译模板, 渲染数据, 这个时间差造成了模板闪烁。
-
解决:
使用vue定义的一个指令 v-cloak, vue会等 实例渲染到视图上时,自动去除视图中所有标签为v-cloak的指令
css设置: [v-cloak]{ display: none; }
7.组件的通信方式❤
1.父向子通信:
父组件中给子组件标签 定义 自定义属性来传递数据,子组件中 定义 props属性来接收
props会自动作为 实例的属性
2.子向父通信:
通过触发子组件的自定义事件, this.$emit('biu', this.msg)
父组件中通过子组件标签v-on来监听这个事件即可
3.兄弟组件通信
- vue2.x 利用事件中心总线 event bus,定义第三方Vue实例 bus, 在兄弟组件1 引入 这个bus实例, bus.$emit() 触发 bus的自定义事件, 在兄弟组件2 中引入 通过bus bus.$on监听;
- vue3.x 废弃了兄弟组件通信,利用第三方的发布订阅的库(mitt)
4.依赖注入通信
祖先组件可以通过 provide 向后代组件暴露多个数据, 子组件inject注入父组件provide的任意一个数据
5.ref 通信
template中给需要获取dom或者子组件实例定义ref属性,通过 this.$refs.ref名
6.vuex或pinia可以做任意组件通信
vuex是专门为vue设计的,用来管理多个组件公共状态的库,采用集中式方式管理多个组件公共的状态
8.父子组件生命周期触发的顺序
父beforeCreate - 父created - 父beforeMount - 子beforeCreate -子created - 子beforeMount - 子mounted - 父mounted
9.多个组件,有公共的属性、方法、计算属性、watch等, 怎么处理?
可以使用混入对象, 将多个组件的公共属性、方法、计算属性等,定义在一个公共的混入对象中, 然后在组件的config中定义mixins属性,灌入 混入对象上的属性和方法
<script>
export default {
mixins: [mixin1]
}
</script>
10.单页面的优缺点
优点:
- 实现了前后端分离 ,开发效率高
- 由于每个页面都是组件,切换页面实际是切换组件 , 不会重新渲染网页,网页切换速度快,性能高
缺点:
- 对于 seo不友好 (vue推出了 nuxt框架解决问题)
- 首页加载速度较慢 (使用路由懒加载可以解决)
11.路由跳转如何传参?❤
-
动态路由传参
path前面加冒号,定义动态参数,通过
this.$route.params.动态参数名来获取 -
query传参
跳转时,使用对象来添加query属性传递参数,通过
this.$route.query.动态参数名来获取;○ 特点:1.参数和值都在地址栏体现;2. 刷新数据不丢失
<script> { path: '/news', query: { id: 10, id2: 20 } } </script> -
params传参(vue-router4.x已经废弃)
跳转时,定义params属性传递参数,通过
this.$route.params.动态参数名来获取;-
特点
1.路由需要定义name,必须通过 name跳转 否则 参数 无法传递
2.隐式传参 地址栏不体现
3.刷新数据丢失
<script> { name: 'home', params: { a: 10, b: 20 } } </script> -
如何监听动态路由的变化?
①通过导航守卫的钩子函数beforeRouteUpdate组件内部守卫
beforeRouteUpdate(to, from){
console.log('动态参数变化了', to);
console.log(to.params.path)
}
②使用watch侦听器直接监听$route
watch: {
$route(to, from){
console.log('动态参数变化了', to);
}
}
- 为什么$route是一个引用数据类型,但是侦听时,使用的是普通数据类型的侦听,没有使用深度侦听?
- 普通侦听,侦听的是值的变化,
- 每一次路由地址变化,vue会自动派发一个新的对象,绑定到实例的原型链上,$route对象的地址发生变化了,正常比较2个地址,用普通侦听即可
说一下vue组件的生命周期是什么?❤
先说生命周期:组件的生命周期就是指组件从初始化到销毁的整个过程,
vue的每个组件都是独立的,每个组件都有自己的生命周期,从组件的创建、数据的初始化、挂载、更新、销毁阶段,在这些阶段,vue提供了不同的钩子函数,在每个阶段都会自动触发相对应的钩子
1.初始化阶段:
①实例的创建
beforeCreate:在这个阶段,首先会初始化实例,解析props,监听事件,会立即调用beforeCreate钩子,但是此时实例没有完全创建完成,data没有变为响应式,实例的数据方法等还不能访问到(可以说是beforeCreate这个钩子基本上是什么都没干);
created:挂载计算属性,watch将data变为响应式,注入实例的属性和方法等,完成后就会触发created,但是此时的template还未挂载,所以dom还无法获取到
②模板编译
beforeMount:开始编译template,触发beforeMount,触发时虚拟dom还未编译(vue2中是虚拟dom编译了,真实dom还未创建)
mounted:初次render,render会调用h函数,渲染虚拟dom,然后创建真实dom,插入到dom节点上,触发mounted(所有获取dom的操作都要在mounted中完成)
2.更新阶段:
beforeUpdate:当数据被修改时,立即触发beforeUpdate,此时还未生成新的虚拟dom
updated:重新调用render生成新的虚拟dom,patch比较新老虚拟dom,更新真实dom,完成后触发updated,updated之后就可以获取到修改后最新的dom了,
注意:避免在updated中修改响应式数据,可能造成死循环;
避免在updated中去获取最新的dom,(因为所有的数据只要修改都会被触发,updated无法精确的监听具体是那个数据,会造成代码割裂,不利于可读性)
3.卸载阶段: beforeUnmount、unmounted (vue2的是beforeDestroy和destroyed),是vue的垃圾回收机制,会自动移除组件的实例,但是只是卸载了实例。
在组件停止使用,比如条件渲染值为false、路由切出当前路由时,需要卸载
从内存中移除,可以释放内存,
组件初始化时,在window上定义了一些挂载,比如全局事件、定时器,在组件卸载的时候只是卸载了组件, 全局的事件并没有清除,可以在beforeUnmount里清除这些挂载,来进一步释放内存
4.此外还提供了两个钩子用于缓存组件,
activated:被缓存组件,再一次使用时触发,局部数据刷新,在这里调用
deactivated:被缓存组件 停用时触发;全局初始化事件在activated绑定,在deactivated移除绑定
生命周期钩子的使用场景
常用钩子:
1.初始化阶段
created钩子,调用一些请求函数
mounted钩子,这里获取dom,所有获取dom的操作都要在这里完成
2.卸载阶段
beforeUnmount:在组件初始化时在全局window上定义的一些挂载(全局事件、定时器等),在组件卸载时,只是卸载了组件实例,这些window上的挂载依然存在,我们需要在组件实例卸载前手动清除这些挂载来进一步释放内存(将事件赋值为null,清除定时器)
组件缓存
使用keep-alive组件包裹,组件停用时,缓存组件不会卸载(不可以滥用,否则会造成内存溢出)
activated:被缓存组件,再一次使用时触发,想要更新局部数据,在这里调用
deactivated:全局初始化事件在activated绑定,在deactivated移除绑定
watch和computed的区别?
1.computed是计算属性,watch是侦听器,监听一个值的变化,然后执行对应的回调
2.computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调
3.computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载做监听,添加immediate属性,设置为true
4.computed中的函数必须要用return返回,他的属性值是函数的返回值,watch中的函数不是必须要用return
5.使用场景:computed----当一个属性受多个属性影响的时候,使用computed-----购物车商品结算。watch–当一条数据影响多条数据的时候,使用watch-----搜索框.
watch和watchEffect的区别
- watch可以明确侦听某个数据源,watchEffect侦听是副作用,回调中任意一个响应式数据发生改变都会触发
- watchEffect默认立即执行,某些场景不需要立即执行,立即执行需要添加immediate为true
- 一般watchEffect可以使用的场景,watch都可以
为什么要避免 v-if 和 v-for 一起使用在同一个标签上?
当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中,造成性能方面的浪费。所以永远不要把 v-if 和 v-for 同时用在同一个元素上。
解决:可以在外面套一个容器
Vue路由模式有哪些?❤
1.hash模式
○ 原理:通过hash值的变化, 利用window上的hashChange()事件来监听匹配路由
○ 优点:
1.hash值改变,不会去改变路径指向
2.切换速度会更高,他真正的path前有个#,这部分不会被发送给服务器,服务器不会去做额外的处理,所以切换速度会更快
○ 缺点:有#,不美观
2.history模式(HTML5模式)
○ 原理:利用H5给history对象新增的pushState和replaceState,来动态的去改变path
○ 优点: 没有#
○ 缺点:每一次路径改变,都会去请求服务器,都会产生新的路径指向,然后就会导致服务器请求404
如何在父组件中修改子组件样式?
1.在外部定义css,在组件的style中用@important来引入,这样可以穿透scoped限制
2.去除scoped属性,直接修改子组件属性(不可行,这样会影响全局样式)
3.预编译器里使用深度选择器
<style>
.home :deep 选择器{
xxx
}
</style>
4.css中可以使用>>>操作符
<style>
.a >>> .b {
xxx
}
</style>
nextTick()是什么?
在Vue里,页面的更新是异步的,如果采用非异步方式,多次更新数据,会导致页面多次刷新,为了解决这个问题,Vue采用了nextTick方法来延迟更新,让数据在都修改完后再刷新页面。
nextTick回调当中的操作不会立即执行,而是等数据和DOM更新完成之后再执行,这样我们拿到的肯定就是最新的了
使用场景:
- created中获取DOM的操作需要使用它
- 在修改了某个数据后,想要获取对应DOM上的值时
- 还有一些第三方插件使用过程中,使用到的情况,具体问题具体分析
vue中我们修改了数据需要立即获取 修改后视图最新dom该如何获取?
- 使用
this.$nextTick()方法,它可以在下次DOM更新循环结束之后执行回调函数,确保获得最新的DOM
// 在Vue组件中
this.$nextTick(() => {
// 这里获取最新的DOM
});
- 侦听器设置flush: 'post', 让侦听器触发的时机 改为 数据修改后 且视图更新完成之后, 让侦听器拿到最新的dom
- 在
updated生命周期钩子函数中进行操作,该钩子函数会在DOM更新之后被调用。
// 在Vue组件中
updated() {
// 在这里获取最新的DOM
},
为什么data不能是对象,而是用函数返回对象的形式?
如果data是一个函数的话,这样每复用一次组件,就会返回一份新的data,让各个组件实例维护各自的数据;
如果data是一个对象的话,两个组件中设置的data都会引用同一个内存地址,造成引用传递
import 和require 区别:
es module 用import,nodejs 模块化 用require
① import 必须在代码编译之前提前引入,参与代码构建的(不能在生产环境代码中引入)
② require 可以按需引入,定义在源码中
③ require 一般在开发环境中 引入 生产环境不用的代码,引入后期需要删除的资源,或者引入媒体资源(音视频图片)(醒目,后期看到后手动删除)
实际开发中axios二次封装,做了哪些事情?
1.定义默认配置:配置请求源、超时时间配置、请求头等
①创建实例:
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_URL,
timeout: 6000
})
②axios.defaults.baseURL = '值'
2.添加请求拦截器:在这里获取token,添加到请求头中,发送给后端,做token校验,请求发送前也可以添加Loading动画
添加响应拦截器:在这里判断token是否过期和未登录的处理(401 token过期,403未登录)
ES6新增api ?
Object.assign(a, b):合并对象, 将b合并到a
Object.keys(), Object.values() 和 Object.entries(): 分别返回一个包含对象键、对象值和键值对的数组
Array.from():将类数组对象转换为真正数组
Array.isArray():判断是不是数组
Array.of(): 根据传入的参数创建一个新的数组,不管参数的类型和数量
find() 和 findIndex(): 分别用于在数组中查找满足条件的元素和索引。find() 返回找到的第一个符合条件的元素,而 findIndex() 返回找到的第一个符合条件的元素的索引。
includes():表示是否包含特定值,并返回一个布尔值。
Map 和 Set:Map 是一种键值对的集合,而 Set 是一种无重复元素的集合
startsWith() 返回布尔值,表示参数字符串是否在原字符串的头部
endsWith() 返回布尔值,表示参数字符串是否在原字符串的尾部
解释vue的单向数据流和双向数据绑定
-
单向数据流:
-
状态提升:父组件中有很多子组件时,父子组件之间一般建议 将子组件中和业务相关数据都提升到父组件(data)中管理,通过props 传递给子组件使用
对于 Vue 来说,组件之间的数据传递具有单向数据流,这样的特性称为单向数据流,单向数据流方式使用一个上传数据流和一个下传数据流进行双向数据通信,两个数据流之间相互独立,单向数据流指只能从一个方向来修改状态。
-
双向数据绑定:
而双向数据绑定 即为当数据发生变化的时候,视图也会发生变化,当视图发生变化的时候,数据也会跟着同步变化,两个数据流之间相互影响。
$route 和 $router 的区别
- $route:用来获取路由的信息的,它是路由信息的一个对象,里面包含路由的一些基本信息,包括name、meta、path、query、params等;
- $router:主要是用来操作路由,它是VueRouter的实例,包含了一些路由的跳转方法push、replace等
说出至少4种Vue当中的指令和它的用法?
v-if(判断是否隐藏,用来判断元素是否创建)
v-show(元素的显示隐藏,类似css中的display的block和hidden)
v-for(把数据遍历出来)
v-bind(绑定属性)
v-model(实现双向绑定)
vue2和vue3的区别?❤
参考了别人的https://www.cnblogs.com/heisetianshi/p/16638872.html
1、生命周期函数钩子不同😊
| 选项式API | 组合式API |
|---|---|
| beforeCreate | setup |
| created | setup |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestory | onBeforeUnmount |
| destoryed | onUnMounted |
| activated | onActivated |
| deactivated | onDeactivated() |
2、数据双向绑定原理不同😊
-
Vue2: 使用Object.defineProperty()进行数据劫持,结合订阅发布的方式实现。
-
Vue3: 使用Proxy代理,使用ref或者reactive将数据转化为响应式数据
-
vue3提供的proxy API代理的优势在于:
-
defineProperty只能监听某个属性,不能对全对象监听
-
可以省去for...in,闭包等内容来提升效率(直接绑定整个对象即可)
-
可以监听数组,不再单独的对数组做特异性处理。可以检测到数组内部数据的变化。
-
3、定义变量和方法不同😊
-
'vue2使用选项式API(Options API)
// vue2定义变量和方法的方式: <script> data(){ return{ a: 10 } }, methods:{ fn(){} } </script> -
vue3使用组合式API(Composition API)
vue3数据和方法都定义在setup中,并统一进行return{}
4、指令和插槽的使用不同😊
- vue2:允许直接slot; 不建议将v-for和v-if一起使用,v-for优先级更高
- vue3:必须使用v-slot; v-for和v-if一起使用时,只会将v-if当作v-for大的一个判断语句,不会相互冲突
5、API类型不同😊
- vue2:使用的是选项式API,在代码中分割不同属性(data、computed、methods等)
- vue3:使用的组合式API,使用方法进行分割,代码看起来更加简便和整洁
6、是否支持碎片😊
- vue3可以拥有多个根节点
7、父子之间传参不同😊
- vue2:
- 父传子:子组件通过prop接收
- 子传父:子组件中通过$emit向父组件触发一个自定义事件,传递参数
- vue3:
- 使用setup()中的第1个参数props接收,第二个参数context上下文(包括attrs、emit、slot),context.emit来触发自定义方法传递参数
8、main.js文件中部分设置不同
- vue2:使用prototype(原型)的形式进行操作,引入的是构造函数
- vue3:需要使用结构的形式进行操作,引入的是工厂函数
vue2选项式api和vue3组合式api的区别?
1.在vue2中主要是往data 和method里面添加内容,一个业务逻辑需要什么data和method就往里面添加,而组合API就是 有一个自己的方法,里面有自己专注的data 和method。
2.组合API 和选项式 API(vue2.0中的data和method)可以共用
组合API本质就是把内容添加到选项式API中进行使用
说下对vue3的setup语法糖的理解
在 Vue 3 中,新添加了一个 setup() 函数,可以看作是 Vue 2中的 beforeCreate 和 created 生命周期函数之间的阶段。它是一种语法糖,用来实现组件内的响应式数据、计算属性、组合式函数、事件处理函数等。
1.<script setup>作为setup()的语法糖,在书写上更加简洁方便,无需再手动书写setup(){},只需在 script 标签上写上 setup,并且无需做return返回
2.data数据的使用:顶层变量默认暴露给模板 ,在 template 中直接使用即可,减少了很多模板代码
3.声明props和emit时需要使用difineProps和defineEmits方法,这两个方法只在

浙公网安备 33010602011771号