HTML、CSS 相关
1、 BFC
1、BFC 是什么?
BFC(Block Formatting Context) 格式化上下文; 指一个独立的渲染区域,或者说是一个隔离的独立容器;可以理解为一个独立的封闭空间。无论如何不会影响到它的外面
2、形成BFC的条件
1)浮动元素,float 除 none 以外的值
2)绝对定位元素,position(absolute, fixed)
3)display 为以下其中之一的值 : inline-block, table-cell,table-caption, flex
4)overflow 除了 visible 以外的值(hidden, auto, scroll)
5)body 根元素
3、BFC 的特性
1)内部的 Box 会在垂直方向上一个接一个的放置
2)垂直方向上的距离由 margin 决定
3)bfc 的区域不会与 float 的元素区域重叠。
4)计算 bfc 的高度时,浮动元素也参与计算
5)bfc 就是页面上的一个独立容器,容器里面的子元素不会影响外面的元素
2、padding 和 margin 有什么不同?
作用对象不同:
padding 是针对于自身的
margin 是作用于外部对象的
3、 vw 与百分比 有什么区别?
在父元素的宽度为 100% 的时候,vw 和 百分比没有区别。
在父元素的宽度为 50% 的时候,vw 不会有变化, 百分比会变成原来的一半。
本质就是: 百分比有继承关系,直接继承父元素的。vw 不会继承父元素的宽度,指针对浏览器原本的宽度。
4、行内元素和块级元素
行内元素 没有自己的宽高, 宽高由内容决定,没有继承关系。
块级元素,独立站一行,继承父级元素的宽度。
5、如何让谷歌浏览器支持小字体。
谷歌浏览器最小字体为 12px。
想要设置 12px 以下,可以使用 transform: scale(0.8) ; -webkit-transform: scale(0.8); 将字体缩放到原来的多少倍即可将字体变小。
js 相关
一、深浅拷贝
1、目标
(1) 什么是深浅拷贝
(2)实现的方式
(3)在vue 中的使用
2、前置知识
(1)js 的一般数据类型的存储
(2)js 的引用类型的数据存储
3、浅拷贝 深拷贝
(1)浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
(2)深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
4、针对引用类型来说 赋值 深拷贝 浅拷贝的区别
(1) 浅拷贝 赋值的区别
-
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
-
浅拷贝 : 重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会互相影响。
-
深拷贝 : 从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
和原数据是否指向同一对象 和原数据是否指向同一对象 第一层数据为一般数据类型 第一层数据不是原始数据 赋值 是 改变会使原始数据一同改变 改变会使原始数据一同改变 浅拷贝 否 改变不会使原始数据一同改变 改变会使原始数据一同改变 深拷贝 否 改变不会使原始数据一同改变 改变不会使原始数据一同改变
5、浅拷贝的实现方式
- Object.assign()
- loadsh 里面的 _.clone
- … 展开运算符
- Array.prototype.concat
- Array.prototype.slice
6、深拷贝的实现方式
- JSON.parse(JSON.stringify())
- 递归的操作
- loadsh 里面的 _.cloneDeep
- Jquery.extend()
- date: new RegExp(‘\w+’)
7、手写深拷贝
// 标准的深拷贝 =》 引用数据类型(数组,对象)
function deepClone(source) {
// [] => Array(基类) {}=>{}
const targetObj = sorce.contructor === Array ? [] : {};
for(let keys in source) {
if(source.hasOwnProperty(keys)) {
if(source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = source[keys].constructor === Array ? [] : {};
//递归
targetObj[keys] = deepClone(source[keys]);
} else {
//基本数据类型,直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
二、防抖节流函数
1、为什么要学习 js 的防抖节流函数
- 作为前端开发不得不知道的
- 面试得得时候是经常会问到的
- 是闭包的实际应用
2、防抖函数-- 固定时间内,将多次操作变成一次
当持续触发事件 一定时间内 没有再触发事件 事件处理函数才会执行一次
如果设定的时间到来之前 又一次触发了事件 就重新开始延时
触发事件 一段时间内 没有触发 事件执行 肯定是定时器
(在设定的时间内 又一次触发了事件 重新开始延时 代表的就是重新开始定时器)
(那么意味着上一次还没有结束的定时器要清除掉 重新开始)
let timer;
clearTimeout(timer);
timer = setTimeout(function(){
},delay)
// 防抖封装--闭包的应用
function antiShake(fn, wait) {
let timeOut = null;
return args => {
if(timeOut) clearTimeout(timeOut);
timeOut = setTimeout(fn,wait);
}
}
3、 实际的应用
使用 echarts 时, 改变浏览器宽度的时候,希望重新渲染
echarts 的图像,可以使用此函数,提升性能。(虽然 echarts 里有自带的 resize 函数)
典型的案例就是输入搜索:输入结束后 n 秒才进行搜索请求, n 秒内又输入的内容,就重新计时。
解决搜索的 bug
//2000
//js 的异步操作
function thro(func, wait) {
// 会执行你点击了 多少次 就会执行多少次
// 这不是我们想要的 我们想要的是 比如说 时间是一秒 然后 哪怕你手速再快 一秒内你点了 100次 也只会执行一次
let timerOut;
return function () {
if(!timerOut) {
timerOut = setTimeOut(function(){
func()
timerOut = null;
},wait)
}
}
}
function handle() {
console.log(Math.random())
}
document.getElementById("test").onclick = thor(handle,2000)
4、节流函数-- 一定时间内的多个变成一个
应用场景: 提交表单、高频的监听事件
function throttle(event, time) {
let timer = null;
return function() {
if(!timer) {
timer = setTimeout(() => {
event();
timer = null;
},time)
}
}
}
三、js 作用域
1、作用域说明:一般理解指一个变量的作用范围
1、全局作用域
(1) 全局作用域在页面打开的时候被创建,页面关闭时被销毁
(2)编写在 script 标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到
(3)在全局作用域中有全局对象 window,代表一个浏览器窗口,由浏览器创建,可以直接调用
(4)全局作用域中声明的变量和函数会作为 window 对象的属性和方法保存
2、函数作用域
(1)调用 函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁
(2)每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
(3)在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量
(4)在函数作用域中访问到变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域
2、作用域的深层次理解
执行期的上下文
-
当函数代码执行的前期,会创建一个执行期上下文的内部对象 AO(作用域)
-
这个内部的对象是预编译的时候创建出来的 因为当函数被调用的时候 会先进行预编译
-
在全局代码执行的前期会创建一个执行期的上下文的对象 GO
延展内容:
1、函数作用域预编译
(1)创建 ao(active object) 对象 AO{}
(2) 找形参和变量声明 将 变量和 形参名 当作 AO 对象的属性名 值为 undefined
(3)实参形参相统一
(4)在函数体里面找函数声明 值赋予函数体
2、全局作用域的预编译
(1)创建 GO 对象
(2)找变量声明 将变量名作为 GO 对象的属性名 值是 undefined
3、预编译习题
function fn(a,c) {
console.log(a);
var a = 123;
console.log(a);
console.log(c);
function a() {};
if(!false) {
var d = 678;
}
console.log(d);
console.log(b);
var b = function (){};
console.log(b);
function c() {};
console.log(c);
}
fn(1,2);
// 预编译
// 作用域的创建阶段 == 预编译的阶段
// 预编译的时候做了哪些事情
// 1、创建了 ao 对象 2、找形参和变量的声明,作为 ao 对象的属性名,值是 undefined 3、实参和形参相统一 4、找函数声明 会覆盖变量的声明
AO:{
a: undefined 1
c: undefined 2
d: undefined
b: undefined
}
四、图片懒加载
let doms = document.getElementsByTagName("img");
let num = doms.length;
let img = doms;
let n = 0;//存储图片加载到的位置,避免每次都从第一张图片开始遍历
let isLoding = false;// 是否当前容器/页面里的图片加载完成
let _clientHeight = document.documentElement.clientHeight;//可见区域高度
let _scrollTop = document.documentElement.scrollTop || document.body.scrollTop;// 滚动条距离顶部的高度
//监听 窗口变化重新计算可视区域
function computedClientHeight() {
_clientHeight = document.documentElement.clientHeight;//可见区域高度
}
//页面载入完毕加载可视区域内的图片
lazyLoad();
function lazyLoad() {
//获取滚动条距离顶部高度
isLoadImg = n >= num;
_scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(let i = n; i < num; i++) {
if(img[i].offsetTop < _clientHeight + _scrollTop) {
if(img[i].getAttribute('src') == '') {
img[i].src = img[i].getAttribute('data-src');
}
n = i+1;
}
}
}
//---------- 分隔线,下面是第二种写法 ------------------------
let doms = document.getElementsByTagName("img");
let num = doms.length;
let img = doms;
let n = 0;// 避免重复加载
lazyLoad();
window.onscroll = lazyLoad;
function lazyLoad() {
let seeHeight = document.documentElement.clientHeight; // 可见区域
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(let i = n; i < num; i++) {
if(img[i].offsetTop < seeHeight + scrollTop) {
if(img[i].getAttribute("src") == "") {
img[i].src = img[i].getAttribute("data-src")
}
n = i + 1;
}
}
}
五、Promise
1、Promise.all 是支持链式调用的,本质上就是返回了一个 Promise 实例,通过 resolve 和 reject 来改变实例状态
Promise.myAll = function(promiseArr) {
return new Promise((resolve,reject) => {
const ans = [];
let index = 0;
for(let i = 0; i < promiseArr.length; i++) {
promiseArr[i].then(res => {
ans[i] = res;
index++;
if(index === promiseArr.length) {
resovle(ans)
}
})
.catch(err => reject(err))
}
})
}
2、Promise.race
Promise.race = function(promiseArr) {
return new Promise((resolve,reject) => {
// 如果不是 Promise实例需要转化为 Promise 实例
Promise.resolve(p).then(
val => resolve(val),
err => reject(err)
)
})
}
六、跨域、发送请求相关
1、jsonp 解决跨域问题
script 标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于 GET 请求。
实现:
const jsonp = ({url,params,callbackName}) => {
const generateUrl = ()=>{
let dataSrc = '';
for(let key in params) {
if(Object.prototype.hasOwnProperty.call(params,key)) {
dataSrc += `${key}=${params[key]}&`;
}
}
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement("script");
scriptEle.src = generateUrl;
document.body.appendChild(scriptEle);
window[callbackName] = data => {
resolve(data);
document.removeChild(scriptEle);
}
})
}
2、封装一个 Promise 请求
1)new 一个 XMLHttpRequest 对象
2)open 方法
3)onreadystatechange 方法
4)send 方法
const getJson = function(url) {
return new Promise((resolve,reject) => {
const xhr = XMLHttpRequest ? new XMLHttRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
xhr.open("GET",url,false);
xhr.setRequestHeader("Accept",'application/json');
xhr.onreadystatechange = function() {
if(xhr.readyState !== 4) return;
if(xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
}
xhr.send();
})
}
3、vue 中解决跨域
1、浏览器的同源策略:
就是两个页面具有相同的 : 协议(protocol)、主机(host)、端口号(port)
2、请求一个接口时,出现 Access-Control-Allow-Origin 等就说明请求跨域了
3、vue 中解决跨域: 配置 vue.config.js 文件,如果没有就自行新建一个
原理:
1)将域名发送给本地的服务器(localhost:8080)
2)再由本地的服务器去请求真正的服务器
3)因为请求是从服务端发出的,所以就不存在跨域问题了
注意: 修改 vue.config.js 文件 需要重启服务
七、ES6 相关面试题
1、事件循环机制
1、 js 中的异步操作 比如 fetch setTimeout setInterval 压入到调用栈中的时候里面的消息会进去到消息队列中去,消息队列中 会等到调用栈清空之后再执行
2、Promise async await 的异步操作的时候会加入到微任务中去,会在调用栈清空的时候立即执行调用栈中加入的微任务会立马执行
// 构造函数同步执行
const promist = new Promise((resolve,reject) => {
console.log(1);
resolve();
console.log(2);
})
// .then 方法是 异步执行的
promise.then(() => {
console.log(3);
})
console.log(4);
//输出结果: 1,2,4,3
let p = new Promise(resolve => {
console.log(4)
resolve(5)
})
function func1() {
console.log(1)
}
function func2() {
setTimeout(()=>{
console.log(2)
},0)
func1();
console.log(3);
p.then(resolve => {
console.log(resolve);
})
}
func2()
//4 1 3 5 2
2、 null 和 undefined
console.log(typeOf null);//表示为 “无” 对象 ,0
console.log(typeOf undefined) // 表示为 “无” 的原始值, NaN
undefined 出现的情况:
1、已声明,为赋值
2、对象某个属性不存在
3、函数调用,少了参数
4、函数的默认返回值
null 出现的情况:
1、手动释放内存
2、作为函数的参数(此参数不是对象)
3、原型链的顶端
3、 ES6 – filter、forEach 与map、递归
1、filter 方法: 不会影响原数组,会返回一个新数组
let arr = [1,3,4,5,89];
// 1、current =》当前值 2、index =》 当前值的下标 3、这个数组对象
let arr2 = arr.filter((current, index, array) => {
return current < 10;
})
console.log(arr);
console.log(arr2)
2、forEach 与map
1) forEach 的特点:
没有返回值;
不能用 break 打断;
遍历的是 value
2)map 的特点:
有返回值(数组),默认 return 是undefined;
接受的参数是一个函数(key,value);
不能用 break 打断.
let arr = ['a','b'];
let res = arr.map((value,key) => {
return value + "1";
})
console.log(res)
3、js 递归求和 1–100
function add(num1, num2) {
let num = num1 + num2;
if(num2 + 1 >100) {
return num;
} else {
return add(num, num2 + 1)
}
}
add(1, 2)
4、如何实现函数柯里化
定义: 一个函数,可以接收多个参数,反复被调用
fn(1,2,3)(6,7)
特点:
1、 不固定参数个数的函数
2、第一次执行 返回函数
3、后续执行 返回函数
4、缓存参数
5、 内部函数 闭包 二次缓存函数
// 闭包的应用场景: 避免全局变量命名冲突
// 第一种实现
function curring() {
// arguments 伪数组,具备数组的属性,但是不具备数组的方法
const args = Array.prototype.slice.call(arguments)
// const args1 = [].__proto__.slice.call(arguments);
// Array.prototype.slice 是一个原型上的方法
// call 改变 this 的指向
// 数组方法依赖于内部是 this 数据容器来执行
console.log(args);
// 一个函数访问外部变量 形成闭包
const inner = function inner() {
args.push(...arguments)
return inner
}
inner.getValue = function (){
console.log(args);
return args.reduce((res,ele)=>{
return res + ele;
},0)
}
return inner
}
const res = curring(1,2,3,4,5)(6,7,8)
res.getValue();
// 可以允许将返回值,进行二次计算。
// 第二种实现
function curring() {
// arguments 伪数组,具备数组的属性,但是不具备数组的方法
const args = Array.prototype.slice.call(arguments)
// const args1 = [].__proto__.slice.call(arguments);
// Array.prototype.slice 是一个原型上的方法
// call 改变 this 的指向
// 数组方法依赖于内部是 this 数据容器来执行
console.log(args);
// 一个函数访问外部变量 形成闭包
const inner = function inner() {
args.push(...arguments)
return inner
}
inner.__proto__[Symbol.toPrimitive] = inner.toString = inner.getValue = function (){
console.log(args);
return args.reduce((res,ele)=>{
return res + ele;
},0)
}
return inner
}
const res = curring(1,2,3,4,5)(6,7,8)
console.log(res - 1);
5、如何实现 ajax 重连机制
需求: 能实现ajax 请求,自动在失败的时候重新连接,重试次数可以传递,延迟时间
const rate = 0.5;
function request() {
return new Promise((resolve,reject) => {
setTimeout(()=>{
// 随机数 计算 来实现失败与 成功
let res = Math.random(); //0~~~0.9999之间
if(res > rate) {
resolve(res);//成功
} else {
reject(res);
}
},rate)
})
}
function retry(func, times=0, delay=0) {
return new Promise((resolve, reject) => {
//func 是一 事件,将它封装起来,,才能后续 times -- 时调用
let inner = async function inner() {
try{
const result = await func();
resolve(result)
} catch(e){
if(times-- <= 0) {
//彻底失败
reject(e)
} else {
//延迟执行
setTimeout(()=>{
inner();//再次尝试
},delay)
}
}
}
inner();
});
}
//函数设计
retry(request, 3, 2000).then(res => {
console.log(res,'成功');
}).catch(e => {
console.log(e,'失败')
})
八、大量数据的处理
1、 渲染大数据时,合理使用 createDocumentFragment 和 requestAnimationFrame ,将操作切分为 一小段一小段执行
setTimeout(() => {
// 插入十万条数据
const total = 100000;
// 一次插入的数据
const once = 20;
// 插入数据需要的次数
const loopCount = Math.ceil(total / once);
let countOfRender = 0;
const ul = document.querySelector("ul");
fucntion add() {
const fragment = document,createDocumentFragment();
for(let i = 0; i < once ;i++) {
const li = document.createElement("li");
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
fucntion loop(){
if(countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
},0)
2、 下拉加载功能的实现
原理就是监听页面滚动事件,分析 clientHeight、scrollTop、scrollHeight 三者的属性关系
window.addEventListener("scroll",function(){
const clientHeight = document.documentElement.clientHeight;
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
if(clientHeight + scrollTop >= scrollHeight) {
//检测到滚动至页面底部,进行后续操作
}
}, false)
3、过滤出页面中所有的 dom 节点
注意点: DOM 操作返回的是类数组,需要转为数组之后才可以调用数组的方法。
const fn = () =>{
return [...new Set([...document.querySelectorAll("*")].map(el=> el.tagName))].length;
}
九、原型与原型链
原型 prototype ==> 函数特有
原型链 proto ==> 大家都有的
从当前实例属性去查找,如果找到了就返回,否则就顺着原型链一层一层往上面找,直到找到 null 为止,如果找到 null 都没有找到,则报错。
如果是他自身就有的 属性(私有属性),hasOwnProperty () 方法可以判断。
1、构造函数-- 创建实例对象
每一个 js 对象(除了 null)都只有一个 --proto-- 的原型对象。
原型链继承 是使用 构造函数来实现的。
1)创建一个 空对象
2)将 this 指向原型对象
function Person() {
this.name = 'paipai';
this.age = 18;
}
let p = new Person();
console.log(p);// 打印 Person 的对象
console.log(p.__proto__ === Person.prototype) // true
十、如何理解 this 指向? new 关键字执行过程
参考: 一般情况下,大部分人会认为 this 指向就是 谁调用 this就指向谁。并且在js 中也确实是 谁调用 this 就指向谁,但是这个东西也分不同场景,如果是普通函数,this 就是指向window。但是箭头函数是没有 this 的,所以 this 是 在调用的时候 确认。还需要区分 this 是根据调用方式来确认的。
let j = {
a:10,
b:{
a:12,
fn:function (){
consosle.log(this.a)
}
}
}
j.b.fn();// 12 this指向它自身的上一级
var id = 66;
function fn5() {
// 箭头函数没有作用域, 没有自己的this。 this 指向外层
setTimeout(()=>{
console.log(this.id)
}, 500)
}
fn5({id:22})// 指向widow,打印 66
fn5.call({id:22})// 改变this
new 关键字执行的时候发生了什么?
默认情况下,函数的返回值是 undefined。
在构造函数中,默认返回新创建的对象。
function Person(){}
let person1 = new Person();
//1、创建一个 空 对象
let obj = new Object();// Object => 基类
//2、设置它的原型链
obj.__proto__ = Person.prototype;
//3、改变 this 的指向
let result = Person.call(obj)
//4、判断返回值类型
if(typeof (result) == 'object') {
person1 = result;
} else {
person1 = obj;
}
// 面试题:obj 和 obj2 有什么区别?
let obj = Object.create(null);
let obj2 = {};
console.log(obj,obj2)
// 参考: obj 没有原型链,Object.create创建的对象是一个很纯净的对象,不存在原型链 ——proto--。obj2有原型链。
// 应用场景:如果只需要调用值,不需要调用方法,使用 Object.create 执行效率会更高。
十一、改变 this 的指向,手写 call,bind,apply
bind() 不调用 ,只改变 this 指向
call,apply 改变之后,并且执行一次
apply 传数组, call 传对象
1、call 方法
1)首先是所有函数都可以调用
2)他有 N 个参数 至少有一个
3)第一个参数就是我们想要去改变的 this , 从第二个开始就是函数本身的参数
4) call 还有返回值,返回值就是函数的函数值
let newCall = function(obj) {
// 当前函数 newCall 的 this 是谁?
// console.log(this) ?? 需要去改变 this 指向的函数
// console.log(obj) ?? 需要去指向的 this
// console.log(arguments) // 函数中的伪数组(具备数组的属性,不具备 数组的方法)
let arr = [];
for(let index = 1; index < arguments.length; index++) {
const element = arguments[index];
arr.push(element);
}
obj.fun = this;
let res = obj.fun(...arr);
delete obj.fun
return res;
}
Function.prototype.newCall = newCall
let obj = {
name:"小明"
}
function getInfo(age,vip) {
return {
name: this.name,
age: age,
vip: vip
}
}
console.log(getInfo.newCall(obj, 20, 'vip'))
2、apply
跟call 方法差不多,唯一的区别就是只有两个参数,并且 第二个参数必须是数组
let newApply = function(obj) {
// 当前函数 newCall 的 this 是谁?
// console.log(this) ?? 需要去改变 this 指向的函数
// console.log(obj) ?? 需要去指向的 this
// console.log(arguments) // 函数中的伪数组(具备数组的属性,不具备 数组的方法)
let arr = [];
for(let index = 1; index < arguments[1].length; index++) {
const element = arguments[index];
arr.push(element);
}
obj.fun = this;
let res = obj.fun(...arr);
delete obj.fun
return res;
}
Function.prototype.newApply = newApply
let obj = {
name:"小明"
}
function getInfo(age,vip) {
return {
name: this.name,
age: age,
vip: vip
}
}
console.log(getInfo.newApply(obj, [20, 'vip']))
3、bind 方法
1) 他有 N 个参数 至少有1个
2)返回一个函数 并且只有调用该返回函数的时候才可以拿到 getInfo 的返回值
3)返回一个函数可以作为构造函数使用 作为构造函数使用的时候 this 就失效了
拓展: 构造函数的 new 是指向实例的。
let obj = {
name:"小明"
}
function getInfo(age,vip) {
return {
name: this.name,
age: age,
vip: vip
}
}
let newBind = function(obj) {
// 只能会只能拿到一部分参数
let _this = this,
arr = Array.slice.call(arguments, 1);
let newFun = function() {
// 也只能会只能拿到一部分参数
// 调用时 拿到 当前函数 getInfo 的返回值
let arr2 = Array.slice.call(arguments);
let list = arr.concat(arr2);
if(this instancef newFun) {
return _this.newCall(this, ...list);
} else {
return _this.newCall(obj, ...list);
}
}
return newFun;
}
Function.prototype.newBind = newBind
let func = getInfo.newBind(obj,18);
console.log(new func("vip"));
console.log(func("vip"));
十二、 es6 Proxy 的理解
1、proxy 代理的方式
1、get(target, prop, receiver) : 拦截对象属性的访问。
2、set(target, prop, value, receiver) : 拦截对象属性的设置,最后返回一个布尔值。
3、apply(target, object, args) : 用于拦截函数的调用,比如 proxy()。
4、construct(target, args) : 方法用于拦截 new 操作符, 比如 new proxy()。 为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有【[Construct]】内部方法(即 new target 必须使有效的)。
5、has(target, prop) : 拦截例如 prop in proxy 的操作,返回一个布尔值
6、deleteProperty(target, prop) :拦截 delete proxy[prop] 的操作,返回一个布尔值。
7、ownKeys(target) : 拦截 Object.getOwnPropertyNames(proxy) 、Object.keys(proxy)、 for in 循环等操作,最终会返回一个数组。
2、使用 Proxy 和 Reflect 实现双向数据绑定
<input type='text' id='input' />
<h2>您输入的内容使:<i id='txt'></i> </h2>
<script>
// 获取dom 元素
let oInput = document.getElementById("input");
let oTxt = document.getElementById("txt");
// 初始化代理对象
let obj = {};
// Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更容易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
// 给obj 增加代理对象
let newProxy = new Proxy(obj, {
get:(target, key, receiver) => {
//console.log("get:",key);
return Reflect.get(target, key, receiver)
},
set:(target, key, value, receiver) => {
// 监听 newProxy 是否有新的变化
if(key === 'text') {
oTxt.innerHTML = value;
}
return Reflect.set(target, key, value, receiver)
},
})
// 监听 input 输入事件
oInput.addEventListener("keyup",e =>{
// 修改代理 对象的值
newProxy.text = e.target.value
})
</script>
defineProperty 无法一次性监听对象所有属性,必须遍历或者递归
十三、闭包
1、递归中的闭包
1、什么是递归? 函数直接或间接调用自己的函数
2、什么是闭包? 父函数里包含子函数;子函数被 return 到父函数之外,这个子函数就是闭包
3、下面这样的写法,每次 return 的,都是一个全新的函数
4、为什么要有闭包? 1)避免变量被污染。2) 私有化。3)保存变量,常驻内存
return {
fn: fucntion(){
// 函数体
}
}
// 闭包应用--- 处理私有数据
let makeCounter = function() {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function(){
changeBy(-1);
},
value: function(){
return privateCounter;
}
}
}
// 使用暴露出来函数,如果使用私有变量则会报错
2、js单例模式 – 实现登录(闭包的应用)
// js 的单例模式,实现登录
let createLogin = function(a,b,c) {
console.log(a,b,c);
let div = document.createElement("div");
div.innerHTML ='我是登录的弹窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
let getSingle = function(fn) {
let result;
return function() {
return result || (result = fn.apply(this, arguments));
}
}
let create = getSingle(createLogin);
document.getElementById("loginBtn").onclick = function() {
let loginLay = create(1,2,3);
loginLay.style.display = 'block';
}
//es6 的实现 单例模式
class Person {
constructor(name, sex) {
this.name = name;
this.sex = sex;
}
say() {
console.log("学前端很开心")
}
}
let person1 = new Person("星空", "女");
console.log(person1.name)
person1.say();
console.log(person1.say === Person.prototype.say)
十四、手写 代码
1、手写 map
let arr = [1,2,3];
let array = arr.map((item,index) => {
return item * 2;
})
console.log(array) // 2,4,6
function map(arr, mapCallback) {
//检查参数是否正确
if(!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return [];
} else {
// 进行下面的操作
let result = [];
for(let i = 0, len = arr.length; i < len; i++) {
result.push(mapCallback(arr[i],i,arr));
}
return result;
}
}
let res = map(arr, item => {
return item * 2;
})
console.log(res)
十五、零散知识点
1、let 与 var
var :
1、具有声明提升。
2、不存在局部作用域(无块级作用域)。
3、声明覆盖。(后面声明的变量 会覆盖前面的同名变量)
let 没有声明提升。具有局部作用域。不允许声明同名变量。
2、原生 JS 实现事件委托
什么是事件委托?
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类的所有事件
let ul = document.getElementById("ul");
ul.onclick = function(event) {
console.log(event);
event = event || window.event;
let target = event.target;
if(target.nodeName == 'LI') {
alert(target.innerHTML);//委托的事情
}
}
let btn = document.getElementById("btn");
btn.onclick = functon (){
let li = document.createElement("li");
li.textContent = ul.children.length;
ul.appendChild(li)
}
3、匿名自执行函数 — 底层封装框架
1、自执行 =》 单例模式
2、防止变量污染
(function(window){
// 获取节点
window.$ = jquery = function(nodeSelector) {
// 存放东西
let nodes = {};
if(typeof nodeSelector === 'string') {
//
let temp = document.querySelectorAll(nodeSelector);
for(let i = 0; i < temp.length; i++) {
nodes[i] = temp[i];
}
//类数组(具备数组的属性,不具备数组的方法)
nodes.length = temp.length;
} else {
throw new Error("必须输入字符串!")
}
// 添加方法
nodes.addClass = function(classes) {
let className = classes.split(" ");
//循环 class 类名
className.forEach(value => {
for(let i =0; i < nodes.length; i++) {// 循环节点,给 节点添加样式类名
nodes[i].classList.add(value);// .classList 是原生 js 的属性
}
})
}
//修改 text
nodes.setText = function(text) {
for(let i 0; i < nodes.length; i++) {
nodes[i].textContent = text;// 原生js修改
}
}
return nodes;
}
})(window)
// 使用
浏览器相关
1、在浏览器中输入 URL 按下回车会发生什么?
https://www.baidu.com
URL: 统一资源定位符,俗称 网址
URL 只是IP 的一个映射
https : 传输协(http 和 TCP 之间加了一层 TSL 或者 SSL)
www: 万维网,可以看作 服务器
baidu.com: 域名
- 1 第一次访问
1.2 第二次访问: 将域名解析的IP存在本地=》读取浏览器缓存
- 解析 url
DNS 域名系统匹配真实的 IP
ping www.baidu.com ( 测试连接)
-
根据真实的 IP 建立一个连接 (TCP 的三次握手)
-
拿数据,渲染页面
-
四次挥手(断开连接)
渲染页面的过程:
客户端拿到数据以后,并行构建 dom 树和css 结构体,生成渲染树,计算布局信息,调用UI引擎渲染 ,最后生成用户所见页面IP 和域名之间的关系:
1、ip地址和域名是一对多的关系,一个ip地址可以有多个域名,但是相反,一个域名只能有一个ip地址;
2、ip地址是数字型的,为了方便记忆,才有了域名,通过域名地址就能找到ip地址;
3、ip,全称为互联网协议地址,是指ip地址,意思是分配给用户上网使用的网络协议的设备的数字标签;
4、常用的ip地址分为IPv4和IPv6两大类;
2、从哪些点做性能优化?
a、加载(优化方向): 1)减少 http 请求(精灵图,将多张图合并成 一个图。文件的合并);2)减少文件大小(资源压缩,图片压缩,代码压缩);3)CDN (第三方库,大文件,大图);4)SSR服务端渲染,预渲染;5)懒加载;6)分包(小程序分包就是为了加快首页的加载)
b、减少 dom 操作,避免回流,文档碎片
3、哪些是性能优化?
1、页面加载性能(加载时间,用户体验)
2、动画与操作性能(是否流畅无卡顿)
大片的dom操作,首选 translate 和定位。
3、内存占用(内存占用过大,浏览器崩掉等)
4、电量消耗(游戏方面,可以暂不考虑)
4、
Vue 相关1(Vue2)
1、 MVC 和MVVM
2、 v-model 原理
1、双向数据绑定
Object.defineProperty() 用来操作对象,也可以用来劫持对象。
3、 data 为什么是一个函数
1、闭包 =》 每一个组件都有自己的私有作用域,确保各组件的数据不会互相干扰
2、纯对象 =》 干扰 let obj = {};
4、 v-if 和 v-show
1、v-if : 不满足条件,不会渲染 dom 单次判断
2、v-show : 隐藏 dom =》 多次切换,(不能用于权限操作,因为可能会被更改)
5、虚拟 Dom 详解
1、虚拟 Dom 是什么?
vue 2.x 版本才有虚拟 dom
本质是 js 对象 --》跨平台
2、虚拟 Dom 在 Vue 中做了什么
vue 的渲染过程 (html、css、JavaScript)
1) 将真是 dom 转化为 虚拟 dom(js 对象)
2)更新的时候做对比
3、 虚拟 Dom 是如何提升 Vue 的渲染效率的?
1)局部更新(节点数据)
2)将直接操作 dom 的地方拿到两个 js 对象之中去做比较
6、 Diff 中的 patch()
初始化
当前的目标元素
7、 Vue 响应式原理
最简单的响应式实现:发布订阅模型+ 数据劫持
响应式核心: 1、数据联动(双向绑定) 2、需要捕获到修改
let Dep = {
clientList: {},// 容器
// 添加订阅
listen: function(key, fn) {
//短路表达式
(this.clientList[key] || (this.clientList[key] = [])).push(fn);
},
// 发布
trigger: function() {
let key = Array.prototype.shift.call(arguments),
fns = this.clientList[key];
if(!fn || fn.length === 0) {
return false;
}
for(let i = 0, fn; fn = fn[i++];) {
fn.appy(this, arguments)
}
}
}
// 数据劫持
let dataHi = function(data, tag, datakey, selector) {
let value = '',
el = document.querySelector(selector);
Object.defineProperty(data, datakey,{
get: function (){
return value;
},
set: function(val) {
value = val;
Dep.trigger(tag, val)
}
})
//订阅
Dep.listen(tag,function(text) {
el.innerHTML = text
})
}
// 使用
let obj = {};
dataHi({
data: obj,
tag: 'view-1',
datakey: 'one',
selector: '.box-1'
})
// 1、 初始化赋值
obj.one = '这是视图1'
//2、劫持数据:更新者负责重新渲染 N 次
8、 Vue3 响应式原理
9、其他
1、 $nexTick()
当 dom 更新之后延迟回调
2、Vue-router 与 location.href 有什么区别?
location.href : 简单方便,刷新页面
Vue-router: 实现了按需加载,减少了 dom 消耗
Vue-router:js 原生 history
10、实现观测数据侦听器(Observer)
// 简单实现对 属性的一个监听
let obj = {}
let val = 20;
Object.defineProperty(obj,'age',{
get(){
console.log(`age属性被读取${val}`);
return val;
},
set(newVal) {
console.log(`age属性被修改了,${newVal}`);
val = newVal;
}
})
// 使用 Observer 变化侦测
export class Observer {
constructor(value) {
this.value = value;
if(Array.isArray(value)) {
// 数组的逻辑
} else {
// 对象的逻辑
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj);
for(let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
}
// 循环 让对象的每一个属性都变成可观测的
function defineReactive(obj, key, val) {
if(arguments.length === 2) {
val = obj[key]; // 对象的某个值
}
if(typeOf val === 'object') {
// 递归
new Observer(val);
}
Object.defineProperty(obj, key,{
enumerable: true, // 可枚举(可被循环)
configurable: true,// 可改变
get() {
console.log(`${key}属性被读取了`);
return val
},
set(newVal) {
console.log(`${key} 属性被修改了,新值是${newVal}`);
val = newVal;
}
})
}
//使用
import {Observer} from './observer.js';
let obj = new Observer({
name: 'xiaoxiao',
age: 18,
demo:{
a:'123'
}
})
console.log(obj.value.name);
obj.value.age = 20
console.log(obj.value.demo.a)
11、 nextTick 在哪里使用?原理是?
1、nextTick 功能是批处理,多次调用默认会将逻辑暂存到队列中,稍后同步代码执行完毕后会采用,同步的方式依次刷新队列(nextTick 本身内部采用了异步方法,但是执行逻辑的时候采用的是同步)
2、内部实现原理(异步的实现原理 先采用 promise.then 不行再采用 mutationObserver 不行再采用 setImmediate 不行再采用 setTimeout 优雅降级)
补充:
语法:this.$nextTick(回调函数)
作用: 在下一次 DOM 更新结束后执行其指定的回调
什么时候用? 当改变数据后,要基于更新后的新 DOM进行某些操作时,要在 nextTick 所指定的回调函数中执行。
12、computed 和 watch 的区别
1、这两个内部都是基于 watcher 的,区别是 computed 数据可以用于页面渲染,wathc 不行,computed 只有在取值时才会执行对应的回调(lazy 为 true 所以不会立即执行)
watch 默认会执行一次(拿到老的值)。computed 用了一个 dirty 属性实现了缓存机制,多次取值如果依赖的值不发生变化不会更改 dirty 的结果,拿到的就是 老的值
13、Vue 中的事件修饰符
1、prevent :阻止默认事件(常用)
2、stop: 阻止事件冒泡(常用)
3、once :事件只触发一次(常用)
4、capture: 使用事件的捕获模式
5、self : 只有 event.target 是当前操作的元素时才触发事件
6、passive : 事件的默认行为立即执行,无需等待事件回调执行完毕
14、 Vue 封装的过度与动画
1、作用: 在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名
2、写法:
1)准备好样式:
元素进入的样式: 【1】v-enter: 进入的起点 【2】v-enter-active: 进入的过程 【3】v-enter-to :进入的终点
元素离开的样式:【1】v-leave :离开的起点 【2】 v-leave-active :离开的过程中 【3】v-leave-to:离开的终点
2)使用 包裹要过度的元素,并配置 name 属性:
<transition name='hello'>
<h1 v-show='isShow'>你好呀</h1>
</transition>
3、备注:若有多个元素需要过度,则需要使用: ,且每个元素都要指定 key 值。
15、插槽-默认插槽、具名插槽、作用域插槽
1、作用域插槽
1)理解: 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定)
// 子组件
<template>
<div>
<div>
{{title}}分类
</div>
<slot :games='games'> 我是默认的一些内容 </slot>
</div>
</template>
<script>
export default {
name: "Category",
props: ['title'],
data() {
return {
games:['小红帽','超级玛丽','穿越火线']
}
}
}
</script>
// 父组件
<Catergory>
<template slot-scope='{games}'>
<h4 v-for='(ite,index) in games' :key='index'>
{{ite}}
</h4>
</template>
</Catergory>
16、一些基础知识点
1、组件的定义: 实现应用中 局部 功能 代码 和资源的 集合
2、组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image。。。)【理解】。一个界面的功能很复杂【为什么】。 复用编码,简化项目编码,提高运行效率【作用】
3、模块:向外提供特定功能的 js 程序,一般就是一个 js 文件【理解】。js 文件很多且很复杂【为什么】。复用 js ,简化 js 的编写,提高 js 运行效率【作用】。
4、模块化: 当应用中的 js 都以模块来编写的时候,那这个应用就是一个模块化的应用
5、组件化: 当应用中的功能都是多组件的方式来编写的时候,那这个应用就是一个组件化的应用
6、不同版本的 Vue:
1) vue.js 与 vue.runtime.xxx.js 的区别: 【1】 vue.js是完整版的 vue,包含: 核心功能+模板解析器 【2】vue.runtime.xxx.js 是运行版的 vue,只包含核心功能,没有模板解析器
2)因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容
7、关于 VueComponet
1)school 组件本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是 Vue.extend 生成的。
2)我们只需要写 或者非闭合标签,Vue 解析时会帮我们创建 School 组件的实例对象,即 Vue 版我们执行的 : new VueComponent(options)
3)特别注意: 每次调用 Vue.extend 。返回的都是一个全新的 VueComponet !!!
4)关于 this 指向: 【1】组件配置中: data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 是 VueComponent实例对象。 【2】 new Vue() 配置中: data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的this 指向是 Vue 实例对象。
5)VueComponent 的实例对象,以后简称 vc (也可称之为:组件实例对象)。Vue 的实例对象,以后简称为 vm
8、mixin(混入)
// 功能:可以把各个组件共用的配置提取成一个混入对象
// 使用方式:
// 第一步定义混合,例如:
{
data(){.....},
methods:{....}
....
}
// 第二步使用混入,例如:
(1)全局混入: Vue.mixin(xxx)
(2)局部混入: mixins:['xxx']
9、插件
功能:用于增强 Vue
// 本质: 包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据
// 定义插件
对象.install = function(Vue, optiosn) {
//1、添加全局过滤器
Vue.filter(...)
// 2、添加全局指令
Vue.directive(...)
// 3、添加实例方法
Vue.prototype.$myMethods = function(){...}
}
使用 插件: Vue.use()
10、scoped 样式
作用: 让样式在局部生效、防止冲突
11、 ref 属性
1)被用来给元素或子组件注册引用信息(id 的替代者)
2)应用在 html 标签上获取的是 真是 DOM 元素,应用在标签上是组件实例对象(vc)
12、 props 配置项
1)功能: 让组件接收外部传过来的数据
2)传递数据:
- 接收数据:
【1】第一种方式(只接收):props:[‘name’] 。
【2】第二种方式(限制类型): props:[name:String]
13、消息订阅与发布(pubsub-js)
1)一种组件间通信的方式,适用于 任意组件间通信
2)使用步骤
//1、安装 pubsub: npm i pubsub-js
//2、引入 : import pubsub from "pubsub-js"
//3、接收数据:A组件想接收数据,则在 A组件中订阅消息,订阅的回调留在 A组件自身
methods(){
demo(data){...}
}
...
mounted() {
this.pid = pubsub.subscribe("xxx",this.demo)// 订阅消息
}
// 4、提供数据: pubsub.publish("xxx",数据)
// 5、最好在 beforeDestroy 钩子中,用 pubsub.unsubscribe(pid) 去取消订阅
14、全局事件总线
// 1)一种组件间通信的方式,适用于 任意组件间通信
// 2) 安装全局事件总线
new Vue({
....
beforeCreate(){
Vue.prototype.$bus = this;// 安装全局事件总现,$bus 就是当前应用的 vm
}
})
// 3)使用事件总线
// 1、接收数据: A组件想要接收数据,则在A组件中给 $bus 绑定自定义事件,事件的回调留在 A组件自身
methods(){
demo(data){...}
}
...
mounted() {
this.$bus.$on('xxx',this.demo)
}
// 2、提供数据: this.$bus.$emit('xxx',数据)
// 4)最好在 beforeDestory 钩子中,用 $off 去解绑当前组件所用到的事件。
Vue 项目优化相关
1、 一行 代码 彻底优化项目性能
1、对于大数据量的列表,如果不需要操作,只需要显示的,就使用 Object.freeze 冻结数据即可。
Object.freeze 冻结无需该百年的数据,避免框架继续遍历其属性,完成响应式(页面和内存的同步)
Object.isFrozen() 用于判断是否冻结数据。
2、代码片段: 虚拟的元素 – 框架作为代码片段来蒙骗框架, 在页面中成为注释 的库(vue-fragment)
对于路由组件的根组件,切换时 出于本身时代码片段,所以无法使用 parent.removeChild , insertBefore 等方法
可以使用在子组件内部(路由根组件有问题)
3、200个组件:
首屏卡顿 ==》懒加载;
常用组件切换时卡顿 ==》缓存常用组件;
keep-alive -随着用户使用的时间,不关闭浏览器会卡 ;
动态缓存
2、DOM、CSS 层面深层优化及 理论思想
3、详解大型项目路由优化方案(解决性能瓶颈)
4、业务需求面试
1、重置表单的优化方案
Object.assign(this.$data, this.$options.data());
Vue相关2(Vue3)
1、Vue3 带来了什么?
1、性能的提升
- 打包大小减少 41%
- 初次渲染快 55%,更新渲染快 133%
- 内存减少 54%
2、新的特性
1、Composition API(组合API)
1)setup 配置
2)ref 和 reactive
3)watch 与 watchEffect
4)provde 与 inject
2、新的内置组件
Fragment、Teleport、Suspense
3、其他改变
1)新的生命周期钩子
2)data 选项应始终被声明为一个函数
3)移除 keyCode 支持作为 v-on 的修饰符
3、拉开序幕的 setup
1、理解: Vue3.0 中一个新的配置项,值为一个函数
2、setup 是所有 Composition API(组合API)“表演的舞台”
3、组件中所用到的: 数据、方法等等,均要配置在 setup 中。
4、setup 函数的两种返回值:
【1】若返回一个对象,则对象中的属性、方法,在
Vuex
1、Vuex 概述
1、使用 Vuex 统一管理状态的好处
1)能够在 vuex 中集中管理共享的数据,易于开发和后期维护
2)能够高效地实现组件之间的数据共享,提高开发效率
3)存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步
补充: Vuex 是 专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间统新的方式,且适用于任意组件间通信
2、什么样的数据适合存储到 Vuex 中?
一般情况下,只有组件之间共享的数据,才有必要 储存到 vuex 中,对于组件中的私有数据,依旧存储在组件自身的 data 中即可。
补充: 多个组件依赖同一状态;来自不同组件的行为需要变更同一状态
2、 Vuex 的基本使用
1、安装 Vuex 的依赖包 :
npm install vuex --save
2、导入 vuex 包:
inport Vuex from 'vuex';
Vue.use(Vuex);
3、创建 store 对象
const store = new Vuex,Store({
// state 中存放的就是全局共享的数据。
state: {
count: 0
}
})
3、Vuex 的核心概念
1、主要核心概念如下: State、Mutation、Action、Getter
2、State
State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 的State 中进行存储。
// 创建 store 数据源,提供唯一公共数据
const store = new Vuex.Store({
state:{
count:0
}
})
1)组件访问 State 中数据的第一种方式:
this.$store.state.全局数据名称
2)组件访问 State 中数据的第二种方式:
// 1、从 vuex 钟按需导入 mapState 函数
import {mapState} from "vuex";
//通过刚才导入的mapState 函数,将当前组件需要的全局数据,映射为当前组件的 computed 计算属性:
//2、将全局数据,映射为当前组件的计算属性
computed: {
...mapState(['count'])
}
3、Mutation
Mutation 用于变更 Store 中的数据
1)只能通过 mutation 变更 Store 数据,不可以直接操作 Store 中的数据。
2)通过这种方式虽然操作起来稍微一些,但是可以集中监控所有数据的变化
// 定义 Mutation
const store = new Vuex.Store({
state:{
count:0
},
mutations: {
add(state){
//变更状态
state.count ++;
}
}
})
// 触发mutation
methos:{
handle() {
// 触发 mutations 的第一种方式
this.$store.commit('add')
}
}
// this.$store.commit('add') 是触发 mutations 的第一种方式
// 触发 mutations 的第二种方式:
// 1、从 vuex 中按需导入 mapMutations 函数
import {mapMutations} from 'vuex';
// 2、将指定的 mutation 函数,映射为当前组件的 methods 函数
methos:{
...mapMutatiosn(['add','addN'])
}
4、Action
// 定义 action
const store = new Vuex.Store({
state:{
count:0
},
mutations: {
add(state){
//变更状态
state.count ++;
}
},
actions: {
addAsync(context){
//在 actions 中,不能直接修改 state 中的数据;
// 必须通过 context,commit 触发某个 mutation 才行
setTimeout(()=>{
context.commit('add')
},1000)
}
}
})
// 触发
methos:{
handler() {
// 这里的 diapatch 函数,专门用来触发 action,
this.$store.dispatch('addAsync')
}
}
5、 Getter — 类似于组件中的计算属性
1)组件访问 getters中数据的第一种方式:
this.$store.getters.名称
2)组件访问 getters中数据的第二种方式:
// 1、从 vuex 钟按需导入 mapGetters 函数
import {mapGetters} from "vuex";
//通过刚才导入的mapState 函数,将当前组件需要的全局数据,映射为当前组件的 computed 计算属性:
//2、将全局数据,映射为当前组件的计算属性
computed: {
...mapGetters(['showNum'])
}
6、模块化+命名空间
1)目的:让代码更好维护,让多种数据分类更加明确
2)修改 store.js
const countAbout = {
namespaced: true,// 开启命名空间
state:{ x:1 },
mutations: {....},
actions: {...},
getters: {
bigSum(state) {
return state.sum * 10;
}
}
}
const personAbout = {
namespaced: true,// 开启命名空间
state:{ x:1 },
mutations: {....},
actions: {...},
getters: {
bigSum(state) {
return state.sum * 10;
}
}
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
3)开启命名空间后,读取数据
1、组件中读取 state 数据
// 方式一:自己直接读取
this.$store.state.personAbout.list
// 方式二: 借助 mapState 读取
...mapState('personAbout',['sum','school','subject'])
2、组件中读取 getters 数据
// 方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
// 方式二: 借助 mapGetters 读取
...mapGetters('personAbout',['sum','school','subject'])
3、组件中调用 dispatch
// 方式一:自己直接读取
this.$store.dispatch['personAbout/jiaWait',person]
// 方式二: 借助 mapActions 读取
...mapActions('personAbout',{incrementWait: "jiaWait"})
4、组件中调用 commit
// 方式一:自己直接读取
this.$store.commit['personAbout/ADD_PERSON',person]
// 方式二: 借助 mapMutations 读取
...mapMutations('personAbout',{incrementWait: "jiaWait"})
vue-router 路由
1、vue-router 的理解
vue 的一个插件库,专门用来实现 SPA 应用
2、对 SPA 应用的理解
1、单页 Web 应用(single page web application, SPA)
2、整个应用只有一个完整的页面
3、点击页面中的导航链接不会刷新页面,只会做页面的局部更新
4、数据需要通过 ajax 请求获取
3、路由的理解
1、什么是路由?
一个路由就是一组映射关系(key-value)
key 为路劲, value 可能是 function 或 component
4、缓存路由组件
1、作用: 让不展示的路由组件保持挂载,不被销毁
2、具体编码:
<keep-alive include='news'>
<router-view> </router-view>
</keep-alive>
5、两个新的生命周期钩子
1、作用: 路由组件所独有的两个钩子,用于捕获路由组件的激活状态
2、具体名字
1) activated 路由组件被激活时触发
2)deactivated 路由组件失活时触发
6、路由守卫–保护路由的安全(权限)
1、作用:对路由进行权限控制
2、分类: 全局守卫、独享守卫、组件内守卫
3、全局守卫:
//1、全局前置路由守卫--初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,form,next) => {
// 判断当前路由是否需要进行权限控制
if(to.meta.isAuth) {
if(有权限) {// 权限控制的具体规则
next()
} else {
alert("暂无访问权限")
}
} else {
next();
}
})
// 全局后置路由守卫,初始化时执行、每次路由切换后执行
router.after((to,from) =>{
if(to.meta.title) {
document.title = to.meta.title// 修改网页的 title
} else {
document.title = 'vue'// 默认名称
}
})
4、独享守卫:
beforeEnter(to, form, next) {
// 判断当前路由是否需要进行权限控制
if(to.meta.isAuth) {
if(有权限) {// 权限控制的具体规则
next()
} else {
alert("暂无访问权限")
}
} else {
next();
}s
}
5、组件内守卫:
// 通过路由规则,进入该组件时被调用-- 跟 mounted 同级
beforeRouteEnter(to, from, next) {
console.log("离开组件--",to, from);
next();
}
// 通过路由规则,离开该组件时 被调用
beforeRouteLeave(to, from, next) {
console.log("离开组件--",to, from);
next();
}
6、路由器的两种工作模式
1)对于一个 url 来说,什么时 hash 值? — #及其后面的内容就是 hash 值
2)hash 值不会包含 在 HTTP 请求中,即: hash 值不会带给服务器
3)hash 模式:
【1】地址中永远带着 # 号,不美观
【2】若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法
【3】兼容性好
4)history 模式:
【1】地址干净、美观
【2】兼容性 和 hash 模式相比略差
【3】应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题
调用 dispatch
// 方式一:自己直接读取
this.$store.dispatch[‘personAbout/jiaWait’,person]
// 方式二: 借助 mapActions 读取
…mapActions(‘personAbout’,{incrementWait: “jiaWait”})
4、组件中调用 commit
// 方式一:自己直接读取
this.$store.commit[‘personAbout/ADD_PERSON’,person]
// 方式二: 借助 mapMutations 读取
…mapMutations(‘personAbout’,{incrementWait: “jiaWait”})
# vue-router 路由
### 1、vue-router 的理解
vue 的一个插件库,专门用来实现 SPA 应用
### 2、对 SPA 应用的理解
1、单页 Web 应用(single page web application, SPA)
2、整个应用只有一个完整的页面
3、点击页面中的导航链接不会刷新页面,只会做页面的局部更新
4、数据需要通过 ajax 请求获取
### 3、路由的理解
1、什么是路由?
一个路由就是一组映射关系(key-value)
key 为路劲, value 可能是 function 或 component
### 4、缓存路由组件
1、作用: 让不展示的路由组件保持挂载,不被销毁
2、具体编码:
```javascript
<keep-alive include='news'>
<router-view> </router-view>
</keep-alive>
5、两个新的生命周期钩子
1、作用: 路由组件所独有的两个钩子,用于捕获路由组件的激活状态
2、具体名字
1) activated 路由组件被激活时触发
2)deactivated 路由组件失活时触发
6、路由守卫–保护路由的安全(权限)
1、作用:对路由进行权限控制
2、分类: 全局守卫、独享守卫、组件内守卫
3、全局守卫:
//1、全局前置路由守卫--初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,form,next) => {
// 判断当前路由是否需要进行权限控制
if(to.meta.isAuth) {
if(有权限) {// 权限控制的具体规则
next()
} else {
alert("暂无访问权限")
}
} else {
next();
}
})
// 全局后置路由守卫,初始化时执行、每次路由切换后执行
router.after((to,from) =>{
if(to.meta.title) {
document.title = to.meta.title// 修改网页的 title
} else {
document.title = 'vue'// 默认名称
}
})
4、独享守卫:
beforeEnter(to, form, next) {
// 判断当前路由是否需要进行权限控制
if(to.meta.isAuth) {
if(有权限) {// 权限控制的具体规则
next()
} else {
alert("暂无访问权限")
}
} else {
next();
}s
}
5、组件内守卫:
// 通过路由规则,进入该组件时被调用-- 跟 mounted 同级
beforeRouteEnter(to, from, next) {
console.log("离开组件--",to, from);
next();
}
// 通过路由规则,离开该组件时 被调用
beforeRouteLeave(to, from, next) {
console.log("离开组件--",to, from);
next();
}
6、路由器的两种工作模式
1)对于一个 url 来说,什么时 hash 值? — #及其后面的内容就是 hash 值
2)hash 值不会包含 在 HTTP 请求中,即: hash 值不会带给服务器
3)hash 模式:
【1】地址中永远带着 # 号,不美观
【2】若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法
【3】兼容性好
4)history 模式:
【1】地址干净、美观
【2】兼容性 和 hash 模式相比略差
【3】应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题