CORS跨域
头部的字段满足CORS的安全规范:一般情况下只要不去改动请求的头部。就是满足安全规范的。
-
常见的媒体格式类型如下:
text/html : HTML格式 text/plain :纯文本格式 text/xml : XML格式 image/gif :gif图片格式 image/jpeg :jpg图片格式 image/png:png图片格式
-
以application开头的媒体格式类型:
application/xhtml+xml :XHTML格式 application/xml : XML数据格式 application/atom+xml :Atom XML聚合格式 application/json : JSON数据格式 application/pdf :pdf格式 application/msword : Word文档格式 application/octet-stream : 二进制流数据(如常见的文件下载) application/x-www-form-urlencoded : <form encType=””>中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
-
另外一种常见的媒体格式是上传文件之时使用的:
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
函数柯里化
https://www.zhihu.com/tardis/bd/art/554854372?source_id=1001
函数柯里化(Currying)是一种将接受多个参数的函数转化为接受单个参数的一系列函数的过程。
其中每次应用都会返回一个新的函数,该函数接受剩余的参数。
作用:
-
将相同的参数固定下来。提高函数复用性
// 正常正则验证字符串 reg.test(txt) // 函数封装后 function check(reg, txt) { return reg.test(txt) } // 即使是相同的正则表达式,也需要重新传递一次 console.log(check(/\d+/g, 'test1')); // true console.log(check(/\d+/g, 'testtest')); // false console.log(check(/[a-z]+/g, 'test')); // true // Currying后 function curryingCheck(reg) { return function (txt) { return reg.test(txt) } } // 正则表达式通过闭包保存了起来 var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) console.log(hasNumber('test1')); // true console.log(hasNumber('testtest')); // false console.log(hasLetter('21212')); // false
-
延迟计算
柯里化允许将多个参数分开传递,可以用于创建需要在将来执行的函数,从而实现延迟计算。
-
函数组合:提高可读性和可维护性
有助于组合多个简单的函数以创建更复杂的函数
##说一说HTML语义化?
在对应的情况下使用对应标签,常见的标签有p,header,footer,nav,aside
为什么要语义化?
① 代码结构: 使页面没有css的情况下,也能够呈现出很好的内容结构
② 有利于SEO: 爬虫依赖标签来确定关键字的权重,因此可以和搜索引擎建立良好的沟通,帮助爬虫抓取更多的有效信息
③ 提升用户体验: 例如title、alt可以用于解释名称或者解释图片信息,以及label标签的灵活运用。
④ 便于团队开发和维护: 语义化使得代码更具有可读性,让其他开发人员更加理解你的html结构,减少差异化。
⑤ 方便其他设备解析: 如屏幕阅读器、盲人阅读器、移动设备等,以有意义的方式来渲染网页。
⑥ 语义化标签有着更好的页面结构,利于个人的代码编写。
##盒模型
CSS盒模型定义了盒的每个部分,包含 margin, border, padding, content 。
根据盒子大小的计算方式不同盒模型分成了两种,标准盒模型和怪异盒模型。
1,盒模型分为两类,一是标准盒模型,二是怪异盒模型。一般我们的盒子默认是标准盒模型。
2,两者区别是标准盒模型的实际宽高会把padding和border计算在内,而怪异盒模型不会。
3,形成怪异盒模型的条件是box-sizing:border-box;形成标准盒模型的条件box-sizing:content-box。
盒子浮动:
作用:
- 让多个盒子水平排列成一行。
- 实现文字环绕图片的效果
特点:会脱离文档流
存在的一个问题:如果父盒子没有给定固定高度,当子盒子浮动时,脱离原本子盒子撑起来的高度就会塌陷
解决方法:
-
给盒子添加固定的高度和宽度,缺点是非自适应
-
给外部的父盒子也添加浮动,让其脱离文档流,缺点是 不易维护,布局不好
-
盒子下方引入清除浮动块: 缺点是会引起不必要的冗余元素
<br style="clear:both;">
-
给父元素添加 overflow:hidden;样式
给父元素.box添加声明overflow: hidden;
使得父元素.box触发了BFC,而BFC特性规定“计算BFC高度时浮动元素也参于计算”,
此时子元素.content虽然设置了浮动,但其高度仍计算至父元素内,从而解决了高度塌陷问题。
.box1{ border:10px red solid; overflow:hidden; }
-
给父元素加伪元素(推荐使用)
.box1::after{ content:''; //加空白内容 display:block; //块级元素 display:block; height:0; clear:both; visibility:hidden; }
行内块之间存在间隙
原因:HTML代码中的回车换行被转成一个空白符,在字体不为0的情况下,空白符占据一定宽度,所以inline-block的元素之间就出现了空隙。这些元素之间的间距会随着字体的大小而变化
解决办法:
- 删除代码中的空白符(代码可读性差)
- 设置父元素字体大小为0(行内元素必须设置字体,否则会继承父元素字体大小)
- 浮动
- 设置父元素,display:table和word-spacing
BFC
https://blog.csdn.net/icemwj/article/details/120625370
BFC(Block Formatting Context)块格式化上下文,
它作为 HTML 页面上的一个独立渲染区域,只有区域内元素参与渲染,且不会影响其外部元素。
如何创建BFC,即BFC的触发条件:
-
根元素,即 HTML 元素。
-
float 除了 none 以外的值。
-
overflow 除了 visible 以外的值(hidden,auto,scroll ) 。
-
position 的值为 absolute 或 fixed,不是 static 或者 relative。
-
display 的值是 inline-block、table-cell、flex、table-caption或者 inline-flex 。
BFC 的布局规则
-
BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
-
内部的 Box 会在垂直方向,一个接一个地放置。
-
Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠。
-
每个盒子(块盒与行盒)的 margin box 的左边,与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
-
BFC 的区域不会与 float box重叠(可以用此来解决自适应布局的问题)。(对定位无效)
-
计算 BFC 的高度时,浮动元素也参与计算(撑开父元素,不会出现高度塌陷问题)。
BFC 的应用
-
利用 BFC 避免外边距重叠
-
利用 BFC 清除浮动
-
自适应两栏布局
- 原理:触发BFC,而BFC特性规定“BFC的区域不会与浮动容器发生重叠”,从而解决了重叠问题
判断变量类型的方法
- 使用typeof
- 常用于判断基本数据类型
- 缺点:不能判断null,因为 返回的是一个Object,对于引用数据类型,除了function其余都返回object
- instanceof
- 用于判断引用数据类型
- 缺点:无法正确判断基础数据类型
- Object.prototype.toString.call()
适用于所有类型的判断检测返回的是该数据类型的字符串
- xx.constrctor.name
暂时性死区
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
原因:let/const声明变量时没有变量提升所导致的。或者我们可以理解为,在变量仅创建,还没有初始化之时就使用了变量。
只要块级作用域内存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
比较隐蔽的暂时性死区
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
路由懒加载
原因: 像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大。
造成进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了loading也是不利于用户体验。
而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
-
方式一:
每个 () => import() 都会生成一个独立的JS文件
也就是说:只要使用这个语法,就是告诉 webpack 这就是一个代码分割点,这样生成一个独立的js文件,来实现按需加载的功能const router = new Router({ routers: [ { path: '/home', component: () =>import('../pages/Home.vue'); } ] })
-
方式二 :component: resolve => require([‘放入需要加载的路由地址’], resolve)
const router = new Router({ routers: [ { path: '/home', component: (resolve) => { require(['@/components/login/Login'], resolve)} } ] })
变量提升
变量提升是指JS的变量和函数声明会在代码编译期,提升到代码的最前面。变量提升的时候只有声明被提升,var、let和const中只有var存在变量提升。
-
变量提升的结果,可以在变量初始化之前访问该变量,返回的是undefined。
-
使用let和const声明的变量,在初始化之前访问会报错。因为会形成暂时性死区
-
函数提升优先于变量提升。
链接:https://www.nowcoder.com/exam/interview/74502500/test?paperId=50270010
继承的六种方式原型链继承
https://blog.csdn.net/weixin_41759744/article/details/125299029
-
原型式继承的优缺点
-
优点:不需要定义子类构造函数,已经原型链.prototype的指向操作。
-
缺点:父类构造函数的变量值还是会共享,保持原型链继承的特征。
-
-
寄生组合式继承
function clone (parent, child) { // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程 child.prototype = Object.create(parent.prototype); //其实和原生式的区别就是 这里使用的是 构造函数的继承,但是原生式使用的是直接让对象继承 //let clone = Object.create(original); //优点是: 不会复制parent中自己的数据,而是复用parent.prototype中的数据 child.prototype.constructor = child; } function Parent6() { this.name = 'parent6'; this.play = [1, 2, 3]; } Parent6.prototype.getName = function () { return this.name; } function Child6() { Parent6.call(this); this.friends = 'child5'; } clone(Parent6, Child6); Child6.prototype.getFriends = function () { return this.friends; } let person6 = new Child6(); console.log(person6); // child6 {name: "parent6",play: [1, 2, 3], friends: "child5"} console.log(person6.getName()); // parent6 console.log(person6.getFriends()); // child5
Object.create(o)
Object.create = function (o) { var F = function () {}; F.prototype = o; return new F(); };
F.prototype属性,进行了重新赋值,且指向传递的参数o;
new的步骤
- 首先创建一个空对象 {}
- 将该对象的
__proto__
指向其构造函数的prototype
- 让函数的this指向第1步创建出来的这个对象
- 如果这个构造函数有返回值,那么就返回,如果没有,就返回this
async和defer的区别
-
async 在js脚本加载完成后立即执行,defer则是在script脚本加载完毕之后等待html文档解析完毕之后在执行js脚本
-
async不能保证js脚本顺序执行,但是defer可以
Promise是什么
promise是异步编程的一种解决方案。用于解决回调地狱
异步编程包括:fs文件操作、数据库操作、Ajax、定时器。
(1)从语法上说,promise是一种构造函数
(2)从功能上说,promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值
三种状态:
pending(进行中,初始状态)
fulfilled(成功状态,也叫resolved)
rejected(失败状态)
一个 promise 对象的状态只能改变一次, 无论变为成功还是失败, 都会有一个结果数据 ,成功的结果数据一般称为 value, 失败的结果数据一般称为 reason。
扩展
Promise.all\any\race方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise.all()全部子实例都成功才算成功,有一个子实例失败就算失败。
Promise.any()有一个子实例成功就算成功,全部子实例失败才算失败。
Promise.race()rece是赛跑机制,要看最先的promise子实例是成功还是失败。
迭代器Iterator
https://blog.csdn.net/m0_52545254/article/details/126741625
-
出现的原因:
- JavaScript原有表示“集合”的数据结构,主要是数组(’ Array ‘)和对象(’ Object ’ ),ES6又添加了Map和Set。这样就有了四种数据集合,
用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。
这样就需要一种统一的接口机制,来处理不同的数据结构。
-
var colors = ["red", "green", "blue"]; for (var i = 0, len = colors.length; i < len; i++) { console.log(colors[i]); }
虽然循环语句语法简单,但如果将多个循环嵌套则需要追踪多个变量,代码复杂度会大大增加,一不小心就错误使用了其他for循环的跟踪变量,从而导致程序出错。迭代器的出现旨在消除这种复杂性并减少循环中的错误。
-
定义:
迭代器一种接口,为不同的数据结构提供一种访问机制。当使用到for…of、展开运算符…时,会自动去寻找 Iterator 接口
-
本质
迭代器对象本质上,就是一个指针对象。通过指针对象的next(), 用来移动指针。
每调用一次next ()方法,都会返回一个对象,都会返回数据结构的当前成员的信息。
这个对象有 value 和 done 两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否有必要再调用一次next () 。
对于遍历器来说,value:undefined和done:false属性都是可以省略的。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性上;
或者说,一个数据结构只要有Symbol.iterator属性,就认为是可遍历的。
模拟迭代器
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"
默认迭代器:
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
// !!注意 xiyou[Symbol.iterator] 对应的值是一个函数,所以要用()解析
let iterator = xiyou[Symbol.iterator]();
//调用对象的next方法
//每调用next方法返回一个包含value和 done 属性的对象 done为 true则表示遍历完成
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
生成器Generator
https://blog.csdn.net/qq_40137978/article/details/131830141
-
作用
生成器是ES6新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力。
这种新能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。
Generator 就是为了解决
异步问题
而出现的。 -
使用方法:
Generator 函数返回是一个迭代器对象,需要通过 xx.next 方法来完成代码执行。
在调用 generator 函数时,它只是进行实例化工作,它没有让函数体里面的代码执行,需要通过 next 方法来让它执行,
比如像下面这样:
function* gen() {
console.log(1)
}
// 定义迭代器对象
const iterator = gen()
iterator.next() // 如果不执行这一局代码,1不会被打印
当 next 方法执行时遇到了 yield 就会停止,直到你再次调用 next 方法
function* gen() {
yield 1
console.log('A')
yield 2
console.log('B')
yield 3
console.log('C')
return 4
}
// 定义迭代器对象
const iterator = gen()
iterator.next() // 执行 gen 函数,打印为空,遇到 yield 1 停止执行
iterator.next() // 继续执行函数,打印 A,遇到 yield 2 停止执行
iterator.next() // 继续执行函数,打印 B,遇到 yield 3 停止执行
iterator.next() // 继续执行函数,打印 C
-
用途
解决Js异步编程(回调地狱)
setTimeout(() => { console.log(111); setTimeout(() => { console.log(222); setTimeout(() => { console.log(333); }, 3000) }, 2000) }, 1000)
改进后:
function one() { setTimeout(() => { console.log(111); iterator.next() //执行第二个暂停点 },1000) } function two() { setTimeout(() => { console.log(222); iterator.next() //执行第三个暂停点 },2000) } function three() { setTimeout(() => { console.log(333); },3000) } function * gen() { yield one() yield two() yield three() } let iterator = gen() iterator.next() //执行第一个暂停点
##无感刷新Token
https://www.jianshu.com/p/216fc40b413c
无感刷新Token技术是一种用于实现持久登录体验的关键技术,
通过在用户登录后自动刷新Token,以延长用户的登录状态,避免频繁要求用户重新登录。
- 方法一
后端返回过期时间,前端判断token过期时间,去调用刷新token接口
缺点:需要后端额外提供一个token过期时间的字段;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。
- 方法二(可行)
写个定时器,定时刷新token接口
缺点:浪费资源,消耗性能,不建议采用。
- 方法三 (推荐)
在响应拦截器中拦截,判断token 返回过期后,调用刷新token接口
##axios拦截器
使用场景:
-
每个请求都附带后端返回的token,
-
拿到response之前loading动画的展示
Axios.interceptors.request.use(fn1,fn2)
fn1 :成功的回调 fn2 :失败的回调
interceptors是是Axios构造函数身上的一个属性,值为一个对象
function Axios(instanceConfig) {
//实例对象上的 defaults 属性为配置对象
this.defaults = instanceConfig;
//实例对象上有 interceptors 属性用来设置请求和响应拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
InterceptorManager是一个构造函数,其内部维护一个handlers数组,
function InterceptorManager() {
//创建一个属性
this.handlers = [];
}
其原型对象上添加了use函数:其作用就是将将成功的回调,失败的回调保存在handlers中
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
然后在Axios文件中,维护了一个 var chain = [dispatchRequest, undefined]
然后遍历request
对象和response
对象中的handlers数组,前者加入到chain的前面,后者加入到chain的后面
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);// promise 成功的Promise
// 遍历实例对象的请求拦截器,
/*
然后在Axios.js的文件中。将请求拦截器的往chain数组的前面放,将响应拦截器往后面放。就形成了请求拦截器是倒序,响应拦截器是顺序的结果
*/
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
//将请求拦截器压入数组的最前面
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
//将相应拦截器压入数组的最尾部
chain.push(interceptor.fulfilled, interceptor.rejected);
});
//如果链条长度不为 0
/*
以跳板的形式去执行。因为是以一组(成功的回调,失败的回调)来读取并执行的
如果,一个失败了,后续都调用失败的函数
前面的结果会影响chain中下一组的执行
*/
while (chain.length) {
//依次取出 chain 的回调函数, 并执行
promise = promise.then(chain.shift(), chain.shift());
}
##requestAnimationFrame动画
https://blog.csdn.net/weixin_44730897/article/details/116532510
https://www.jianshu.com/p/e044b5144746
requestAnimationFrame是由浏览器, 专门为动画提供的API,使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。
返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行
特点:
-
把每一帧中的所有DOM操作集中起来,执行步伐跟着系统的绘制频率走。它==能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,==这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
-
隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
-
运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
与setTimeout和setInterval的区别
setTimeout和setInterval都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
注意:使用requestAnimationFrame可能会导致速度变慢,因为受制于屏幕刷新率最大 60fps , 数据自增的速度会明显变慢
// 使用定时器的方式,由于存在eventLoop导致代码优先级的问题.会出现定时并不准确的情况.
let box = document.querySelector('#box'), rbox = document.querySelector('#request-animation-box')
normalWay()
function normalWay() {
let id = setInterval(() => {
box.style.width = `${++box.clientWidth}px`
if (box.clientWidth > 300) clearInterval(id)
console.log(box.style.width)
}, 17); // 1000ms / 17 = 每分钟 60 fps
}
// rWay
rWay()
function rWay() {
rbox.style.width = `${++rbox.clientWidth}px` // 场景内,有物体的变化,必须刷新浏览器
if (rbox.clientWidth <= 300)
requestAnimationFrame(rWay) // 因为必须要刷新浏览器,所以会调用 requestAnimationFrame .
// requestAimationFrame 的参数是一个函数.
// 但是只调用一次,它只会在下一帧渲染之前调用.后续帧刷新时,就不会调用.
// 所以需要不停的调用这个函数才可以.
}
一句话解释就是:
浏览器说: 我马上要刷新下一帧了,你有啥事情(动画)需要我在刷新下一帧之前做完的吗? 有的话赶紧给我.
我说: 有啊!我需要你在每下一次帧数渲染之前执行 rWay
方法.
浏览器说: 好的.我会让我的小弟去干这个事情.对了,忘了告诉你了,我小弟的名字叫 requestAnimationFrame
总结:
- 浏览器的刷新机制是客观存在的.
requestAnimationFrame
仅仅只是在浏览器每一次刷新渲染新帧之前的一个切片或者说一个时机.你用或者不用,这个切片都在那里.- 它和你是否执行动画效果,还是单纯的业务逻辑代码并没强求.我们可以将任意代码都丢在这个切片里面执行.
- 有时候,一些业务代码要求的是执行效率,丢在在这个切片里,受制于浏览器刷新性能,反而会拖慢整个流程的运行.
reqeustAnimation
不要一提到,就是动画动画. 它仅仅是一个浏览器在每一帧渲染之间给予我们的一个切片时机.仅此而已.
##vue组件按需加载
https://blog.csdn.net/qq_51066068/article/details/126826001
https://blog.csdn.net/asfasfaf122/article/details/128787922
-
普通的引入方式:
我们在component中定义完组件名字,还需要去通过import去引入组件.
这种方法不推荐,因为- 优点: 易理解,
- 缺点:webpack在打包的时候会把整个路由打包成一个js文件,如果页面一多,会导致这个文件非常大,加载缓慢
2,如何实现路由懒加载
- 方法一(ES中的import 推荐使用):
import Vue from 'vue' import Router from 'vue-router' const home = ()=>import("@/components/page/home/home") //按需加载 Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'home', component: home },
-
方法二:vue异步组件resolve
它主要是使用了resolve的异步机制,用require代替了import,实现按需加载
//const 组件名 = resolve => require([‘组件路径’],resolve) //(这种情况下一个组件生成一个js文件) const home = resolve => require(['../view/home'],resolve)
- 方法三:webpack提供的require.ensure()实现懒加载
1:这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
2:require.ensure可实现按需加载资源,包括js,css等。他会给里面require的文件单独打包,不会和主文件 打包在一起。
3:第一个参数是数组,表明第二个参数里需要依赖的模块,这些会提前加载。
4:第二个是回调函数,在这个回调函数里面require的文件会被单独打包成一个chunk,不会和主文件打包在一 起,这样就生成了两个chunk,第一次加载时只加载主文件。
5:第三个参数是错误回调。
6:第四个参数是单独打包的chunk的文件名
import Vue from 'vue'; import Router from 'vue-router'; Vue.use('Router') export default new Router({ routes:[{ {path:'./', name:'HelloWorld', component: r => require.ensure([], () => r(require('@/components/home')), 'demo') } }] })
总结:对于 Vue 路由的按需加载,推荐使用 import()
语法。它是标准的 ES6 语法,具有更好的浏览器支持,并且支持代码分割,可以实现更好的性能优化。
vue页面跳转后退回滚动到原位置
https://www.jianshu.com/p/a0a71d14fc53
-
使用 缓存,即不销毁列表页
APP.js中
<template> <div id="app"> <!-- <router-view/> --> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive" /> </div> </template>
router.js中
routes: [ { path: '/', name: 'List', //component: List component: () => import('./views/index/list.vue'), meta: { keepAlive: true // 需要缓存 } }, { path: '/content/:contentId', name: 'content', component: () => import('./views/index/content.vue'), meta: { keepAlive: false // 不需要缓存 } }, ]
-
使用路由守卫
原理就是在beforRouterLeave的路由钩子记录当前页面滚动位置
//在页面离开时记录滚动位置,这里的this.scrollTop可以保存在vuex的state或者浏览器本地 beforeRouteLeave (to, from, next) { this.scrollTop = document.documentElement.scrollTop || document.body.scrollTop next() }, //进入该页面时,用之前保存的滚动位置赋值 beforeRouteEnter (to, from, next) { next(vm => { document.body.scrollTop = vm.scrollTop }) },
-
使用vue-router方法scrollBehavior
router.js中
const scrollBehavior = function scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition; }else { return { x: 0, y: 0 } } }; const router = new Router({ routes, scrollBehavior, });
但是注意:必须是浏览器的前进后退才可以,路由的router-link跳转并不可以
vue-meta-info动态设置meta标签
https://www.jb51.net/article/245040.htm
vue-meta-info 是一个基于vue 2.0的插件,它会让你更好的管理你的 app 里面的 meta 信息。
可以直接 在组件内设置 metaInfo 便可以自动挂载到你的页面中。
使用步骤:
-
npm install vue-meta-info --save
-
在main.js中全局引入
import MetaInfo from 'vue-meta-info'; Vue.use(MetaInfo);
-
在组件内静态使用
<template> ... </template> <script> export default { metaInfo: { title: '操作手册', meta: [ { name:'viewport', content: 'width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0'} ] }, } </script> 在这里插入图片描述
注意:vue-meta-info并不是直接改变页面的meta信息,而是在下面追加一条覆盖上面的
所以只为某一个页面设置了,会影响别的页面的。就需要根据路由切换来动态设置了。在App.vue中监听路由变化
<script>
export default {
name: "App",
metaInfo() {
return {
meta: this.metaData
}
},
data() {
return {
metaData: [{
name: 'viewport',
content: 'width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'
}]
}
},
watch: {
$route(to, from) {
if (to.name == 'pdf') {
this.metaData = [{
name: 'viewport',
content: 'width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0'
}]
} else {
this.metaData = [{
name: 'viewport',
content: 'width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'
}]
}
},
},
};
</script>
虚拟列表
https://blog.csdn.net/qq_58355643/article/details/130891670
出现的原因:列表数据过多,一次性渲染所有数据将耗费大量的时间和内存。当我们上下滚动时,性能低的浏览器或电脑都会感觉到非常的卡
原理:
只渲染可视区域内的列表项,而不是渲染整个列表
当用户滚动容器时,虚拟列表会根据滚动位置和可视区域的大小计算出当前应该显示的列表项。
区别于懒加载:
懒加载:当资源到达可视窗口内时,继续向服务器发送请求获取接下来的资源,不过当获取的资源越来越多时,此时浏览器不断重绘与重排,这样的开销也是要考虑的当数量多到一定程度时,页面也会出现卡顿。
另外,需要注意的是,懒加载和虚拟列表也是有区别的:
-
实现方式不同。
-
懒加载是将页面上的图片、视频等资源延迟加载,只有当用户将它们滚动到可视区域时才会加载,从而减少页面的加载时间。
虚拟列表是将大型列表数据分段加载,只渲染当前可见的部分数据,随着滚动条的滚动,只渲染当前可见的列表项,从而大大减少了渲染时间。
-
-
优化的点不同。
- 懒加载主要是针对页面上的资源进行优化,减少页面的加载时间,这对网络繁忙卡顿有帮助。
- 虚拟列表主要是针对大型列表数据进行优化,减少渲染时间和内存占用。
-
使用场景不同。
- 懒加载适用于需要加载大量图片、视频等资源的页面,如图片展示、视频播放等。
- 虚拟列表适用于需要渲染大量数据的页面,如电商网站中的商品列表、社交网站中的好友列表等。
js代码实现虚拟列表
https://blog.csdn.net/weixin_37795155/article/details/125860982
vue列表实现
https://www.jb51.net/javascript/2851657hg.htm
defineProperty和Proxy区别
https://segmentfault.com/a/1190000041084082?sort=votes
-
监听数据的角度
defineproperty
只能监听某个属性而不能监听整个对象。proxy
不用设置具体属性,直接监听整个对象。defineproperty
监听需要知道是哪个对象的哪个属性,而proxy
只需要知道哪个对象就可以了。也就是会省去for in
循环提高了效率。
-
监听对原对象的影响
- 因为
defineproperty
是通过在原对象身上新增或修改属性增加描述符的方式实现的监听效果,一定会修改原数据。 - 而
proxy
只是原对象的代理,proxy
会返回一个代理对象不会在原对象上进行改动,对原数据无污染。
- 因为
-
实现对数组的监听
- 因为数组
length
的特殊性(length 的描述符configurable 和 enumerable 为 false,并且妄图修改 configurable 为 True 的话 js 会直接报错:VM305:1 Uncaught TypeError: Cannot redefine property: length)
defineproperty
无法监听数组长度变化,Vue
只能通过重写数组方法的方式变现达成监听的效果,光重写数组方法还是不能解决修改数组下标时监听的问题,只能再使用自定义的$set
的方式- 而
proxy
因为自身特性,是创建新的代理对象而不是在原数据身上监听属性,对代理对象进行操作时,所有的操作都会被捕捉,包括数组的方法和length
操作,再不需要重写数组方法和自定义set
函数了。
- 因为数组
-
监听的范围
-
defineproperty
只能监听到value
的get set
变化。 -
proxy
可以监听除[[getOwnPropertyNames]]
以外所有JS
的对象操作。(链接看下方)监听的范围更大更全面。
-
defineProperty无法监听到 对象的添加属性和删除属性
也无法监听通过数组下标形式的更改
Proxy 可以捕获到增加和删除操作.
当修改p的某个属性、或给p增加某个属性时调用set()
-
defineProperty
当使用
Object.defineProperty
修改对象属性时,它会直接修改原始对象,并可能更改其属性的特性(例如可枚举性、可写性等)。这意味着对原始对象的修改是原地进行的,可能会导致原始数据的污染。其他引用或访问该对象的代码也会受到影响。const obj = { name: 'John' }; Object.defineProperty(obj, 'name', { value: 'Jane', writable: false, }); console.log(obj.name); // 输出 'Jane' obj.name = 'Alice'; // 由于 writable 属性被设置为 false,赋值操作无效 console.log(obj.name); // 仍然输出 'Jane'
在上述示例中,通过
Object.defineProperty
修改了obj
对象的name
属性,并将其设置为不可写。这导致后续对obj.name
的赋值操作无效。这种修改是直接对原始对象进行的,并且可能会对其他使用该对象的代码产生影响重复定义一个属性时会报错
const obj = { name: 'John' }; Object.defineProperty(obj, 'c', { get(){ return 3 } }); Object.defineProperty(obj, 'c', { get(){ return 55 } }); //报错 //aught TypeError: Cannot redefine property: c at Function.defineProperty (<anonymous>)
-
Proxy
const obj = { name: 'John' }; const proxy = new Proxy(obj, { set(target, key, value) { if (key === 'name') { console.log('Setting name is not allowed.'); return false; } return Reflect.set(target, key, value); }, }); console.log(proxy.name); // 输出 'John' proxy.name = 'Jane'; // 设置被拦截,不允许修改 name 属性 console.log(proxy.name); // 仍然输出 'John'
过
Proxy
创建了一个代理对象proxy
,并在拦截器函数中对name
属性的设置进行了限制。当尝试修改proxy.name
时,拦截器函数会拦截操作并返回false
,从而阻止了对原始对象的修改。
ane’,
writable: false,
});
console.log(obj.name); // 输出 ‘Jane’
obj.name = ‘Alice’; // 由于 writable 属性被设置为 false,赋值操作无效
console.log(obj.name); // 仍然输出 ‘Jane’
在上述示例中,通过 `Object.defineProperty` 修改了 `obj` 对象的 `name` 属性,并将其设置为不可写。这导致后续对 `obj.name` 的赋值操作无效。这种修改是直接对原始对象进行的,并且可能会对其他使用该对象的代码产生影响
**重复定义一个属性时会报错**
```js
const obj = { name: 'John' };
Object.defineProperty(obj, 'c', {
get(){
return 3
}
});
Object.defineProperty(obj, 'c', {
get(){
return 55
}
});
//报错
//aught TypeError: Cannot redefine property: c at Function.defineProperty (<anonymous>)
-
Proxy
const obj = { name: 'John' }; const proxy = new Proxy(obj, { set(target, key, value) { if (key === 'name') { console.log('Setting name is not allowed.'); return false; } return Reflect.set(target, key, value); }, }); console.log(proxy.name); // 输出 'John' proxy.name = 'Jane'; // 设置被拦截,不允许修改 name 属性 console.log(proxy.name); // 仍然输出 'John'
过
Proxy
创建了一个代理对象proxy
,并在拦截器函数中对name
属性的设置进行了限制。当尝试修改proxy.name
时,拦截器函数会拦截操作并返回false
,从而阻止了对原始对象的修改。