一、盒模型
标准盒模型:box-sizing: content-box。
外边距+边框+内边距+内容区。
IE盒模型,怪异盒模型:box-sizing: border-box。
外边距+内容区(边框+内边距+内容区)。
二、CSS特性
继承性: 父元素的字体大小,字体颜色,行高,display:none...也会出现在子元素上。
优先级: 谁的权重高就显示谁的样式。
!important 内联 ID class 属性 伪类 元素 继承 通配 (vue中有深度选择器 deep)
层叠性: 多个CSS声明应用在同一个元素时,权重相加,决定最终样式
预处理器 sass less 预处理器 变量 函数 混入,全局颜色,css原子化
1、变量的声明
声明变量的时候,变量名前面要加两根连词线 --。使用变量用var()函数。
<style>
body{
--foo:red;
}
p{
color: var(foo);
}
</style>
2、变量的继承
<style>
body{
--foo: red;
}
p{
--foo: initial;
}
</style>
3、js控制css变量
通过js的dom节点对象,可以获取和修改当前节点的css变量值。
获取css变量:getPropertyValue('--name')
设置css变量:setProperty('--name', value)
<div id="header">hello world</div>
<style>
#header {
--bgColor: #ffffff;
}
</style>
<script>
let dom = document.getElementById("header");
// 获取css变量
let color = dom.getPropertyValue("--bgColor").trim();
// 设置css变量
dom.setProperty("--bgColor", '#f34e25');
</script>
css函数
1、var变量函数,作用:引用自定义的变量值。
第一个参数:引用的变量名称,第二个参数:回退值,表示如果第一个参数未定义,
使用回退值代替。
body {
// 定义变量bgColor
--bgColor: #ffffff;
// 背景色取变量--bgColor的值,如果未定义,则取red
background-color: var(--bgColor, red);
}
2、calc 主要通过简单的数学计算,进行一些单位的计算,表达式支持加、减、乘、除。
3、min(val1... valN):接受任意数量的参数,每个参数也可以是表达式。取出参数中最小的值。
max(val1...valN):同上,取最大的值。
4、:where伪类选择器函数
作用::where()函数实际是一个高级的伪类选择器,他的作用是将一系列的选择器列表,
都应用相同的样式,简化多个选择器样式编码的流程。
:where(class1,...classN) { css样式 }- 接受n个参数,参数值为选择器(任意的css选择器)
// 传统方式
.main span {
font-size: 12px;
}
.line span {
font-size: 12px;
}
// 如果有大量样式相同,选择:where函数选择器(终极版)
:where(.main, .line) span {
font-size: 12px;
}
5、:is伪类选择器函数
作用:与:where()函数选择器用法一致。
优先级不同。:where()函数选择器的优先级总是为0(即使他的样式代码在最下面)
而:is()函数选择器的优先级取决于他的参数选择器列表中最高的选择器
.main span {
font-size: 12px;
}
:is(.main) span {
font-size: 13px;
}
span {
font-size: 14px;
}
:where(.main) span {
font-size: 15px;
}
:where()优先级最低;其次是span选择器;第一个选择器和:is()函数选择器的优先级都是.main span,
因此他们优先级一致,但是因为is()函数在下面,因此font-size为13px。
sass
1、使用变量 $
$aaa: 16px;
.fontSize {
font-size: $aaa;
}
2、@extend 继承
.success{
color:green;
}
.msg{
@extend .success;
color: #555555;
}
3、@if
当 @if 的表达式返回值不是 false 或者 null 时,条件成立,输出 {} 内的代码:
p{
$num : 3;
@if $num == 1 {
color:red;
}@else{
border:red;
}
}
4、@for
指令可以在限制的范围内重复输出格式,每次按要求(变量的值)对输出结果做出变动。
@for $i from 1 through 3 {
.item-#{$i} {
width: 2em * $i;
}
}
上面等于
.item-1 { width: 2em; }
.item-2 { width: 4em; }
.item-3 { width: 6em; }
三、隐藏元素的方法
display:none;元素在页面上消失,不占据空间
visibility:hidden; 让元素消失,占据空间位置,一种不可见的状态
opacity:0; 设置了元素的透明度为0,元素不可见,占据空间位置。父元素设置的透明度,子元素也就被设置了对应的透明度
position:absolute; 使用定位,让元素移到页面之外
四、px、rpx、vw、vh、em、rem
px:绝对长度、固定单位,无法根据页面的大小而改变
rpx: 小程序独自有的
vw:viewpoint width,视窗宽度,1vw等于视窗宽度的1%。
vh:viewpoint height,视窗高度,1vh等于视窗高度的1%。
em和rem:相对长度,适用于响应式布局(em的大小相对于父元素大小而改变,rem的大小相对于根元素的大小而改变)
据不同屏幕的宽度,以相同的比例动态修改html的font-size适配,并将px替换成rem,它可以很好的根据根元素的字体大小来进行变化,从而达到各种屏幕基本一直的效果体验
配置rem
window.onload = () => { document.documentElement.style.fontSize = window.innerWidth / 375 * 100 + 'px'; }
window.onresize = () => { document.documentElement.style.fontSize = window.innerWidth / 375 * 100 + 'px'; }
五、重排重绘
重排(回流):布局引擎会根据所有的样式计算出盒模型在页面上的位置和大小,对DOM的大小、位置进行修改后,浏览器需要重新计算元素的这些几何属性,就叫重排。减少重排可以增强浏览器效率。
重绘:计算好盒模型的位置、大小和其他一些属性之后,浏览器就会根据每个盒模型的特性进行绘制,对DOM的样式进行修改,比如color、透明度和background-color,浏览器不需要重新计算几何属性的时候,直接绘制了该元素的新样式,那么这里就只触发了重绘
使用虚拟 DOM就减少了重排和重绘
六、元素水平垂直居中
1、定位 + margin
2、定位 + transform
3、flex布局
4、gird布局
5、table布局
七、响应式开发(移动端适配)
1、使用rem、em、vw、vh、百分比、flex、@media媒体查询等布局
2、监听是手机端打开还是PC端打开,配置两套路由或样式
3、监听页面窗口大小,配置多套样式
八、JS三部分
1、ECMAScript:JS的核心内容,描述了语言的基础语法。
2、文档对象模型(DOM):DOM把整个HTML页面规划为元素构成的文档。DOM 文档对象模型,就是一个API,可以通过操作这些API在页面上进行绘制节点,设置节点的属性和内容。
例如:document.getElementById('app');document.createElement('hr');app.appendChild(hr);app.setAttribute('class', 'aaa');
3、浏览器对象模型(BOM):对浏览器窗口进行访问和操作。BOM 浏览器对象模型,可以用来操作浏览器的打开、关闭、重加载等。
例如:location 当前页面的地址;history 浏览器浏览过的历史;navigator 浏览器当前的用户设备信息(可以看到浏览器内核等等);window 浏览器的高度、宽度。浏览器的滚动和打印功能......
九、JS部分内置对象
String、Boolean、Number、Array、Object、Function、Math、Date、RegExp...
Math:abs() 函数返回绝对值 sqrt() 接受一个非负正数,返回平方根 max() 最大值 min() 最小值......
Data:new Data() 获取当前时间戳 getYear() 获取年份 ......
十、数组方法
1、改变原数组:push 最后面加一个、pop 最后面删一个、unshift 最前面加一个、shift 最前面删一个、sort 排序、splice 可以删除替换增加元素
2、不改变原数组:reverse 反转顺序、concat 合并数组、join 将元素拼接成字符串、isArray 判断一个值是否为数组、findIndex 返回满足指定条件的第一个元素的索引,如果没有找到则返回 -1。
map:创建一个新数组,其元素是原数组经过指定函数处理后的结果。
const numbers = [1, 2, 3, 4, 5];
const squares = numbers.map(num => num * num);
console.log(squares); // 输出: [1, 4, 9, 16, 25]
filter:创建一个新数组,其中包含原数组中满足指定条件的所有元素。
let arr = [1, 2, 3, 4, 5];
let evenNumbers = arr.filter(item => item % 2 === 0);
console.log(evenNumbers); // 输出 [2, 4]
every:判断数组中所有元素是否都满足指定条件,如果是则返回 true,否则返回 false。
let arr = [2, 4, 6, 8, 10];
let allEven = arr.every(item => item % 2 === 0);
console.log(allEven); // 输出 true
some:判断数组中是否存在满足指定条件的元素,如果有则返回 true,否则返回 false。
let arr = [1, 3, 5, 7, 8];
let hasEven = arr.some(item => item % 2 === 0);
console.log(hasEven); // 输出 true
reduce:将数组中的元素通过指定函数进行累积计算,返回一个最终的结果。
let arr = [1, 2, 3, 4, 5];
let sum = arr.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 输出 15
为什么调用接口时,用 push 而不用 concat?
因为 push 是改变原数组,而 concat 是生成新数组。
for 数组、forEach 数组、for of 数组、for in 数组和对象都能遍历
let arr = [ "a", "b", "c", "d", "e" ];
let obj = { a: 1, b: 2, c: 3 }
for(let i = 0; i < arr.length; i++){
console.log(i, arr[i]) // 下标 值
}
arr.forEach((item, index) => {
console.log(item, index) // 值 下标
})
for(let i of arr){
console.log(i) //值
}
for(let i in arr){
console.log(i, arr[i]) // 下标 值
}
for(let i in obj){
console.log(i, obj[i]) // 键 值
}
for、forEach差别:forEach 无法使用 break、return、continue 进行中断操作或跳出循环。只能使用 try、catch 中断,抛出异常。for循环能控制起点,forEach 不能(必须从第一个开始)。运行速度:for > forEach
try {
arr.forEach((item, index) => {
if (item == 1) {
throw new Error("打断施法")
}
})
} catch (e) {
if (e.message !== "打断施法") {
throw e
}
}
forEach 和 map 的区别
1、forEach()方法没有返回值,会更改原数组。
2、map() 有返回值,返回一个新数组,不会改变原数组,map() 不会对空数组进行检测。
3、map 的速度大于 forEach()
find:用于找出第一个符合条件的数组成员,并且返回该数组元素,如果没有满足条件的数组元素该方法返回undefined。
let arr1 = [1, 2, 3, 4, 5]
let num1 = arr1.find(function(item) {
return item > 1;
});
console.log(num1); //2
findIndex:用于找出第一个符合条件的数组成员的索引值,并且返回该数组元素的索引值。如果没有满足条件的数组元素该方法返回-1。
let arr2 = [1, 7, 9, 4, 5]
let num1 = arr2.findIndex(item => item > 9);
console.log(num1); //-1
let num2 = arr2.findIndex(item => item > 3);
console.log(num2); //2
includes:判断数组是否包含指定值—是全等判断,第一个值是包含的指定值,第二个值是指定值的索引。
let arr4 = [2, 3, 4, 7, 5]
let flag = arr4.includes(2);
console.log(flag); //true
let flag1 = arr4.includes(3, 1);
console.log(flag1); //true
十一、字符串方法
charAt() 方法可返回指定位置的字符;
concat() 方法用于连接两个或多个字符串或数组;
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。没有为 -1;
lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索;
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
slice、substr、substring截取字符串; split 分割成数组; trim 去空格; toLowerCase toUpperCase 转英文大小写
十二、JS对数据类的四种检测方式
typeof() 可以判断除了null以外的基本数据类型,引用数据类型不管用,识别数组等均为 object
instanceof() 只能判断引用类型,不能判断基本数据类型
constructor 几乎可以判断所有。如果声明一个构造函数,并把它的原形指向Array,他就识别不出来了
Object.prototype.toString.call()
十三、闭包
什么是闭包?函数嵌套函数,内层函数访问外层函数的变量。
特点:变量持久化,不会被垃圾回收机制回收;防止变量和参数被外部污染,变量只在闭包内部可访问。
缺点:闭包较多的时候,会消耗内存,导致页面的性能下降,在IE浏览器中才会导致内存泄漏。
解决:在不需要用的时候,设为 null(垃圾回收机制自动回收)。
使用场景:防抖,节流,函数嵌套函数避免全局污染的时候。
防抖和节流区别:操作时不执行,确定不操作了才执行;节流到了时间执行一次。
function makeCounter() {
let count = 0;
return function() {
count++;
console.log(count)
}
}
const conter = makeCounter()
console.log(makeCounter) // 整体函数
console.log(makeCounter()) // 内部函数
conter() // 1 只执行了内部函数,所以 count 的值增加
conter() // 2 只执行了内部函数,所以 count 的值增加
conter() // 3 只执行了内部函数,所以 count 的值增加
十四、内存泄漏
JS里已经分配内存地址的对象,但是由于长时间没有释放或者没办法清除,造成长期占用内存的现象,会让内存资源大幅浪费,最终导致运行速度慢,甚至崩溃的情况。
垃圾回收机制:js具有的回收机制,将不再使用的变量和对象进行回收,进行空间的释放。
因素:1 意外的全局变量泄露 2 console.log 使用 3 闭包泄露,赋值给全局变量后,对函数的引用一直存在 4 dom 泄露,remove dom 节点之后,但是remove 的 dom 节点对子节点的引用一直存在 5 一些未清空的定时器 6 监听事件没被销毁
小技巧:把功能写在函数里面能解决大部分内存泄漏问题问题,还有记得用let 和 const 声明变量别用var了
十五、基本数据类型(栈)和引用数据类型(堆)
基本数据类型:基本数据类型保存在栈内存当中,保存的就是一个具体的值。
String Number Boolean undefined null Symbol
引用数据类型:保存在堆内存当中,声明一个引用类型的变量,它保存的是引用类型数据的地址。假如声明两个引用类型同时指向了一个地址的时候,修改其中一个那么另外一个也会改变。
Object Function Array
十六、undefined 和 null
undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。当需要释放一个对象时,直接赋值为 null 即可。
两个表示 无 的值,一个null,一个undefined。undefined 表示无的原始值,转化为数字时为 NAN;null表示为无对象,转化为数字时为 0。
十七、事件委托(事件代理)
利用事件冒泡的机制来实现,也就是说把子元素的事件绑定到了父元素的身上。
好处:提高性能,减少事件的绑定,也就减少了内存的占用。
如果子元素阻止了事件冒泡,那么委托也就不成立,阻止事件冒泡event.stopPropagation()
在 DOM 中,事件捕获发生在事件冒泡之前,并且是事件处理的第一个阶段。在这个阶段,事件从文档的根节点向下传播到达目标元素。调用 event.stopPropagation() 只能阻止事件在捕获阶段或冒泡阶段中的继续传播,但它不能阻止事件捕获阶段的执行。
要完全阻止事件捕获阶段的执行,可以考虑在捕获阶段的事件处理函数中使用 event.stopImmediatePropagation() 方法,它会立即阻止事件继续传播,包括捕获和冒泡阶段。
addEventListener('click', 函数, true/false) 默认是false(事件冒泡),true(事件捕获)
三个参数( 指定事件名 指定要事件触发时执行的函数 可选,默认false。布尔值,指定事件是否在捕获或冒泡阶段执行)
事件捕获速度高于事件冒泡
十八、原型链
当我们访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 就会通过__proto__属性向上查找,直到找到对应的属性或方法,或者到达原型链的尽头 null。
原型链的顶端是 null,这表示原型链的终点。在 JavaScript 中,对象的原型链最终会追溯到 null。
实例对象的隐式原型__proto__全等于构造函数的原型prototype
for in可遍历原型链上扩展的属性,Object.keys() 只遍历自身属性
// 定义一个构造函数 Person
function Person(name) {
this.name = name;
}
// 在 Person 的原型上定义一个方法 sayHello
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
// 创建一个 Person 的实例
var person = new Person("John");
// 实例对象的隐式原型__proto__全等于构造函数的原型prototype
console.log(person.__proto__ === Person.prototype); // 输出:true
// for in可遍历原型链上扩展的属性,Object.keys() 只遍历自身属性
Object.prototype.say="123";
var person ={ age: 18 };
for (var key in person) {
console.log(key, person[key]);
}
// age 18
// say 123
console.log(Object.keys(person));
// ["age"]
十九、new操作
1、先创建一个空对象
2、把空对象和构造函数通过原型链进行链接
3、把构造函数的this绑定到新的空对象身上
4、返回结果 res,如果 返回结果res 是一个对象则返回 res,否则返回 obj
function _new(aFn, ...args) {
let obj = {};
obj.__proto__ = aFn.prototype;
const res = aFn.apply(obj, args);
// 返回结果 res,如果 res 是一个对象则返回 res,否则返回 obj
return res instanceof Object ? res : obj;
}
二十、Js创建对象的3种方式和Js继承5种方式
// 1 对象字面量方式 var obj = {}; obj.name = "张三"; // 2 工厂模式(利用函数创建对象) function createFactory(name) { var obj = new Object; obj.name = name; return obj; } var obj = new createFactory("王五"); // 3 构造函数模式 function Person(uname) { this.uname = uname } Person.prototype.sing = function(songName){ console.log(this.uname+"会唱"+songName); } var person1 = new Person("刘德华"); person1.sing("有只小鸟掉下水") 1. 原型链继承(子类原型 等于 new 父类) 优点:父类方法可以复用 缺点:父类中所有的引用类型(对象、数组)会被子类共享,更改一个子类的数据,其他数据会受到影响,一直变化。(基本数据类型不会);子类实例不能给父类构造函数传参。 2. 构造函数继承(在子类里,将父类的构造函数的this指向子类里的this) 优点:父类的引用类型不会被子类共享,不会互相影响。 缺点:子类不能访问父类原型属性(Person.prototype)上的方法和参数 3. 组合式继承(将1和2结合起来) 优点:父类可以复用;父类构造函数中的引用属性数据不会共享 缺点:会调用两次父类的构造函数,会有两份一样的属性和方法,影响性能。 4. 寄生组合继承(目前最优的继承方案,创建第三个构造函数,将第三构造函数的原型和父类的构造函数连接,再将子类的原型等于 new 一个第三构造函数,再与2结合) 5. ES6的class类继承 extends class Student extends Person{}
二十一、this指向的问题
1 全局对象中的this指向(指向的是window)
2 全局作用域或者普通函数中的this(指向全局window)
3 匿名函数中的this(永远指向了window,匿名函数的执行环境具有全局性,因此this指向window)
4 new 关键词改变了this的指向(指向新的实例对象)
5 call,apply,bind(可以改变this指向,指向第一个参数,不是箭头函数)
6 this永远指向最后调用它的那个对象(在不是箭头函数的情况下,谁调用指向谁)
7 箭头函数中的this,它的指向在定义的时候就已经确定了。(箭头函数它没有this,看外层是否有函数,有就是外层函数的this,没有就是window,因为箭头函数没有自己的this,也无法用call等进行更改this指向)
call, apply 第二个参数是数组, bind 有返回值的
function fun() {
console.log(this.name)
}
let name = "window name"
let cat = {
name: "喵喵"
}
fun() // window name
// call 可以改变函数中的 this 指向
fun.call(cat) // 喵喵
let dog = {
name: "旺财",
sayName() {
console.log("我是" + this.name)
},
eat(foot1, foot2) {
console.log("我喜欢吃" + foot1 + foot2)
}
}
dog.sayName() // 我是旺财
dog.sayName.call(cat) // 我是喵喵
dog.eat("骨头", "饲料") // 我喜欢吃骨头饲料
dog.eat.call(cat, "鱼", "饼干") // 我喜欢吃鱼饼干
dog.eat.apply(cat, ["鱼", "饼干"]) // 我喜欢吃鱼饼干
let aaa = dog.eat.bind(cat, "鱼", "饼干")
aaa() // 我喜欢吃鱼饼干
二十二、setTimeout、setInterval、requestAnimationFrame
定时器(setTimeout 延时调用、setInterval 定时调用)和requestAnimationFrame定时器 实现的动画在某些低端机上会出现卡顿、抖动的现象,任务被放进了异步队列中,只有当主程上的任务执行完之后,才会去检查该队列里的任务是否需要执行,因此setTimeout的实际执行时间一般要比其设定的时间晚一些。
requestAnimationFrame H5新增 在下一次重绘之前会更新动画帧所调用的函数。在隐藏元素中和页面不激活的状态下,requestAnimationFrame 不进行工作,减少CPU、GPU、内存的使用量。
二十三、事件循环(EventLoop)
JS是一个单线程的脚本语言,执行栈、宿主环境、任务队列、同步、异步(微任务、宏任务【定时器、点击事件】)
先同后异,先微后宏,先进先出。同步任务放在执行栈优先执行,异步任务放在宿主环境等待。异步任务的时间到了,或被触发(类似点击事件等),将异步任务推送到任务队列。
当执行栈执行完,执行栈就回去执行任务队列里面的任务。
promise 是同步任务,直接执行里面的代码,.then 才是异步任务,resolve 是调用成功,把值传递到 .then 里面。.then 是微任务
DOM 操作在原生JS中是同步操作,在VUE中是异步操作(在 Vue.js 中,DOM 操作本身并不是异步的。就像在原生 JavaScript 中一样,直接的 DOM 操作(例如通过 ref 或 this.$refs 访问元素并修改其属性)是同步执行的。但是,Vue.js 的响应式系统和虚拟 DOM 机制确实会引入一些异步的特性.this.$nextTick 可以确保回调函数在 DOM 更新完成后执行,用于获取更新后的 DOM 状态或执行其他操作)。
二十四、页面渲染过程
1 浏览器会获取HTML和CSS的资源,然后把HTML解析成DOM树
2 再把CSS解析成CSSOM
3 把DOM和CSSOM合并为渲染树
4 计算和布局、把渲染树的每个节点渲染到屏幕上(绘制)
DOM树是和HTML标签一一对应的,包括head和隐藏元素;渲染树是不包含head和隐藏元素,渲染树包含CSSOM
二十五、浏览器的存储方式
1 Cookies
优点: Cookies 是最早引入的本地存储技术,具有广泛的浏览器兼容性,并支持跨域存储。Cookies可以存储较小的数据量(通常限制为4KB),可以设置过期时间,支持持久化保存和定时清除。
缺点:Cookies 在每个 HTTP 请求中都会被发送到服务器端,造成额外的网络流量。受到浏览器限制,每个域名下的 Cookies 数量和总大小都有限制。同时,Cookies 中的数据可以被读取和算改,安全性有限。
2 Web Storage (Localstorage 和 SessionStorage)
Localstorage 本地存储(永久存在) SessionStorage 会话存储(页面关闭就没了,多个页面不共享)
优点: web storage 提供了更大的存储容量(通常限制为5MB) 且只在客户端存储,不会在每个请求中发送到服务器。Localstorage 和 SessionStorage 提供简单的键值对存储,并且在同一浏览器窗口下共享数据。
缺点: Localstorage 在浏览器中是永久性的,除非手动删除,否则数据会一直保留。由于浏览器限制,数据在不同的浏览器窗口和标签页之间无法共享。同时,对于较旧的浏览器版本,对 Webstorage 的支持可能不完整。
3 IndexedDB
IndexedDB 是一种通用的浏览器本地微型数据库,它不依赖于特定的应用或服务,而是作为现代 Web 应用程序的一部分,提供了一种持久化存储数据的机制。
二十六、Token
登录流程
1 客户端用账号密码请求登录
2 服务端收到请求后,需要去验证账号密码
3 验证成功之后,服务端会签发一个token,把这个token发送给客户端
4 客户端收到token后保存起来,可以放在cookie也可以是localstorage
5 客户端每次向服务端发送请求资源的时候,都需要携带这个token
6 服务端收到请求,接着去验证客户端里的token,验证成功才会返回客户端请求的数据
存储位置
localStorage的数据会在浏览器关闭后仍然存在,因此可以保持用户的登录状态。同时,localStorage中的数据可以在同一浏览器的所有标签页和窗口中共享。然而, localStorage中的数据可能会受到XSS(跨站脚本)攻击,因此需要对数据进行适当的编码和验证。
sessionStorage中的数据只在当前会话中存在,当用户关闭浏览器后,sessionStorage中的数据将被清除。
无感登录和无感刷新
为了保证安全性,后端设置的Token不可能长期有效,过了一段时间Token就会失效。而发送网络请求的过程又是需要携带Token的, 一旦Token失效,用户就要重新登陆,这样用户可能需要频繁登录,体验不好。为了保证安全性,后端设置的Token不可能长期有效,过了一段时间Token就会失效。 而发送网络请求的过程又是需要携带Token的,一旦Token失效,用户就要重新登陆,这样用户可能需要频繁登录,体验不好。
方法一:调用定时器,定时刷新,浪费性能。
为了解决这个问题,采取双Token(Access_Token,Refresh_Token)无感刷新,用户完全体会不到Token的变化,但实际上,Token已经刷新了。
方法二:双Token机制
Access_Token:用于鉴定用户身份,即每次发送网络请求都需要携带这个Access_Token。有效时间短。
Refresh_Token:用于刷新Access_Token,即调用A接口需要携带Refresh_Token, 用它换得最新的Access_Token。有效时间长。
流程
1.登录成功后保存token 和 refresh_token
2.在响应拦截器中对401状态码引入刷新token的api方法调用
3.替换保存本地新的token
4.把错误对象里的token替换
5.再次发送未完成的请求
6.如果refresh_token过期了,判断是否过期,过期了就清楚所有token重新登录
应用场景 小程序,单点登录,移动应用或者是web网站应用当中,或者是第三方授权操作处理等
二十七、HTTP协议规定的协议头和请求
1 请求头信息
Accept:浏览器告诉服务器所支持的数据类型
Host:浏览器告诉服务器我想访问服务器的哪台主机;
Referer:浏览器告诉服务器我是从哪里来的(防盗链)
User-Agent:浏览器类型、版本信息
Date:浏览器告诉服务器我是什么时候访问的
Connection:连接方式
Cookie
X-Request-With:请求方式
2 响应头信息
Location:这个就是告诉浏览器你要去找谁
Server:告诉浏览器服务器的类型
Content-Type:告诉浏览器返回的数据类型
Refresh:控制了的定时刷新
二十八、常见的http状态码
200 请求成功,表示从客户端发送给服务器的请求被正常处理并返回。
204 表示客户端发送给服务端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分,即没有资源可以返回。
301 永久重定向,被请求的资源已永久移动到新位置(URL),之后应使用更改的URL。
302 临时重定向,请求的资源现在临时从不同的URLI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。
400 表示前端请求报文中存在语法错误
401 访问者访问的页面未经授权
403 服务器拒绝该次访问
404 文件请求不到,当用户试图访问Web服务器(通常是一个网页)上某个实际不存在的资源时,就会发生404错误。无效链接
500 内部服务器错误,一般是服务器内部代码出现错误。
502 无效网关,作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
503 表示服务器暂时处于超负载或正在进行停机维护,无法处理请求。
二十九、http和https区别
1 https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2 http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3 http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
三十、AJAX请求
1 在不重新加载整个网页的前提下,与服务器交换数据并更新部分内容
过XmlHttpRequest对象向服务器发送异步请求,然后从服务器拿到数据,最后通过JS操作DOM更新页面
2 get和post区别
get 通过 地址栏 url 传输,可见,数据量小,相对不安全,当回退再次进入页面时,不清除数据,不重复调用接口,有缓存
post 通过 body 传输,可见可不见,数据量大
3 浏览器的缓存策略
本地缓存(强缓存)不发起请求,直接使用缓存里的内容,浏览器把JS,CSS,image等存到内存中,下次用户访问直接从内存中取,提高性能
协商缓存(弱缓存)需要像后台发请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器就用缓存里的内容
4 同源策略
http:// www. aaa.com:8080/index/vue.js
协议 子域名 主域名 端口号 资源
同源策略是浏览器的核心,如果没有这个策略就会遭受网络攻击
主要指的就是协议 + 域名(子 + 主) + 端口号三者一致,若其中一个不一样则不是同源,会产生跨域
三个允许跨域加载资源的标签:img link script
跨域是可以发送请求,后端也会正常返回结果,只不过这个结果被浏览器拦截了!
解决方法:JSONP;CORS;websocket;反向代理(proxy)
三十一、文件上传
分片上传(Chunked Upload)
1 选择文件:使用 input type='file' 或者Vue组件库中的文件选择组件,让用户选择要上传的文件。
2 文件分片:当用户选择文件后,首先需要对文件进行分片处理。分片的大小可以根据实际需求设定,通常在1MB到10MB之间。分片可以使用File API中的File.slice()方法进行分割,并依次上传到服务器
3 上传片段:上传时需要注意为每个分片添加必要的标识(如文件名、片段序号等)以及处理进度条显示
4 完成上传:当所有分片上传完成后,调用接口,通知服务器进行合并操作,确保文件完整性
断点续传
1 保存上传状态:在前端,需要保存每个块的上传状态,以便在上传中断时能够恢复。可以使用本地存储(如localStorage)来保存已上传块的信息,例如上传进度、已上传块的索引等。
2 恢复上传:如果上传中断,用户可以在适当的时机重新加载页面或重新访问上传页面。此时,前端可以从保存的状态中获取已上传的块信息,并从中断处继续上传。
秒传(快传)
1 在Vue.js前端,实现秒传功能的关键在于在文件上传前计算文件的哈希值,并将该哈希值与后端进行比对。如果后端返回文件已存在的信息,即可视作秒传成功。
2 在前端使用浏览器的File API计算文件的哈希值,一般使用MD5算法。可以使用第三方库如spark-md5来简化哈希计算过程。
3 在上传文件之前,先计算文件的哈希值,并将该值与服务器进行比对。如果服务器返回文件已存在的信息,则可以停止上传。
并发多文件上传
1 选择文件:允许用户选择多个文件,并将这些文件存储在一个数组或者列表中,准备进行并发上传。
2 并发上传:使用Promise.all或其他并发控制机制,同时上传多个文件。在Vue组件中,可以使用Array.map方法创建一个上传每个文件的Promise数组,并使用Promise.all来等待所有文件上传完成。
3 上传进度:可以实现上传进度条或者上传进度显示,以提升用户体验。通常在每个文件上传的过程中更新进度条
进度条显示和用户体验、错误处理和安全性
进度条、全部传输完成进行弹出提示、网络错误时,重新连接或者提示
对文件大小格式等进行限制
三十二、语义化的理解
1 在没有CSS样式情况下也能够让页面呈现出清晰的结构
2 有利于SEO和搜索引擎建立良好的沟通,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重
3 方便团队开发和维护,语义化更具可读性,遵循W3C标准的团队都遵循这个标准,可以减少差异化
三十三、元素、文档流、BFC
块元素:独占一行,自上而下排列
行内元素:只占自身大小,从左向右排列,超过换行。不支持 margin-top 和 margin-bottom
两者:水平内边距padding求和,垂直外边距margin重叠
文档流:文档流处在页面的最底层,我们所创建的页面默认都是处在文档流中。
块元素在文档流中的特点:独占一行,自上而下排列;宽度默认为父元素的100%;高度默认被内容撑开。
行内元素:只占自身大小,从左向右排列,超过换行;宽度和高度默认被内容撑开。
BFC(块级格式化上下文)Block Formatting Context
它是一块独立的渲染区域。不同的 BFC 区域,进行渲染时互不干扰
触发条件
根元素(html);float值非none;overflow值非visible;position值为absolute、fixed;display值为inline-block、flex、inline-flex...
元素开启BFC后特点
1.开启BFC的元素不会被浮动元素所覆盖
2.开启BFC的元素子元素和父元素外边距不会重叠
3.开启BFC的元素可以包含浮动的子元素(解决高度塌陷)
.clearfix::after{ content: ''; display: table; clear: both; }
BFC功能总结
1、可以利用BFC解决两个相邻元素的上下margin重叠问题;
2、可以利用BFC解决高度塌陷问题;
3、可以利用BFC实现多栏布局(两栏、三栏、圣杯、双飞翼等)
三十四、画布 canvas
提供作画能力,fillStyle 填充绘画的颜色、渐变;rect() 绘制矩形;fillText() 绘制文本;createImageData() 创建新的图片......
场景:海报分享、截取图片上传
如果需要动态创建图形和图像,用canvas。如果要高质量的图形和图像则用矢量图 svg (图标、logo)
// 获取要截图的盒子元素,假设它有一个唯一的 ID 为 "boxToCapture",盒子或图片可拖拽
const elementToCapture = document.getElementById('boxToCapture');
// 创建一个 Canvas 元素
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;
const ctx = canvas.getContext('2d');
// 将盒子元素内容绘制到 Canvas 上
ctx.drawImage(elementToCapture, 0, 0, 200, 200);
// 将 Canvas 中的内容导出为 PNG 图片
const dataURL = canvas.toDataURL('image/png');
// dataURL 现在包含截图的图像数据,可以进行进一步处理,比如展示在页面上或者上传到服务器
三十五、过渡和自定义动画
过渡:CSS3中,我们为了添加某种效果可以从一种样式转变到另一个的时候,无需使用JavaScript。性能提升。
transition: width 2s ease 1s; transition 四个参数: 属性的名称、过渡效果花费的时间、过渡效果、何时开始。
动画:CSS3 可以创建动画,它可以取代许多网页动画图像、JavaScript 实现的效果。还有很多参数。
添加了 aaaClass 的盒子背景会从红色变成黄色,过程为5秒,5秒后又还原为红色
.aaaClass {
animation: myfirst 5 s;
}
@keyframes myfirst {
from {
background: red;
}
to {
background: yellow;
}
}
三十六、JSON
JSON是一种纯字符串形式的数据,它本身不提供任何方法,适合在网络中进行传输。
可以把JSON数据以字符串的形式保存在数据库,缓存中。提供了JSON.parse() JSON.stringify()
三十七、SVG
基于XML语法格式的图像格式,可缩放矢量图,其他图像是基于像素的,SVG是属于对图像形状的描述,本质是文本文件,体积小,并且不管放大多少倍都不会失真
1.SVG可直接插入页面中,成为DOM一部分,然后用JS或CSS进行操作
2.SVG可作为文件被引入 img src='pic.svg'
3.SVG可以转为base64引入页面
三十八、Git 操作
git clone 克隆项目、git branch -a 查看项目的所有分支、git status 查看状态、git add . 添加全部
git reset HEAD^hello.php 回退 hello.php 版本、git commit -m '内容'、git pull 下拉、git push 推
三十九、JS的延迟加载方式
script标签的 defer 属性,立即下载,但是延迟执行
setTimeout 操作dom,动态创建 script 标签
把 script 放在最后
普通 <script>
标签:同步加载,立即阻塞 HTML 解析,加载完毕后执行。
defer
脚本:异步加载,不阻塞 HTML 解析,等 HTML 全部解析完后按顺序执行。
async
脚本:异步加载,不阻塞 HTML 解析,那个先加载完就立即执行那个,无序。
四十、深浅拷贝
浅拷贝是创建一个新的对象或数组,并将原始数据的值复制到新对象中。当原始数据是基本数据类型(如数字、字符串、布尔值)时,浅拷贝会直接复制其值。然而,当原始数据是引用类型(如对象或数组)时,浅拷贝只会复制引用,而不是创建新的独立副本。这意味着新对象和原始对象会共享同一份数据,对其中一个对象的修改会影响到另一个对象。
深拷贝是创建一个全新的对象或数组,并递归地复制原始数据中的所有值和嵌套对象。深拷贝会创建独立的副本,使得新对象和原始对象完全独立,互不影响。
直接赋值 浅拷贝
使用...扩展运算符、一层深拷贝,多层浅拷贝
Object.assign() 主要用于对象合并,将源对象中的属性复制到目标对象中,他将返回目标对象。一层深拷贝,多层浅拷贝
JSON.parse(JSON.stringify()) 深拷贝,但是无法拷贝对象方法
双for循环实现深拷贝函数
四十一、let const var
let 和 const 存在块级作用域,var 不存在;
var 存在变量提升,let 和 const 不存在, 即只能在声明后使用, 否则报错;
var 定义的变量可以重复声明,let 和 const 不行。
var 在全局声明的变量为全局变量, 并将该变量添加为全局对象的属性(在浏览器环境下是window对象,在Node.js环境下是global对象)。let 和 const 不会;
var a = 2
let b = 3
console.log(a) // 2
console.log(this.a) // 2
console.log(b) // 3
console.log(this.b) // undefined
四十二、解构赋值、模板字符串、三点运算符、形参默认值
let username = obj.username; let age = obj.age; ==> let { username, age } = obj;
模板字符串必须用 `` 包含, 动态部分使用 $ {} 拼接
let arr1 = [1, 3, 5]; ===> let arr2 = [2, ...arr1, 6];
四十三、箭头函数
当箭头函数只有一句时, 不需要用括号包围; 箭头函数没有自己的this,( 普通函数谁调用, 指向谁, 直接使用默认为window调用), 箭头函数指向父级上一层, 无法改变 this 指向
let obj = {
afn: function() {
console.log(this, "afn")
},
bfn: () => {
console.log(this, "bfn")
}
}
obj.afn() // obj
obj.bfn() // window
1 箭头函数只有一句时候可以省略return,参数只有一个时,可以省略()。
2 没有自己的this
3 没有arguments对象
四十四、promise、 async、 await
回调地域问题
// 要先拿到汉堡,再拿可乐,只能将可乐写在汉堡里面,这就是回调地域
setTimeout(() => {
console.log("汉堡")
setTimeout(() => {
console.log("可乐")
}, 1000)
}, 2000)
promise 可以将异步操作以同步的流程表达出来, 避免了层层嵌套的回调函数, 把异步操作队列化, 解决“ 回调地域”。promise是同步任务,.then才是异步任务。
三种状态: pending 初始状态, fulfilled 操作成功, rejected 操作失败
let hb = function() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve("汉堡01")
}, 2000)
})
}
let kl = function() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve("可乐01")
}, 1000)
})
}
// 先拿到汉堡 再拿到可乐 链式代码
hb().then((res) => {
console.log(res)
return kl()
}).then((res) => {
console.log(res)
})
async await 同步代码做异步的操作, 两者必须搭配使用、 async表明函数内有异步操作, 调用函数会返回promise。
await后的promise如果是reject状态, 那么整个async函数都会中断, 后面的代码不执行
// async awit
let afn = async () => {
console.log(await hb())
console.log(await kl())
}
afn()
async await 的返回
1 函数前面加上 await 关键字,则该函数会返回一个结果为 promise 的对象。
2 await 是在等一个表达式的结果,等一个返回值(回调成功的内容)
3 如果 await 右侧是一个 promise 对象,则 await 返回的是 promise 成功的值。
注意:1.await 必须写在 async 函数中,但 async 函数中可以没有 await。
注意:2.如果 await 的 promise 失败了,就会抛出异常,该异常需要通过 try catch 捕获处理。
四十五、generator
新增了generator *函数, 解决异步编程的一种方案
let fn = function*() {
yield "a"
yield "b"
yield "c"
}
let f = fn();
console.log(f.next()) // {value: "a", done: false}
console.log(f.next()) // {value: "b", done: false}
console.log(f.next()) // {value: "c", done: false}
console.log(f.next()) // {value: undefined, done: true}
四十六、基本数据类型( Symbol)
用法: 定义对象唯一的属性名。
特点: 1 Symbol 属性对应的值唯一, 解决命名冲突问题。 2 Symbol 的值不能与其他数据进行计算, 包括字符串拼接。 3 for in、for of 遍历时, 不会遍历 Symbol 属性。
let sy = Symbol("key01");
let syObj = {};
syObj[sy] = "kk";
console.log(syObj); // { syObj(key01): "kk" }
四十七、set集合 和 map集合
// set集合用于存放不重复的数据
// add(数据): 添加一个数据到set集合末尾, 如果数据已存在, 则不进行任何操作
// has(数据): 判断set中是否存在对应的数据, 返回true / false
// delete(数据): 删除匹配数据
// clear(): 清空整个set集合
// size: 获取set元素集合的元素数量( 只读, 不能赋值, 赋值无效)
const s1 = new Set()
s1.add(1)
s1.add(2)
s1.add(3)
s1.add(1) // 无效
console.log(s1) // Set(3) {1, 2, 3}
console.log(s1.has(4)) // false
s1.delete(2)
console.log(s1) // Set(2) {1, 3}
s1.clear()
console.log(s1) // Set(0)
console.log(s1.size) // 0
// set的应用:求两个数组的并集、交集、差集,要求:不能出现重复项,得到的结果是一个新数组
const arr1 = [22, 33, 44, 55, 66, 77, 11, 22, 44]
const arr2 = [55, 88, 99, 656, 888, 44, 33, 11]
// 将数组转换为 Set,去除重复元素
const set1 = new Set(arr1);
const set2 = new Set(arr2);
// 并集 (两个数组的所有元素)
cosnt result = [...new Set(arr1.concat(arr2))]
// map集合 键值对数据集合的特点: 键不可重复, 专门用于存储多个键值对数据。( 在map出现之前使用对象存储键值对,但是有几个问题, 键名只能是字符串, 获取数据的数量不方便。)
const str = new Map([
["a", 1],
["b", 2],
["c", 3]
])
console.log(str) // Map(3) {"a" => 1, "b" => 2, "c" => 3}
// size:只读属性,获取当前map中的键数量
console.log(str.size) // 3
// set(键,值):设置一个键值对,键和值可以是任何类型
// 如果键不存在,则添加一项
// 如果键已存在,则修改它的值
str.set("d", 4)
str.set("a", 11)
console.log(str) // Map(4) {"a" => 11, "b" => 2, "c" => 3, "d" => 4}
// get(键):根据一个键得到对应的值
console.log(str.get("a")) // 11
// has(键):判断某个键是否存在
console.log(str.has("a")) // true
// delete(键):删除指定的键
str.delete("a")
console.log(str) // Map(3) {"b" => 2, "c" => 3, "d" => 4}
// clear() :清空map
str.clear()
console.log(str) // Map(0) {}
str.set({
name: "张三"
}, 4)
console.log(str) // Map(1) {{ name: "张三" } => 4}
四十八、迭代器( iterator)
拥有next 方法返回对象。 value 是当前迭代的值, 而 done 代表迭代是否完成。
可以迭代的对象, 是因为本身拥有 迭代器。
数组, set集合, Map集合 本身拥有迭代器可以直接for of对象不行。 需要自己添加迭代器。( 使用for in )
四十九、新增了定义类的语法糖( class)
// es6 class
class Student {
constructor(name) {
this.name = name
}
say() {
console.log("你好,我是" + this.name)
}
}
let stu1 = new Student("stu1")
let stu2 = new Student("stu2")
stu1.say()
stu2.say()
// 在class中, 可以使用static关键字定义静态方法。
// 静态方法不需要实例化类就可以被调用,可以通过类名去访问属性和方法, 而实例对象访问不了。
class ShuiGuo {
static type = "西瓜";
static eat = function() {
console.log("水果真好吃!");
}
}
let shuiguo = new ShuiGuo();
console.log(ShuiGuo.type); //西瓜
ShuiGuo.eat(); //水果真好吃!
console.log(shuiguo.type); //undefined
shuiguo.eat(); //Uncaught TypeError: shuiguo.eat is not a function
// super关键字 作为函数使用
class Parent {
constructor() {
console.log("Parent constructor");
}
}
class Child extends Parent {
constructor() {
super(); // 调用父类的构造函数
console.log("Child constructor");
}
}
const child = new Child(); // 输出 "Parent constructor" 和 "Child constructor"
// super关键字 作为对象使用
class Parent {
constructor() {
this.name = "Parent";
}
sayHello() {
console.log("Hello from " + this.name);
}
}
class Child extends Parent {
constructor() {
super(); // 调用父类的构造函数
this.name = "Child";
}
sayHello() {
super.sayHello(); // 调用父类的sayHello方法
console.log("Extra greeting from " + this.name);
}
}
const child = new Child();
child.sayHello(); // 输出 "Hello from Child" 和 "Extra greeting from Child"
五十、新增了 import 和 export 语句, 用于模块化导入和导出
1 CommonJS 模块的require是同步加载模块,ES6 模块的import命令是异步加载
2 require 运行时加载,可以在代码的任何地方使用。import 编译时加载,必须放在文件开头。
3 require导入整个模块支持解构赋值。import 可以选择性地导入模块中的特定部分。
4 require导入的值被修改时,源对象不会被改变,相当于深拷贝;import导入的对象值被改变时,源对象也会被改变,相当于浅拷贝。
五十一、v-if v-show v-for
v-if和v-show的区别(都可以控制元素的显示和隐藏)
1. v-show时控制元素的display值来让元素显示和隐藏; v-if显示隐藏时把DOM元素整个添加和删除
2. v-if有一个局部编译 / 卸载的过程, 切换这个过程中会适当的销毁和重建内部的事件监听和子组件; v-show只是简单的css切换
3. v-if的切换效率比较低 v-show的效率比较高
[Vue] 中为何不要把 v-if 和 v-for 同时⽤在同一个元素上
1 优先级冲突: v-for 指令在 Vue 中的优先级比 v-if 高。这意味着,如果你在一个元素上同时使用 v-if 和 v-for,v-for 将首先运行并渲染列表中的所有项目,然后 v-if 将根据条件决定是否显示整个列表。这可能会导致不必要的渲染和性能问题。
2 性能问题: 当 v-if 的条件不满足时,Vue 仍然会对列表中的每个项目执行 v-for,即使这些项目最终不会被渲染到 DOM 中。这种额外的计算和虚拟 DOM 的更新可能会影响性能,特别是当列表中的项目数量较大时。
在vue2中,v-for的优先级高于v-if; 在vue3中,v-if的优先级高于v-for. 外层使用另外的div或template嵌套
五十二、vue生命周期( 组件从创建到销毁的过程就是它的生命周期)
创建前 beforeCreat( 在这个阶段属性和方法都不能使用)
创建时 created( 这里时实例创建完成之后, 在这里完成了数据监测, 可以使用数据, 修改数据, 不会触发updated, 也不会更新视图)
挂载前 beforeMount( 完成了模板的编译, 虚拟DOM也完成创建, 即将渲染, 修改数据, 不会触发updated)
挂载时 Mounted( 把编译好的模板挂载到页面, 这里可以发送异步请求也可以访问DOM节点)
更新前 beforeUpdate( 组件数据更新之前使用, 数据是新的, 页面上的数据时旧的, 组件即将更新, 准备渲染, 可以改数据)
更新时 updated( render重新做了渲染, 这时数据和页面都是新的, 避免在此更新数据)
销毁前 beforeDestroy( 实例销毁前, 在这里实例还可以用, 可以清除定时器等等)
销毁时 destroyed( 组件已经被销毁了, 全部都销毁)
keep-alive 组件缓存( 不销毁, 刷新的时候, 保存状态)
多了两个生命周期( activited 组件激活时 deactivited 组件没被激活时)缓存组件, 避免组件内数据重复渲染, 直接可以在页面中调用。
优点: 组件切换过程中, 组件保存在内存中, 防止重复渲染, 减少加载时间, 提高性能。
五十三、created和mounted
created: 在渲染前调用, 通常先初始化属性, 然后做渲染
mounted: 在模板渲染完成后, 一般都是初始化页面后, 在对元素节点进行操作在这里请求数据可能会出现闪屏的问题, created里不会
请求的数据对DOM有影响, 那么使用created; 如果请求的数据对DOM无关, 可以放在mounted
五十四、vue中的修饰符
1. 事件修饰符
.stop 阻止冒泡 .prevent 组织默认行为 .once 事件只会触发一次
2. 按键修饰符
.keyup 键盘抬起 .keydown 键盘按下
3. 鼠标修饰符
.left 鼠标左键 .right 鼠标右键 .middle 鼠标中键
4. 表单修饰符
.lazy 等输入完之后再显示 .trim 删除内容前后的空格 .number 输入是数字或转为数字
五十五、VUE组件
组件通信
父组件通过 props 传值给子组件,子组件通过 $emit 传值给父组件,或触发父组件事件
父组件 $refs 获取子组件 值或方法,子组件 $parent 获取父组件值或方法。 如果是多重嵌套, 也可以使用多层。
祖先组件通过 provide 传值给 孙子组件(通过 inject 接受)。允许一个祖先组件向其所有子孙后代注入一个依赖, 不论组件层次有多深, 并在其上下游关系成立的时间里始终生效。 注意: provide 和 inject 绑定并不是可响应的
attrs 实现孙子组件获取祖先组件的 attribute 绑定的值(class和style除外)listeners 则包含了 祖先组件 中(不含.native 修饰器的) v - on 事件监听器
注意:孙子组件无法直接向祖先组件传值,需要通过父组件或者使用事件总线,本地存储等进行通信
兄弟组件通信(兄传父, 父传弟, 反之亦然。 太繁琐)
安装引用 插件 mitt 或者 插件 $bus( 两者都是基于 事件总线event - bus)
父子组件生命周期钩子函数执行顺序
创建: 父beforeCreate - > 父created - > 父beforeMount - > 子beforeCreate - > 子created - > 子beforeMount - > 子mounted - > 父mounted
销毁: 父beforeDestroy - > 子beforeDestroy - > 子destroyed - > 父destroyed
VUE的单项数据流
Vue 的单向数据流是指数据在 Vue 应用中的流动方向是单向的,数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样会防止从子组件意外改变父组件的状态。
defineProps 父传子(用在子组件,接收父组件的传值,用来声明props)
defineEmits 子传父(用在子组件,将子组件的方法传递给父组件,用来声明emits)
defineExpose 子传父(用在子组件,暴露想传递的值或方法,父组件通过ref属性获取子组件暴露的)
Suspense
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child />
</template>
<template v-slot:fallback>
<h3>稍等,加载中...</h3>
</template>
</Suspense>
</div>
</template>
// import Child from './components/Child'//静态引入
import { defineAsyncComponent } from "vue";
const Child = defineAsyncComponent(() => import("./components/Child")); //异步引入
export default {
name: "App",
components: { Child },
};
在上面的例子中,AsyncComponent 被定义为异步组件,它只有在被实际渲染到页面时,才会从 './AsyncComponent.vue'文件中加载和解析,而不是在页面加载时就被引入。通过 defineAsyncComponent,Vue3 提供了一种简单而强大的方式来管理和优化组件的加载行为,使得应用程序可以更高效地使用和分发组件资源
五十六、Vuex页面刷新数据丢失,解决方法
需要做 vuex 数据持久化 ,一般使 用本地储存的方案 来保存数据,可以自己设计存储方案,也可以使用第三方插件。
推荐使用 vuex-persist(脯肉赛斯特 )插件,它是为 Vuex 持久化储存而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者localStorage 中
五十七、vue路由的hash模式和history模式区别
1. hash的路由地址上有 #号, history模式没有
2. hash模式支持低版本浏览器, history不支持, 因为是H5新增的API
3. hash模式利用的是锚点 history借助 history 对象中的 pushState() 函数重写 URL 路径, 都是通过控制页面的display: none 属性
4. hash模式简单,部署容易,不需要服务器端的配置。history需要
5. hash不会重新加载页面,在做回车刷新的时候, hash模式会加载对应页面。history会报错404
五十八、vue-router
路由传参方式
router 传值方式(router-link 和 this.$router.push)
1 query 传参(显示参数) this.$router.push({ name: 'Child', query: { id: 123 } })
2 params 传参(显示参数和不显示参数)
显示参数 this.$router.push({path:'/child/${id}',})
不显示参数 this.$router.push({ name: 'Child', params:{ id: 123 } })
五十九、三种导航守卫
1. 全局守卫(Global Before Guards) 写在 router/index.js 文件中的全局
用于全局的路由控制逻辑,如登录验证、权限控制等。
2. 路由守卫(Per-Route Guards) 写在 router/index.js 文件中的const routes = [] 中的每一个路由内
用于特定路由或路由群组的控制逻辑,如权限验证、数据预加载等。
3. 组件守卫(In-Component Guards)写在该组件的Vue页面中,类似 生命周期的写法
用于处理单个组件在路由导航过程中的逻辑,如特定路由下的数据加载、页面交互等。
to:目标路由对象;from:即将要离开的路由对象; next:它是最重要的一个参数,调用该方法后,才能进入下一个钩子函数。
next()//直接进to 所指路由
next(false) //中断当前路由
六十、路由拦截
路由拦截, 需要在路由配置中添加一个字段, 它是用于判断路由是否需要拦截,然后再在全局或路由守卫进行拦截
{
name: "index",
path: "/index",
component: Index,
meta: {
requirtAuth: true
}
}
router.beforeEach((to, from, next) => {
if (to.meta.requirtAuth) {
next()
}
})
六十一、AXIOS的封装和拦截
import axios from 'axios'
import getBaseUrl from './getBaseUrl'
// 创建axios实例
const request = axios.create({
baseURL: getBaseUrl(),// 所有的请求地址前缀部分(没有后端请求不用写)
timeout: 80000, // 请求超时时间(毫秒)
})
// request拦截器
request.interceptors.request.use(
config => {
// 如果你要去localStor获取token,(如果你有)
// let token = localStorage.getItem("x-auth-token");
// if (token) {
//添加请求头
//config.headers["Authorization"]="Bearer "+ token
// }
return config
},
error => {
// 对请求错误做些什么
Promise.reject(error)
}
)
// response 拦截器
request.interceptors.response.use(
response => {
// 对响应数据做点什么
return response.data
},
error => {
// 对响应错误做点什么
//响应错误
let message = "";
if (error.response && error.response.status) {
const status = error.response.status;
switch (status) {
case 401:
message = "未授权";
break;
case 404:
message = "请求地址出错";
break;
case 500:
message = "服务器内部错误!";
break;
default:
message = "请求失败";
}
return Promise.reject(error);
}
return Promise.reject(error);
}
)
export default request
六十二、vue 强制刷新
1. localtion.reload() // 跟按F5一样 不会利用缓存,直接重新加载 浪费性能
2. this.$router.go(0) // 跟按F5一样 不会利用缓存,直接重新加载 浪费性能
3. 找个空白页过渡一下 // 地址闪动效果,页面闪动
4. 利用 provider inject, 在孙组件中就可以直接调用祖先组件的方法, 进行刷新页面, 推荐
其本质上是通过控制 app.vue 中 router-view 标签设置 v-if='show' ,控制他先消失再显示
因为是 刷新按钮 一般不在 app.vue 文件,而是在其子孙组件中,所以使用到 provider 和 inject
六十三、computed和watch
1. computed是计算属性,data中无定义的数据,computed的值,可以直接使用;watch是监听, 监听的是data中数据的变化
2. computed是支持缓存, 依赖的属性值发生变化, 计算属性才会重新计算, 否则用缓存; watch不支持缓存
3. computed不支持异步, watch是可以异步操作
4. computed是第一次加载就监听, watch不监听( 有 deep: true // 开启深度侦听,immediate: true // 立马监听)
5. computed函数中必须有return watch不用
watch和watchEffect
1 watchEffect是立即执行的,在页面加载时会主动执行一次,来收集依赖;而watch是惰性地执行。
2 watchEffect只需要传递一个回调函数;watch至少要有两个参数(第三个参数是配置项),第一个参数是侦听的数据,第二个参数是回调函数。
3 watchEffect获取不到更改前的值;而watch可以同时获取更改前和更改后的值。
4 watchEffect自动追踪所有使用的响应式数据,当任何相关数据变化时,重新运行整个函数;watch 用于监测一个或多个特定的响应式数据源,并在变化时执行回调。
六十四、vue过滤器
vue的特性, 用来对文本进行格式化处理。 使用它的两个地方, 一个是插值表达式, 一个是v-bind
1. 全局过滤器
Vue.filter("add", function(v) {
return v < 10 ? "0" + v : v
})
{{33 | add}}
2. 本地过滤器( 和methods同级)
filter: {
add: function(v) {
return v < 10 ? "0" + v : v
}
}
六十五、插槽Slot
1. 占位符 2. slot 名称位置( template) 3. 子组件传值 v-slot = 'slotProps'
六十六、Vue 中 delete 和 Vue.delete 删除数组
delete 只是被删除的元素变成了empty/undefined 其他的元素的键值还是不变。Vue.delete 直接删除了数组 改变了数组的键值 。
let arr = [1, 2, 3];
delete arr[1];
console.log(arr); // 输出:[1, undefined, 3]
let arr = [1, 2, 3];
vue.$delete(arr, 1);
console.log(arr); // 输出:[1, 3]
六十七、内置指令和自定义指令
v-model的双向绑定原理
v-model 是一个语法糖,结合了 v-bind 和 v-on 两个指令的功能。v-bind:用于将表单控件的值绑定到 Vue 实例的数据属性上。v-on:用于监听表单控件的输入事件,然后将事件的新值更新到 Vue 实例的数据属性上。
v-html、 v-bind、 v-on、 v-model......
// 自定义指令
Vue.directive("focus", {
inserted: function(el) {
el.focus()
}
})
开发过自定义指令: 拖拽,复制,长按,防抖,节流,过滤日期。
六十八、vue2 $set 和 $nextTick
只有vue2 有 $set 方法用于向响应式对象添加响应式属性,并确保这个新添加的属性是响应式的。Vue.js在初始化实例时会将data中的属性转换为getter/setter,从而使其变成响应式的。但是,对于新增的属性,Vue无法自动实现响应式,因此需要使用$set方法来手动添加响应式属性。
$nextTick方法主要用于确保在DOM更新完成后执行特定的回调函数。
六十九、VueX(Vue2) 和 Pinia(Vue3)
VueX: state 存储变量; getters state的计算属性; mutations 提交更新数据的方法; actions 和 mutations 差不多, 他是提交mutations来修改数据, 可以包括异步操作
Pinia:state 存储变量; getters state的计算属性; actions(包含 mutations)
七十、hooks详解
Vue3 Hooks是一种函数式的API,允许我们在组件之间复用状态逻辑。这些函数包括setup、reactive、ref等,以及一系列生命周期函数如onMounted、onUpdated等。
// 1 获取宽高hooks,可以变成获取不同设备的hooks
// hooks/xxx.js
import{ ref, onMounted, onUnmounted } from 'vue'
export function useWindowResize(){
const width = ref(window.innerWidth);
const height = ref(window.innerHeight);
const handleResize=()=>{
width.value = window.innerWidth;
height.value = window.innerHeight;
}
onMounted(() => {
window.addEventListener('resize',handleResize)
});
onUnmounted(() => {
window.removeEventListener('resize',handleResize)
});
return { width, height }
}
// 使用就更简单了,只需要调用这个钩子就可以获得 window 的宽度和高度。
// xxx.vue
import useWindowResize from "../hooks/xxx"
const { width, height } = useWindowResize()
// 2 剪切hooks
// hooks/xxx.js
function copyToClipboard(text){
// 这个 input 最好变成参数传递进来 id 或者 class 名
let input = document.createElement('input');
input.setAttribute('value',text);
document.body.appendchild(input);
input.select();
let result= document.execCommand('copy');
document.body.removechild(input);
return result;
}
export const useCopyToclipboard=()=>{
return(text)=>{
if(typeof text === "string" || typeof text == "number"){
return copyToClipboard(text);
}
return false;
}
}
// 使用xxx.vue
const copyToClipboard = useCopyToclipboard()
copyToClipboard('just copy')
// 3 滚动条滚动到底部hooks
import { onMounted,onUnmounted } from 'vue'
export const useScrollToBottom = (callback= () =>{}) => {
const handleScrolling = () => {
if((window.innerHeight + window.scrollY) >= document.body.scrollHeight){
callback()
}
}
onMounted(() => {
window.addEventListener('scroll',handleScrolling)
});
onUnmounted(() => {
window.removeEventListener('scroll',handleScrolling)
});
}
// 使用xxx.vue
useScrollToBottom(() => {
console.log("到底了")
})
hooks 和 units 区别
1. 表现形式不同: hooks 是在 utils 的基础上再包一层组件级别的东西(钩子函数等); utils 一般用于封装相应的逻辑函数, 没有组件的东西;
2. 数据是否具有响应式: hooks 中如果涉及到 ref, reactive, computed 这些 api 的数据, 是具有响应式的; 而 utils 只是单纯提取公共方法就不具备响应式;
3. 作用范围不同: hooks 封装, 可以将组件的状态和生命周期方法提取出来, 并在多个组件之间共享和重用; utils 通常是指一些辅助函数或工具方法, 用于实现一些常见的操作或提供特定功能。
七十一、TS
JS弱类型语言 TS强类型语言(进行类型约束)
类型推断
类型注解
let str: string = 'abc'
类型断言
let numArr = [1,2,3]; const result = numArr.find(item => item > 2) as number; result * 5
基础类型
let v1:string = 'abc'; let v2:number= 10;
联合类型
let v3:string|number = 10; let v4: 1 | 2 | 3 = 2
数组
let arr:number[] = [1,2,3]; let arr:Array<string> = ['a', 'b', 'c']
元组
let t1:[number,string,number?] = [1,'a']
枚举
enum myEnum(A, B, C); console.log(myEnum.A, myEnump[0]);
函数
function myFn(a:10, b:string, c:boolean, ...d:number[]):number{ return 100 }
接口
interface obj{ name: string, age:number }; const obj1:obj = { name: 'a', age: 10 }
类型别名
type MyUserName = string | number; let a:MyUserName
泛型
function myFn<T>(a:T, b:T):number{ return [a,b] }; myFn<number>(1,2); myFn<string>('a','b')
接口和类型别名
1 都能描述基本对象(描述一个字段,可能有多种属性,只能使用类型别名 type)
interface IPerson{
name: string
age: number
}
type TPerson{
name: string
age: number
}
const person: TPerson = { name: 'i47', age: 18 }
// 描述一个字段,可能有多种属性,只能使用类型别名 type
// 期待描述一个性别,可能是数字或字符串,只能使用类型别名 type
type aaa = string | number
const sex: aaa = 0
2 都能进行类型拓展(interface 用 extends,type 用 & 形成交叉类型)
interface I2d {
x: number
y: number
}
interface I3d extends I2d {
z: number
}
const the3: I3d = {
x: 4, y: 5, y: 6
}
type T2d = {
x: number
y: number
}
type T3d = T2d & {
z: number
}
const the3: T3d = {
x: 4, y: 5, y: 6
}
3 名字相同时的表现(相同的interface会合并,相同的type会报错)
interface I2d {
x: number
}
interface I2d {
y: number
}
=>
interface I2d {
x: number
y: number
}
----------------------------------
type T2d {
x: number
}
type T2d {
y: number
}
=> 报错
泛型是一种在定义函数、类或接口时使用类型参数的方式,以增加代码的灵活性和重用性。
在 TypeScript 中,any
和 unknown
都是用于处理不确定类型的值。
unknown类型比any类型安全,但unknown类型不能调用它的属性和方法。
let m: any = "hello";
console.log(m)
m = 12;
console.log(m)
m = [12, 23];
console.log(m)
m = {
code: 200,
msg: 'success',
fn(): string => {
return "你好! TS"
}
}
console.log(m.code, m.msg, m.fn)
let n: unknown = "hello";
console.log(n)
n = 12;
console.log(n)
n = [12, 23];
console.log(n)
n = {
code: 200,
msg: 'success',
fn(): string => {
return "你好! TS"
}
}
console.log(n.code, n.msg, n.fn) // 报错
七十二、webSocket
webSocket和http 一样都是基于TCP的可靠性传输协议,都是应用层协议。webSocket是双向传输,http只能单向传输
前端实现WebSocket心跳机制的方式主要有两种:1. 使用setInterval定时发送心跳包。2. 在前端监听到WebSocket的onclose()事件时,重新创建WebSocket连接
重连机制: 实现WebSocket的重连机制,当连接断开时,自动尝试重新连接服务器。
心跳机制: 定时向服务器发送心跳消息,以保持连接的活跃状态。如果长时间没有收到服务器的响应,可以视为连接断开,并进行重连操作。
断线提示: 当连接断开时,及时向用户提示网络异常,并提供重新连接的按钮或提示。
定时器心跳模式不准,处理方式
JavaScript单线程特性: JavaScript是单线程的,当浏览器执行其他耗时任务时,定时器可能无法准确按时触发。
使用requestAnimationFrame: 如果定时器主要用于动画或页面渲染方面的操作,可以考虑使用requestAnimationFrame API,它更加适合在每一帧绘制之前执行任务。
使用Date对象计算时间差: 可以通过Date对象获取当前时间,并计算与上一次定时器触发的时间差,以此来调整下一次定时器的触发时间,从而减小误差。
流式WebSocket
JS WebSocket 的断线处理方式和代码
1. 监听 onclose
事件
当 WebSocket 连接因为某些原因关闭时,onclose
事件会被触发。你可以在这个事件处理器中添加重新连接的逻辑。
let socket = new WebSocket('ws://yourserver.com/socket');
socket.onclose = function(event) {
console.log('Connection closed. Code:', event.code, 'Reason:', event.reason);
// 尝试重新连接
setTimeout(function() {
connect();
}, 5000); // 延迟5秒后重连
};
function connect() {
socket = new WebSocket('ws://yourserver.com/socket');
socket.onopen = function() {
console.log('Connected to the server.');
};
socket.onmessage = function(message) {
console.log('Received:', message.data);
};
socket.onerror = function(error) {
console.error('Error detected: ', error);
};
}
2. 监听 onerror
事件
虽然 onerror
通常用于报告在建立连接过程中的错误,但也可以用来作为额外的断线检测手段。
socket.onerror = function(error) {
console.error('WebSocket error observed:', error);
// 可以在这里添加重新连接的逻辑
};
3. 心跳检测
为了确保连接的有效性,可以定期发送心跳消息给服务器。如果一段时间内没有收到服务器的响应,则认为连接已经断开,并尝试重新连接。
const HEARTBEAT_INTERVAL = 15000; // 每15秒发送一次心跳
let heartbeatInterval;
function startHeartbeat() {
heartbeatInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: "heartbeat" }));
}
}, HEARTBEAT_INTERVAL);
}
function stopHeartbeat() {
clearInterval(heartbeatInterval);
}
socket.onopen = function() {
console.log('Connected to the server.');
startHeartbeat(); // 连接成功后开始心跳检测
};
socket.onclose = function() {
console.log('Connection closed.');
stopHeartbeat(); // 断线后停止心跳检测
// 重新连接逻辑...
};
socket.onmessage = function(message) {
const data = JSON.parse(message.data);
if (data.type === "heartbeat") {
console.log("Server heartbeat received");
} else {
console.log('Received:', message.data);
}
};
4. 使用库
对于更复杂的应用场景,考虑使用成熟的 WebSocket 库,如 Socket.io 或者 SockJS,这些库内置了重连机制和心跳检测功能,能够更好地处理断线情况。
七十三、微前端
postMessage 用法(可以给iframe传值,将message传递给指定窗口)
postMessage(data,origin)方法接受两个参数
// 发送消息
let iframeWindow = document.getElementById("myframe").contentWindow;
iframeWindow.postMessage("aaa", "http://www.wrox.com");
1. data:
- 要传输的数据,推荐使用字符串格式,其他格式的浏览器的兼容性不好
- 如果要传输结构化数据,可以通过JSON.stringify处理,接收时用JSON.parse转换回来
2. origin:
- 指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写
- 如果不想限制接收目标源:可以传 "*"
- 如果目标与当前窗口同源:可以传 "/"
// 接收消息
window.addEventListener("message", (event) => {
// 判断源路径是否来自预期发生者
if (event.origin.includes("http://www.wrox.com")) {
// 获取传过来的数据
console.log(event.data)
// 再传回去一条消息
event.source.postMessage("已收到消息", "p2p.wrox.com")
}
})
// event 包含3个参数
- event.data: 接收到的数据
- event.origin: 发送消息的文档源
- event.source: 发生消息的文档中window对象的代理
乾坤
在前端微前端架构中,如果你使用了乾坤(Qiankun)作为主应用的基座,以及多个子应用,可以通过以下方式在基座和子应用之间传递数据:
1. 基座向子应用传值:
通过 props 传递: 如果你在使用乾坤时,将子应用视为组件加载,你可以通过 props 的方式将数据传递给子应用。在基座中的配置中,可以通过 props 传递数据给子应用。
通过自定义事件或消息总线: 在基座中使用自定义事件或消息总线(例如 Event Bus 或者 Pub/Sub模式)来发布事件,子应用在初始化时订阅这些事件,并在事件被触发时获取数据。
2. 子应用向基座传值:
通过事件通知基座: 子应用可以通过触发事件的方式来通知基座,基座监听这些事件并获取数据。使用乾坤提供的通信API: 乾坤提供了initGlobalState和onGlobalStateChange等方法,可以在基座中初始化一个全局状态,并监听全局状态的变化,子应用可以修改全局状态,从而实现基座与子应用之间的通信。
3. 子应用之间传值:
通过事件总线: 子应用之间可以使用事件总线或者状态管理库(如 Vuex、Redux 等)来共享数据,一个子应用发布事件,另一个子应用监听事件并获取数据。使用状态管理库: 如果子应用之间需要共享状态,可以使用状态管理库来管理共享状态,确保不同子应用之间的数据一致性。
总的来说,基座和子应用之间的数据传递可以通过 props、自定义事件、消息总线、乾坤提供的通信API等方式实现,具体方法取决于你的应用场景和需求
使用微前端(qiankun),解决样式污染问题
方案一:使用插件转换子应用class的前缀名称,尽量不和主应用重名(不推荐)
change-prefix-loader或者postcss-change-css-prefix
方案二:使用qiankun自带的沙箱隔离(不推荐)
start({
sandbox: {
// 开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 [shadow dom]节点,从而确保微应用的样式不会对全局造成影响。
strictStyleIsolation: true,
// 设置实验性的样式隔离特性,即在子应用下面的样式都会包一个特殊的选择器规则来限定其影响范围
experimentalStyleIsolation: true
}
})
使用qiankun自带的沙箱隔离,在主应用中start函数中,通过配置sandbox做到样式隔离。但这样直接使用沙箱隔离会遇到很多坑。
坑:当启用strictStyleIsolation时,因为某些UI框架(例如element、ant-design等)可能会生成一些弹出框直接挂载到主应用的document.body下,此时由于脱离了shadow tree。会导致页面报错。
七十四、虚拟 DOM、diffing 算法、key
虚拟 DOM, 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是内部在用,无需真实 DOM 上那么多的属性。本质是object类型的对象(一般对象)。虚拟DOM最终会被转化为真实DOM,呈现在页面上。
diffing 算法,对于页面的更新,内部会调用diffing算法,将旧的虚拟dom和新的虚拟dom进行一层一层的节点比较,如果节点相同就不重新渲染到真实dom,如果不相同就重新渲染。最小的对比单位是一个节点。
key的作用,key作为节点的表示,在节点发生更新的时候起着重要作用。在状态发生改变后,新旧虚拟dom发生比较,先比较新dom中是否有相同key值的节点,如果有则进行比较节点,如果没有,则直接渲染新的节点。 key属性是DOM元素的唯一标识
最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
禁止使用整个 item,会造成性能问题
七十五、Vue2 的实现原理
vue.is 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤
第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和 getter这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁
第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过Observer来监听自己的 model数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher搭起 Observer和 Compile 之间的通信桥梁,达到数据变化 ->视图更新;视图交互变化(input)-> 数据 model 变更的双向绑定效果。
Model(模型) View(视图) ViewModel(视图模型)
七十六、vue3 proxy
一、defineProperty 是对属性进行劫持,proxy是代理整个对象
二、defineProperty 无法监听对象新增属性,Proxy可以(所以vue2用到了 $set)
三、defineProperty 无法监听对象删除属性,Proxy可以
四、defineProperty 无法监听数组下标改变值的变化,proxy 可以且不需要对数组的方法进行重写
vue2和vue3已实现数据响应式来更新DOM了,为什么还有diff算法?
1 性能优化:直接操作真实 DOM 是非常昂贵的,而虚拟DOM 可以在内存中快速进行比较和计算差异。Diff算法帮助减少了更新操作的次数和范围,从而提升了页面渲染的性能。
2 批量更新:Diff算法能够将多次 DOM 更新操作合并为一次,避免了频繁的 DOM 操作,减少了浏览器的重排和重绘。
3 Diff算法可以智能地比较新旧DOM树的变化,只更新必要的部分,从而提高了更新效率。
七十七、vue2 和 vue3 的区别
1.双向数据绑定的原理不同
2.生命周期的不同
3.组件、指令和插槽使用方式都不同
4.没有this, 通过hooks的方式,设置选项式API转变组合式API
5.对TS的支持,diff算法的优化,性能提升
七十八、Vue3 组合式Api及其作用
reactive 和 ref 是用来创建响应式数据的函数。
ref 用于创建一个包含单一值的响应式引用,可以通过 .value 属性访问其值。
reactive 用于创建一个包含多个属性的响应式对象。
toRefs 用于将一个响应式对象转换为普通对象,对象的每个属性都被包装成 ref,使得对象的属性可以像 ref 一样使用。
computed 用来创建计算属性,依赖于响应式数据,并在依赖数据更新时自动重新计算其值。
watch 用来监视指定的响应式数据或计算属性,并在其变化时执行特定的操作。
watchEffect 会立即执行一个函数,并响应其内部响应式数据的变化。
生命周期钩子,路由等
七十九、简述 Vue 单页面和传统的多页面区别
单页面应用(SPA):通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html,is,css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端
多页面(MPA):指一个应用中有多个页面,页面跳转时是整页刷新
单页面的优点:用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点 spa 对服务器压力较小;前后端分离;页面效果会比较炫酷(比如切换页面内容时的专场动画)。
单页面缺点:不利于 seo;导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理);初次加载时耗时多;页面复杂度提高很多
vue单页面不利于seo,改成多页面为什么也不利于seo?
因为Vue是需要在创建前生命周期调用后才开始渲染页面的
八十、vue项目性能优化
1、v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if
2、防止内部泄露,组件销毁后把全局变量和时间销毁
3、图片、路由、懒加载,第三方插件的按需加载
4、防抖、节流的运用
5、服务端渲染 SSR or 预渲染(由于浏览器在渲染出页面之前,需要先加载和解析相应的 html、css 和 js 文件,为此会有一段白屏的时间,可以添加loading,或者骨架屏幕尽可能的减少白屏对用户的影响体积优化)
注意:SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。SSR有着更好的SEO、并且首屏加载速度更快等优点。
6、减少不必要的请求
7、使用插件等进行打包优化
八十一、webPage 和 Vite
Loader 和 Plugin
功能不同
Loader: Loader 主要用于处理文件类型的转换和处理,比如将 ES6/ES7 代码转换成ES5 代码,将LESS/SASS/CSS 文件转换成浏览器可识别的CSS 文件等
Plugin: Plugin 主要用于在打包过程中做一些额外的处理工作,比如文件压缩、代码分离、资源优化、生成 HTML 文件等。
作用范围不同
Loader 是针对于每个文件进行处理的,每个文件都会经过 Loader 进行转换处理,因此 Loader 的作用范围比较小。
Plugin 是针对于整个项目进行处理的,它们能够修改打包的结果、优化打包过程、生成文件等。
VITE 浏览器他只认识html,css,js 企业级项目里都可能会具备哪些功能 1.typescript:如果遇到ts文件我们需要使用tsc将typescript代码转换为js代码 2.React/vue:安装react-compi1er/vue-complier,将我们写的jsx文件或者.vue文件转换为render函数 3.less/sass/postcss/component-style:我们又需要安装less-1oader,sass-loader等一系列编译工具 4.语法降级:babe1--->将es的新语法转换旧版浏览器可以接受的语法 5.体积优化:ugl1fyjs--->将我们的代码进行压缩变成体积更小性能更高的文件 6.等等 稍微改第一点点东西,非常麻烦 将App.tsx--->tsc--->App.jsx--->React-complier--->js文件 有一个东西能够帮你把tsc,react-compiler,1ess,babel,ug11fyjs全部集成到一起 我们只需要关心我们写的代码就好了 我们写的代码一变化--->有人帮我们自动去tsc,react-compiler,1ess,babel,uglifyjs全部挨个走一遍 --> 转换成 js 这个东西就叫做**构建工具** 一个构建工具他到底承担了哪些脏活累活 1.模块化开发支持:支持直接从node_modules里引入代码+多种模块化支持 2.处理代码兼容性:比如babe1语法降级,1ess,ts 语法转换(**不是构建工具做的,构建工具将这些语法对应的处理工具集成进来自动化处理**) 3.提高项目性能:压缩文件,**代码分割** 4.优化开发体验 构建工具会帮你自动监听文件的变化,当文件变化以后自动帮你调用对应的集成工具进行重新打包,然后再浏览器重新运行(整个过程叫做热更新,hotreplacement) 市面上主流的构建工具有哪些: webpack vite parcel esbuild ro1lup grunt gulp 起因:我们的项目越大---->构建工具(webpack)所要处理的js代码就越多 [跟webpack的一个构建过程(工作流程)有关系)] 如果一旦要改 那么将会动到webpack的大动脉, webpack支持多种模块化 // ----- js ----- // index.js // 这一段代码最终会到浏览器里去运行 const lodash =require("lodash");// commonjs 规范 import Vue from "vue"; // es6 module //webpack是允许我们这么写的 // ----- js ----- webpack的编译原理,AST 抽象语法分析的工具 分析出你写的这个js文件有哪些导入和导出操作构建工具是运行在服务端的 // ----- js ----- //webpack的一个转换结果 const lodash = webpack_require("lodash"); const vue = webpack_require("vue"); // ----- js ----- // 因为webpack支持多种模块化,他一开始必须要统一模块化代码,所以意味着他需要将所有的依赖全部读一遍 vite会不会直接把wcbpack干翻,vte是基于es modules的,侧重点不一样,webpack更多的关注兼容性,而vite关注浏览器端的开发体验 然而,当我们开始构建越来越大型的应用时,需要处理的 javaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。 我们开始遇到性能瓶颈- 使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器即使使用 HMR(热更新), 文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。 我们敲了 yarn create vite 1,帮我们全局安装一个东西:create-vite(vite的脚手架) 2,直接运行这个create-vite bin目录的下的一个执行配置 create-vite和vite的关系是什么呢? create-vite内置了vite 就好比 使用vue-cli 会内置 webpack webpack/vite 预设:买房子 毛坯房(我们的工程)买沙发,做装修,修各个厕所,埋线,等等 我们自己搭建一个项目:下载vite,vue,post-css,less,babel vue-cli/create-vite (开发商)给我们提供已经精装修的房子模板:帮你把rgact/vue都下好了,同时他还帮你把配置调整到了最佳实践 给你一套精装修模板:下载vite,vue,post-css,less,babel好了,并且给你做好了最佳实践的配置 vite 开发环境 测试环境 生产环境 配置 // ********** vite.config.js 后端 ********** import { defineconfig } from "vite"; import viteBaseConfig from "./vite.base.config"; import viteDevConfig from "./vite.dev.config"; import viteProdConfig from "./vite.prod.config"; export default defineConfg( config:({ command :"build"|"serve" })=> { if(command === "build"){ // 代表生产环境 }else{ // 代表开发环境 } } // 上述精简 if else 模式 为 策略模式 const envResolver = { "build": () => ({ ...viteBaseConfig, ...viteProdConfig }), "serve": () => ({ ...viteBaseConfig, ...viteDevConfig }), } export default defineConfg( config:({ command :"build"|"serve" })=> { return envResolver[command] }) // ********** vite.config.js 后端 ********** // ********** .env.development 前端 ********** ENV_APP_KET = 110 ENV_BASE_URL = http://dev.api/ // ********** .env.production 前端 ********** ENV_APP_KET = 112 ENV_BASE_URL = http://pro.api/ // ********** vite.base.config.js 前端 ********** import { defineconfg } from "vite"; export default defineconfig( config: { optimizeDeps:{ exclude:[], // 将指定数组中的依赖不进行依赖预构建 }, envPrefx:"ENV_", // 默认为 VITE_ }); // ********** request.js 前端 ********** console.log("import.meta.env", import.meta.env) // ********** package.json 前端 ********** "scripts": { "dev": "vite", "build": "vite build", "test": "vite --mode test", } 在vite中处理css vite天生就支持对css文件的直接处理 vite在读取到main.js中引用到了Index.css 直接去使用fs模块去读取index.css中文件内容 直接创建一个style标签,将index.css中文件内容直接copy进style标签里 将style标签插入到index.html的head中 将该css文件中的内容直接替换为js脚本(方便热更新或者csS模块化),同时设置Content-Type为js 从而让浏览器以JS脚本的形式来执行该css后缀的文件 场景: 一个组件最外层的元素类名一般取名: .wrapper 一个组件最底层的元素雷明明我们一般取名: .footer 你取了footer这个名字,别人因为没有看过你这个组件的源代码,也可能去取名footer这个类名 最终可能会导致样式被覆盖(因为类名重复),这就是我们在协同开发的时候很容易出现的问题 cssmodule就是来解决这个问题的 原理:基于node module.css(module是一种约定,表示需要开启css模块化) 他会将你的所有类名进行一定规则的替换(将footer 替换成_footer_i22st_1),同时创建一个映射对象 将替换过后的内容塞进style标签里然后放入到head标签中(能够读到index.html的文件内容),替换成JS脚本 WebPack WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。 传统的项目,使用 script 引入 css 和 js 等,需要引入整个文件。webpack 引入了 ES6 的import和export loader:文件转换器。例如把es6转为es5,scss、less 转为css等 loader 运行在打包文件前,plugins 运行在整个编译周期。 常见 Loader babel-loader:把es6转成es5;css-loader:加载css,支持模块化,压缩,文件导入等特性; style-loader:把css代码注入到js中,通过dom操作去加载css; eslint-loader:通过Eslint检查js代码; image-loader:加载并且压缩图片晚间; plugin:扩展webpack功能的插件,打包优化。在webpack构建的生命周期节点上加入扩展hook, 添加功能。在webpack运行的生命周期中会广播出许多事件, plugin 会监听这些事件,在合适的时间通过webpack提供的 API 改变输出结果。 常见 Plugin CleanWebpackPlugin:在每次构建前清理打包目录,避免旧文件对新文件的影响。 UglifyJsPlugin:压缩JavaScript代码,减小文件体积,提高应用程序的性能。 webpack-bundle-analyzer:可以直观地分析打包出的文件有哪些,及它们的大小、占比情况、各文件 Gzipped 后的大小、模块包含关系、依赖项等,对应做出优化,从而帮助提升代码质量和网站性能。类似小程序 Vite 快速的冷启动,按需编译,不用等待整个项目编译完成。
八十二、场景题
购物车页面的制作逻辑
拿到data,for循环设置状态,async await,根据图片名称异步调用图片,点击勾选时更改状态,利用 Computed 进行计算,删除购物利用状态和过滤器进行操作。
Vue首页加载优化
懒加载路由组件 异步组件结合Suspense 减少首页组件数量 图片优化 代码优化 缓存 服务器端渲染
一些多端兼容问题
1 手机端快速向下滚动时,IOS浏览器的地址栏和任务栏会隐藏;快速向上滚动时,会显示;缓慢滚动不改变;此时点击弹出层时,就会出现各种各样的页面布局影响。
解决方法:在打开弹出层时把滚动条位置设置为0的基础上加上 1. 弹出层不占满手机大小 2. 弹出层的大小随手机的变化而变化
2 小程序谷歌地图 web-view 传值时,H5和app 无法使用同一个方法: H5 无法使用 web-view 的 @message 方法,而 app 无使用 web-view 存储区。
解决方法:分开页面跳转,进行不同的操作。H5 直接使用 本地储存进行地址传值。app 则通过 web-view 自身的 @message 进行传值
uniapp分包小程序
1 配置manifest.json,'optimization':{'subPackages':true}
2 在pages.json中新建数组'subPackages',数组中包含两个参数:1.root:为子包的根目录,2.pages:子包由哪些页面组成,参数同pages;
3 注意:主包和分包是不能再同一目录下,在构建uniapp项目时,可以考虑一下目录结构,以便后期进行分包;
-
小程序分包:是将小程序的代码和资源分割成多个包,主要用于降低主包体积,提升加载速度。适合大型小程序,能按需加载。
-
小程序独立分包:是指某个分包可以独立于主包运行,不需要主包中的代码和资源。适用于功能相对独立且体积较大的模块,比如复杂的游戏或工具。
-
小程序分包预加载:允许在用户进入某个页面之前,提前加载该分包的资源,确保用户体验流畅。常用于用户可能频繁访问的功能,以减少等待时间。
Vite -构建优化 - 分包策略 + 打包压缩
分包策略就是把不会常规更新的文件【类似node_module,还有引入的第三方依赖(lodash工具包)】,单独打包处理。
// 在 vite.config.js 进行配置
import { defineConfig } from "vite"
export default defineConfig({
// 主要看的就是这个 build 的部分
build:{
minify:false, // 这个是设置打包后的文件不压缩,方便查看
rollupOptions:{
output:{ // 打包输出的配置
manualChunks:(id)=>{
// 这个ID,就是所有文件的绝对路径
if(id.includes("node_modules")){
// 因为 node_modules 中的依赖通常是不会改变的
// 所以直接单独打包出去
// 这个return 的值就是打包的名称
return "vendor";
}
}
}
}
}
})
// 以后打包只打包我们写文件,不打包重复文件提高效率。
vite 打包压缩配置
需要一个插件 : vite-plugin-compression2
/**
* 文件压缩的配置
*/
import { defineConfig} from "vite"
import { compression } from 'vite-plugin-compression2'
export default defineConfig({
plugins:[
// 就是使用这个插件实现的文件压缩
compression({
threshold:2000, // 设置只有超过 2k 的文件才执行压缩
deleteOriginalAssets:false, // 设置是否删除原文件
skipIfLargerOrEqual:true, // 如果压缩后的文件大小与原文件大小一致或者更大时,不进行压缩
// 其他的属性暂不需要配置,使用默认即可
})
]
})
前后端加密
传递重要参数加密、登录加密
密钥固定加密:前后端无需传递密钥,只需传递加密完的字符就行了
动态密钥加密:前端生成随机密钥,将加密好参数和密钥一起发送给后端
Crypto.JS:是一个流行的JavaScript加密库,支持多种加密算法,包括AES,DES,RC4等加密方式或者md5,SHA等哈希散列。
import CryptoJS from 'crypto-js';
// AES加密 要加密的内容 密钥
const ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();
console.log(ciphertext);
// AES解密 要解密的内容 密钥
const bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
const originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log(originalText);
阿里巴巴矢量库图标导入
https://blog.youkuaiyun.com/weixin_59528719/article/details/136343957
按照官方配置后,类名就能输出对应的图标(借助before)
.icon-test:before{ content: '\e8c5'}
前端优化
前端分页
你需要从后端或其他数据源获取完整的数据列表。设置当前页码和每页显示的条目数,利用计算属性来计算要展示的数据
滚动分页
监听滚动事件:在Vue组件中,可以使用window对象的scroll事件来监听用户滚动。计算滚动位置:确定用户是否滚动到页面底部,通常是通过比较滚动位置和页面高度的差异来实现的。加载更多数据:当用户滚动到底部时,触发加载更多数据的操作,例如获取下一页的内容。
数据懒加载
当用户未滑到不可见区域时,数据已经请求回来了,数据较多情况下是必然发送很多次无效的请求,我们想要效果应该是等所在模块进入到 可视区 再发请求获取数据,这就是数据懒加载;
八十三、node 包管理工具(npm,cnpm,nvm,pnpm,yarn)
买个服务器作为代码仓库(registry),在里面放所有需要被共享的代码 发邮件通知 jQuery、Bootstrap、Underscore 作者使用 npm publish 把代码提交到 registry 上,分别取名 jquery、bootstrap 和 underscore(注意大小写) 社区里的其他人如果想使用这些代码,就把 jquery、bootstrap 和 underscore 写到 package.json 里,然后运行 npm install ,npm 就会帮他们下载代码 下载完的代码出现在 node_modules 目录里,可以随意使用了
npm 是 Node.js 的默认包管理工具,用于安装、管理和发布 JavaScript 模块。它是一个强大的工具,支持管理项目依赖、执行脚本、发布包等功能。
cnpm(China npm) 是 npm 的镜像,专为中国用户优化,使用 cnpm 可以加速 npm 的包下载和安装。使用的是国内的镜像源。
nvm (Node Version Manager)是 Node.js 的版本管理工具,允许用户在同一台机器上安装和切换多个 Node.js 版本。可以用于在不同项目中使用不同的 Node.js 版本,同时避免全局环境的版本冲突。
pnpm 是一个快速、节省磁盘空间的包管理器,与 npm 和 yarn 不同,它采用硬链接和符号链接的方式共享依赖。可以减少依赖的重复安装,节省磁盘空间,并且速度比传统的 npm 和 yarn 快。
yarn 是 Facebook 开发的包管理工具,旨在解决 npm 的一些性能和安全问题。它使用并行安装和缓存等策略来提高速度,并且支持离线安装和版本锁定等功能。
不要在一个项目上使用多种安装方式,不然会有兼容性报错风险。
八十四、乱七八糟
同源策略是浏览器的安全策略
TCP连接建立时需要经过3次握手,断开时经过4次挥手。
HTTP协议作为应用层协议
小程序的支付 openId 备注 获取用户信息、位置、分享
地图 app_key 苹果打包上线 appleId
使用vue-cli或者vite创建出来的项目是不具备多端兼容的,其中的路由方式、接口请求方式、标签等等。使用taro或uniapp为底层框架,做了多端适配,引入vue,可以做多端系统。
v-for的key值不要绑定为item 为什么,会导致 表格 内嵌入输入框的无法连续输入等问题
Doctype是HTML5的文档声明,通过它可以告诉浏览器,使用哪一个HTML版本标准解析文档。在浏览器发展的过程中,HTML出现过很多版本,不同的版本之间格式书写上略有差异。
如果没有事先告诉浏览器,那么浏览器就不知道文档解析标准是什么?此时,大部分浏览器将开启最大兼容模式来解析网页,我们一般称为怪异模式,这不仅会降低解析效率,而且会在解析过程中产生一些难以预知的bug,所以文档声明是必须的。
严格模式:严格模式是 JavaScript 的一种运行模式,不仅适用于前端开发,也适用于后端和任何 JavaScript 环境中。它的主要目的是增强 JavaScript 引擎的错误检查,并且使一些不安全或不推荐使用的语法在运行时抛出错误,从而提高代码质量和安全性。
在浏览器中,开启严格模式的方法是在 JavaScript 文件或 <script> 标签中的代码开头加上 "use strict"; 或者 'use strict';。
特点: 禁止使用全局变量,必须显式声明变量。严格模式下的 this 是 undefined 而不是全局对象。
混杂模式:页面以宽松向下兼容的方式显示,模拟老式浏览器的行为
script、script async 和 script defer 的区别
1 <script> 标签
普通的 <script> 标签用于引入 JavaScript 文件或内嵌 JavaScript 代码。它的特点是在浏览器遇到 <script> 标签时,会立即下载并执行其中的代码,然后再继续解析 HTML 文档。
2 <script async>
async 属性用于异步加载 JavaScript 文件。异步加载的脚本不会阻止 HTML 解析,而是在加载完成后立即执行,同时继续解析 HTML。
3 <script defer>
defer 属性也用于延迟加载 JavaScript 文件,但是与 async 不同的是,defer 保证脚本的执行顺序与它们在文档中的顺序一致。
阻塞 HTML 解析:普通 <script> 会阻塞 HTML 解析,async 和 defer 不会。
执行上下文类型(3种) 1 全局执行上下文:全局执行上下文是默认的、最外层的执行上下文。它在整个页面生存周期内存在,负责全局变量的声明和函数的执行。 var globalVar = "I am global"; // 全局变量 function globalFunction() { console.log("I am a global function"); } globalFunction(); // 输出:I am a global function 2 函数执行上下文:每当调用一个函数时,都会创建一个新的函数执行上下文。函数执行上下文在函数执行结束后被销毁。 function example() { var localVar = "I am local"; // 局部变量 console.log(localVar); } example(); // 输出:I am local 3 Eval执行上下文:eval函数执行的代码会在一个新的执行上下文中运行,被称为Eval执行上下文。但由于使用eval存在安全和性能问题,应尽量避免使用。 eval("var evalVar = 'I am from eval';"); console.log(evalVar); // 输出:I am from eval 执行上下文的生命周期 执行上下文的生命周期包括两个阶段:创建阶段(Creation Phase)和执行阶段(Execution Phase)。 创建阶段: 1 创建变量对象(VO):根据上下文的类型创建一个空的变量对象。 2 建立作用域链:作用域链是一个指向父级作用域的链表,用于查找变量的值。 3 确定this指向:在全局上下文中,this指向全局对象(如window)。在函数上下文中,this的值取决于函数的调用方式。 4 初始化变量对象:将函数的参数、函数声明和变量添加到变量对象中。 执行阶段: 1 执行代码:按照代码的顺序执行,对变量赋值等操作。 2 访问变量:通过作用域链查找变量的值。 3 执行函数:在函数上下文中,执行函数体内的代码。
作用域: 1 全局作用域(Global Scope): 全局作用域是指在代码的任何地方都能访问到的变量和函数。在浏览器中,全局作用域是 window 对象。 2 函数作用域(Function Scope): 函数作用域是指在函数内部定义的变量和函数只在该函数内部可见。函数外部无法直接访问函数内部的变量或函数。 3 块级作用域(Block Scope)(ES6 引入): 块级作用域指由一对花括号 {} 定义的区域。在块级作用域中定义的变量只在该区域内部可见,常见于 if 语句、for 循环等。 作用域链: 在查找变量时,JavaScript 引擎会从当前执行上下文的作用域开始查找,如果在当前作用域中找不到,就会向上一级执行上下文的作用域查找,直到找到该变量或到达全局作用域(最顶层的执行上下文)为止。这种链式查找的过程就是作用域链。
匿名函数可以作为只用一次,不需要在其他地方使用的回调函数。当处理函数在调用它们的程序内部被定义时,代码具有更好地自闭性和可读性,可以省去寻找该处理函数的函数体位置的麻烦。
post请求两次的原因 POST 请求前发送的 OPTIONS 请求实际上是 HTTP 的一种特性,称为“预检请求”(Preflight request)。 这主要发生在跨域请求(CORS, Cross-Origin Resource Sharing)的场景中,尤其是当请求涉及一些可能不太安全的方法(如 PUT、DELETE 或 POST)或使用了一些自定义的 HTTP 头部时。 预检请求的目的是检查服务器是否允许来自不同源(域、协议或端口)的请求进行某些操作。这样做可以确保客户端在发送实际请求之前,先得到服务器的明确许可。
从输入 url 到页面发生变化的全过程 1 用户输入URL:用户首先在浏览器地址栏中输入想要访问的网站的URL。 2 浏览器解析URL:浏览器内部代码会解析这个URL。它首先会检查本地hosts文件,看是否有对应的域名。如果有,浏览器就会直接向该IP地址发送请求。如果没有,浏览器会将域名发送给DNS服务器进行解析,将域名转换成对应的服务器IP地址。 3 建立TCP连接:浏览器得到IP地址后,会通过TCP协议与服务器建立连接。TCP/IP协议是Internet的基础,它负责确保数据在网络中的可靠传输。这一过程中会进行三次握手,确保双方都已准备好进行通信。 4 发送HTTP请求:TCP连接建立后,浏览器会向服务器发送HTTP请求。这个请求包含了请求行(如GET方法、请求的URI、HTTP版本等)、请求头部(如Accept-Charset、Accept-Encoding等)以及可能存在的请求正文。 5 服务器处理请求:服务器收到请求后,会根据请求的内容进行相应的处理。这可能包括查询数据库、生成动态内容等。 6 发送HTTP响应:服务器处理完请求后,会发送一个HTTP响应给浏览器。这个响应包含了状态行(如HTTP版本、状态码、状态描述等)、响应头部(如Content-Type、Content-Length等)以及响应正文(即实际要显示的页面内容)。 7 浏览器解析和渲染页面:浏览器收到响应后,会解析响应正文中的HTML代码,并下载所需的CSS、JavaScript等资源文件。然后,浏览器会根据这些资源来渲染页面,最终将页面呈现给用户。
web安全攻击方式以及防御 1 SQL注入是一种常见的Web应用安全漏洞,攻击者通过在Web应用的输入参数中注入恶意的SQL代码,从而执行未经授权的数据库操作。这种类型的攻击允许攻击者访问,删除或修改数据库中的数据。 防御方法:使用参数化查询、输入验证和限制数据库权限等方法来防止SQL注入攻击。移除或转义特殊字符,如单引号等,防止恶意SQL代码的执行。为数据库用户分配最小必要的权限,以减少攻击者能够利用SQL注入漏洞对数据库进行的操作。 2 跨站脚本攻击 (XSS)是一种常见的网络安全漏洞,攻击者通过在网页上注入恶意脚本,以获取用户的敏感信息或劫持用户的会话。 防御方法:对用户输入的数据进行严格的验证和过滤,移除或转义潜在的恶意脚本。在把用户输入的数据输出到网页时,进行适当的编码,以防止恶意脚本的执行。对敏感信息进行加密存储和传输,防止泄露。加强身份认证,实施访问控制和权限管理,强化密码策略,强化操作系统和应用程序安全。 3 文件上传漏洞:恶意用户通过应用程序的文件上传功能,将恶意文件(如病毒、木马、恶意脚本等)上传到服务器上,从而导致网站安全漏洞和潜在的安全威胁。 防御方法:严格限制上传文件类型和大小,对上传的文件进行合理的检查和过滤,设定合理的文件权限。在服务器端对上传的文件进行扩展名检查,以确保上传的文件扩展名与其真实类型一致,防止对文件伪装的攻击。
八十五、react
1 react基础
npx create-react-app my-react-app
cd my-react-app; npm start; jsx
函数式组件(主要); 类组件
{} 单大括号 传值; 驼峰 className; Fragment 相当于 template; 事件 onClick
useState 状态处理; const [data, setData] = useState({ title: '标题', content: '内容'}); 状态变更,响应式; setData 需要写全属性
JSX语法,react的v-if和v-for是通过JS判断不同的状态下生成相对应得JSX。
React组件三大类:类组件、函数组件、高阶组件
类组件是React早期版本中使用的组件类型,它基于JavaScript
类实现。类组件通常用于具有复杂状态和生命周期的组件。类组件的优点是可以继承和复用,并且可以访问this关键字。
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
message: 'Hello, World!',
};
}
handleClick = () => {
this.setState({ message: 'Hello, React!' });
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={this.handleClick}>Click me</button>
</div>
);
}
}
export default App;
函数式组件没有状态,没有生命周期方法
import React from 'react';
const App = ({ message }) => {
const handleClick = () => {
return <h1>{message}</h1>;
};
return (
<div>
<h1>{message}</h1>
<button onClick={handleClick}>Click me</button>
</div>
);
};
export default App;
高阶组件是一种特殊的组件类型,它接受一个组件作为参数并返回一个新的组件。高阶组件通常用于封装通用逻辑和功能,如数据处理、路由管理等。高阶组件的优点是可以提高代码复用性和模块化。
import React from 'react';
// 这是一个高阶组件,它接收一个组件作为参数,并返回一个新的组件
const withLoading = (WrappedComponent, isLoading) => {
return class extends React.Component {
render() {
const { isLoading } = this.props;
if (isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...this.props} />;
}
};
};
export default withLoading;
React 的函数式组件在早期版本中并不支持生命周期方法,因为它们本质上是无状态的纯函数。
React Hooks
,函数式组件也可以拥有类似于类组件中的生命周期能力了。
useEffect(() => {
// 初始化阶段 类似 componentDidMount
console.log('Component did mount');
// 初始化阶段 当 prop 或 state 改变时触发
console.log('Prop or state changed');
// 卸载阶段 返回一个清理函数,类似 componentWillUnmount
return () => {
console.log('Component will unmount');
};
}, [propName, stateName]); // 注意空数组表示只在组件挂载和卸载时运行
2 react传值
props传值(父给子传)
事件(回调函数)(子给父传)
context 上下文 createContext .Provider (祖孙组件传值)
事件总线,redux(兄弟组件或其他所有)
3 react的hooks
useState 管理组件内部的状态。
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
useContext 跨组件传递数据,避免"prop drilling"
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button style={{ backgroundColor: theme === 'dark' ? 'black' : 'white' }}>Theme Button</button>;
}
useReducer 管理复杂的状态逻辑
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useEffect 处理组件生命周期,例如数据获取、订阅、手动更改 DOM 等。类似 watchEffect
import { useState, useEffect } from 'react';
function FetchData() {
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
}
fetchData();
}, []);
return (
<div>
<h1>Fetched Data</h1>
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
useMemo 缓存计算结果,优化性能,类似 计算属性
import { useState, useMemo } from 'react';
function ExpensiveCalculation() {
const [number, setNumber] = useState(0);
const doubledNumber = useMemo(() => {
return slowlyDoubleNumber(number);
}, [number]);
return (
<div>
<input type="number" value={number} onChange={(e) => setNumber(parseInt(e.target.value))} />
<p>Doubled number: {doubledNumber}</p>
</div>
);
}
function slowlyDoubleNumber(n) {
// 模拟一个耗时的计算
let i = 0;
while (i < 1000000000) i++;
return n * 2;
}
useCallback 缓存函数引用,优化性能
import { useState, useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
}
function ChildComponent({ onClick }) {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Increment</button>;
}
useRef 获取 DOM 元素引用,保存可变状态
import { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
useLayoutEffect 处理 DOM 操作,需要同步执行
import { useLayoutEffect, useRef } from 'react';
function MeasureElement() {
const elementRef = useRef(null);
useLayoutEffect(() => {
const element = elementRef.current;
console.log('Element width:', element.offsetWidth);
}, []);
return <div ref={elementRef}>This is the element to measure</div>;
}
useImperativeHandle 暴露组件实例方法给父组件
import { forwardRef, useImperativeHandle, useRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input type="text" ref={inputRef} />;
});
function App() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
useDebugValue 为自定义 Hooks 提供调试标签
import { useState, useDebugValue } from 'react';
function useCustomHook() {
const [state, setState] = useState(0);
useDebugValue(`Current state: ${state}`);
// 自定义 Hook 的其他逻辑
return [state, setState];
}
八十六、react 性能优化
复用组件, 同一层级, 同一类型, 同一key值
减少组件的重新render
使用 React.memo, 记忆组件
避免多层级的嵌套组件
懒加载组件
React.PureComponent 或 shouldComponentUpdate
优化 React 中的条件渲染
Suspense
八十七、react 生命周期(挂载、更新、卸载)
挂载(Mounting)
constructor: 构造函数,在组件创建之前被调用。
getDerivedStateFromProps: 在组件创建之前和更新时被调用,用于计算状态。
render: 组件渲染时被调用,必须返回一个React元素。
componentDidMount: 组件挂载到DOM后被调用。
更新(Updating)
shouldComponentUpdate: 在组件接收到新的props或state时被调用,决定是否需要重新渲染。
getDerivedStateFromProps: 同挂载阶段,用于计算状态。
render: 同挂载阶段,组件重新渲染时被调用。
componentDidUpdate: 组件更新后被调用。
卸载(Unmounting)
componentWillUnmount: 组件从DOM中移除之前被调用。
八十八、redux
Store :定义数据state 只读
action :通过action来更新数据
reducer: 纯函数来执行修改
类似vuex和pinia
八十九、Nginx(解决跨域的一种方法)
一款轻量级的Web服务器、反向代理服务器。占用内存少,启动快,高并发能力强,在互联网中广泛应用。
1 正向代理.由于防火墙原因,我们并不能直接访问谷歌、推特、Github等,我们可以借助VPN来实现,这就是一个简单的正向代理的例子。这里你能够发现,正向“代理”的是客户端,而且客户是知道目标的,而目标是不知道客户端是通过VPN访问的。
Client(客户端/你本机) --> Proxy(VPN)--> Server (Google/目标)
2 反向代理 当外网访问百度时,其实会进行一个转发,代理到内网去,这就是所谓的反向代理(Reverse Proxy),即反向代理的是服务器端,而且这一个过程对于客户端而言是透明的。
Client(客户端/你本机) --> Proxy(Nginx)--> Server (百度内部)
正向代理是指客户端通过代理服务器访问外部网络资源的一种方式。
反向代理是指代理服务器接收外部的请求,并将这些请求转发给内部网络中的多个服务器之一,然后将从内部服务器接收到的响应返回给原始请求者。
Nginx的配置文件通常位于/etc/nginx/nginx.conf
,这个文件由多个块(blocks)组成,每个块都包含一些指令(directives)。以下是最基础的Nginx配置文件结构:
# 指定运行Nginx的用户和用户组
user nginx;
# 设置工作进程的数量,auto表示根据CPU核心数自动设置
worker_processes auto;
# 错误日志的位置及记录级别
error_log /var/log/nginx/error.log warn;
# 存放主进程PID的文件路径
pid /var/run/nginx.pid;
events {
# 单个工作进程的最大并发连接数
worker_connections 1024;
}
http {
include /etc/nginx/mime.types; # 包含MIME类型定义文件
default_type application/octet-stream; # 默认的MIME类型
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'; # 自定义访问日志格式
access_log /var/log/nginx/access.log main; # 访问日志的位置及使用的格式
sendfile on; # 开启高效文件传输模式
tcp_nopush on; # 在完成所有HTTP头信息后发送数据
tcp_nodelay on; # 立即发送数据,不等待更多的数据以填充网络包
keepalive_timeout 65; # 保持连接的时间
# 负载均衡配置示例
upstream myapp1 {
server 192.168.1.1:8000;
server 192.168.1.2:8000;
}
server {
listen 80; # 监听的端口
server_name example.com; # 服务器域名或IP地址
# 处理静态文件请求
location / {
root /usr/share/nginx/html; # 网站根目录
index index.html index.htm; # 默认索引页面
}
# 反向代理配置
location /app/ {
proxy_pass http://myapp1; # 后端应用地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
error_page 404 /404.html; # 定制错误页面
location = /404.html {
internal; # 仅内部重定向可用
}
}
}
以上配置最重要的就是以下代码:意思是监听你本地的80端口,当这个端口下的请求路径包含 /app 时,则将改请求路径变成 http://myapp1
server {
listen 80; # 监听的端口
server_name example.com; # 服务器域名或IP地址
# 处理静态文件请求
location / {
root /usr/share/nginx/html; # 网站根目录
index index.html index.htm; # 默认索引页面
}
# 反向代理配置
location /app/ {
proxy_pass http://myapp1; # 后端应用地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
error_page 404 /404.html; # 定制错误页面
location = /404.html {
internal; # 仅内部重定向可用
}
}
Lodash
VUE项目每次打包的版本号是可以配置不同编码(哈希值)的。这样的话不用线上强制刷新。
内存泄漏位置定位
打开开发者工具,选择 Performance(性能) 面板,在顶部的 capture 字段勾选 Memory(内存),或直接选择 Memory 面板。内存分析工具中,可以手动触发一次内存快照,记录当前时刻的内存使用情况。
前端中断请求的方式 AbortController
九十、vue中的data为什么是一个函数
1 隔离作用域 2 实例化多个组件
当组件被复用时,如果data是一个对象,那么所有的组件实例将共享同一个数据对象,这意味着一个组件的修改会影响到其他组件。
所以把data设置为工厂函数,数据不会相互影响。
九十一、浏览器乱码原因是什么?如何解决?
服务器在发送响应头时指定了错误的字符编码,或者没有指定字符编码。
解决:确保服务器在发送响应头时正确设置了 Content-Type
,包含正确的字符编码信息。例如:
HTML文档中没有使用 <meta charset="UTF-8">
或者使用了错误的字符编码声明。
nginx、数据库配置 utf-8
九十二、懒加载和预加载
懒加载是指延迟加载页面上的某些资源,直到它们真正需要时才进行加载。通常用于图片、视频或其他非关键资源,以减少初始页面加载时间和带宽消耗。
预加载是指在页面加载过程中提前加载某些资源,以便在用户需要这些资源时能够快速访问。这些资源可以是图片、字体、CSS 文件、JavaScript 文件等。
九十三、DocumentFragment
DocumentFragment
是一个轻量级的、无父节点的 DOM 的节点类型,它不包含在文档树中,因此对它的操作不会引起页面的重新渲染或回流。
直接操作 DOM
const container = document.getElementById('container');
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
container.appendChild(div);
}
使用 DocumentFragment
const container = document.getElementById('container');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment);
使用 DocumentFragment
可以显著减少页面的回流和重绘次数,提高性能。
九十四、Vite打包
vue底层是rollup 和 esbuild在起作用
npm run build 后 打包生成 dist 文件,给后端部署
1 配置打包文件和打包后位置
默认是
vite.config.ts 中的配置 rollupOptions 中 output 设置新的文件位置
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
entryFileNames: 'js/[name].[hash].js',
chunkFileNames: 'js/[name].[hash].js',
assetFileNames(assetInfo){
if(assetInfo.name.endsWith('.css')){
return 'css/[name].[hash].css'
}
if(['.png', '.jpg', '.jpeg', '.wenp', '.svg', '.git'].some(ext => assetInfo.name.endsWith(ext))){
return 'img/[name].[hash].[ext]'
}
return 'assets/[name].[hash].[ext]'
}
}
}
}
})
2 配置分包,控制每次build的时候进行包的那些打包和那些不打包
分包策略 就是把不会常规更新的文件,单独打包处理。
vite 在进行打包的时候,会在文件名中添加一个hash值,这个hash值与文件内容有关,当文件内容发生变化时,这个hash值就会发生变化。 之所以使用这个hash值的方式,就是为了让浏览器能够在文件内容更新时及时地去重新请求新的资源。
manualChunks
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
// manualChunks: {
// aaa: ['lodash', 'vue'],
//}
manualChunks(id){
// 把 node_modules 下的不需要重复打包的文件 分包成 vendor
if(id.includes('node_modules')){
return 'vendor'
}
console.log(id)
}
}
}
})
配置打包后的文件名,处理浏览器硬性清除缓存问题。打包配置HASH后缀
一百
1 请求的封装包括不同环境
.env 判断不同坏境:
开发(.env.development)、正式(.env.production)、测试(.env.test)
NODE_ENV = development/production/test
VUE_APP_API_URL = 'http://*****'
VUE_APP_SERVER_URL = '/api'
// 创建axios实例
const request = axios.create({
baseURL: getBaseUrl(),// 所有的请求地址前缀部分(没有后端请求不用写)
timeout: 80000, // 请求超时时间(毫秒)
// headers: {
// 设置后端需要的传参类型
// 'Content-Type': 'application/json',
// 'token': x-auth-token',//一开始就要token
// 'X-Requested-With': 'XMLHttpRequest',
// },
})
// request拦截器(请求拦截)
request.interceptors.request.use(
config => {
// 如果你要去localStor获取token,(如果你有)
// let token = localStorage.getItem("x-auth-token");
// if (token) {
//添加请求头
//config.headers["Authorization"]="Bearer "+ token
// }
return config
},
error => {
// 对请求错误做些什么
Promise.reject(error)
}
)
// response 拦截器(响应拦截)
request.interceptors.response.use(
response => {
// 对响应数据做点什么
return response.data
},
error => {
// 对响应错误做点什么
//响应错误
let message = "";
if (error.response && error.response.status) {
const status = error.response.status;
switch (status) {
case 404:
message = "请求地址出错";
break;
default:
message = "请求失败";
}
return Promise.reject(error);
}
return Promise.reject(error);
}
)
总结:配置.env文件设置不同环境下的请求地址,创建axios实例,设置请求头,请求地址和请求超时时间限制。设置请求拦截器,关于一些接口,判断系统是否存在token做相对应处理,设置响应拦截,关于接口返回的状态码结合当前组件库做出对应的提示。
2 多数据前端分页,多表单处理方式,图片懒加载
前端分页:调接口拿到数据,存放所有数据的数组,存放当前页面数据的数组,设置keyWord,page,size等查询参数。
state.showList = state.list.filter(item => item.name.includes(state.keyWord)).slice((state.currentPage - 1) * state.pageSize, state.currentPage * state.pageSize)
state.total = state.list.filter(item => item.name.includes(state.keyWord)).length
多表单:通用的数据使用VueX存储,分页展示,组件Suspense异步加载、延迟加载loading,扩展数据懒加载
图片懒加载:现在很多组件库都可以直接加 lazy 设置图片懒加载。
图片懒加载 监听图片是否进入到可视区,正式进入可视区之后将图片url数据交给img标签的src属性。
VueUse 是一个基于 Vue 3 的插件库。
VueUse 提供了大量的功能性钩子函数和工具函数,包括但不限于:
useLocalStorage:用于在 Vue 组件中方便地使用 localStorage。
useClipboard:用于在 Vue 组件中方便地操作剪贴板。
useMouse:用于获取鼠标事件信息的钩子函数。
useDebounce 和 useThrottle:用于创建防抖和节流的函数。
useIntersectionObserver:用于观察元素是否进入视口的钩子函数。
//图片加载失败所显示的默认图片
import defaltImg from '@/assets/images/200.png'
// 引入监听是否进入视口
import { useIntersectionObserver } from '@vueuse/core'
export default {
// 需要拿到main.js中由createApp方法产出的app实例对象
install (app) {
// app实例身上有我们想要的全局注册指令方法 调用即可
app.directive('imgLazy', {
mounted (el, binding) {
// el:img dom对象
// binding.value 图片url地址
// 使用vueuse/core提供的监听api 对图片dom进行监听 正式进入视口才加载
// img.src = url
const { stop } = useIntersectionObserver(
// 监听目标元素
el,
([{ isIntersecting }], observerElement) => {
if (isIntersecting) {
// 当图片url无效加载失败的时候使用默认图片替代
el.onerror = function () {
el.src = defaltImg
}
el.src = binding.value
stop()
}
})
}
})
}
}
img v-imgLazy="item.picture" alt=""
3 聚合支付详解
新西兰项目,微信支付、支付宝、VISA、PayPal、cash,选完订单选择支付方式,后端返回地址,进入地址。如果支付成功,后端返回支付页面(在聚合支付后台可以配置)地址加上完成订单id,前端做判断,如果存在完成订单的id则支付成功,跳到订单详情页面,如果不存在订单id,则用户未支付,再次回到餐厅页面。
4 谷歌地图 多端适配问题
要钱的,公司申请的,apiKey 还有秘钥
pubilc 引进 谷歌地图 获取定位传回app
getlocation
小程序谷歌地图 web-view 传值时,H5和app 无法使用同一个方法: H5 无法使用 web-view 的 @message 方法,而 app 无使用 web-view 存储区。
vue谷歌地图 vue-google-maps 插件
5 webscoket 流式读取
fetch 使用 async await 。第一个await等待的是响应头,第二个await等待的是响应体,在第二个await进行操作,结合 getReader 可读器,reader.read() 一小部分一小部分读取(done【false还未读完】和value(值)),TextDecoder解码器,二进制加码成文本。使用while循环。
const url = 'http://xxx.com/posts';
async function getResponse() {
const resp = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
while (true) {
const {
done,
value
} = await reader.read();
if (done) break;
const txt = decoder.decode(value);
console.log(done);
console.log(txt);
}
}
getResponse();
6 文件形式 bolb file base64,流
file(文件流)、bolb(本地流)、base64(二进制流)三者可以互相转换
以下的方法无论是图片、excel文件、PDF都可使用,设置responseType,结合URL.createObjectURL生成链接,然后自点击进行下载
export function postExportData(data) {
return request({
method: 'POST',
url: '/synthetic/exportTodoMatterList',
responseType: 'blob',
data
}).then(res => {
console.log(`[res]: `, res)
// const fileName = res.headers['Content-Disposition'].split('filename=')[1]
const fileName = '123.xlsx'
const url = URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.download = fileName
link.href = url
link.click()
})
}
7 eChart
需要定义宽高,找到ID,init元素,setOption,当离开页面时,要去销毁。
import * as echarts from 'echarts';
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}
]
};
option && myChart.setOption(option);
佛山市地图
获取地图的GeoJSON,DataV.GeoAtlas地理小工具系列,阿里云数据可视化平台,点击你想要生成的地图省市,生成JSON数据,准备容器,放置JSON数据,进行展示和resize跳转。
8 海康摄像头
进入官网下载对应的web视频插件,script标签引入js文件
div标签类似echart生成图表的做法,结合WebControl,后端获取每个学校不同区域的摄像头设备号和端口号,进行连接画面
9 Excel导入导出 PDF预览打印
excel导入(使用Element UI的文件上传组件获取文件,并使用xlsx
库解析Excel文件内容。)
excel导出可以前端操作(xlsx
和file-saver,
使用xlsx
库生成Excel文件,并使用file-saver
库将其保存到用户设备。),也可以后端操作(后端下载文件,然后生成地址的方式)
PDF预览、使用vue-pdf插件或者iframe标签
10 小程序支付
小程序注册微信支付商户号,绑定小程序ID
1 前端调用uniapp登录接口,这个code用户登录凭证,有效期5分钟,可以换取 opendId,unionId,session_key等核心信息
uni.login({
provider: 'weixin', // 使用微信登录
success: (res) => {
// 这个code用户登录凭证,有效期5分钟,可以换取 opendId,unionId,session_key等核心信息
res.code
}
});
2 发送请求调用微信官方接口,用code凭证换取用户openId
uni.request({
// 微信官方接口
url: `https://pi.weixin.qq.com/sns/jscode2session`,
data: {
appid: '小程序appid',
secret: '小程序密钥',
js_code: '上一步获取的code',
// 固定值
grant_type: 'authorization_code',
},
success: (res => {
// 获取openId 用户真实唯一ID
console.log(res.data.openId)
})
})
3 调用公司后端接口,获取支付核心数据
uni.request({
url: `公司后端接口`,
data: {
金额:
订单号:
},
method:'POST',
success: (res => {
// 获取openId 用户真实唯一ID
console.log(res.data.openId)
})
})
4 调用微信官方支付接口,弹出支付页面
wx.requestPayment({
timeStamp: '时间戳',
noncesTR: '随机字符',
package: 'prepay_id',
signType: 'MD5',
paySign: '后端返回的签名',
success(res){},
fail(res){}
})
11 前端加密
在项目中引入 CryptoJS
心理医生信息资料,文章加密
12 餐桌独立点单和JSBridge 生成餐厅桌椅摆放图
Safari浏览器自带的扫一扫,扫描二维码进入餐桌,传来餐桌ID改变餐桌状态,和生成临时用户信息给后端,结束订单付款的时候,工作人员点击结束订单。
和安卓开发一起联调,给我个地址,使用script标签引入 基本的 JSBridge,当进入制定餐桌页面时,就是我的前端页面,调用JSBridge方法判断有没有登录,有没有权限进行设置,sucess返回状态,有则进行页面展示,使用拖拉拽API和画布生成图片(详解),然后保存给后端。如果没有权限则使用JSBridge.openWindow,打开app本身的登录页面进行登录。
window.JSBridge.openWindow({
url: `http://yqt-dev.digitalgd.com.cn/default/#/pages/mine/personal/company/index`
})
通过设置鼠标的按下、移动、松开事件,再根据鼠标的偏移量,设置元素的偏移。
13 生成画布分享朋友圈
设置是全局分享功能
如果是点击右上角的三个点得分享功能 onShareAppMessage 分享朋友 onShareTimeline 分享朋友圈
onShareTimeline(res) { //发送到朋友圈
onShareAppMessage(res) { //发送给朋友
// 获取加载的页面
let pages = getCurrentPages();
// 获取当前页面的对象
let view = pages[pages.length - 1];
//分享的页面路径
let path = `/${view.route}`;
let imageUrl = '/static/11111.png'
return {
title: 'czxml',
path: path,
imageUrl: imageUrl,
success(res) {
console.log('success(res)==', res);
uni.showToast({
title: '分享成功'
})
},
fail(res) {
console.log('fail(res)==', res);
uni.showToast({
title: '分享失败',
icon: 'none'
})
}
}
},
如果是通过自己设置得分享按钮 button open-type="share"
<button @click="clickShare" style="width: 100rpx; height: 100rpx; border: none; opacity: 0;" open-type="share"></button>
14 阿里巴巴矢量库图标导入
官网有
15 流程问题
新建计划、计划分派、检查方案、检查准备,预通知、检查报告、综合评定报告、结果处置。