- A+
@
HTML
1. 如何理解HTML语义化
- 让人更容易读懂(代码结构清晰,增加代码的可读性)
- 让搜索引擎更容易读懂(SEO),搜索引擎爬虫会根据不同的标签来赋予不同的权重
语义化标签 : header
nav
main
article
section
aside
footer
2. 默认情况下,哪些HTML标签是块级元素、哪些是内联元素
- 块级元素:
display: block/table
,有div
div
h1
h2
table
ul
ol
li
p
等 - 内联元素:
display: inline/inline-block
,有span
img
input
button
i
b
等
3. HTML5 新增内容和 API
- classList 属性
- querySelector() 与 querySelectorAll()
- getElementsByClassName()
- 自定义数据属性
- 本地存储
- insertAdjacentHtml()、insertAdjacentText()、insertAdjacentElement()
- 内容可编辑
- 预加载
CSS
1. 盒子模型
CSS盒子模型包含2种:
- W3C标准盒子模型(
box-sizing: content-box
,默认),宽高不受 padding、border影响 - IE怪异盒子模型 (
box-sizing: border-box
),宽高受 padding、border影响
2. margin 纵向重叠
- 相邻元素的
margin-top
和margin-bottom
会重叠 - 空内容的元素也会重叠
思考:如下代码,AAA
和BBB
之间的间距是多少 ?
<style> p{ font-size:16px; line-height:1; margin-top:10px; margin-bottom:15px } </style> <body> <p>AAA</p> <p></p> <p></p> <p></p> <p>BBB</p> </body>
答案是 15px
3. margin 负值
margin-top
负值,元素会上移margin-left
负值,元素会左移margin-right
负值,右侧元素会左移,自身不受影响margin-bottom
负值,下方元素会上移,自身不受影响
4. BFC(块级格式化上下文)
具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素
只要元素满足下面任一条件即可触发 BFC 特性:
- 根元素(即
<html>
标签) - 浮动元素 float 不为 none (为
left
、right
) - 绝对定位元素 position 不为 static 或 relative。(为
absolute
、fixed
) - overflow 的值不为 visible 的块元素(为
auto
、scroll
、hidden
) - display 的值为
inline-block
、flex
、grid
、table
、table-cell
、table-caption
...
同一BFC内:
- Box会在垂直方向上一个接一个的放置
- 垂直方向的距离由margin决定(属于同一个BFC的两个相邻Box的margin会发生重叠,与方向无关)
- 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此
- BFC的区域不会与float的元素区域重叠
- 计算BFC的高度时,浮动子元素也参与计算
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素,反之亦然
应用:
- 分属于不同的BFC时,可以防止margin重叠
- 清除内部浮动
- 自适应多栏布局
5. float布局
.fl { float: left; } .fr { float: right; } .clearfix { zoom: 1; // 兼容IE } .clearfix:after { content: ''; display: block; clear: both; visibility: hidden; overflow: hidden; }
6. flex布局
7. 三栏布局
- 浮动布局
- 定位布局
- flex布局
- 表格布局
- 网格布局
- calc函数布局
- 圣杯布局
- 双飞翼布局
面试常考的圣杯布局和双飞翼布局:
- 三栏布局,中间一栏最先加载和渲染(内容最重要)
- 两侧内容固定,中间内容随着宽度自适应
- 一般用于PC端
8. CSS定位
思考:relative
、absolute
、fixed
依据什么定位?
答案:
relative
:依据自身定位(相对的是它原本在文档流中的位置而进行的偏移)。在最外层时,是以<body>
标签为定位原点的。absolute
:依据最近一层的定位元素定位(根据postion
非static
的祖先类元素进行定位)。在无父级是postion
非static
定位时,是以<html>
作为原点定位。fixed
:根据窗口为原点进行偏移定位 (也就是说它不会根据滚动条的滚动而进行偏移)
9. 居中对齐实现方式
10. line-height的继承问题
思考: 以下代码中p
标签的行高是多少?
<style> body { font-size: 20px; line-height: 200%; } p { font-size: 16px; } </style> <p>AAA</p>
答案是 40px
- 写具体数值,如
body{ line-height: 30px;}
,则继承该值 (p
的行高就是30px
) - 写比例,如
body{ line-height: 2;}
,则继承该比例 (p
的行高就是16px*2 = 32px
) p字体大小的2倍 - 写百分比(有坑),如
body{ line-height: 200%;}
,则继承计算出来的值 (p
的行高就是20px*2 = 40px
) body字体大小的2倍
11. CSS长度单位
px
固定的像素,一旦设置了就无法因为适应页面大小而改变。em
相对于父元素的长度单位。(不常用)rem
相对于根<html>
元素的长度单位。(常用)rpx
微信小程序的相对长度单位。小程序规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。(仅微信小程序)
12. CSS响应式媒体查询
假如一个终端的分辨率小于 980px,那么可以这样写:
@media only screen and (max-width: 980px) { #head { … } #content { … } #footer { … } }
假如我们要设定兼容 iPad 和 iPhone 的视图,那么可以这样设置:
/** iPad **/ @media only screen and (min-width: 768px) and (max-width: 1024px) {} /** iPhone **/ @media only screen and (min-width: 320px) and (max-width: 767px) {}
媒体查询一般配合rem
单位实现响应式,因此rem
具有阶梯性
的弊端
13. 网页视口尺寸
- 屏幕高度 window.screen.height (显示器屏幕设备高度)
- 网页视口高度 window.innerHeight (去掉浏览器自身的头部和底部后的高度,含滚动条高)
- body高度 document.body.clientHeight (页面内容的真实高度)
神图
宽度同理 略~
14. CSS3 vw / vh
vw
网页视口宽度的1% (window.innerWidth = 1vw
)vh
网页视口高度的1% (window.innerHeight = 1vh
)vmin
选取vw
和vh
中最小的那个vmax
选取vw
和vh
中最大的那个
JS
1. ES6
2. 数据类型与检测
JavaScript 数据类型:
- Number (基本类型)
- String (基本类型)
- Boolean (基本类型)
- null (基本类型)
- undefined (基本类型)
- symbol (ES6 - 基本类型)
- bigInt (ES10 - 基本类型)
- object (引用类型,包含 function、[ ]、{ })
基本类型的特点:直接存储在栈(stack)内存中的数据
引用类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆(heap)内存中
3. 深拷贝和浅拷贝
深拷贝和浅拷贝最根本的区别在于是否是真正获取了一个对象的拷贝实体,而不是引用。
浅拷贝只拷贝一层对象的属性,而深拷贝则递归拷贝了所有层级。
- 深拷贝在计算机中开辟了一块新的内存地址用于存放拷贝的对象
- 浅拷贝仅仅是指向被拷贝的内存地址,如果原地址中对象被改变了,那么浅拷贝出来的对象也会相应改变
4. 原型与原型链(三座大山之一)
prototype(显式原型)
所有函数(仅限函数)拥有 prototype
属性
prototype
对象用于放某同一类型实例的共享属性和方法,实质上是为了内存着想。
Person.prototype.sayHello = function() { console.log('Hello!') } console.log(person1.sayHello === person2.sayHello) // true,同一个方法
_proto _ (隐式原型)
所有对象拥有 _proto _
属性
_proto _
指向谁?分以下三种情况:
/*1、字面量方式*/ var a = {}; console.log(a.constructor === Object); // true (即构造器Object) console.log(a.__proto__ === a.constructor.prototype); // true console.log(a.__proto__ === Object.prototype); // true /*2、构造器方式*/ var A = function (){}; var a = new A(); console.log(a.constructor === A); // true(即构造器function A) console.log(a.__proto__ === a.constructor.prototype); // true /*3、Object.create()方式*/ var a1 = {a:1} var a2 = Object.create(a1); console.log(a2.constructor === Object); // true (即构造器Object) console.log(a2.__proto__ === a1); // true console.log(a2.__proto__ === a2.constructor.prototype); //false(此处即为图1中的例外情况)
constructor ( 指向创建该对象的构造函数)
每个原型对象都可以通过对象.constructor
指向创建该对象的构造函数
function Person() {}; var person1 = new Person(); var person2 = new Person(); // 实例化对象的constructor属性指向构造函数本身 person1.constructor === Person; // 构造函数的prototype属性有个constructor属性,指向构造函数本身 Person.prototype.constructor === Person; // 由以上两条得出 person1.constructor === Person.prototype.constructor person1.__proto__ === Person.prototype Person.constructor === Function; Function.constructor === Function;
原型链
a.__proto__ === A.prototype A.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null
下图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
原型与原型链的终极图
这个图要是看懂了,原型与原型链就基本摸清了。
instanceof 原理
instanceof
只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object
都是 true
instanceof
的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype
。
class People {}; class Student extends People {}; let s1 = new Student(); console.log(s1 instanceof Student); // true console.log(s1 instanceof People); // true console.log(s1 instanceof Object); // true console.log(s1.__proto__ === Student.prototype); // true console.log(Student.prototype.__proto__ === People.prototype); // true console.log(People.prototype.__proto__ === Object.prototype); // true s1.__proto__ === Student.prototype => s1 instanceof Student Student.prototype.__proto__ === People.prototype => Student.prototype instanceof People People.prototype.__proto__ === Object.prototype => People.prototype instanceof Object
Instanceof的判断队则是:沿着s1的__proto__这条线来找,同时沿着Student的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。这就很好地解释了上述代码的输出结果啦。
继承方式
5. 作用域、this 和闭包 (三座大山之二)
作用域
在 Javascript 中,作用域分为 全局作用域 和 函数作用域
全局作用域
代码在程序的任何地方都能被访问,window 对象
的内置属性都拥有全局作用域。
函数作用域
在固定的代码片段才能被访问
作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如上,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
作用域链
变量取值:到创建这个变量的函数的作用域中向上取值,而不是调用这个函数时向上取值,
如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
思考:以下代码输出什么?
function create() { const a = 100; return function () { console.log(a); } } const fn = create(); const a = 200; fn(); // 100
function print(fn) { const a = 200; fn(); } const a = 100; function fn() { console.log(a); } print(fn); // 100
从创建的函数向上取值,而不是调用函数时向上取值
this
this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的
特别注意:
- 匿名函数的自我执行,没有被上级对象调用,所以this指向window
setTimeout(function () { console.log(this) });
,this指向windowsetTimeout(() => { console.log(this) });
,this指向上下文- 构造函数中的this,指向实例对象
bind
、call
、apply
可以改变 this 指向
JavaScript中call,apply,bind方法的总结
闭包
闭包就是指有权访问另一个函数作用域中的变量的函数。
创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量
闭包的特性:
- 函数嵌套函数
- 函数内部可以引用外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
function aaa() { var a = 1; return function(){ alert(a++) }; } var fun = aaa(); fun();// 1 执行后 a++,,然后a还在~ a会长期驻扎在内存中 fun();// 2 fun = null;//a被回收!!
6. 异步 (三座大山之三)
单线程与多线程
- JavaScript是单线程语言(可以说这是JavaScript最核心也是最基本的特性)
- 浏览器的内核是多线程的
虽然JavaScript是单线程的,可是浏览器内部不是单线程的。
一些I/O操作、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其他线程来完成的。
同步与异步
-
同步:是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务 -
异步:是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
当我们打开网站时,像图片加载等网络请求(ajax、axios)、定时任务(setTimeout),其实就是一个异步任务
console.log(1); alert(2); // 同步,会阻塞代码的执行 console.log(3);
setTimeout(function(){ console.log(1); // 异步,不会阻塞代码的执行 },100) console.log(2);
事件循环(Event Loop)
事件循环机制:
- 首先判断JS是同步还是异步,同步就进入主线程,异步就进入
event table
- 异步任务在
event table
中注册函数,当满足触发条件后,被推入消息队列(event queue
) - 同步任务进入主线程后一直执行,直到主线程空闲时,才会去消息队列中查看是否有可执行的异步任务,如果有就推入主线程中
异步任务又可以分为:
-
macrotask(宏任务)
:
等待执行栈和微任务队列都执行完毕才会执行,并且在执行完每一个宏任务之后,会去看看微任务队列有没有新添加的任务,如果有,会先将微任务队列中的任务清空,才会继续执行下一个宏任务
包括:script代码块,setTimeout,setInterval,I/O -
microtask(微任务)
:
当执行栈中的代码执行完毕,会在执行宏任务队列之前先看看微任务队列中有没有任务,如果有会先将微任务队列中的任务清空才会去执行宏任务队列
包括:Promise,nextTick,callback,Object.observe,MutationObserver
执行的顺序是 执行栈中的代码 => 微任务 => 宏任务 => 微任务 => 宏任务 => ...
。
DOM事件也是基于Event Loop,但不是异步
异步任务的执行也是有先后顺序的:
- 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
- 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
Promise
Promise
是异步编程的一种解决方案,有三种状态:
pending
(等待态)fulfiled
(成功态)rejected
(失败态)
一旦 Promise
被 resolve
或 reject
,不能再迁移至其他任何状态(即状态 immutable
)。创造 promise
实例后,它会立即执行。
基本过程:
- 初始化
Promise
状态(pending
); - 立即执行
Promise
中传入的fn
函数,将Promise
内部resolve
、reject
函数作为参数传递给fn
,按事件机制时机处理; - 执行
then(…)
注册回调处理数组(then
方法可被同一个promise
调用多次); Promise
里的关键是要保证,then
方法传入的参数onFulfilled
和onRejected
,必须在then
方法被调用的那一轮事件循环之后的新执行栈中执行;
简单用法:
let p = new Promise((resolve, reject) => { var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if (num <= 5) { resolve(num); } else { reject('数字太大了'); } }) // then的用法 p.then((data) => { console.log('resolve:' + data); }, (err) => { console.log('reject:' + err); }) // catch的用法 p.then((data) => { console.log('resolve:' + data); }) .catch((err) => { console.log('reject:' + err); })
async与await
核心:
- 执行 async 函数,默认返回一个 promise 对象
- await 相当于 promise 的 then
- try...catch 可捕获异常,代替了 promise 的 catch
function dice(val) { return new Promise((resolve, reject) => { let sino = parseInt(Math.random() * 6 + 1); if (sino > 3) { val === '大' ? resolve(sino) : reject(sino); } else { val === '大' ? reject(sino) : resolve(sino); } }) } async function test() { // try...catch 可捕获异常,代替了 Promise 的 catch try { //把await及获取它的值的操作放在try里 let n = await dice('大'); // await 相当于 Promise 的 then console.log('赢了' + n); } catch (error) { //失败的操作放在catch里 console.log('输了' + error); // 相当于 Promise 的 catch } } test();
思考:以下代码输出顺序
async function async1() { console.log(1); // 同步2 await async2(); // 先执行async2(),再await console.log(2); // 异步(await下面所有的代码都是异步) } async function async2() { console.log(3); // 同步3 } console.log(4); // 同步1 setTimeout(() => { console.log(5); // 异步2 宏任务 }, 0); async1(); console.log(6); // 同步4
答案
4 1 3 6 2 5
await 下面所有的代码都是异步
异步加载JS方式
1. 匿名函数自调动态创建script标签加载js
(function(){ var scriptEle = document.createElement("script"); scriptEle.type = "text/javasctipt"; scriptEle.async = true; scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js"; var x = document.getElementsByTagName("head")[0]; x.insertBefore(scriptEle, x.firstChild); })();
2. async属性
// async属性规定一旦加载脚本可用,则会异步执行 <script type="text/javascript" src="xxx.js" async="async"></script>
3. defer属性
// defer属性规定是否对脚本执行进行延迟,直到页面加载为止 <script type="text/javascript" src="xxx.js" defer="defer"></script>
7. DOM 与 BOM
DOM
DOM (Document Object Model)是 W3C 的标准,是指文档对象模型(树结构)。
DOM 定义了访问和操作 HTML 文档的标准方法。通过它,可以访问HTML文档的所有元素。
1. HTML DOM 树:
2. DOM 节点:
根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点(NODE):
- 文档节点:整个文档(document对象)
- 元素节点:每个 HTML 元素(element 对象)
- 文本节点:HTML 元素内的文本(text对象)
- 属性节点:每个 HTML 属性(attribute对象)
- 注释节点:注释(comment对象)
3. DOM 查找:
// 根据标签名获取标签合集 const div1 = document.getElementsByTagName("div"); // div1 div2 div3 div4 div5 (元素集合 HTMLCollection) const div2 = document.querySelectorAll("div"); // div1 div2 div3 div4 div5 (节点集合 NodeList) // 根据class属性获取 const div3 = document.getElementsByClassName("div"); // div1 div2 (元素集合 HTMLCollection) const div4 = document.querySelectorAll(".div"); // div1 div2 (节点集合 NodeList) // 根据id属性值获取 const div5 = document.getElementById("div"); // div3 (一个标签) const div6 = document.querySelectorAll("#div"); // div3 (节点集合 NodeList) // 根据name属性值获取 const div7 = document.getElementsByName("div"); // div4 div5 (节点集合 NodeList) // 根据标签名获取标第一个 const div8 = document.querySelector("div"); // div1 (一个标签)
4. DOM 操作:
// 创建节点 var divEle = document.createElement("div"); var pEle = document.createElement("p"); var aEle = document.createElement("a"); // 添加节点 document.body.appendChild(divEle); // 将上面创建的div元素加入到body的尾部 document.body.insertBefore(pEle, divEle); // 在body下,将p元素添加到div元素前面 //替换节点 document.body.replaceChild(aEle, pEle); // 在body下,用a元素替换p元素 //设置文本节点 aEle.innerText = "在干嘛" divEle .innerHTML = "<p>在干嘛<p/>" //设置属性 divEle .setAttribute("class","list"); // 给div元素加上class='list'属性 //获取class值 divEle.className // 获取div元素上的class // 设置style样式 divEle.style.color = "red"; // 把div元素的color样式设置成red divEle.style.margin = "10px" divEle.style.width = "10px" divEle.style.left = "10px" divEle.style.position = "relative"
5. DOM 优化:
DOM 操作都是代价昂贵的操作,它会导致 WEB 应用程序的 UI 反应迟钝。所以,应当尽可能减少这类过程的发生。
// 不缓存 DOM 查询结果 for (let i = 0; i < document.getElementsByTagName("div").length; i++) { // 每次循环,都会计算length,频繁进行 DOM 查询 } // 缓存 DOM 查询结果 const div = document.getElementsByTagName("div"); const length = div.length; for (let i = 0; i < length; i++) { // 只进行一次 DOM 查询 }
将频繁的 DOM 操作改成一次性操作:
var el = document.getElementById('mydiv'); // 未优化前的DOM操作,会导致三次重排 el.style.borderLeft = '1px'; el.style.borderRight = '2px'; el.style.padding = '5px'; // 优化后的DOM操作,只会一次重排 el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
BOM
BOM(Browser Object Model)是指浏览器对象模型,可以对浏览器窗口进行访问和操作。
使用 BOM,开发者可以移动窗口、改变状态栏中的文本以及执行其他与页面内容不直接相关的动作。使 JavaScript 有能力与浏览器"对话"。
- Window 对象 (
window.alert()
、window.open()
、window.setTimeout()
...) - Navigator 对象(
navigator.userAgent
...) - Screen 对象 (
screen.width
、screen.height
...) - Location 对象 (
location.href
、location.reload()
、location.replace()
...) - History 对象(
history.forward()
、history.back()
...)
8. 事件流
事件传播的顺序对应浏览器的两种事件流模型:
- 冒泡型事件流中click事件传播顺序为
<div> => <body> => <html> => document
(默认) - 捕获型事件流中click事件传播顺序为
document => <html> => <body> => <div>
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <div id="div">Click me!</div> <script> document.getElementById("div").addEventListener("click", (event) => { console.log('this is div'); }); document.body.addEventListener("click", (event) => { console.log('this is body'); }); document.documentElement.addEventListener("click", (event) => { console.log('this is html'); }); document.addEventListener("click", (event) => { console.log('this is document'); }); // 默认是事件捕获,因此按顺序输出: // this is div // this is body // this is html // this is document </script> </body> </html>
事件捕获
事件冒泡(默认)
DOM 标准事件流
绑定事件时通过addEventListener函数,它有三个参数,第三个参数若是true,则表示采用事件捕获,若是false(默认),则表示采用事件冒泡。
<div id="box1">box1 <div id="box2">box2 <div id="box3">box3</div> </div> </div> <script> box1.addEventListener('click', function () { console.log('box1 捕获阶段'); }, true); box2.addEventListener('click', function () { console.log('box2 捕获阶段'); }, true); box3.addEventListener('click', function () { console.log('box3 捕获阶段'); }, true); box1.addEventListener('click', function () { console.log('box1 冒泡阶段'); }, false); box2.addEventListener('click', function () { console.log('box2 冒泡阶段'); }, false); box3.addEventListener('click', function () { console.log('box3 冒泡阶段'); }, false); </script>
element.addEventListener(event, function, useCapture)
第三个参数useCapture
,可选。布尔值,指定事件是否在捕获或冒泡阶段执行:
true
- 事件句柄在捕获阶段执行false
(默认)- 事件句柄在冒泡阶段执行
阻止事件冒泡/捕获
使用 event.stopPropagation()
起到阻止捕获和冒泡阶段中当前事件的进一步传播。
- W3C的方法是:
event.stopPropagation()
- IE则是使用:
event.cancelBubble = true
p.addEventListener("click", (event) => { event.stopPropagation(); // 阻止事件冒泡 console.log('this is p'); // 只会输出 'this is p' }); document.addEventListener("click", (event) => { event.stopPropagation(); // 阻止事件捕获 console.log('this is document'); // 只会输出 'this is document' }, true);
兼容IE的写法:
window.event? window.event.cancelBubble = true : event.stopPropagation();
阻止默认事件
- W3C的方法是:
event.preventDefault()
- IE则是使用:
event.returnValue = false
既然是说默认行为,当然是元素必须有默认行为才能被取消,如果元素本身就没有默认行为,调用当然就无效了。
<a href="https://www.baidu.com/" id="a">阻止默认跳转</a> <script> document.getElementById("a").addEventListener("click", (event) => { event.preventDefault(); console.log('已阻止a链接跳转'); }); </script>
事件代理/委托
事件代理的原理用到的就是事件冒泡和目标元素,把事件处理器添加到父元素,等待子元素事件冒泡,并且父元素能够通过target(IE为srcElement)判断是哪个子元素,从而做相应处理。
<ul id="color-list"> <li>red</li> <li>orange</li> <li>yellow</li> <li>green</li> <li>blue</li> <li>indigo</li> <li>purple</li> </ul> <script> // 不使用事件代理 (function(){ var colorList = document.getElementById("color-list"); var colors = colorList.getElementsByTagName("li"); for (var i = 0; i < colors.length; i++) { colors[i].addEventListener('click', showColor); // 给每个li绑定一个点击事件 }; function showColor(e) { e = e || window.event; var targetElement = e.target || e.srcElement; console.log(targetElement.innerHTML); } })(); // 使用事件代理 (function(){ var colorList = document.getElementById("color-list"); colorList.addEventListener('click', showColor); // 通过冒泡,只需要给li的父级一个点击事件 function showColor(e) { e = e || window.event; var targetElement = e.target || e.srcElement; console.log(targetElement.innerHTML); } })(); </script>
9. 跨域
跨域是指从一个域名的网页去请求另一个域名的资源。比如从 www.baidu.com
页面去请求 www.google.com
的资源。
非同源,在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制。
同源策略
同源策略是浏览器最核心也最基本的安全功能。如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
- 同源:协议、域名、端口,三者全部相同,才是同源。
- 跨域:协议、域名、端口,只要有一个的不同,就是跨域。
不存在跨域的情况(无视同源策略)
- 服务端请求服务端不存在跨域(浏览器请求服务器才存在同源策略)
<img src="跨域的图片地址">
(<img>
标签的src
属性不存在跨域)<link href="跨域的css地址">
(<link>
标签的href
属性不存在跨域)<script src="跨域的js地址"></script>
(<script>
标签的src
属性不存在跨域)
常见的几种跨域方法
- jsonp 跨域 (动态添加
<script>
标签,利用src属性跨域。 常用) - CORS 跨域资源共享(由服务端实现。 常用且主流)
- node 代理跨域(利用
proxyTable
使本地的node服务器代理请求真正的服务器。 常用) - document.domain + iframe 跨域
- postMessage 跨域
安全
10. HTTP
HTTP协议是一个基于 TCP/IP
通信协议来传递数据(HTML 文件, 图片文件, 查询结果等),用于从服务器传输超文本到本地浏览器的传送协议。
HTTP三大特点
- HTTP是无连接的:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
- HTTP是无状态的:无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
HTTP 消息结构
客户端请求消息(Request Headers):
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
服务器响应消息(Response Headers):
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
HTTP 状态码
RFC 规定 HTTP 的状态码为三位数,被分为五类:
- 1xx: 信息,服务器收到请求,需要请求者继续执行操作
- 2xx: 成功,操作被成功接收并处理 (200 - 请求成功)
- 3xx: 重定向,需要进一步的操作以完成请求(302 - 资源(网页等)被临时转移到其它URL,浏览器自动处理)
- 4xx: 客户端错误,请求包含语法错误或无法完成请求(404 - 请求的资源(网页等)不存在)
- 5xx: 服务器错误,服务器在处理请求的过程中发生了错误(500 - 内部服务器错误)
HTTP 请求方法
方法 | 协议版本 | 描述 |
---|---|---|
GET | HTTP1.0 | 请求指定的页面信息,并返回实体主体。(获取数据) |
HEAD | HTTP1.0 | 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头。 |
POST | HTTP1.0 | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。(新建数据) |
PUT | HTTP1.1 | 从客户端向服务器传送的数据取代指定的文档的内容。 |
DELETE | HTTP1.1 | 请求服务器删除指定的页面。(删除数据) |
CONNECT | HTTP1.1 | 预留给能够将连接改为管道方式的代理服务器。 |
OPTIONS | HTTP1.1 | 允许客户端查看服务器的性能。 |
TRACE | HTTP1.1 | 回显服务器收到的请求,主要用于测试或诊断。 |
PATCH | HTTP1.1 | 是对 PUT 方法的补充,用来对已知资源进行局部更新 。(更新数据) |
HTTP 缓存
当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。
常见的http缓存只能缓存get请求响应的资源,对于其他类型的响应则无能为力。
缓存的优点:
- 减少了冗余的数据传输,节省了你的网络费用。
- 缓解了网络瓶颈的问题。不需要更多的带宽就能够更快地加载页面。
- 降低了对原始服务器的要求。服务器可以更快地响应,避免过载的出现。
- 降低了距离时延,因为从较远的地方加载页面会更慢一些。
强缓存与协商缓存:
- 第一次请求资源时,服务器返回资源,并在
respone header
中回传资源和资源标识(Last-Modified
、Etag
)。 - 第二次请求资源时,浏览器会判断
response headers
是否命中(cache-control属性
)强缓存,如果命中,直接从本地读取缓存(状态码200),不会向服务器发送请求。 - 当强缓存没有命中时,就把请求参数(含
If-Modified-Since
、If-Not-Match
)加到request header
头中传给服务器,判断协商缓存是否命中,如果命中(If-Modified-Since == Last-Modified
、If-Not-Match == Etag
)则服务器将请求返回(状态码304),不会返回资源,告诉浏览器从本地读取缓存。 - 当协商缓存没有命中(
If-Modified-Since != Last-Modified
、If-Not-Match != Etag
)时,服务器直接返回新的资源(状态码200)和新的资源标识(新的Last-Modified
、新的Etag
) 。
资源标识:
- Last-Modified:资源的最后修改时间(只能精确到秒级)
- Etag:资源的唯一标识,会优先使用(一个字符串,类似人类的指纹)
如果资源被重复生产,而内容不变,则 Etag 更精准
区别:
- 强缓存命中:不会请求服务器,直接请求缓存;(非常快)
- 协商缓存命中:会请求服务器,不会返回内容,然后读取缓存;(服务端缓存策略)
11. 手写常见JS方法
- 判断数据类型
- 深拷贝
- 对象是否全等
- 防抖
- 节流
- 数组拍平
- 数组去重
- new函数
工具
1. Git
项目常用命令:
1. git init // 在当前目录新建一个Git代码库 2. git branch dev-bing // 创建本地分支(dev-bing) 3. git checkout dev-bing // 切换到本地分支(dev-bing) git checkout -b dev-bind // 创建并切换到本地分支(dev-bing) 相当于上面第2 + 第3 的简写 4. git branch // 查看分支 5. git push --set-upstream origin dev-bing // 上传本地当前分支代码到master分支 6. git status // 显示有变更的文件 如果字体为红色,则表示列出的是当前目录所有还没有被git管理的文件和被git管理且被修改但还未提交(git commit)的文件,也就是所有改动文件。 7. git diff // 查看所有修改内容 git diff test.txt // 查看具体文件修改内容 8. git log // 查看提交的记录日志 git log test.txt // 查看具体文件提交的记录日志 9. git stash // 临时保存,可跨分支 (只能在未add之前才能使用) 10. git stash pop // 恢复之前缓存 11. git checkout . // (有个点) 撤销当前所有的修改 git checkout test.txt // 撤销具体文件的修改 12. git add . // (有个点) 表示添加当前目录下的所有文件和子目录(或git add -A) git add test.txt // 添加具体文件(test.txt) 13. git commit -m 'test' // 将文件上传至远程 master 分支并添加备注"test" 14. git pull origin dev-bing // 从远程仓库下载到dev-bing仓库 git pull // 如果当前分支是dev-bing git pull相当于git pull origin dev-bing 15. git push origin dev-bing // 从本地仓库上传到远程仓库(提交) 16. git checkout master // 切换到master主分支 17. git merge --no-ff dev-bing // 把dev-bing分支合并到master :wq 18. git push origin master // 提交合并后的master分支 git push -u origin master // 将本地的master分支推送到origin主机,同时指定origin为默认主机,后面就可以不加任何参数使用git push了。 git push // 设置默认主机后(git push -u origin master)可简写 19. git checkout dev-bing // 返回dev-bing分支
2. 浏览器
浏览器从输入URL到渲染完页面的整个过程
- 获取IP地址
- TCP/IP三次握手建立连接
- 浏览器向web服务器发送http请求
- 浏览器渲染
- 四次挥手断开连接
浏览器渲染过程
- DOM 树:解析 HTML 构建 DOM(DOM 树)
- CSS 树:解析 CSS 构建 CSSOM(CSS 树)
- 渲染树:CSSOM 和 DOM 一起生成 Render Tree(渲染树)
- 布局(layout):根据Render Tree浏览器就知道网页中有哪些节点,以及各个节点与 CSS 的关系,从而知道每个节点的位置和几何属性(重排)
- 绘制(Paint):根据计算好的信息绘制整个页面(重绘)
3. 其他
yarn
gulp
babel
vConsole
Vue
1. MVVM
MVVM 分为 Model
、 View
、 ViewModel
三者:
Model
:数据层,数据和业务逻辑都在Model层中定义。View
:视图层,也就是用户界面,负责数据的展示。ViewModel
:视图数据层, ViewModel层通过双向数据绑定将View层和Model层连接了起来(View和Model层的桥梁),使得View层和Model层的同步工作完全是自动的。
Model和View并无直接关联,而是通过ViewModel这个桥梁来进行联系的,ViewModel就是View与Model的连接器,View与Model通过ViewModel实现双向绑定。
2. 生命周期
Vue2生命周期
- beforeCreate:创建之前(
el
、data
和message
都还是undefined
,不可用的) - created:创建完毕(能读取到数据
data
的值,但是DOM
还没生成) - beforeMount:挂载之前(生成
DOM
,但此时{{ message }}
还没有挂载data
中的数据) - mounted:挂载完毕(
{{ message }}
已经成功挂载渲染data
的值) - beforeUpdate:更新之前
- updated:更新完毕
- beforeDestroy:销毁之前
- destroyed:销毁完毕(实例与视图的关系解绑,再修改
message
的值,视图再也不会更新了) - activated:
keep-alive
组件激活时调用 - deactivated:
keep-alive
组件停用时调用
注:
activated
和deactivated
是比较特殊的两个钩子,需要keep-live
配合使用- 当引入
keep-alive
的时候,页面第一次进入,钩子的触发顺序created
=>mounted
=>activated
,退出时触发deactivated
。当再次进入(前进或者后退)时,只触发activated
。
Vue3生命周期
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
- onActivated
- onDeactivated
- onErrorCaptured
- onRenderTracked
- onRenderTriggered
3. computed 与 watch
computed(计算属性)
- 属性的结果会被缓存(默认走缓存),当
computed
中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取,除非依赖的响应式属性变化时才会重新计算; - 不支持异步,当
computed
内有异步操作时无效,无法监听数据的变化; computed
中的函数必须用return
返回最终的结果。computed
更高效,优先使用;- 当一个属性受多个属性影响的时候,一般用
computed
(例如:详细地址 = 省+市+区+街道+楼栋+房号 );
watch(监听属性)
- 不支持缓存,数据变,直接会触发相应的操作;
- 支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值(newVal);第二个参数是输入之前的值(oldVal);
- 当一条数据影响多条数据的时候,一般使用
watch
(例如:搜索数据(异步) ,触发一系列数据的变化);
4. v-if 与 v-show
v-if
- 是通过控制 DOM 元素的存在与否来控制元素的显隐;
- 切换时,是对 DOM 元素进行一个创建和销毁的动作;
- 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译;
- 有更高的切换消耗;
v-show
- 是通过设置 DOM 元素的
display
样式,block
为显示,none
为隐藏; - 切换时,只是简单的基于CSS切换;
- 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且 DOM 元素保留;
- 有更高的初始渲染消耗;
基于以上区别,因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
5. data 必须是一个函数,而不是对象
如果 Vue 组件中的 data 是个对象,那么就像所有复用这个组件的地方,都在使用这个组件里面唯一的一个 data,所有使用组件的地方的 data 都会指向栈内这一个 data 的地址,那么会造成一个改 data 的值,所有其他组件的 data 都会被改变。
如果在函数中返回一个对象,那么在每次创建一个组件的时候,每次返回的都是一个新对象(Object的实例),在内存中同时开辟一块空间给当前组件存放 data 。这样就不会出现共用一个 data 的现象
6. diff 算法
diff 算法是一种优化手段。比如有时候我们修改了某个数据,如果直接渲染到真实 DOM 上会引起整个 DOM 树的重绘和重排,这样开销是非常大的。
我们可以先根据真实 DOM 生成一棵虚拟DOM(virtual DOM)树,当虚拟DOM某个节点的数据改变后,会生成一个新的 Vnode
(虚拟节点)。然后 Vnode
和 oldVnode
作对比,发现有不一样的地方就直接修改在真实的 DOM 上,然后使 oldVnode
的值为Vnode
。此时我们只更新我们修改的那一小块 DOM,而不要更新整个 DOM。
diff的过程就是调用patch函数,比较新旧节点,一边比较一边给真实的DOM打补丁。
在采取 diff 算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。
概括起来就是:对操作前后的 DOM 树同一层的节点进行对比,一层一层对比,然后再插入真实的dom中,重新渲染。
7. for 循环中 key 的作用
vue中列表循环需加 :key="唯一标识"
, 唯一标识可以是 item
里面 id
、 index
等。
- key 的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点;
- Vue 在 patch 过程中判断两个节点是否是相同节点, key 是一个必要条件,渲染一组列表时,key往往是唯一标识,所以如果不定义 key 的话,Vue 只能认为比较的两个节点是同一个,哪怕它们实际上不是,这导致了频繁更新元素,使得整个 patch 过程比较低效,影响性能;
- 从源码中可以知道,Vue 判断两个节点是否相同时主要判断两者的 key 和元素类型等,因此如果不设置 key ,它的值就是undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,这造成了大量的 DOM 更新操作,明显是不可取的。
不建议 用 index
作为 key
,和没写基本上没区别,因为不管你数组的顺序怎么颠倒,index
都是 0, 1, 2 这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作
8. 双向绑定
当一个Vue实例创建时,Vue会遍历 data 选项的属性,利用 Object.defineProperty
将它们转为 getter/setter
并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher
程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter
被调用时,会通知 watcher
重新计算,从而致使它关联的组件得以更新。
极简版双向绑定:
<input type="text" id="input" value=""> <span id="span"></span> <script> let obj = {} Object.defineProperty(obj, 'text', { // 监听obj的text属性 set(newVal = '') { document.getElementById('input').value = newVal; document.getElementById('span').innerHTML = newVal; } }); document.addEventListener('keyup', function (e) { obj.text = e.target.value; }) </script>
v-model
vue 中双向绑定是一个指令v-model
,可以绑定一个动态值到视图,同时视图中变化能改变该值。
v-model
是语法糖,默认情况下相于:value
和@input
:
<template> <div id="app"> {{username}} <br /> <input type="text" v-model="username"></input> <!-- 等价于 --> <input type="text" :value="username" @input="username=$event.target.value"> </div> </template> <script> export default { name: 'App', data() { return { username: '' } } } </script>
9. 组件的通信
父组件向子组件传值(props)
<div id="app"> <!-- 1.通过 v-bind 将数据传给子组件 --> <test :ss='name'></test> </div> <script> var a = new Vue({ el:'#app', data:{ name:'bing', }, components: { // 子组件<test> test:{ props: ['ss'], // 2.接收父组件传递过来的数据 template:"<p>{{ss}}</p>" } } }) </script>
子组件向父组件传值($emit)
<div id="app"> <p>{{ total }}</p> <button-counter @increment="incrementTotal"></button-counter> <!-- 步骤3 --> </div> <script> Vue.component('button-counter', { template: '<button @click="increment">{{ counter }}</button>', // 步骤1 data: function () { return { counter: '子组件的数据' } }, methods: { increment: function () { // 通过 $emit 定义一个自定义事件 increment ,同时传入 this.counter 参数,对外抛出 this.$emit('increment', this.counter); // 步骤2 } } }); new Vue({ el: '#app', data: { total: '父组件的数据:' }, methods: { // 能接收到子组件传递过来的数据(e) incrementTotal: function (e) { // 步骤4 this.total = this.total + e[0] console.log(e); } } }) </script>
兄弟组件传值(EventBus)
- 新创建一个
event-bus.js
文件
// event-bus.js import Vue from 'vue' export const EventBus = new Vue();
- A 页面发送数据
<!-- A.vue --> <template> <button @click="sendMsg()">-</button> </template> <script> import { EventBus } from "../event-bus.js"; export default { methods: { sendMsg() { EventBus.$emit("aMsg", '来自A页面的消息'); // 对外发送数据 } } }; </script>
- B 页面接收数据
<!-- B.vue --> <template> <p>{{msg}}</p> </template> <script> import { EventBus } from "../event-bus.js"; export default { data() { return { msg: '' } }, mounted() { EventBus.$on("aMsg", (msg) => { // 接收 A 发送来的消息 this.msg = msg; }); } }; </script>
- 移除事件监听
import { EventBus } from "../event-bus.js"; EventBus.$off('aMsg', {}); // 移除应用内所有对此某个事件的监听 // 或 EventBus.$off(); // 移除所有事件监听
Vuex
- state:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- getter:可以认为是
store
的计算属性(有缓存,只有当它的依赖值发生了改变才会被重新计算)。 - mutation:是唯一更改
store
中状态的方法,且必须是同步函数。 - action:用于提交
mutation
(再由mutation
更改store
中状态),而不是直接变更状态,可以包含任意异步操作。 - module:可以将 store 分割成模块(module)。每个模块拥有自己的
state
、mutation
、action
、getter
、甚至是嵌套子模块
基本用法:
//创建一个 store const store = new Vuex.Store({ //state存储应用层的状态 state: { count: 5 // 组件中通过 this.$store.state.count; 获取 }, getters: { newCount: state => state.count * 3 // 组件中通过 this.$store.getters.newCount; 获取 }, // mutations是修改state中数据的唯一途径 mutations: { increment(state, value) { state.count += value; // 组件中通过 this.$store.commit("increment", 'hello'); 修改 } }, actions: { getParamSync(store, value) { // 处理异步操作 setTimeout(() => { store.commit('increment', value); // 组件中通过 this.$store.dispatch('getParamSync','hi'); 修改 }, 1000); } } });
module 用法
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
10. 路由
Vue路由导航 router-link 和 router.push
router-link
1. 不携带参数
// 字符串 <router-link to="login">to login</router-link> <router-link to="/login">to login</router-link> // 对象 <router-link :to="{path:'/login'}"> to login</router-link> // 命名路由 <router-link :to="{name: 'Login'}"> to login</router-link>
2. 通过 query
携带参数:
- 地址栏变成
/login?color=red
- 可通过
{{$route.query.color}}
或this.$route.query.color
获取参数
<router-link :to="{path: '/login', query: {color: 'red' }}"> to login</router-link> <router-link :to="{name: 'Login', query: {color: 'red' }}"> to login</router-link>
3. 通过 params
携带参数:
- 地址栏变成
/login/red
- 可通过
{{$route.params.color}}
或this.$route.params.color
获取参数
// 无法获取参数 // 报警告(Path "/login" was passed with params but they will be ignored. Use a named route alongside params instead.) <router-link :to="{path: '/login', params: { color: 'red' }}"> to login</router-link> // 通过 {{$route.params.color}} 或 this.$route.params.color 获取参数。 <router-link :to="{name: 'Login', params: { color: 'red' }}"> to login</router-link>
此时 router.js
中需要设置 path: '/login/:color?'
// router.js var router = new Router({ routes: [{ path: '/login/:color?', // :color? => ?问号的意思是该参数不是必传项,不传不会报错 name: 'Login', component: Login }]
router.push
1. 不携带参数
// 字符串 router.push('/login') // 对象 router.push({path:'/login'}) // 命名路由 router.push({name: 'Login'})
2. 通过 query
携带参数:
// 可通过 {{$route.query.color}} 或 this.$route.query.color 获取参数 router.push({path: '/login', query: {color: 'red' }}) // 可通过 {{$route.query.color}} 或 this.$route.query.color 获取参数 router.push({name: 'Login', query: {color: 'red' }})
3. 通过 params
携带参数:
// 无法获取参数 router.push({path:'/login', params:{ color: 'red' }}) // 通过 {{$route.params.color}} 或 this.$route.params.color 获取参数。 router.push({name:'Login', params:{ color: 'red' }})
$router 和 $route 的区别
$router
:是指整个路由实例,你可以操控整个路由,用法如下:this.$router.go(-1); // 向前或者向后跳转n个页面,n可为正整数或负整数 this.$router.push('/'); // 跳转到指定url路径,history栈中会有记录,点击返回会跳转到上个页面 this.$router.replace('/'); // 跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面
$route
:是指当前路由实例$router
跳转到的路由对象;路由实例可以包含多个路由对象,它们是父子包含关系// 获取路由传递过来的参数 this.$route.params.userId this.$route.query.userName
router.js (含路由懒加载和路由鉴权)
import Vue from 'vue' import Router from 'vue-router' import { getStore } from 'js/store' Vue.use(Router); var router = new Router({ routes: [ { path: '*', redirect: '/' }, { path: '/', name: '/', component: () => import('./views/Index.vue'), // vue路由懒加载 异步加载 meta: { title: '首页', requireAuth: false // 此字段为false,不需要做鉴权处理 } }, { path: '/test', name: 'test', component: () => import('./views/Test.vue'),// vue路由懒加载 异步加载 meta: { title: 'test', requireAuth: true // 只要此字段为true,必须做鉴权处理 } }, { path: '/login', name: 'login', component: () => import('./views/Login.vue'),// vue路由懒加载 异步加载 meta: { noNav: true // 不显示nav } }] }); let indexScrollTop = 0; router.beforeEach((to, from, next) => { // 路由鉴权:在路由meta对象中由个requireAuth字段,只要此字段为true,必须做鉴权处理 if (to.matched.some(res => res.meta.requireAuth)) { const token = getStore({ name: 'access_token', type: "string" });// 获取localstorage中的access_token console.log(token); // 路由进入下一个路由对象前,判断是否需要登陆 if (!token) { // 未登录 next({ path: '/login', query: { redirect: to.path // 将跳转的路由path作为参数,登录成功后再跳转到该路由 } }) } else { // 用户信息是否过期 let overdueTime = token.overdueTime; let nowTime = +new Date(); // 登陆过期和未过期 if (nowTime > overdueTime) { // 登录过期的处理,君可按需处理之后再执行如下方法去登录页面 // 我这里没有其他处理,直接去了登录页面 next({ path: '/login', query: { redirect: to.path } }) } else { next() } } } else { next() } if (to.path !== '/') { // 记录现在滚动的位置 indexScrollTop = document.body.scrollTop } document.title = to.meta.title || document.title }) router.afterEach(route => { if (route.path !== '/') { document.body.scrollTop = 0 } else { Vue.nextTick(() => { // 回到之前滚动位置 document.body.scrollTop = indexScrollTop }) } }) export default router;
路由懒加载的不同写法:
- webpack < 2.4版(vue-cli2)的懒加载
const Index = resolve => require(['./views/Index.vue'], resolve);
- webpack > 2.4版(vue-cli3)的懒加载
const Index = () => import('./views/Index.vue');
11. 其他