1-- HTML
2-- CSS
1.scoped组件css作用域
默认情况下, 如果子组件和父组件有相同的选择器样式,优先加载父组件css样式 scoped : 1.属性作用: 让子组件加载自己的样式(添加组件css作用域) 2.属性原理: 给组件添加一个自定义属性 data-v-xxxx , 本质通过css属性选择器增加权重
2.浮动
为什么出现浮动 浮动定位将元素排除在普通流之外,即元素将脱离文档流,不占据空间。浮动元素碰到包含它的边框或者浮动元素的边框停留. 为什么需要清除浮动 1、父元素的高度无法被撑开,影响与父元素同级的元素; 2、与浮动元素同级的非浮动元素(内联元素)会跟随其后; 3、若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构 清除浮动的方式 1.给父级设置双伪元素 2.给父级元素设置 overflow:hidden;或 overflow:auto;本质是构建一个BFC
3.重绘和回流
重绘 : 当元素的一部分属性发生改变, 如外观, 背景, 颜色等不会引起布局的变化, 只需要游览器根据元素的新属性重新绘制 回流:元素的字体大小边距,页面的结构发生变化,就会引发回流 重绘不一定回流 , 回流一定重绘
4.em和rem的区别?
em是相对于元素自身字体大小的一个单位(可以继承) , 而rem是相对于根元素(html)字体大小的一个单位
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
html {
font-size: 100px;
}
.box1 {
font-size: 50px;
}
.box2 {
font-size: 20px;
/* em相对于元素自身字体大小的一个单位 em会继承父盒子字体大小*/
width: 2em; /* 40px */
height: 2em; /* 40px */
background-color: green;
}
.box3 {
/* rem相对于html根元素字体大小的一个单位 */
width: 3rem; /* 300px */
height: 3rem; /* 300px */
background-color: orange;
}
</style>
</head>
<body>
<div class="box1">
<div class="box2"></div>
<div class="box3"></div>
</div>
</body>
</html>
5.rem适配的原理是什么?
利用媒体查询或者js动态检测/获取设备的宽度 , 不同的宽度设置相应的根元素字体大小,当设备宽度变 =>根元素字体大小变 =>所有使用rem做单位的那些元素就跟着变了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
// 动态的获取屏幕的宽度除以10+px赋给根元素字体大小
document.documentElement.style.fontSize =
document.documentElement.clientWidth / 10 + "px"
</script>
<style>
/* 清除所有边距 */
* {
margin: 0;
padding: 0;
}
.box {
/* 占屏幕尺寸的一半 */
width: 5rem;
height: 100px;
background-color: green;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
6.如何实现一个盒子水平垂直居中?
方法1 父元素设置display:flex ; 然后通过进行 justify-content: center;水平居中 , 和 align-items: center;垂直居中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: green;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
} 麦包包菜
.inner {
width: 100px;
height: 100px;
background-color: deeppink;
}
</style>
</head>
<body>
<div class="box">
<div class="inner"></div>
</div>
</body>
</html>
方法2 父相子绝 父元素设置相对定位 position: relative , 子元素设置绝对定位 position: absolute
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: green;
position: relative;
}
.inner {
position: absolute;
width: 100px;
height: 100px;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -50px;
background-color: deeppink;
}
</style>
</head>
<body>
<div class="box">
<div class="inner"></div>
</div>
</body>
</html>
注意点:这儿的 margin 往往不要用百分比,因为 margin 使用百分比是相对于其父盒子的宽度的,CSS 百分比都是相对于谁。
方法3 上面的方案 : margin进行位移的缺点是 : 需要明确知道自身元素的宽高 有时候在不知道盒子自身宽高的情况下推荐使用 transform: translate(-50%, -50%) 进行位移,因为 translate 的百分比是相对于盒子自身的.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: green;
position: relative;
}
.inner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 55%;
height: 33%;
background-color: deeppink;
}
</style>
</head>
<body>
<div class="box">
<div class="inner"></div>
</div>
</body>
</html>
7.说一说你对BFC的理解
1.BFC是什么 块级格式上下文, 是一种特性 , 拥有自己的一套渲染规则 , 它决定了其子元素将如何布局,以及和其他元素的相互关系和作用 2.如何触发BFC特性
- 根元素(html)
- 浮动元素 (元素的float不是none)
- 绝对定位元素 (元素的 position为 absolute 或 fixed )
- 行内块元素 (元素的display 为 inline - block)
- 表格单元格 ( 元素的 display 为 table-cell , HTML表格单元默认为该值)
- overflow不等于 visible
3.BFC 特性有什么作用 1.可以包含内部浮动元素 2.可以排除外部浮动带来的影响 3.阻止外边距重叠 4.解决margin塌陷
3-- JS
1.JavaScript 中什么情况下会返回 undefined
访问声明,但是没有初始化的变量 访问不存在的属性 访问函数的参数没有被显式的传递值 访问任何被设置为 undefined 值的变量 没有定义 return 的函数隐式返回 函数 return 没有显式的返回任何内容
2.数组扁平化
1.递归实现 : 创建一个空变量arr1接收 用for循环数组arr 用Array.isArray(arr[i])判断内层是否是函数 如果是 ,则用arr1.concatnewArr(arr[i]))合并两个数组给空变量arr 不是则直接 arr.push() 最后返回arr1
export const newArr = (arr) => {
let arr1 = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
arr1 = arr1.concat(newArr(arr[i]));
} else {
arr1.push(arr[i]);
}
}
return arr1
};
2.reduce实现数组扁平化 用reduce 第一个参数是累加和 用a.concat()合并数组 判断 第二个参数当前遍历的元素是否是数组 是的话再次调用函数, 不是的话就是当前项的元素合并
// reduce实现数组扁平化
export const newArr1 = (arr) => {
return arr.reduce((a,b) => {
return a.concat(Array.isArray(b) ? newArr1(b) : b)
},[])
};
3.flat实现多维数组扁平化
// Array.prototype.flat是es6新增的数组方法
export const newArr2 = (arr) => {
return arr.flat(Infinity)
}
3.如何遍历对象的属性
**1.**Object.keys() 方法 - 遍历自身可枚举的属性(可枚举,非继承属性) **2.**Object.getOwnPropertyNames()方法 - 遍历自身的所有属性(可枚举,不可枚举,非继承属性) 3.for in 遍历对象的属性 - 遍历可枚举的自身属性和继承属性 (可枚举,可继承的属性
4.什么是伪数组,如何转化为真数组
伪数组 : 可以使用索引对数据进行操作;具有length(长度)属性;但是不能使用数组的方法,如push,pop. **如何转化为真数组 : ** 1.利用es6 提供的Array.from( )方法 2.遍历数组, 创建一个空数组,将遍历的数据逐一放在空数组中 3.常用 arr.push.apply(arr,伪数组) 4.拓展运算符 **5.**Array.prototype.slice.call() 6.arr.slice.call()
5.判断两个对象相等
1.判断两个对象相等,我们要判断他们两个对象的引用地址是否一致
let obj1={
a:1
}
let obj2={
a:1
}
console.log(Object.is(obj1, obj2)) // false
let obj3 = obj1
console.log(Object.is(obj1, obj3)) // true
console.log(Object.is(obj2, obj3)) // false
2.当需求是比较两个对象内容是否一致时就没用了 想要比较两个对象内容是否一致,思路是要遍历对象的所有键名和键值是否都一致 (1)判断两个对象是否指向一内存 (2)使用Object.getOwnPropertyNames获取对象所有键名数组 (3)判断两个对象的键名数组是否相等 (4)遍历键名,判断键值是否都相等
let obj1 = {a: 1,b: {c: 2}}
let obj2 = {b: {c: 3},a: 1}
function isObjectValueEqual(a, b) {
// 判断两个对象是否指向同一内存,指向同一内存返回 true
if (a === b) return true
// 获取两个对象键值数组
let aProps = Object.getOwnPropertyNames(a)
let bProps = Object.getOwnPropertyNames(b)
// 判断两个对象键值数组长度是否一致,不一致返回 false
if (aProps.length !== bProps.length) return false
// 遍历对象的键值
for (let prop in a) {
// 判断 a 的键值,在 b 中是否存在,不存在,返回 false
if (b.hasOwnProperty(prop)) {
// 判断 a 的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回 false
if (typeof a[prop] === 'object') {
if (!isObjectValueEqual(a[prop], b[prop])) return false
} else if (a[prop] !== b[prop]) {
return false
}
} else {
return false
}
}
return true
}
console.log(isObjectValueEqual(obj1, obj2)) // false
6.this的指向
- this : 谁
调用我,我就指向谁 - 普通函数
函数名(): window - 对象方法
对象名.方法名(): 对象 - 构造函数
new 函数名(): new创建的实例对象 - 箭头函数 取决于外部父环境
7.JS中8大数据类型 ?
JS类型由原始值和对象组成
原始值 : Boolen ,
Null ,
Undefined ,
Number ,
String ,
BigInt(大数字) , 它的目的是比Number数据类型支持的范围更大的整数值
symbol(符号) 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
**对象 : **Object
8.forEch和map的区别
forEach没有返回值 , map有返回值 foreach会修改原数组 , map会得到一个新的数组并返回 foreach在并不打算改变数据的时候用 map适用于要改变数据值的时候
9.说一说对事件委托的理解
是什么 : 事件委托 , 就是把子元素绑定的事件 , 统一委托(绑定)到祖先元素身上 原理 : 事件冒泡 为什么要用事件委托(好处是什么?) 1.性能高 2.对后续新增的元素同样具有事件绑定的效果 如何按需触发 ? 自定义属性
10.说一下new的执行过程?
1.在内存中开辟一个空间 2.this指向这个内存空间 3.执行构造函数中的代码 4.返回this实例 在构造函数中 , 如果手动返回了简单数据类型, 会忽略 , new出来的结果还是实例
function Person(name, age) {
this.name = name
this.age = age
// 如果手动返回了简单数据类型,会忽略
return 'hello world'
}
const p1 = new Person('ifer', 18)
console.log(p1) // Person { name: 'ifer', age: 18 }
如果手动返回复杂数据类型 , 则new出来的结果就是这个返回的复杂数据类型
function Person(name, age) {
this.name = name
this.age = age
// new 出来的结果就是这个返回的复杂数据类型
return { name: 'elser', age: 19 }
}
const p1 = new Person('ifer', 18)
console.log(p1) // { name: 'elser', age: 19 }
11.说一下call , apply , bind的异同
**相同点 : **第一个参数都是用来改变函数内部this指向的 , 都可以利用后续参数传参 不同点 : 1.call 和 apply 会直接调用函数 , bind会返回一个新的函数 2.call 和 bind 可以传递任意多个参数 , 而apply只能传递两个参数(第二个参数是数组或者是伪数组)
<script>
function test (a,b,c) {
console.log(a+b+c);
}
// 直接调用
test.call({name:'xxx'},1 , 2 , 3) // 6
test.apply({name:'xxx'},[4,5,6]) // 15
// 返回新的函数
const newFh = test.bind()
newFh(10,20,30) // 60
</script>
12.防抖和节流的理解
防抖就是持续触发(事件)不执行 , 不触发一段时间后才执行
节流就是持续触发也执行 , 只不过执行的频率变低了
**在哪用 **
防抖 : 根据用户输入的内容发送请求
节流 : 获取窗口滚动的位置
怎么做
一般可以结合定时器封装方法来实现 , 或者使用lodash提供的 debounce 和throttle方法
// 在data中定义 延时器的 timerId
timerId: null,
input(value) {
// 用户每次输入,清除上次输入启动的延时器
clearTimeout(this.timerId)
// 每次输入,在清除了上次延时器后,再重新启动一个 500 毫秒的延时器
// 如果 500 毫秒内容用户没有再次输入,则执行搜索
// 如果 500 毫秒内用户再次输入了,取消上次的延时器,再重新启动一个 500 毫秒的延时器
// 防抖处理
this.timerId = setTimeout(() => {
// 如果 500 毫秒内,没有触发新的输入事件,则为搜索关键词赋值
this.kw = value
// 根据关键词,查询搜索建议列表
this.getSearchResults()
}, 500)
},
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#oBox {
width: 500px;
height: 260px;
background-color: pink;
margin: 100px auto;
}
</style>
<script src="./node_modules/lodash/lodash.min.js"></script>
</head>
<body>
<div id="oBox"></div>
<script>
oBox.onmousemove = _.debounce(function (e) {
const left = e.pageX - this.offsetLeft
const top = e.pageY - this.offsetTop
this.innerHTML = `left: ${left}, top: ${top}`;
}, 500);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#oBox {
width: 500px;
height: 260px;
background-color: pink;
margin: 100px auto;
}
</style>
<script src="./node_modules/lodash/lodash.min.js"></script>
</head>
<body>
<div id="oBox"></div>
<script>
oBox.onmousemove = _.throttle(function (e) {
const left = e.pageX - this.offsetLeft
const top = e.pageY - this.offsetTop
this.innerHTML = `left: ${left}, top: ${top}`;
}, 500);
</script>
</body>
</html>
13.说一下你对EventLoop的理解
EventLoop又叫事件循环 , 是单线程语言JS在运行代码时不被阻塞的一种机制 JS代码的执行分为同步代码和异步代码 , 当碰到同步代码时直接在执行栈中执行 当碰到异步代码并且时机符合时(例如定时器时间到了) , 就会把异步代码添加到任务队列当中 当执行栈中的同步代码执行完毕后 , 就去任务队列中把异步代码拿到执行栈中执行 这种反复去任务队列中取异步代码并拿到执行栈中执行的操作 , 就是EventLoop EventLoop
console.log('react')
setTimeout(() => {
console.log('vue')
}, 1000)
console.log('angular')
// 执行
react --> angular --> 同步执行完并且定时器时间到了 --> vue
怎么保证定时器那个时间是准确的 ? web worker => 开启多线程 => 把耗时的操作放到另一个线程
14.for...in 与for...of的区别
**for...in : **可以遍历对象 , 遍历数组的下标 , 可以遍历原型的属性 **for...of :**不能遍历对象 , 遍历数组的元素 , 不可以遍历原型的属性
<script>
// for...of 不能遍历对象 , 遍历数组的元素
let arr = [1, 2]
let obj = { a: "1" }
// for (let i of obj) {
// console.log(i)
// }
// for (let i of arr) {
// console.log(i)
// }
// for... in 可以遍历对象 , 遍历数组的下标
for(let i in obj){
console.log(i);
}
// 遍历数组的下标
for(let i in arr) {
console.log(i);
}
</script>
15.原型与原型链
构造函数通过new创建实例对象 实例对象(对象)身上的一个属性,叫 __proto__就是对象原型 原型对象是构造函数上的一个属性prototype 只有函数有prototype ,对象没有
对象原型有什么作用? 对象原型(proto) 默认指向了 原型对象(prototype) , 导致实例对象可以调用公共方法 proto 就是实例对象和原型对象之间的一个桥梁
**constructor **
概念:是原型对象(prototype) 和 对象原型(proto)身上的一个属性
作用: constructor会记录当前对象属于哪个构造函数
16.数据类型的判断
**判断原始数据类型typeof:区分不出null , **除了 function,区分不出复杂数据类型(都是 'object')
typeof undefined // "undefined"
typeof null // "object"
typeof 1 // "number"
typeof "1" // "string"
typeof Symbol() // "symbol"
**区分null : ** 用全等 === 判断 或者 Object.prototype.toString 方法 **引用数据类型intanceof:**返回的是布尔值 A instanceof B,是判断 A 是否能够通过原型链(proto) 找到 B 的 prototype。
17.判断是否为一个对象
**1.Object.prototype.toString.call **(数据) 判断对象
Object.prototype.toString.call(obj) === '[object Object]'
2.constructor
obj.constructor === Object
3.**instanceof **需要注意的是由于数组也是对象,因此用 arr instanceof Object 也为true。
obj instanceof Object
18.判断对象为空
第一种 : JSON.stringify( ) 通过JSON.stringify()将json对象转化为json字符串,再判断该字符串是否为”{}”,这就直接可以得出来这个对象是否为空。
let data = {};
let a = (JSON.stringify(this.projectData) === "{}"); //把data转换为字符串a
if(a === true)){ //如果data为空,返回为true
return;
}
第二种 : for…in 循环判断 通过for…in 循环来判断对象**(obj)**是否为空
let data = {};
let a = function() {
for(let key in data) {
return false;
}
return true;
}
alert(a()); //为true,说明data为空对象
第三种 : Object.keys( ) 通过Object.keys()来进行对象**(obj)**是否为空的判断,该方法也是ES6新增的方法, 返回值是对象中属性名组成的数组。
let data = {};
let array = Object.keys(data);
if(array.length === 0){ //如果数组array的长度为0 ,则返回为true,说明data对象为空
return ;
}
还有两种不常用的 : ** **
jquery的isEmptyObject方法
**Object.getOwnPropertyNames( )**方法。
19.深浅拷贝
浅拷贝 : 如果是基本数据类型, 拷贝的就是基本类型的值 ; 如果是引用类型, 拷贝的是引用地址 ; 即浅拷贝拷贝的第一层 1.浅拷贝 1.扩展运算符 既不是深拷贝,也不是浅拷贝。一半一半,他只能深拷贝第一层。第二层的拷贝还是浅拷贝 2.Object.assign() 语法 : Object.assign(target, ...sources) 注意事项 : 1.不会拷贝对象的继承属性; 2.不会拷贝对象的不可枚举的属性; 3.可以拷贝 Symbol 类型的属性。 **3. Array.prototype.concat()拷贝数组 ** 浅拷贝,适用于基本类型值的数组 4.Array.prototype.slice()拷贝数组 浅拷贝,适用于基本类型值的数组 2.深拷贝 1:JSON.stringify( ) JSON.stringfy( ) 其实就是将一个 JavaScript 对象或值转换为 JSON 字符串,最后再用 JSON.parse( ) 的方法将JSON字符串生成一个新的对象。(JSON.stringfy()、JSON.parse()) 2.递归实现
20.slice splice split的区别
slice 截取 : 可操作数组 和 字符串 slice中存在2个参数,slice(start,end),start表示数组索引,end是数字位置,若只存在一个参数则显示参数位置到最后
let a=[1,2,3,4,5]; a.slice(0,3)=[1,2,3]; a.slice(3)=[4,5];
# 可以看第二个参数减去第一个参数的值,为几一般就是显示几个数字
let a="this is a test"; a.slice(0,6)="this i";
splice 删除替换 : 可操作数组 和 字符串 splice(start,deletecount,item) start 起始位置索引 deletecount 删除位数, 替换的item 返回值为被删除的字符串
let a={'a','b','c'};
let b=a.splice(1,1,'e','f');
# 结果
let a={'a','e','f','c'};
split 分割 : 字符串变数组
let a="01234";
a.split("",3)=["0","1","234"];
21.hash和history路由的区别?
1.hash 路由:监听 url 中 hash 的变化,然后渲染不同的内容,这种路由不向服务器发送请求,不需要服务端的支持; 2.history 路由:监听 url 中的路径变化,需要客户端和服务端共同的支持; **原理 ** hash 模式是通过监听 onhashchange 事件做的处理。 history 模式是利用 H5 新增的 History 相关 API 实现的,例如 onpopstate 事件、pushState、replaceState 等方法。 对于后端的表现 刷新页面时,hash 地址也就是(也就是 # 号后面的内容),不会作为资源发送到服务端,后端拿到的都是 / 这个请求地址,它只需要返回 index.html 页面就好啦。 刷新页面时,history 地址对于服务端来说是一个新的请求,后端拿到的是不同的请求地址,也就意味着需要服务端对这些请求做处理,否则会 404。 hash 不存在兼容性问题
22.跨域的理解
一、说一说, 你对跨域的理解 ? 1.跨域的概念 : 两个协议(http,https), 地址, 端口号有一个不相同, 就是跨域的 2.跨域的优缺点 : 优点就是安全 : DOM/cookie/ajax无法正常执行 ; 缺点 : 无法正常处理数据 3.解决方案 : 1.cors模块 解决方案 : 后端配置(后端通过设置一个响应头Access-Control-Allow-Origin) , 前端正常调用 2.代理服务器 , 如果是Vue-CLI创建的项目, 可以再vue-config.js文件中配置devServer的proxy 3.jsonp 要求前后端配合完成 ; 前端通过script标签发送请求 , 然后把全局函数名称发送给后端 , 后端接受到函数名称 , 会把函数放入到参数中 , 作为字符串响应给前端
23.null 和undefined的区别
null 表示一个"无"的对象(空对象指针) , 转为数值时为0 undefined 是一个表示"无"的原始值 , 转为数值时为NaN
typeof null // 'object'
typeof undefined // 'undefined'
null + 4 // 4
undefined + 7 // NaN
// Number(null) // 0
// Number(undefined) // NaN
JSON.stringify({ foo: undefined, bar: null }) // '{bar: null}'
24.JS继承
1.ES6的class继承 2.原型链继承
Child.prototype = New Parent()
3.借用构造函数继承
Parent.call(this)
4.组合式继承 : 用构造函数继承 + 原型链继承
4-- Es6
1..async /await
await 表示强制等待的意思,await关键字的后面要跟一个promise对象,它总是等到该promise对象resolve成功之后执行,并且会返回resolve的结果
async里面是异步函数 , await后面是同步函数
async / await 用同步的方式 去写异步
promise可以通过catch捕获,async / await捕获异常要通过 try / catch
2.catch 之后再写 then 还会触发吗
catch 之后再写 then 还会触发吗, 正常是会的,因为这儿也有一个默认的返回
3.var、let、const之间的区别
var声明变量可以重复声明,而let不可以重复声明 var是不受限于块级的,而let是受限于块级 const声明之后必须赋值,否则会报错 const定义不可变的量,改变了就会报错 const和let一样不会与window相映射、支持块级作用域、在声明的上面访问变量会报错
4.箭头函数与普通函数的区别
1.箭头函数没有原型对象prototype,不能作为构造函数使用(不能被new) 2.箭头函数没有arguments(类似数组但不是数组的对象), 可以使用 ... 拿到所有实参的集合数组 3.箭头函数中的 this 在定义时就已经确定,取决于父级的环境 4.箭头函数不能通过call ,apply , bind方法修改它的this指向(会忽略第一个参数 , 其他功能可以正常使用) 5.箭头函数不能用作Generator函数 , 不能使用yeild关键字(function*)
5.localStorage与sessionStorage和cooike区别
生命周期和存储不一样 localStorage : 硬盘存储 , 永久存储 , 除非手动删除 存储大小4.98M , **sessionStorage **: 内存存储 , 关闭当前页后会自动清除 存储大小4.98M **cookie **: 默认关闭游览器后失效, 如果设置了过期时间,则达到过期时间后失效 存储大小4KB 生效范围不一样 localStorage : 同域可以共享 **sessionStorage : **只有当前标签页才能访问 **cookie : **同域且path匹配的情况下才能访问 操作主体和请求是否会携带 localStorage / sessionStorage:只有客户端才能设置 , 请求时不会自动携带。 cookie:客户端和服务端都可以设置。每次请求都会随着请求头带到后端 什么是 path 匹配? 例如 www.baidu.com:3000/ 下创建的 cookie 或者设置 cookie 的时候加了 path=/,同域下的任意路径都能访问,因为任意路径都属于 / 的子路径,又称为和 / 是匹配的。 例如 www.baidu.com:3000/a/b 下创建的 cookie 或者设置 cookie 的时候加了 path=/a/b,同域下和 /a 匹配的任意路径都能访问,例如 /a、/a/b、/a/c、/a/b/c 等都称为是和 /a 匹配,或者称为是 /a 的子路径。
6.对promise的理解
Promise是ES6新增的函数 自身是同步的 一般作为构造函数使用 , 需要new一下创建一个promise实例 , 它有三种状态 : pending( 进行中) , fulfilled( 已成功) , rejected( 已失败) ; 成功会触发 then , 失败会触发 catch ; 还有一个finally是永远都会被触发的 Promise 相关的几个静态方法?
- Promise.race():接收多个 Promise 实例,可以得到最先处理完毕的结果(可能是成功,也可能是失败)。
Promise.race([p1, p2, p3])
- Promise.all():接收多个 Promise 实例,都成功了会触发 then,有一个失败就会触发 catch。发送多个ajax
Promise.all([p1, p2, p3]).then(r => console.log(r))
// [p1 的结果, p2 的结果, p3 的结果]
- Promise.any():接收多个 Promise 实例,可以得到最先处理成功的结果,都失败才会触发 catch。
Promise.any([p1, p2, p3])
它解决了回调地狱的问题
虽然解决了回调地狱的问题但是没有简化代码 , 所以工作中我们会配合async / await 来使用
7.对闭包的理解
一个函数 使用了其 外部函数 中的局部变量 , 使用变量的地方我们称为发生了 闭包现象 变量定义所在的函数我们称为 闭包函数 **特性 : **普通函数调用完毕, 内部局部变量马上销毁 ; 闭包函数调用完毕,会使内部形成这个【闭包函数】的变量(age)常驻内存,所以滥用闭包会造成内存浪费。
8.const能否修改?
const 定义的简单数据类型,绝对不能改 const 定义的复杂数据类型,引用地址绝对不能改,内容可以改
// const 定义的简单数据类型,绝对不能改
const num = 8
// const 定义的复杂数据类型,引用地址绝对不能改,内容可以改
const arr[] = []
arr.push(8) // 这就叫内容可以改
arr = [] // 这就叫引用不能改
9.++i 和 i++
++i : +在前先自增在运算 i++ : + 先运算在自增
let i = 8
// 先赋值(先把 i 给了 n),再自加(i 加了 1)
let n = i++
console.log(n) // 8
console.log(i) // 9
10.Set 和Map
Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。 Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。 Set:
- 成员唯一、无序且不重复。
- [value, value],键值与键名是一致的(或者说只有键值,没有键名)。
- 可以遍历,方法有:add、delete、has。
Map:
- 本质上是键值对的集合,类似集合。
- 可以遍历,方法很多可以跟各种数据格式转换。
**set : ** Set 是ES6 新增的一种新的数据结构 , 类似于数组,成员唯一(内部元素没有重复的值) Set 本身是一种构造函数
const s = new Set()
[1, 2, 3, 4, 3, 2, 1].forEach(x => s.add(x))
for (let i of s) {
console.log(i) // 1 2 3 4
}
// 去重数组的重复对象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)] // [1, 2, 3]
操作方法:
- add(value):新增,相当于 array里的push。
- delete(value):存在即删除集合中value。
- has(value):判断集合中是否存在 value。
- clear():清空集合。
便利方法:
- keys():返回一个包含集合中所有键的迭代器。
- values():返回一个包含集合中所有值得迭代器。
- entries():返回一个包含Set对象中所有元素得键值对迭代器。
- forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值。
Map :
const m = new Map()
const o = {p: 'haha'}
m.set(o, 'content')
m.get(o) // content
m.has(o) // true
m.delete(o) // true
m.has(o) // false
操作方法:
- set(key, value):向字典中添加新元素。
- get(key):通过键查找特定的数值并返回。
- has(key):判断字典中是否存在键key。
- delete(key):通过键 key 从字典中移除对应的数据。
- clear():将这个字典中的所有元素删除。
遍历方法:
- Keys():将字典中包含的所有键名以迭代器形式返回。
- values():将字典中包含的所有数值以迭代器形式返回。
- entries():返回所有成员的迭代器。
- forEach():遍历字典的所有成员。
5-- webpack
1.你对webpack了解哪些东西
**是什么 : **Webpack是一个打包构建工具 , 可以基于loader处理各种各样类型文件 怎么用 : 一般使用的时候会在 wenpack.config.js中做相关配置 , 例如entry , output , module , plugins , mode(development 或 production) 别名 deServer的Proxy 不过,实际开发中我更喜欢用基于 Webpack 的 Vue CLI,它也可以再 vue.config.js 中对 Webpack 进行二次配置。
2.webpack热更新过程
依赖了 hot-module-replacement 插件
- 使用 WDS(webpack-dev-server) 托管静态资源,同时注入 HMR(hot module replacement) 代码。
- 浏览器加载页面后,与 WDS 建立 WebSocket(双向数据通信的协议) 连接。
- Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件。
- 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围。
- 浏览器加载发生变更的增量模块。
- Webpack 触发变更模块的 module.hot.accept 回调,执行代码变更逻辑
6-- git
7-- http
1.get与post请求区别
1.get请求一般是去取获取数据 ; post请求一般是去提交数据。 2.get因为参数会放在url中,安全性较差,请求的数据长度有限制(不大于2kb) ; post请求没有长度限制,请求数据放在body中 3.get请求刷新服务器或者回退没有影响,post请求回退时会重新提交数据请求。 4.get请求可以被缓存,post请求不会被缓存。 5.get请求会被保存在浏览器历史记录当中,post不会。 get 请求发送的是 查询参数(query参数) ,通过params传递 post 请求发送的是 请求体(post)参数 ,通过data传递
2.ajax与axios的区别
axios是一个基于Promise的HTTP库 , 而ajax是对原生XHR的封装 ajax技术实现了局部数据的刷新, 而axios实现了对原生ajax的封装 **ajax原理 : **由客户端请求ajax引擎,ajax请求服务器 , 服务器响应返回给ajax引擎 , 由ajax决定将这个结果写到客户端什么位置, 实现无刷新更新数据 核心对象 HMLHttpRequest 简单来说 xioas有的ajax都有 , ajax有的axios不一定有
3.一个页面从输入URL到页面加载显示完成,这个过程中都发生了什么
三大步骤:
- 网络请求
- DNS 查询(得到 IP),建立 TCP 连接(三次握手)
- 浏览器发送 HTTP 请求
- 收到请求响应,得到 HTML(解析 HTML 过程中,遇到静态资源还会继续发起网络请求)
- 解析(字符串 -> 结构化数据)
- HTML 构建 DOM 树
- CSS 构建 CSSOM 树
- 两者结合形成 render tree
- 渲染
- layout 计算布局:(文档流, 盒模型, 计算各个元素的大小、位置)
- paint 绘制:(将边框颜色、文字颜色、阴影等都画出来)
- composite 合成:根据层叠关系显示画面
- 遇到 JS 执行
- 异步 CSS,图片加载,可能会触发重新渲染
4.什么是TCP连接的三次握手
TCP是因特网中的传输层协议,建立一个TCP连接时需要客户端和服务端发送三个数据包的过程。进行三次握手是为了确定双方的接收能力和发送能力是否正常。
**第一次握手 : **客户端给服务端发送一个 SYN 报文,并指定客户端的初始化序列号 ISN ,此时客户端处于 SYN_SEND 状态。
**第二次握手 : **服务端受到客户端的SYN报文后,会以自己的SYN报文作为应答,并且也是指定了自己的初始化序列号ISN。同时会把客户端的ISN+1作为ACK的值,表示自己已经收到了客户端的SYN,此时服务器处于SYN_REVD状态。
**第三次握手 : **客户端受到 SYN 报文后,会发送一个 ACK 报文,把服务器的 ISN + 1 作为自己Acknowledgment number的值,表明自己已经收到了 SYN 报文,此时客户端处于ESTABLISHED状态,服务器收到 ACK 报文后,也处于 ESTABLISHED状态。此时双方已经建立链接
5.什么是四次挥手?
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。 (2)服务器B收到这个FIN,它发回一个ACK(确认号),确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。 (3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。 (4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。
8-- Vue2
1.组件data为什么是一个函数
因为组件是需要在多个地方使用的
- 如果data是一个对象,对象是引用类型。 一旦某一个地方修改,就会全部修改
- data是一个函数,每一次复用组件的时候就会从这个函数返回一个新的对象。 这样组件在复用的时候就可以做到数据互不干扰。
2.Vuex的actions和mutations有什么区别?
1.actions主要用于响应组件中的动作,通过 commit( )来触发 mutation 中函数的调用, 间接更新 state,不是必须存在的; 2.actions可以进行异步操作,可用于向后台提交数据或者接受后台的数据; mutations中是同步操作,不能写异步代码、只能单纯的操作 state ,用于将数据信息写在全局数据状态中缓存,不能异步操作
3.mapState 和 mapGetters的区别
**mapState : **是将state中的数据映射到组件的计算属性当中
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
**mapgetters : **用于将getter(store的计算属性)中的计算属性映射到组件的计算属性当中
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
4.mutation 里面为什么建议只写同步代码去修改 state?写异步会怎样?
写同步代码可以保证,mutation 执行完毕后可以得到一个确定的状态,方便 devtools 打个快照存下来,实现 time-travel 调试(在调试工具中能根据触发 mutation 的时间来进行切换和调试)。 如果 mutation 里面写异步代码,严格模式下会有警告,因为不好追踪状态是何时变更的,给调试带来困难。
5.v-if与v-show的区别
v-if : 条件渲染。JS控制元素的创建与销毁 如果不满足条件,则该元素不会添加到DOM树中 ; 只有满足才会添加到dom树中 (底层原理: appendChild 和 removeChild) , 有较高切换开销 应用场景: 不需要频繁切换,初始为false则直接不添加到dom中 v-show: CSS控制显示与隐藏。 只是修改元素的display属性值 (修改css样式) , 有较高初始渲染开销 应用场景: 需要频繁切换
6.v-for和v-if为什么不建议放一行
**Vue2 : **因为Vue2中v-for的优先级比v-if高,Vue会先循环出所有的元素 , 再把不符合条件的销毁 , 多了一些没必要的创建和销毁的操作, 性能浪费 **Vue3 : **Vue3中v-if比v-for的优先级高 , 更不能放一行 , 因为v-if判断的时候需要依赖循环项进行 , 这个时候循环项还没有,所以会直接报错 **如何解决 : **解决方式就是通过计算属性 , 先计算出需要循环的那些元素就好 需求:数组中的 angular 不需要渲染。
<template>
<div id="app">
<ul>
<li v-for="(item, index) in arr" :key="index" v-if="item !== 'angular'">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
arr: ['vue', 'react', 'angular'],
}
},
}
</script>
解决
<template>
<div id="app">
<ul>
<li v-for="(item, index) in renderArr" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
arr: ['vue', 'react', 'angular'],
}
},
computed: {
renderArr() {
return this.arr.filter((item) => item !== 'angular')
},
},
}
</script>
7.生命周期
钩子 : 一种回调函数 , 4个阶段,8个周期
1.beforeCreate(){} el和data还没有创建
vue实例创建了,但是el和data还没有创建 (准备创建data) 底层(初始化vue实例,初始化钩子函数,初始化一些事件和侦听器配置项)
2.created() {} data创建 , el还没创建
data数据创建了,但是el挂载点还没有创建 (准备创建el) 底层:初始化data中的数据和methods中的方法
3.beforeMount() {} el创建 , data还没渲染
el挂载点创建了,但是data数据还没有渲染(准备渲染中) 底层:创建el挂载点,并且生成虚拟DOM
4.mounted() {} data第一次渲染完
data数据 第一次 渲染完毕 (完成初始渲染) 底层:将虚拟dom渲染成真实DOM
5.beforeUpdate() {} data变化还没渲染
检测到data数据变化,但是还没有开始重新渲染 (data变了,准备重新渲染中) 会执行多次
6.updated() {} data变化渲染完
变化后的data数据 ,完成渲染到页面 (完成重新渲染)会执行多次
7.beforeDestroy() {} 解除data与el的关联
vue实例销毁即将销毁(解除data与el的关联),之后修改data,页面不会被渲染 底层 : 解除 事件绑定、侦听器、组件
8.destroyed() {}
vue实例已经销毁(而是指解除data与el的关联)
**组件钩子 : **activated和deactivated没有keep-alive的时候是不会被触发的
在存在keep-alive的时候可以将activated当作created进行使用
deactivated是组件销毁的时候触发,此时的destory是不执行的
component 动态组件
路由守卫钩子 :
路由守卫分类
【1】全局守卫:是指路由实例上直接操作的钩子函数,特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数
beforeEach(to,from, next)
afterEach(to,from)
beforeResolve(to,from, next)
【2】路由守卫: 是指在单个路由配置的时候也可以设置的钩子函数
beforeEnter(to,from, next)
【3】组件守卫:是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。
beforeRouteEnter(to,from, next)
beforeRouteUpdadte(to,from, next)
beforeRouteLeave(to,from, next)
8.父子生命周期顺序
初始化:父 beforeCreate、父 created、父 beforeMounte、儿子走完、父 mounted 更新:父 beforeUpdate、子走完、父 updated 销毁:父 beforeDestroy、子走完、父 destroyed
9.会在vue哪个生命周期钩子里发送请求 , 为什么
📌 首先呢,发请求的时机肯定越早越好,那么 beforeCreate 最早; 📌 但是还有一点,有时候请求前需要依赖 data 里面的数据或调用 methods 里面的方法(请求后可能也需要); 📌 而 data 和 methods 都是需要实例创建完毕后(created)才具有的,所以一般我会在 created 里面发请求; 📌 既保证了请求实际相对较早,又保证了可以使用 data 里面的数据或 methods 里面的方法。 不过有时候这个请求需要依赖 DOM 相关操作的话,我会选择在 mounted 里面进行,因为 mounted 阶段才能保证页面已经渲染完毕了,也就可以操作 DOM 啦。
10.created和mounted的区别
**created:**在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。 **mounted:**在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
11.Vue2 哪些情况对数据的操作不是响应式的?
哪些操作不是响应式, 为什么? Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 1.对后续新增的key(属性)进行赋值 Object.definePropert( )是递归劫持对象中的每一个属性, 后续新增的属性劫持不到 解决:Vue.set() 或 this.$set()
data() {
return {
obj: {}
}
}
this.obj.age = 18
2.删除对象中的某一项 Object.definePropert( ) 不支持对数组删除操作的劫持 Vue.delete() 或 this.$delete()
data() {
return {
obj: {
age: 18
}
}
}
delete this.obj.age
3.通过索引去删除数组的某一项 Object.definePropert( ) 并不是不支持, 而是作者出于对性能的考虑没有这样做 Vue.set() 或 this.$set() 或 arr.splice() **Vue3 呢? 1. 给对象后续新增的 key 进行赋值; Vue3 响应式的原理换成了 Proxy,可以直接代理整个对象,就无所谓这个属性是不是后续添加的了。 2. 删除对象中的某一项; Proxy 支持对删除操作的拦截。 3. 通过索引去修改数组的元素; Proxy 对数组的操作不存在性能问题。
12.$set用法
为什么要用 ? 在Vue中并不是任何时候数据都是双向绑定的 总结 : 只要值的地址没有改变,vue是检测不到数据变化的。 用法 ? 解决数据没有被双向绑定我们可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名
13.说一下你对Vue.use的理解
Vue.use( ) 作用是通过全局方法使用插件(组件) , 它接收的参数限制Function | Object 如果插件是一个对象, 它必须暴露一个install方法 如果插件本身是一个函数, 它将视为安装方法
14.说一说对自定义指令的理解
Vue有一组默认指令 , 比如v-model , v-for , 同时也允许用户注册自定义指令来扩展Vue能力 自定义指令分为, 定义 , 注册 , 使用三步 定义有两种方式: 对象形式 : 有各种钩子函数 ; 指令的钩子会传递以下几种参数:
-
el:指令绑定到的元素。这可以用于直接操作 DOM。
-
binding:一个对象,包含一些属性。
name,指令名,不包含v-前缀。 value,指令的绑定值。如v-focus=“1+1”,value即为2。
函数形式 : 只会在mounted 和updateed中执行 注册 : 通过Vue.directive( )全局注册 , Vue.directive第一个参数是指令的名字(不需要写上v-前缀),第二个参数可以是对象数据,也可以是一个指令函数 使用{directives:{xxx}} 局部注册 , 局部注册通过在组件options选项中设置directive属性 使用时 : 注册名称前加上v- , 比如 v-focus 项目使用: 复制v-copy , 光标v-focus
15.说一下你对keep-alive的理解
keep-alive是vue中的内置组件 ,用来缓存组件的, 提升性能
能在组件切换过程中将状态保存到内存中, 防止重复渲染
比如 : 首页进入详情页 , 用户每次点击都是相同的, 那么详情页就没必要再次请求了, 直接缓存起来再用, 点击的不是同一个 , 就再次发送
缓存后如何获取数据?
- beforeRouteEnter
每次组件渲染的时候都会执行 beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},
- actived
在keep-alive缓存的组件被激活的时候,都会执行actived钩子
activated(){
this.getData() // 获取数据
},
activated和deactivated没有keep-alive的时候是不会被触发的 在存在keep-alive的时候可以将activated当作created进行使用 deactivated是组件销毁的时候触发,此时的destory是不执行的
16.父传子, 子传父,兄弟通信,跨层级通信你了解的都有哪些方法?
- 通过 props 传递 父传子 父亲通过自定义属性传递, 儿子通过props接收
- 通过 $emit 子传父 触发自定义事件
- 使用 ref 父组件使用子组件 通过ref来获取子组件的实例,调用子组件的方法来传递
- EventBus 兄弟组件传值
- root 共同祖辈兄弟传值
- listeners 祖先传递给子孙
- Provide 与 Inject 祖先传递给子孙
- Vuex 复杂关系的组件数据传递 , 是一个存储共享变量的容器
17.虚拟dom和真实dom
虚拟DOM和真实DOM (1)真实DOM : document.querySelector('选择器') 特点: 内存中的属性很多,有上百个 (2)vue虚拟DOM : 本质是一个js对象 特点: 只存储少部分有用的属性 虚拟DOM好处 (1)提高了更新DOM的性能(不用把页面全删除重新渲染) (2)存储更少的数据(节省内存) vue渲染虚拟DOM流程 (0)根据真实DOM 生成 新的虚拟DOM 把真实DOM上百个属性提取核心属性出来,生成虚拟 VNode (1)使用 Object.defineProperty() 监听data变化 (2)新旧 虚拟DOM对比, 找不同的地方 底层:使用 diff算法来找新旧 VNode 不同点 (3)更新虚拟DOM (4)将虚拟DOM渲染到真实DOM
18.vue2统一功能的数据和业务逻辑如何复用
可以使用 mixins 复用 缺点 : 数据来源不清楚 ; 命名冲突
19.侦听器 计算属性 methods 区别
面试点 : 侦听器与计算属性区别 开启深度侦听 deep:true (1)计算属性有缓存, 侦听器没有缓存 (2)计算属性不支持异步操作 , 侦听器支持异步操作 (3)侦听器只能检测一个数据变化, 计算属性可以检测多个数据变化 (4)侦听器只能侦听data里面有的数据, 计算属性可以给vue实例新增属性 面试点 : 计算属性和methods的区别(计算属性有缓存机制) (1)第一次使用计算属性的时候, vue会执行一次这个函数,然后把返回值放入缓存中 (2)之后使用计算属性, vue不会执行函数,而是从缓存读取 (3)只有当计算属性内部所 ‘依赖的数据’ 发生改变,vue才会重新执行函数然后将返回值缓存
20.说说对vuex的理解?
Vuex是一个全局的状态管理库 配置项 一般使用的时候它里面有 state、mutation、action、getters、modules、plugins 等配置项。 触发流程 视图 => dispatch => action => 异步请求并拿到结果 => commit => mutation => 修改 state => 视图改变。 解决了什么? 解决了非关系型组件间数据的传递和共享的问题 有时候组件关系明确的时候, 我们会使用 props , ref , EvenBus , Provide 等方法
21.router和route的区别
- routes : 数组。 路由匹配规则
- router : 对象。 路由对象
- $router : VueRouter的实例,相当于一个全局的路由器对象对象。 用于跳转路由 和 传递参数
两种传参方式 : 分别是编程式导航 和 声明式导航 编程式导航写在js函数中,通过this.$router.push(xxx)来触发路径 声明式导航是写在组件的template中,通过router-link来触发
- $route :路由信息对象。 用于接收路由跳转参数 跳转方式 : path,name ; 传参方式 : params, query
22.query和params之间的区别是什么?
1、query要用path来引入,params要用name来引入 2、接收参数时,分别是this.route.params.name(注意:是router)
23.如何并发多个请求, 按请求的书写顺序拿到结果?
// # 能保证顺序,但是不是并发,串行
await axios.get('/news')
await axios.get('/sports')
// # 并发,不能保证顺序
axios.get('/news')
axios.get('/sports')
const p1 = axios.get('/news')
const p2 = axios.get('/sports')
Promise.all([ p1, p2 ]).then(r => {
r // [p1 的结果, p2 的结果]
}).catch(e => {
// 如果中间有一个出错,就会到 catch
// catch 之后再写 then 还会触发吗,正常是会的,因为这儿也有一个默认的返回
// return Promise.resolve(undefined)
}).then(r => {
});
24.Vue传递过来的数据可以修改吗?
传递过来的如果是简单数据类型,不能改,改了会有警告(传递过来的确实会变,外部还是原来的)。 传递过来的如果是复杂数据类型,引用地址不能改,但是内容可以改,即便内容可以改,也不建议改,因为单项数据流思想。
25.$nextTick
为了解决 : 修改数据的时候, 页面dom不会立即更新 **用法 : ** 如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick() vm.$nextTick() , 等待当前dom异步更新之后立即执行 原理是 : , vue 通过异步队列控制 DOM 更新 , 当数据发生变化,Vue将开启一个异步队列,视图需要等队列中所有数据变化完成之后,再统一进行更新 nextTick底层是promise,所以是微任务
26.Vue响应式怎么实现的
①首先,vue的响应式指的是当组件data发生变化的时候,会立即触发视图的更新。 ②从原理层面来讲,它是采用数据劫持结合发布订阅的模式来实现数据的响应。具体来讲就是对data中的所有属性进行劫持,然后在getter中收集依赖,在setter中触发依赖。也就是说,把用到该属性的组件所对应的watcher对象都收集到属性订阅器dep里面,之后当属性值发生变化的时候,dep就会循环遍历所有的watcher对象并通知它们调用update方法,从而使得跟这个属性相关联的组件都得到更新。 ③刚刚提到了数据劫持这个概念,那么 vue2和vue3采用的数据劫持方法也有所不同,vue2使用的是object.defineProperty,vue3使用的是proxy, vue3之所以不再沿用vue2的方法主要是由于使用object.defineProperty存在一些缺陷,比如它不能监听对象的新增属性和删除属性,同时也无法监听到通过索引改变数组元素的操作。
27.发布订阅模式和观察者模式
发布订阅模式 : 发布订阅模式中有三个角色,发布者 Publisher ,事件调度中心 Event Channel ,订阅者 Subscriber 。
观察者模式 : 在观察者模式中,只有两种主体:目标对象 (Object) 和 观察者 (Observer)。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
28.Vue双向绑定的原理
Vue 数据双向绑定原理是通过 数据劫持 + 发布者-订阅者模式 的方式来实现的,首先是通过 ES5 提供的 Object.defineProperty() 方法来劫持(监听)各属性的 getter、setter,在getter中收集依赖,在setter中触发依赖 , 并在当监听的属性发生变动时通知订阅者,是否需要更新,若更新就会执行对应的更新函数。
什么是数据劫持
数据劫持比较好理解,通常我们利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。
发布者模式 / 订阅者模式
在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
这里很明显了,区别就在于,不同于观察者和被观察者,发布者和订阅者是互相不知道对方的存在的,发布者只需要把消息发送到订阅器里面,订阅者只管接受自己需要订阅的内容
29.如何监听子组件的某个钩子,这个触发后父亲做一些事情?
第一种 : 调用父亲的方法
// 第一种方法
// 子
export default {
created() {
# 调用父亲的方法
}
}
第二种 : @hook
// 第二种方法
// 父
// 监听儿子的 created 钩子触发后,指定父亲的 handleFn
<Child @hook:created="handleFn"/>
30.VUE Router导航钩子
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
31.sync
.sync与v-model区别是
相同点:都是语法糖,都可以实现父子组件中的数据的双向通信。 区别点:格式不同: v-model=“num”, :num.sync=“num” v-model: @input + value :num.sync: @update:num 另外需要特别注意的是: v-model只能用一次;.sync可以有多个。
9-- Vue3
1.Vue3 丢失响应式
1.解构赋值 2.直接把简单数据赋值给一个变量 3.把简单数据类型赋值给对象中的某一个
2.说一下你对 Vue3 的了解?说一下 Vue3 和 Vue2 的差异?
1.性能更高了 (得益于响应式的原理换成了Proxy , VNode Diff算法进行了优化) 2.体积更小了 (删除了一些不常用的api , 例如 filter , EventBus ; Vue3中所有的api都是按需导入 , 能配合Webpack支持TreeShaking) 3.对TS的支持更好了 (源码使用TS写的) 4.Composition API(组合 API,Vue2 叫 Options API),对于大型项目更利于同一功能的数据和业务逻辑维护和复用! 5. 新特性(Fragment、Teleport、Suspense...自对应 render 渲染器)。
10-- TS
1.为什么要用ts ?
ts 有类型的校验和提示 , 是js的超集 , 对于开发大型项目很合适
2.any 和 unknown的区别
1.unknown是更加安全的any类型 , 例如当做方法直接调用或者访问某个属性其实在写 TS 的时候也能把错误报出来的,这往往正是我们期望的,所以一般我会优先使用 unknown。
let num: unknown = 88
num = 'abc'
console.log(num)
// 编译成 JS 会报错
// num() // error: 不能调用方法
// console.log(num.length) // error: 不能访问属性
- 任何类型可以给 any,any 也可以给任何类型;任何类型可以给 unknown,unknown 只能给 unknown 或 any 类型
11-- Vite
1.vite是什么?
是什么 : 是下一代前端开发与构建工具 , 热更新, 打包构建速度相比较比webpack更快 为什么快 : 1 . Webpack 会将所有模块提前编译 , 打包 , 不管这个模块是否被用到 , 随着项目越来越大, 打包启动速度自然越来越慢 2 . Vite 瞬间开启一个服务 , 并不会先编译所有文件 , 当游览器用到某个文件时 , Vite服务会收到请求然后编译后响应到客户端
12-- 小程序
1.如何模拟小程序中的请求拦截器?
因为 wx.request 并没有提供所谓拦截器的功能,每次携带 token 麻烦,让你去做你该怎么模拟请求拦截器,参考。 总结如下! 所谓请求拦截器的模拟,无非是提前准备一个方法(请求拦截器方法),每次请求前调用一下这个方法。 调用方法的同时,传递过去一个对象(this),方法内部接收对象并修改此对象(_this.header = {}),只不过 wx.request 的时候 header 所应用的值就是这对象(wx.request({ header: this.header }))。
<script>
// 问题,每次携带 Authorization 玛法,怎么解决。
/* wx.request({
header: {
Authorization: 'Bearer xxx',
},
})
wx.request({
header: {
Authorization: 'Bearer xxx',
},
}) */
function Request() {
// 实例对象
this.header = {}
}
Request.prototype.get = function () {
// #1 每次发请求前调用 this.beforeRequest 并把 this 实例传递到此方法中
this.beforeRequest(this)
this._()
}
Request.prototype._ = function () {
wx.request({
// #3 后续通过 this.header 就能生效了呀
header: this.header,
success: () => {
this.afterRequest()
},
})
}
const $http = new Request()
// 希望这样写以后,后面每次请求都能携带 header
$http.beforeRequest = function (_this) {
// #2 此方法往 _this.header 上挂到内容
_this.header = {
Authorization: 'Bearer xxx',
}
}
$http.afterRequest = function () {
console.log(1)
}
$http.get()
$http.get()
</script>
2.小程序登录你是怎么做的?
1.通过wx.getUserProfile 获取登录接口需要的相关参数, 例如encryptedData(后端需要的) 2.通过wx.login 拿到code(登录凭证) 3.把上面两个api拿到的参数组装好调用后端接口 , 后端根据收到的信息返回token 4.前端拿到token后存储到vuex中 (注意 : 只有用uni-app开发才有vuex , 原生小程序并没有,只需要持久化到本地即可) , 并持久化到本地 5.后续有一些需要携带 token 的接口(例如支付接口),在请求头处进行统一携带即可。
3.小程序支付你是怎么做的?
1.调用后端接口生成订单编号 (请求头携带 token,需要传递收货地址、需要传递购物车中的信息) 2.预支付(根据订单编号调用接口拿到支付相关的参数)。 3.根据上面获取到的支付参数,调用 wx.requestPayment 发起微信支付。 4.再次根据订单编号调用接口查询支付状态,根据支付状态做相关的操作。 5. 如果支付成功,跳转到支付页面(包含了支付方式、金额、状态...)。
4.结算 , 登录支付之前
结算之前 : 先要进行判断 , 满足后才执行结算程序 1.是否勾选了一件商品 2. 是否选择了收货地址 3.是否登录 支付之前 : 1.把订单信息发送到服务器 , 服务器返回拿到订单编号 2.根据订单编号拿到 订单预支付对象 , 包含了订单支付相关的必要参数 3.调用api , 发起微信支付
5.做过小程序开发吗,说一下和网页开发的区别
1、运行环境不同 网页运行在浏览器环境中 小程序运行在微信环境中 2、api不同 小程序无法调用dom和bom的api,但是可以调用微信环境中提供的api.比如地理定位、扫码、支付等 3、开发模式不同 网页开发模式:浏览器+代码编辑器 小程序:申请小程序开发账号、安装小程序开发者工具、创建和配置小程序项目
13-- 移动端
1. 移动端 click 有 300ms 延迟
可以使用 fastclick.js 解决。
2. 1像素问题
1px 在 iPhone6 上占 2 个物理像素,因为 dpr(物理像素比)2,所以看起来有点粗,这并不是我们期望的,transform: scale(1, 0.5)。
3. IOS 设备表单元素其实自带一些样式的,需要手动清除。
input[type="text"] { -webkit-appearance: none; }
4. 在H5页面给字体设置了固定的尺寸15px,在andriod手机上横竖屏字体大小不变,但是在苹果手机上,横屏字体会变大
解决如下。
-webkit-text-size-adjust: 100%;
14-- 项目
1.说一说你再开发中遇到的问题, 怎么解决的?
- 1操作data中的数据,发现没有响应式
- 原因: 数组中有很多方法,有的会改变数组(例如pop push),有的不会改变数组(例如slice, filter)
- 解决方案:通过Vue.set(对象,属性,值)这种方式就可以达到,对象新添加的属性是响应式的
- 2.在created操作dom的时候,是报错的,获取不到dom,这个时候实例vue实例没有挂载
- 解决方案:Vue.nextTick(回调函数进行获取)
2.图片懒加载
背景 如果我们使页面包含的所有图片一次性加载完成,那用户体验很差。 这样做的好处,一是页面加载速度快(浏览器进度条和加载转圈很快就结束了,这样用户的体验也比较好),而是节省流量,因为不可能每一个用户会把页面从上到下滚动完 原理 1.存储图片的真实路径 , 真实路径绑定给以data开头的自定义属性data-url 2.设置img的默认src为一张1px*1px,很小很小的gif透明图片(所有的img都用这一张,只会发送一次请求),之所以需要是透明的,是需要透出通过background设置的背景图(一张loading.png,就是一个转圈圈的背景效果图) 3.需要一个滚动事件,判断元素是否在浏览器窗口,一旦进入视口才进行加载,当滚动加载的时候,就把这张透明的1px.gif图片替换为真正的url地址(也就是data-url里保存的值) 4.等到图片进入视口后,利用js提取data-url的真实图片地址赋值给src属性,就会去发送请求加载图片,真正实现了按需加载 三种方式 方法一:滚动监听+scrollTop+offsetTop+innerHeight
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
img {
background: url('./img/loading.gif') no-repeat center;
width: 250px;
height: 250px;
display: block;
}
</style>
</head>
<body>
<img src="./img/pixel.gif" data-url="./img/1.jpeg">
<img src="./img/pixel.gif" data-url="./img/2.jfif">
<img src="./img/pixel.gif" data-url="./img/3.jfif">
<img src="./img/pixel.gif" data-url="./img/4.jfif">
<img src="./img/pixel.gif" data-url="./img/5.jfif">
<img src="./img/pixel.gif" data-url="./img/6.webp">
<script>
let imgs = document.getElementsByTagName('img')
// 1. 一上来立即执行一次
fn()
// 2. 监听滚动事件
window.onscroll = lazyload(fn, true)
function fn() {
// 获取视口高度和内容的偏移量
let clietH = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
console.log(clietH, scrollTop);
for (let i = 0; i < imgs.length; i++) {
let x = scrollTop + clietH - imgs[i].offsetTop //当内容的偏移量+视口高度>图片距离内容顶部的偏移量时,说明图片在视口内
if (x > 0) {
imgs[i].src = imgs[i].getAttribute('data-url'); //从dataurl中取出真实的图片地址赋值给url
}
}
}
// 函数节流
function lazyload(fn, immediate) {
let timer = null
return function () {
let context = this;
if (!timer) {
timer = setTimeout(() => {
fn.apply(this)
timer = null
}, 200)
}
}
}
</script>
</body>
</html>
方法二:滚动监听+getBoundingClientRect()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
img {
background: url('./img/loading.gif') no-repeat center;
width: 250px;
height: 250px;
display: block;
}
</style>
</head>
<body>
<img src="./img/pixel.gif" data-url="./img/1.jpeg">
<img src="./img/pixel.gif" data-url="./img/2.jfif">
<img src="./img/pixel.gif" data-url="./img/3.jfif">
<img src="./img/pixel.gif" data-url="./img/4.jfif">
<img src="./img/pixel.gif" data-url="./img/5.jfif">
<img src="./img/pixel.gif" data-url="./img/6.webp">
<script>
let imgs = document.getElementsByTagName('img')
// 1. 一上来立即执行一次
fn()
// 2. 监听滚动事件
window.onscroll = lazyload(fn, true)
function fn() {
// 获取视口高度和内容的偏移量
let offsetHeight = window.innerHeight || document.documentElement.clientHeight
Array.from(imgs).forEach((item, index) => {
let oBounding = item.getBoundingClientRect() //返回一个矩形对象,包含上下左右的偏移值
console.log(index, oBounding.top, offsetHeight);
if (0 <= oBounding.top && oBounding.top <= offsetHeight) {
item.setAttribute('src', item.getAttribute('data-url'))
}
})
}
// 函数节流
function lazyload(fn, immediate) {
let timer = null
return function () {
let context = this;
if (!timer) {
timer = setTimeout(() => {
fn.apply(this)
timer = null
}, 200)
}
}
}
</script>
</body>
</html>
方法三-intersectionObserve() 优
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
img {
background: url('./img/loading.gif') no-repeat center;
width: 250px;
height: 250px;
display: block;
}
</style>
</head>
<body>
<img src="./img/pixel.gif" data-url="./img/1.jpeg">
<img src="./img/pixel.gif" data-url="./img/2.jfif">
<img src="./img/pixel.gif" data-url="./img/3.jfif">
<img src="./img/pixel.gif" data-url="./img/4.jfif">
<img src="./img/pixel.gif" data-url="./img/5.jfif">
<img src="./img/pixel.gif" data-url="./img/6.webp">
<script>
let imgs = document.getElementsByTagName('img')
// 1. 一上来立即执行一次
let io = new IntersectionObserver(function (entires) {
//图片进入视口时就执行回调
entires.forEach(item => {
// 获取目标元素
let oImg = item.target
// console.log(item);
// 当图片进入视口的时候,就赋值图片的真实地址
if (item.intersectionRatio > 0 && item.intersectionRatio <= 1) {
oImg.setAttribute('src', oImg.getAttribute('data-url'))
}
})
})
Array.from(imgs).forEach(element => {
io.observe(element) //给每一个图片设置监听
});
</script>
</body>
</html>
3.说一下路由权限的处理流程?
首先权限本质是后端控制的 , 每个用户登陆成功之后 , 后端会返回当前用户的标识 , 以数组的方式 前端拿到这个标识后 , 去筛选出有权限的路由 筛选出来后呢通过addrouters/addroute 添加到了路由实例(router) , 一旦添加到了路由实例 , 当前用户就有了访问某个页面的权限 , 这是路由级别的 按钮级别的权限只需要封装一个全局的一个方法或者是自定义指令 , 这个方法只做一件事情 , 接收一个标识 , 内部判断一下这个标识在不在后端返回的功能数组里面, 在的话就返回true , 不在就返回false , 接下来只需要在做按钮控制的地方, 调用一下这个方法, 传过去当前功能标识, 根据这个方法返回的是true还是false , 对这个按钮做一个禁用或者启用, 显示或隐藏的操作 第二种方式 封装全局自定义指令 注册一个全局自定义指令 , 里面用inserted钩子(被绑定元素插入父节点时调用) , 传入el , 和bianding两个对象 再
// 注册一个全局自定义指令 `v-allow`
Vue.directive('allow', {
inserted: function(el, binding) {
// 从vuex中取出points,
const points = store.state.user.userInfo.roles.points
// 如果points有binding.value则显示
if (points.includes(binding.value)) {
// console.log('判断这个元素是否会显示', el, binding.value)
} else {
el.parentNode.removeChild(el)
// el.style.display = 'none'
}
}
})
<el-button
+ v-allow="'import_employee'"
type="warning"
size="small"
@click="$router.push('/import')"
>导入excel</el-button>
// 或者
function fn(tag) {
return [后端返回的, 'DEL_USER', 'b', 'c'].includes(tag)
}
<button v-if="fn('DEL_USER')">删除用户信息</button>
4.图片预览是怎么做的?
**第一种 : 使用 FileReader ** FileReader.prototype.readAsDataURL()方法把图片文件转成base64编码 , 然后把base64编码替换到预览图片的src属性即可
<input type="file" hidden id="oInput" />
<img id="oImg" alt="" />
<button id="oBtn">上传</button>
<script>
oBtn.onclick = function () {
oInput.click()
}
oInput.onchange = function (e) {
const f = e.target.files[0]
const reader = new FileReader()
// 把文件信息 f 读取为 base64 格式的
reader.readAsDataURL(f)
console.log(reader) // 读取后把base64保存到result中
reader.onload = function () {
//console.log(this.result) // base64格式地址
oImg.src = this.result
oImg.width = 600
}
}
</script>
第二种 : 使用URL.createObjectURL URL.createObjectURL是将图片转换成blob的格式。
<input type="file" hidden id="oInput" />
<img id="oImg" alt="">
<button id="oBtn">上传</button>
<script>
oBtn.onclick = function() {
oInput.click()
}
oInput.onchange = function(e) {
const f = e.target.files[0]
// blob url => 临时的图片地址
const blobUrl = URL.createObjectURL(f)
oImg.src = blobUrl
}
</script>
5.登录的流程
前端要收集数据 => 校验数据 => 校验通过后把数据提交到后端 => 后端根据接收到的用户信息生成 token 并返回到前端 => 前端拿到 token 之后存储到 Vuex(目的:是为了方便和响应式)和本地(目的:持久化)。 如何把本地的数据和 Vuex 保持同步?需要在初始化 Vuex 数据的时候从本地去一下就行了。
6.用户没有登录该如何不让他访问内页?
我会在全局路由前置导航守卫(beforeEach)内部做一些处理,如果有 token 就直接放行,如果没有 token,就看一下访问的页面(to.path)在不在白名单,如果在也执行 next 放行,否则拦截到登录页。
7.token 过期你是怎么处理的?
在响应拦截器内部做的处理,如果说后端返回的是 401,就表示 token 过期了,此时我会清除过期的用户信息并跳转到登录页。 token 一般一个小时过期?目的是为了安全。但是用户体验不好,因为只要用户使用系统超过了一个小时,就会被你上面的操作拦截到登录页,怎么办呢?可以用 refresh_token 换取新的 token,然后把错误的请求重新发送到后端。
8.axios封装
**怎么封装的 : **一般我们会在utils文件夹新建一个request.js文件, 创建axios实例 , 配置baseURL(一般通过process.env.VUE_APP_BASE_URL读取配置文件中的环境变量) , timeout , transfromResponse, 请求拦截器 (干了啥 : 统一携带token , token过期的主动处理 , 开启全局的进度条) , 响应拦截器 (响应数据的脱壳, token过期的被动处理(401) , 错误状态码的统一处理) 怎么使用的 : 封装好axios后 , 还会在api文件夹封装接口为请求函数 , 在组件中直接调用api文件夹中请求函数
9.和 Vue 相关的性能优化的手段都有哪些?
一般,我在写代码的时候就会可以考虑这些东西/优化。
- v-for 加 key(VNode Diff 的时候快速找到变化的那一个元素,按需更新)。
- v-if 和 v-for 不要放一行,(可以通过计算属性先把需要循环的计算出来)。
- 动态组件(),可以简化 if else 的判断。
- 异步组件。
// 平常写法,启动项目后,无论 Question 组件看到没看到,其实代码已经被打包过来了,性能不好
import Question from './Question.vue'
export default {
name: 'App',
component: {
Question
}
}
// 异步组件,只有用到/看到这个组件的时候才会去加载对应的代码
export default {
name: 'App',
component: {
// 通过函数,返回 import 引入组件的形式
Question: () => import('./Question.vue')
}
}
- 路由懒加载,用到这个路由的时候才加载此路由对应的组件的代码。
- keep-alive,组件缓存的,一般用 keep-alive 包裹路由的出口或动态组件,可以通过 include 指定组件名来控制要缓存谁,和它相关的两个钩子是什么。
- 方法和计算属性区分使用场景,优先计算属性(缓存)。
- v-show 和 v-if 区分使用场景,例如切换频率用 v-show。
10.cdn加速
假设通过CDN加速的域名为www.a.com,接入CDN网络,开始使用加速服务后,当终端用户(北京)发起HTTP请求时,处理流程如下:
- 当终端用户(北京)向www.a.com下的指定资源发起请求时,首先向LDNS(本地DNS)发起域名解析请求。
- LDNS检查缓存中是否有www.a.com的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向授权DNS查询。
- 当授权DNS解析www.a.com时,返回域名CNAME www.a.tbcdn.com对应IP地址。
- 域名解析请求发送至阿里云DNS调度系统,并为请求分配最佳节点IP地址。
- LDNS获取DNS返回的解析IP地址。
- 用户获取解析IP地址。
- 用户向获取的IP地址发起对该资源的访问请求。
- 如果该IP地址对应的节点已缓存该资源,则会将数据直接返回给用户,例如,图中步骤7和8,请求结束。
- 如果该IP地址对应的节点未缓存该资源,则节点向源站发起对该资源的请求。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,例如,图中的北京节点,并返回给用户,请求结束。
从这个例子可以了解到: (1)CDN的加速资源是跟域名绑定的。 (2)通过域名访问资源,首先是通过DNS分查找离用户最近的CDN节点(边缘服务器)的IP (3)通过IP访问实际资源时,如果CDN上并没有缓存资源,则会到源站请求资源,并缓存到CDN节点上,这样,用户下一次访问时,该CDN节点就会有对应资源的缓存了
11.文档碎片
1.假如有 10000 个元素需要添加到页面上,你觉得怎么操作性能最好(考察文档碎片) 当时我们给一个后管系统维护的时候,客户要新增“上传文件”需求,想要存放一些公司内部 学习的文件。我们刚开始用了elementui里面的上传文件组件。但在测试的时候,发现文件较 大时,会上传的很慢,考虑到内部学习文件一般会有很多大文件 用Fargment 把所有要构造的节点都放在文档片段中执行 , 这样可以不影响文档树 , 也就不会造成页面渲染. 当节点都构造完成后 , 再将文档片段对象添加到页面当中, 这时所有的节点都会一次性渲染出来 , 这样就能减少游览器负担 , 提升页面渲染速度
<ul id="container">
</ul>
// 优化前
<script>
var container = document.getElementById('container')
for(let i = 0; i < 10000; i++){
let li = document.createElement('li')
li.innerHTML = 'hello world'
container.appendChild(li);
}
</script>
// 优化后
<script>
var container = document.getElementById('container')
var fragment = document.createDocumentFragment()
for(let i = 0; i < 10000; i++){
let li = document.createElement('li')
li.innerHTML = 'hello world'
fragment.appendChild(li)
}
container.appendChild(fragment);
</script>
12.把平铺的数组结构转成树形结构
[
{id:"01", pid:"", "name":"老王" },
{id:"02", pid:"01", "name":"小张" }
]
上面的结构说明: 老王是小张的上级
*/
export function tranListToTreeData(list) {
// 1. 定义两个变量
const treeList = [], map = {}
// 2. 建立一个映射关系,并给每个元素补充children属性.
// 映射关系: 目的是让我们能通过id快速找到对应的元素
// 补充children:让后边的计算更方便
list.forEach(item => {
if (!item.children) {
item.children = []
}
map[item.id] = item
})
// 循环
list.forEach(item => {
// 对于每一个元素来说,先找它的上级
// 如果能找到,说明它有上级,则要把它添加到上级的children中去
// 如果找不到,说明它没有上级,直接添加到 treeList
const parent = map[item.pid]
// 如果存在上级则表示item不是最顶层的数据
if (parent) {
parent.children.push(item)
} else {
// 如果不存在上级 则是顶层数据,直接添加
treeList.push(item)
}
})
// 返回
return treeList
}
13.长列表优化(虚拟列表优化)
1.不把长列表数据一次性全部直接显示在页面上; 2.截取长列表一部分数据用来填充屏幕容器区域; 3.长列表数据不可视部分使用使用空白占位填充; 4.监听滚动事件根据滚动位置动态改变可视列表;scrolltop 5.监听滚动事件根据滚动位置动态改变空白填充。 我们也把上面的优化行为简称为「虚拟滚动」
15-- 杂物
1.sass和less的区别
关于变量在Less和Sass中的唯一区别就是Less用@,Sass用$。 差不多 , 里面有些很小的区别 , 项目不是很大的话体现不出来 less比css比多出好些功能(如变量、嵌套、运算,混入(Mixin)、继承、颜色处理,函数等),更容易阅读 less混入
.hoverShadow () {
transition: all 0.5s;
&:hover {
transform: translate3d(0, -3px, 0);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
}
}
<style lang="less" scoped>
// 必须导入variables.less
@import "@/assets/styles/variables.less";
@import "@/assets/styles/mixins.less";
div {
background-color: @warnColor;
.hoverShadow();
}
</style>
在vite.config.ts 统一注入全局变量 webpack也可以
2.路由缓存问题
当路由地址的切换匹配的是同一个 path 时,Vue 出于性能的考虑,对应的路由组件会被复用。 也就意味着,即便路由参数每次发生了变化,路由组件中相应的生命周期钩子也就只会被触发 1 次。 例如当在 setup 中需要根据路由参数的变化发请求时,会发现拿到的永远是最初的旧数据。 解决方案
- 给路由出口的地方加 key。
<router-view :key="$route.fullPath" />
- 通过 watch 监听 route 的变化,然后重新获取数据。
watch(
() => route.params.id,
(newId) => {
getDetail(newId)
}
)
- onBeforeRouteUpdate 中拿到最新的路由参数重新发请求。
onBeforeRouteUpdate((to) => {
getDetail(to.params.id as string)
})
3.函数传参的差异性
简单数据类型传递的是值的拷贝,函数内部对参数的修改不会影响外部; 复杂数据类型传递的是引用地址,函数内部对参数内容的修改会影响外部,对参数引用的修改不会影响外部。 简单数据类型传参
let username = 'qsp'
function foo(username) {
// 简单数据类型传参传递的是值的拷贝,把这个值的拷贝给了 username 局部变量
// 所以对这个 username 局部变量的修改当然不会影响外部的
username = 'zsf'
}
foo(username)
console.log(username) // 'qsp'
复杂数据类型传参的 2 种处理情况 修改内容 引用地址没变 所以外部也会受影响
const obj = {
name: 'ifer',
}
function foo(obj) {
// 赋值数据类型传参传递的是引用地址,把这个引用地址拷贝给了 obj 局部变量,而这个 obj 引用地址指向的还是曾经的那个空间
// 所以对这个 obj 局部变量【内容的修改】当然会影响外部的
obj.name = 'elser'
}
foo(obj)
console.log(obj) // 'elser'
修改引用地址
let obj = {
name: 'ifer',
}
function foo(obj) {
// 赋值数据类型传参传递的是引用地址,把这个引用地址拷贝给了 obj 局部变量,而这个 obj 引用地址指向的还是曾经的那个空间
// 所以对这个 obj 局部变量【引用的修改】当然不会影响外部的
obj = {
name: 'elser',
}
}
foo(obj)
console.log(obj) // 'ifer'
4.说一下你对 Node 的了解
Node.js...它既是开发平台,也是运行环境,也是个新的语言...它本身是基于google的 javascriptv8引擎开发的,因此在编写基于它的代码的时候使用javascript语言 他是一个javascript运行环境,依赖于ChromeV8引擎进行代码解释 特征:单线程、事件驱动、非阻塞I/O,轻量,可伸缩,适于实时数据交互应用 单进程,单线程(一个应用程序对应一个进程,一个进程下面会有多个线程,每个线程用 于处理任务..) Node无法直接渲染静态页面,提供静态服务 Node没有根目录的概念 Node必须通过路由程序指定文件才能渲染文件 Node比其他服务端性能更好,速度更快
16-- 人事
17.生疏面试题
Vue双向绑定的原理
Vue 数据双向绑定原理是通过 数据劫持 + 发布者-订阅者模式 的方式来实现的,首先是通过 ES5 提供的 Object.defineProperty() 方法来劫持(监听)各属性的 getter、setter,并在当监听的属性发生变动时通知订阅者,是否需要更新,若更新就会执行对应的更新函数。
什么是数据劫持
数据劫持比较好理解,通常我们利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。
发布者模式 / 订阅者模式
在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
这里很明显了,区别就在于,不同于观察者和被观察者,发布者和订阅者是互相不知道对方的存在的,发布者只需要把消息发送到订阅器里面,订阅者只管接受自己需要订阅的内容
Object.defineProperty
Object.defineProperty( obj, prop, descriptor )
三个参数:
obj 要定义的对象
prop 要定义或修改的属性名称或 Symbol
descriptor 要定义或修改的属性描述符
属性描述符
get
属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
默认为 [**undefined**]
set
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 [**undefined**]
v-model在vue2和vue3中的区别
v-model十个语法糖,它是通过自定义函数@input和自定义属性value进行组件之间的传值, 在vue2中v-model只能使用一次,若想使用多次,需要使用.sync修饰符通过$emit("update:value",@input)来实现 但是在vue3中v-model可以进行多次传值, 写法上也有所差别 父组件绑定v-model:name="name" 子组件利用props接收modelValue,
.sync:
1.父组件 :my-prop-name.sync 子组件@update:my-prop-name 的模式来替代事件触发,实现父子组件间的双向绑定。
2.一个组件可以多个属性用.sync修饰符,可以同时"双向绑定多个“prop”
3..sync针对更多的是各种各样的状态,是状态的互相传递,是status,是一种update操作。
函数式组件
一般的组件是声明式组件,是不受控制的 函数式组件 是命令式组件 更加灵活
vue暴露的api h: 把一个组件变成一个VNode(虚拟dom) render : 将VNode渲染成一个真实Dom
函数式组件使用流程
- 把已经写好的组件通过 h 函数变成 VNode
- 准备一个渲染挂载的节点
- 调用render api 把VNode 挂载到准备好的节点上 完成渲染
脚手架配置流程
第一个版本 V1
第一个版本的功能比较简单,大致为:
- 用户输入命令,准备创建项目。
- 脚手架解析用户命令,并弹出交互语句,询问用户创建项目需要哪些功能。
- 用户选择自己需要的功能。
- 脚手架根据用户的选择创建
package.json文件,并添加对应的依赖项。 - 脚手架根据用户的选择渲染项目模板,生成文件(例如
index.html、main.js、App.vue等文件)。 - 执行
npm install命令安装依赖。
第二个版本 v2
第二个版本在 v1 的基础上添加了一些辅助功能:
- 创建项目时判断该项目是否已存在,支持覆盖和合并创建。
- 选择功能时提供默认配置和手动选择两种模式。
- 如果用户的环境同时存在 yarn 和 npm,则会提示用户要使用哪个包管理器。
- 如果 npm 的默认源速度比较慢,则提示用户是否要切换到淘宝源。
- 如果用户是手动选择功能,在结束后会询问用户是否要将这次的选择保存为默认配置。
webpack优化
构建时间优化
thread-loader
多进程打包,可以大大提高构建的速度,使用方法是将thread-loader放在比较费时间的loader之前,比如babel-loader
由于启动项目和打包项目都需要加速,所以配置在
webpack.base.js
开启热更新
比如你修改了项目中某一个文件,会导致整个项目刷新,这非常耗时间。如果只刷新修改的这个模块,其他保持原状,那将大大提高修改代码的重新构建时间
只用于开发中,所以配置在
webpack.dev.js
exclude & include
- exclude`:不需要处理的文件
include:需要处理的文件
合理设置这两个属性,可以大大提高构建速度
在
webpack.base.js中配置
提升webpack版本
webpack版本越新,打包的效果肯定更好
打包体积优化
构建区分环境
区分环境去构建是非常重要的,我们要明确知道,开发环境时我们需要哪些配置,不需要哪些配置;而最终打包生产环境时又需要哪些配置,不需要哪些配置:
开发环境:去除代码压缩、gzip、体积分析等优化的配置,大大提高构建速度生产环境:需要代码压缩、gzip、体积分析等优化的配置,大大降低最终项目打包体积
CSS代码压缩JS代码压缩
CSS代码压缩使用css-minimizer-webpack-plugin,效果包括压缩、去重
JS代码压缩使用terser-webpack-plugin,实现打包后JS代码的压缩
用户体验优化
路由懒加载
如果不进行路由懒加载,当用户进入页面会请求所有的ajax , 导致页面加载速度慢,用户体验差.
Gzip
开启Gzip后,大大提高用户的页面加载速度,因为gzip的体积比原文件小很多,当然需要后端的配合
小图片转base64
对于一些小图片,可以转base64,这样可以减少用户的http网络请求次数,提高用户的体验。
1、微信小程序有几个文件
WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件WXSS (WeiXin Style Sheets)是一套样式语言,用于描述WXML的组件样式js逻辑处理,网络请求json小程序设置,如页面注册,页面标题及tabBar
2、微信小程序怎样跟事件传值
给 HTML 元素添加
data-*属性来传递我们需要的值,然后通过e.currentTarget.dataset或onload的param参数获取。但data -名称不能有大写字母和不可以存放对象
3、小程序的 wxss 和 css 有哪些不一样的地方?
wxss的图片引入需使用外链地址没有 Body;样式可直接使用import导入
4、小程序关联微信公众号如何确定用户的唯一性
使用 wx.getUserInfo方法 withCredentials 为 true 时 可获取 <encryptedData,里面有 union_id。后端需要进行对称解密
5、微信小程序与vue区别
- 生命周期不一样,微信小程序生命周期比较简单
- 数据绑定也不同,微信小程序数据绑定需要使用
{{}},vue直接:就可以 显示与隐藏元素,vue中,使用v-if和v-show - 控制元素的显示和隐藏,小程序中,使用
wx-if和hidden控制元素的显示和隐藏 - 事件处理不同,小程序中,全用
bindtap(bind+event),或者catchtap(catch+event)绑定事件,vue:使用v-on:event绑定事件,或者使用@event绑定事件 - 数据双向绑定也不也不一样在
vue中,只需要再表单元素上加上v-model,然后再绑定data中对应的一个值,当表单元素内容发生变化时,data中对应的值也会相应改变,这是vue非常nice的一点。微信小程序必须获取到表单元素,改变的值,然后再把值赋给一个data中声明的变量。
6、小程序的双向绑定和vue哪里不一样
小程序直接 this.data属性是不可以同步到视图的,必须调用:
this.setData({
// 这里设置
})
7、简述微信小程序原理
- 微信小程序采用
JavaScript、WXML、WXSS三种技术进行开发,本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口 - 微信的架构,是数据驱动的架构模式,它的
UI和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现 - 小程序分为两个部分
webview和appService。其中webview主要用来展现UI ,appService有来处理业务逻辑、数据及接口调用。它们在两个进程中运行,通过系统层JSBridge实现通信,实现 UI 的渲染、事件的处理
8、小程序的生命周期函数
onLoad页面加载时触发。一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径中的参数onShow()页面显示/切入前台时触发onReady()页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互onHide()页面隐藏/切入后台时触发。 如navigateTo或底部tab切换到其他页面,小程序切入后台等onUnload()页面卸载时触发。如redirectTo 或 navigateBack到其他页面时
9、哪些方法可以用来提高微信小程序的应用速度
1、提高页面加载速度
2、用户行为预测
3、减少默认 data 的大小
4、组件化方案
10、微信小程序的优劣势
优势
即用即走,不用安装,省流量,省安装时间,不占用桌面 依托微信流量,天生推广传播优势 开发成本比 App 低
缺点
用户留存,即用即走是优势,也存在一些问题 入口相对传统 App 要深很多 限制较多,页面大小不能超过2M。不能打开超过10个层级的页面
11、怎么解决小程序的异步请求问题
- 小程序支持大部分
ES6语法 - 在返回成功的回调里面处理逻辑
Promise异步
12、如何实现下拉刷新
首先在全局 config 中的 window配置 enablePullDownRefresh ,在 Page 中定义onPullDownRefresh 钩子函数,到达下拉刷新条件后,该钩子函数执行,发起请求方法 请求返回后,调用wx.stopPullDownRefresh停止下拉刷新
13、bindtap和catchtap的区别是什么
相同点:首先他们都是作为点击事件函数,就是点击时触发。在这个作用上他们是一样的,可以不做区分
不同点:他们的不同点主要是bindtap是不会阻止冒泡事件的,catchtap是阻值冒泡的
14、小程序页面间有哪些传递数据的方法
1、使用全局变量实现数据传递。在 app.js 文件中定义全局变量 globalData, 将需要存储的信息存放在里面
2、使用 wx.navigateTo与 wx.redirectTo 的时候,可以将部分数据放在 url 里面,并在新页面onLoad的时候初始化
3、使用本地缓存Storage 相关
15、小程序wxml与标准的html的异同?
相同:
- 都是用来描述页面的结构;
- 都由标签、属性等构成;
不同:
- 标签名字不一样,且小程序标签更少,单一标签更多;
- 多了一些
wx:if这样的属性以及{{ }}这样的表达式 - WXML仅能在微信小程序开发者工具中预览,而
HTML可以在浏览器内预览; - 组件封装不同,
WXML对组件进行了重新封装, - 小程序运行在
JS Core内,没有DOM树和window对象,小程序中无法使用window对象和document对象。
16、小程序简单介绍下三种事件对象的属性列表?
基础事件(BaseEvent)
type:事件类型timeStamp:事件生成时的时间戳target:触发事件的组件的属性值集合currentTarget:当前组件的一些属性集合
自定义事件(CustomEvent)
detail
触摸事件(TouchEvent)
toucheschangedTouches
17、小程序对wx:if 和 hidden使用的理解?
wx:if有更高的切换消耗。hidden有更高的初始渲染消耗。- 因此,如果需要频繁切换的情景下,用
hidden更好,如果在运行时条件不大可能改变则wx:if较好。
18、微信小程序与H5的区别?
- 运行环境的不同
传统的HTML5的运行环境是浏览器,包括webview,而微信小程序的运行环境并非完整的浏览器,是微信开发团队基于浏览器内核完全重构的一个内置解析器,针对小程序专门做了优化,配合自己定义的开发语言标准,提升了小程序的性能。
- 开发成本的不同
只在微信中运行,所以不用再去顾虑浏览器兼容性,不用担心生产环境中出现不可预料的奇妙BUG
- 获取系统级权限的不同
19、app.json 是对当前小程序的全局配置,讲述三个配置各个项的含义?
- ``pages字段` —— 用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目录。
window字段—— 小程序所有页面的顶部背景颜色,文字颜色定义在这里的tab字段—小程序全局顶部或底部tab
20、小程序onPageScroll方法的使用注意什么?
由于此方法调用频繁,不需要时,可以去掉,不要保留空方法,并且使用onPageScroll时,尽量避免使用setData(),尽量减少setData()的使用频次。
21、小程序视图渲染结束回调?
使用setData(data, callback),在callback回调方法中添加后续操作代码
22、小程序同步API和异步API使用时注意事项?
wx.setStorageSync是以Sync结尾的API为同步API,使用时使用try-catch来查看异常,如果判定API为异步,可以在其回调方法success、fail、complete中进行下一步操作。
23、简述下 wx.navigateTo(), wx.redirectTo(), wx.switchTab(), wx.navigateBack(), wx.reLaunch()的区别
wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到tabbar页面wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到tabbar页面wx.switchTab():跳转到abBar页面,并关闭其他所有非tabBar页面wx.navigateBack():关闭当前页面,返回上一页面或多级页面。可通过getCurrentPages()获取当前的页面栈,决定需要返回几层wx.reLaunch():关闭所有页面,打开到应用内的某个页面
24、如何封装微信小程序的数据请求的?
1、将所有的接口放在统一的js文件中并导出。
2、在app.js中创建封装请求数据的方法。
3、在子页面中调用封装的方法请求数据。
25、小程序与原生App哪个好?
小程序除了拥有公众号的低开发成本、低获客成本低以及无需下载等优势,在服务请求延时与用户使用体验是都得到了较大幅度 的提升,使得其能够承载跟复杂的服务功能以及使用户获得更好的用户体验。
26、webview中的页面怎么跳回小程序中?
首先要引入最新版的jweixin-x.x.x.js,然后
wx.miniProgram.navigateTo({
url: '/pages/login/login'+'$params'
})
27、小程序关联微信公众号如何确定用户的唯一性?
使用wx.getUserInfo方法withCredentials为 true时 可获取encryptedData,里面有union_id。后端需要进行对称解密。
28、小程序调用后台接口遇到哪些问题?
1.数据的大小有限制,超过范围会直接导致整个小程序崩溃,除非重启小程序;
2.小程序不可以直接渲染文章内容页这类型的html文本内容,若需显示要借住插件,但插件渲染会导致页面加载变慢,所以最好在后台对文章内容的html进行过滤,后台直接处理批量替换p标签div标签为view标签,然后其它的标签让插件来做,减轻前端的时间。
29、webview的页面怎么跳转到小程序导航的页面?
答:小程序导航的页面可以通过switchTab,但默认情况是不会重新加载数据的。若需加载新数据,则在success属性中加入以下代码即可:
success: function (e) {
var page = getCurrentPages().pop();
if (page == undefined || page == null) return;
page.onLoad();
}
webview的页面,则通过
wx.miniProgram.switchTab({
url: '/pages/index/index'
})
30、微信小程序的优劣势?
优势:
1、无需下载,通过搜索和扫一扫就可以打开。
2、良好的用户体验:打开速度快。
3、开发成本要比App要低。
4、安卓上可以添加到桌面,与原生App差不多。
5、为用户提供良好的安全保障。小程序的发布,微信拥有一套严格的审查流程,不能通过审查的小程序是无法发布到线上的。
劣势:
1、限制较多。页面大小不能超过1M。不能打开超过5个层级的页面。
2、样式单一。小程序的部分组件已经是成型的了,样式不可以修改。例如:幻灯片、导航。
3、推广面窄,不能分享朋友圈,只能通过分享给朋友,附近小程序推广。其中附近小程序也受到微信的限制。