1. 网站优化
1)代码压缩/第三方工具进行代码压缩
2)控制图片大小,超出的图片用canvas进行处理
3)CDN加速,把公用的的js包或CSS包放在公共服务器或npm仓库进行异步加载减少页面阻塞
4)图片懒加载,尽量用CSS替换图片,图片存放在服务器中转换成https链接的形式,减小包容量
5)大数据分页处理,减少DOM节点读取的卡顿
6)利用缓存存储必要信息,减少接口的频繁调用,减轻服务器压力
7)防爆操作,如支付/表单提交等,用户不得频繁调用接口,只有接口处理返回之后才能重新调用,惰 性函数/一次性函数/异步函数增强业务逻辑的同时,也可以合理延长请求时间,减少接口请求频率
8)组件仓库化(npm仓库进行管理),减少项目体积
【补充】
【1】按需加载第三方库(UI 组件库或工具库等);
【2】移除调试信息,例如 log;
【3】合理使用 SourceMap;
【4】通过 externals 忽略第三方包再配合 CDN 的方式进行加载;
【5】路由/组件/图片/数据懒加载、图片压缩/雪碧图、压缩合并 CSS、提取公共 JS 等;
【6】合理的使用缓存(包含前端缓存或 HTTP 缓存);
【7】webpack实现代码压缩/代码分区合并/图片压缩/console.log打印清除/注释清除/debugger清除。
2、网站安全
1)使用第三方的图片验证码实现防字典爆破
2)md5/sha1实现数据加密,譬如支付前的token加密,确保信息安全;
3)前端生成验证码,短信验证码,登陆密码校验等实现真人校验
4)输入信息校验/过滤,防SQL语句注入
3、浏览器兼容
1)兼容IE9及以上,360浏览器,火狐浏览器和谷歌浏览器
2)JS通过渐进增强和平稳退化的方式,
比如,IE的JS的api不兼容,冒泡模式不一样,怪异盒模型,IE8以下浏览器的浮动样式问题
3)CSS兼容问题
不同浏览器的默认样式存在差异,可以使用 Normalize.css 抹平这些差异。当然,你也可以定制属于自己业务的 reset.css
<link href="https://cdn.bootcss.com/normalize/7.0.0/normalize.min.css" rel="stylesheet"></link>
4、前端跨域问题
1)JQuery的jsonP跨越【需要配置jsonp接口】,get请求可通过<a> / <script>
标签实现跨域访问
2)webpack 或 vue/cli 或 vite 的dev-serve设置跨域
3)服务器设置代理跨域,后台跨域返给前端,或者后端做转发,把数据返给前端
4)uniapp内置浏览器自带跨域
5)使用谷歌浏览器的跨域插件,或者解除谷歌跨域限制【推荐,关于如何设置谷歌浏览器禁止检查ajax跨域问题的解决方案】
6)使用nodeJS实现代理转发
5、面向对象的程序设计(设计模式)
常用模式:
1)工厂模式
2)构造函数模式
3)原型模式
4)原型模式和构造函数模式混合使用
5)动态原型模式
6)发布者订阅者模式
【补充】推荐文章:15分钟带你了解前端工程师必知的javascript设计模式(附详细思维导图和源码)
单例模式
构造器模式
建造者模式
代理模式
外观模式
观察者模式
策略模式
迭代器模式
6、ES6新特性
网址:官网
7、开发项目中遇到的难点
1)小程序唤起微信浏览器打开H5
解决办法:
1、微信城市服务的资格申请【直接能打开H5,也能关闭H5回到小程序中】
2、利用客服会话,推送H5跳转链接【要经过一遍客服会话,增加流程】
3、使用该小程序关联的公众号的文章的二维码实现H5跳转【先扫码关注公众号,公众号模板链接跳H5】
2)解决小程序中canvas层级过高的问题
通过小程序处理canvas的wx.canvasToTempFilePath使之变为图片,打开蒙层的那一刻之前先变为图片,关闭蒙层,则重新渲染
3)10w+数据处理
前端处理:分页处理,预加载处理(譬如说下一页的预加载),或者分批请求,1w为节点请求,有十万就请求十次。
后端: redis缓存
4)小程序组件生命周期响应大于页面周期,数据不渲染的问题
1、this.setData数据超出258k的限制,数据切片处理/分批渲染等
例如:通过迭代器将数组中的元素遍历出来 entries()
<script>
function main() {
var arr = ["red", "blue", "green"]
var x = arr.entries()
console.log(x); // Array Iterator {}
console.log(x.next()) //{value: Array:[0, "red"],done:false}
console.log(x.next()) //{value: Array:[1, "blue"],done:false}
console.log(x.next()) //{value: Array:[2, "green"],done:false}
console.log(x.next()) //{value: undefined, done: true}
}
// 也可以通过 for of 遍历循环
function main1(){
const options = [1, 2, 3, 4, 5];
for (const [index, value] of options.entries()) {
console.log(value);
}
}
main()
main1()
</script>
2、组件更加细分,组件与组件之间异步加载,数据加载更快
3、尽可能少使用大数据的二维数组,如果要使用,也得一个组件只能有一个v-for
5)PC端微信小程序的wx.getLocation无效【因为PC端是没GPS定位的】
1、通过wx.getSystemSync()获取platform信息,除了andiron,ios之外的系统都是PC端。给定经纬度的默认值
2、通过授权登录获取用户信息,获取手机号判断用户上次用手机定位的信息
6)前端版本控制清除用户本地缓存
通过前端版本控制清除用户本地缓存使之重新登陆,刷新用户本地缓存信息,并推送相应的版本更新提示,或者通过对localStorage过期时间设置实现,推荐文章:面试官: 如何让localStorage支持过期时间设置?
大概思想为:再存入对应的键值时,同时通过加连接符绑定一个过期时间。当下次进入页面使用时,与当前时间比较,比较后做对应的操作
7)iphoneX的安全区域问题
1、CSS的constant(),env()方法实现安全区域兼容
2、通过wx.getSystemSync()获取statusHeight和windowHeight来判别安全高度
8)react中声明的纯函数组件的ref为null
纯函数组件的状态更改只能够通过hooks,手动更改或渲染
9)react的纯函数组件视图与数据分离,视图更改数据未更改,数据更改视图未更改
10)数组对象里的属性更改,视图不渲染
React 并不会检查数组里的元素是否有改变,它只会检查数组本身的引用是否有改变。通过扩展运算符更改数组的引用
8、js事件循环(event loop)
推荐视频
js事件循环包括宏任务和微任务、js单线程执行过程、浏览器异步执行机制
下述文字讲解的是浏览器异步执行原理和事件驱动的基础上开拓的js的事件循环机制
1)浏览器js异步执行原理
浏览器是多线程的,js是单线程的(指的是执行js的代码是单线程的,是浏览器提供的js引擎线程);浏览器还有定时器线程和http请求线程等等。
比如说主线程中会发送一个ajax请求,就会把这个任务交给浏览器的http请求线程去请求,js主线程不受影响继续向下执行,当请求成功之后,会将请求中的回调函数在返给js主线程。
换句话说,浏览器才是真正执行请求的角色,而js是处理请求的回调函数的角色
浏览器包括浏览器进程、渲染进程、GPU进程、网络进程等
我们前端主要关注的是渲染进程【包括js引擎线程、http请求线程,定时器线程,条件出发、GUI线程等】
浏览器异步执行的原理是通过事件驱动(事件触发、任务选择、任务执行)来触发的
事件循环就是在事件驱动模式中来管理和执行事件的一套流程
事件驱动和状态/数据驱动来改变渲染,浏览器中典型的事件处理也是典型的事件驱动,在事件驱动中,当有事件触发后,被触发的事件会按顺序暂时存在一个队列中,等待js的同步任务执行完成之后就从这个队列中取出要处理的事件并处理,什么时候取出任务,优先处理那些任务,这都是由我们的事件循环来控制
浏览器中的事件循环、必须了解执行栈和任务队列
js在执行一段代码时,会将同步代码按顺序排列在某个地方,这就是执行栈。依次执行里面的函数,当遇到异步任务时就交给其他线程来处理。等待当前同步代码执行完成之后,它会从一个队列中去取出完成中的异步任务的回调加入执行栈继续执行。遇到异步任务然后又交给其他线程。通过这样一个循环来执行完整个代码
js按顺序执行执行栈里面的方法,每次执行一个方法时,都会为这个方法生成一个独有的执行环境,也就是我们平时说的上下文(context) 等待这个方法执行完成之后,会销销毁掉这个环境,并从执行栈中弹出此方法,然后又继续调用下一个方法
由此可见,在事件驱动的模式下,至少包含了一个执行循环来检测任务队列是否有新的任务,通过不断循环取出异步回调来执行,这个过程就是我们所说的事件循环(事件周期)
宏任务和微任务
任务队列根据任务不同可以分为宏任务队列和微任务队列。执行栈在同步代码执行完成之后优先会去检查微任务队列是否会有任务需要执行,如果没有,再去宏任务队列检查。如此往复
微任务一般会在当前循环优先执行,而宏任务会等到下一次循环开始的时候执行。
微任务要比宏任务先执行,并且微任务只有一个队列,而宏任务有多个【因为宏任务就包括定时器任务,请求任务,鼠标点击,键盘按下等等事件】
宏任务包括 setTimeOut() \ setInterval() \ setImmediate()
微任务包括 Promise.then() \ Promise.catch \
宏任务特征:有明确的异步任务需要执行和回调,需要其他异步线程支持
微任务特征,没有明确的异步任务需要执行,只有回调,不需要其他异步线程支持
如果我们将定时器设置为0ms,谷歌浏览器会默认为4ms,Node.js会默认为1ms
定时器误差
因为js单线程先执行同步代码,再取出执行异步回调。当执行setTimeOut时,浏览器会启用新的线程去计时,计时结束后重新出发定时器事件,将回调存入宏任务队列中,等待JS主线程空了的时候再取出执行。如果同步代码还在执行,这个时候的宏任务只能挂起,这就会导致计时不准确的问题。同步代码越长/微任务耗时长,计时也就越不准确
微任务队列执行完成之后,也就是一次事件循环结束之后,浏览器会执行视图渲染。当然这里也有浏览器自己的一个优化,它可能合并多次事件循环的结果做一次视图重绘。因此视图更新是在事件循环之后,所以并不是每一次操作dom都会立马刷新视图,视图重绘之前会先执行requestAnimationFrame回调
9、js判断空对象的5种方法
知识点:
1)Object.getOwnPropertyNames ,获取对象的全部属性名存放在一个数组中
2)Object.keys(obj) 获取给定对象的所有可枚举属性的字符串数组
3)hasOwnProperty检测属性是否存在对象实例中(可枚举属性),如果存在返回true,不存在返回false
例如:
let arrObj = { name: "字符串", age: 18 };
// 第一种方法
console.log(JSON.stringify(arrObj) === "{}"); //false
// 第二种方法 for...in
let fun = (arr) => {
for (let i in arr) {
return false;
}
return true;
};
console.log(fun(arrObj)); // false,表示不是空对象
// 第三种方法
let _arr = Object.getOwnPropertyNames(arrObj);
console.log(_arr); // ["name" , "age"]
console.log(_arr.length != 0); // true,表示不为空对象
// 第四种方法
let _arr1 = Object.keys(arrObj);
console.log(_arr.length != 0); // true,表示不为空对象
// 第五种方法
let fun1 = (arr) => {
for (let i in arr) {
if (arr.hasOwnProperty(i)) {
return false;
}
}
return true;
};
console.log(fun1(arrObj)); // false,表示不为空对象
10、forEach方法跳出循环
知识点:
1)forEach是函数,不是if、for之类的语句。它的参数是一个匿名函数,是一个callback,实际每次forEach操作的是一个函数,所以不能使用for语句相关的continue,break
2)forEach方法机制,是对数组的每个有效元素执行一次callback函数
3)forEach跳出循环通过try…catch…方式
let arr = [1, 2, 3, 4];
// 第一种示例,未跳出循环
arr.forEach((n) => {
// forEach方法内是传递的一个匿名函数
if (n === 2) {
// break;
// continue; 这两个适用于for语句,不适用于forEach函数的
return;
}
console.log(n); // 控制台分别会打印1,3,4
});
// 第二种示例,跳出循环
let k = null;
try {
arr.forEach((n) => {
// forEach方法内是传递的一个匿名函数
if (n === 2) {
k = n;
throw new Error();
}
});
} catch (e) {}
console.log(k); // 2
11、什么是中间件
中间件是一个起连接作用的东西
只要能够获取数据的,都可以算是中间件。比如axios
12、JSONP跨域
1)什么是跨域?
当请求url时,它的协议、域名、端口,任意一个与你当前页面的url不同即为跨域
2)什么是JSONP?
JSONP其实是一个跨域解决方案
3)JSONP跨域原理
1、img\script等标签的src属性,它不受同源策略的限制
2、JSONP请求数据时,服务器返回的是一段可执行的javascript代码
4)JSONP用途:解决请求其他服务器时的跨域问题
5)JSONP局限:只能用get方式,需要服务器做一些处理、配合
6)分为三个部分:
1、安装nodeJS Express框架:npm i express -D
2、创建静态目录:app.use(‘/public’ , express.static(‘public’))
3、中间件js文件,响应get请求
示例:
<script>
// 处理请求回调函数
function dataFun(res) {
console.log(res)
}
let url = 'http://localhost:2244/get_data?callback=dataFun'
let btn = document.getElementById('btn')
btn.onclick = ()=>{
let _script = document.createElement('script')
_script.setAttribute('src' , url)
_script.setAttribute('type' , 'text/javascript')
document.getElementsByTagName('body')[0].appendChild(_script)
}
</script>
13、Promise链式调用,和axios处理高并发
应用场景:下一个操作依赖于上一个操作的状态和结果
前提条件:.then方法中必须返回一个promise对象
运行过程:1).then在链式调用时,会等其前一个then中回调函数执行完毕;2)并且成功返回状态的promise,才会执行下一个then的回调函数
情景如下,我们需要请求三个接口,这三个接口都是要等上一次的请求完成之后,才能请求下一个的请求
let url1 = 'https://123.com/api/api1'
let url2 = 'https://123.com/api/api2'
let url3 = 'https://123.com/api/api3'
// 链式调用,回调地狱模式【不推荐】,可维护性差
axios.get('url1').then(res1=>{
console.log(res1)
axios.get('url1').then(res2 => {
console.log(res2)
axios.get('url1').then(res3 => {
console.log(res3)
})
})
})
// 因为 axios.get('url1') 就会返回一个Promise对象
// 所以要这样处理,可维护性强
axios.get('url1').then(res1 => {
console.log(res1)
return axios.get('url2')
}).then(res2=>{
console.log(res2)
return axios.get('url3')
}).then(res3 => {
console.log(res3)
})
Promise.all() 异步任务同步请求
任务一:【推荐用这种】
getProps () {
const that = this
const cellStyle = this.$refs.formTable0.submit() // 这是一个Promise对象
const rowStyle = this.$refs.formTable1.submit()
// 异步任务同步执行
Promise.all([cellStyle, rowStyle]).then(res => {
console.log('res', res)
}).catch(res => {
console.log('任务阻塞', res)
that.$emit('update:submit', res)
})
}
任务二: await会阻塞下面的await,Promise.all中的catch捕获reject传来的错误
async getProps () {
const that = this
const cellStyle = await this.$refs.formTable0.submit()
const rowStyle = await this.$refs.formTable1.submit()
// 异步任务同步执行
Promise.all([cellStyle, rowStyle]).then(res => {
console.log('res', res)
}).catch(res => {
console.log('任务阻塞', res)
that.$emit('update:submit', res)
})
}
}
【补充】Promise.all和Promise.race的区别和使用
详细Promise介绍:Promise 的各种方法的详细使用
1)Promise.all 类似于Promise链式请求
比如当一个页面需要在很多个模块的数据都返回回来时才正常显示,否则loading
比如当数组里的P1,P2都执行完成时,页面才显示。
值得注意的是,返回的数组结果顺序不会改变,即使P2的返回要比P1的返回快,顺序依然是P1,P2
// 获取表格下拉框数据
async getSelectList () {
const that = this
const P1 = await orderumber({ type: 'order' })
const P2 = await getCustomerList()
const P3 = await listBaseOtherOffer()
// 异步任务同步执行,为了让所有的数据都请求完毕,再打开新增弹框,否则不打开
// 优点保证数据加载完毕,缺点是加载时长过长
Promise.all([P1,P2,P3]).then(res => {
that.outerVisible = true
}).catch(res => {
console.log('下拉框数据获取失败')
})
},
2)Promise.race
race是赛跑的意思,也就是说Promise.race([p1, p2, p3])里面的结果哪个获取的快,就返回哪个结果,不管结果本身是成功还是失败
// 获取表格下拉框数据
async getSelectList () {
const that = this
const P1 = await orderumber({ type: 'order' })
const P2 = await getCustomerList()
const P3 = await listBaseOtherOffer()
// 异步任务同步执行,为了让所有的数据都请求完毕,再打开新增弹框,否则不打开
// 优点保证数据加载完毕,缺点是加载时长过长
Promise.race([P1,P2,P3]).then(res => {
that.outerVisible = true
}).catch(res => {
console.log('下拉框数据获取失败')
})
},
一般用于和定时器绑定,比如将一个请求和一个三秒的定时器包装成Promise实例,加入到race队列中,请求三秒中还没有回应时,给用户一些提示或一些相应的操作。
3)Promise.resolve()
将现有对象转换未Promise对象
4)Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
5)实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误
Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。
【若想详细了解Promsie的底层原理】推荐视频:bilibili视频
14、Vue的render函数
render函数的用途: 创建html模板
知识点:
1)render函数的参数是createElement函数,createElement返回值是一个虚拟dom,即VNode,也就是我们需要渲染的节点
2)createElement有三个参数
第一个参数,必需,{ String | Object | Function } 是要渲染的html标签
第二个参数,可选:{ Object } html标签的各种属性
第三个参数,可选,{ String | Array } 当前html标签的子元素
3)渲染过程
1、独立构建,包括模板编译器,HTML字符串=> render函数=> VNode=>真实的DOM节点
2、运行时构建,不包含模板编译器,render函数=>VNode=>真实的DOM节点
<!-- <template>
<view>
</view>
</template> -->
<script>
export default {
// 当我们在某些场景下,不能使用<template>标签
render( createElement) {
return createElement('div' , {
// 设置样式类名
class:'class',
// 设置内联样式style
style:{ color:'#f00' , fontSize:'32rpx' },
// 设置html内容
// 子元素标签内有内容,外部父元素的domProps属性不能有,不然不显示子元素内容
domProps:{ innerHtml:'render函数渲染模板' },
// 设置html属性
attrs:{ id : 'domID' }
} , [
createElement('h1' , 'render子元素标签'),
createElement('h1' , '子元素标签内有内容,外部父元素的domProps属性不能有,不然不显示子元素内容')
])
},
onLoad(options){
this.url = options.url
}
}
</script>
也可以将创建的模板存放在一个变量里面
<!-- <template>
<view>
</view>
</template> -->
<script>
export default {
data() {
return {
url:''
};
},
// 当我们在某些场景下,不能使用<template>标签
render( createElement) {
let ele = createElement('div' , {
// 设置样式类名
class:'class',
// 设置内联样式style
style:{ color:'#f00' , fontSize:'32rpx' },
// 设置html内容
// 子元素标签内有内容,外部父元素的domProps属性不能有,不然不显示子元素内容
// domProps:{ innerHtml:'render函数渲染模板' },
// 设置html属性
attrs:{ id : 'domID' }
} , [
createElement('h1' , 'render子元素标签'),
createElement('h1' , '子元素标签内有内容,外部父元素的domProps属性不能有,不然不显示子元素内容')
])
console.log('创建的元素', ele)
return ele
},
onLoad(options){
this.url = options.url
}
}
</script>
也可以将创建的子元素存放在一个data属性里面
<!-- <template>
<view>
</view>
</template> -->
<script>
export default {
data() {
return {
strArr:['子元素1' , '子元素1' ,'子元素3']
};
},
// 当我们在某些场景下,不能使用<template>标签
render( createElement) {
let ele = createElement('div' , {
// 设置样式类名
class:'class',
// 设置内联样式style
style:{ color:'#f00' , fontSize:'32rpx' },
// 设置html内容
// 子元素标签内有内容,外部父元素的domProps属性不能有,不然不显示子元素内容
domProps:{ innerHtml:'render函数渲染模板' },
// 设置html属性
attrs:{ id : 'domID' }
} ,
//[
// createElement('h1' , 'render子元素标签'),
// createElement('h1' , '子元素标签内有内容,外部父元素的domProps属性不能有,不然不显示子元素内容')
//]
this.strArr.map(ele=>{
createElement('li',ele)
})
)
console.log('创建的元素', ele)
return ele
},
onLoad(options){
this.url = options.url
}
}
</script>
15、Vue的动态组件
作用:通过component的is属性,来切换不同的组件
知识点:
使用将允许您按名称访问全局和本地组件,可以动态绑定我们的组件,根据数据的不同更换不同的组件。在这种情况下,它更像一个容器
:is 属性 是is-bind的缩写
component标签中的is属性决定了当前采用的是哪个组件
<template>
<view>
<view>
Vue 动态组件
</view>
<!-- component标签的is属性可以动态绑定,那么就可以实现组件的动态切换 -->
<component :is="switchComp"></component>
</view>
</template>
<script>
// 创建三个组件
import component1 from './component1'
import component2 from './component2'
import component3 from './component3'
export default {
// 注册这三个组件
components:{
component1,component2,component3
},
data() {
return {
url: '',
// 我们可以通过改变switchComp的值动态切换组件
switchComp:'component1'
};
}
}
</script>
【典型案例】动态组件的方式实现不同标签的转换
应用场景为: 侧边菜单栏 实现跳转页面内或跳转外链
示例代码:
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
// 检测是否是外链
const isExternal = (path)=> {
return /^(https?:|mailto:|tel:)/.test(path)
}
export default {
props: {
// 必传,跳转路径
to: {
type: String,
required: true
}
},
computed: {
isExternal () {
return isExternal(this.to)
},
type () {
// 如果是外链,则显示a标签
if (this.isExternal) {
return 'a'
}
// 不是外链则展示router-link标签
return 'router-link'
}
},
methods: {
linkProps (to) {
// 如果是外链
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
}
}
// 如果是页面路径
return {
to: to
}
}
}
}
</script>
v-bind 可以返回一系列属性v-bind="linkProps(to)"
16、Vue的keep-alive
会缓存当前不活动组件的状态
作用:避免多次重复渲染降低性能
常用知识点:1)include – 字符串或正则表达式,只有名称匹配的组件会被缓存
2)exclude-- 字符串或正则表达式,任何名称匹配的组件都不会背后缓存
3)max – 数字。最多可以缓存多少组件实例
4)结合router。缓存部分页面 。 $router.meta.keepAlive选项
1)第一种方式
通过设置include/exclude/max在标签上设置属性,实现组件活性保持
2)第二种方式
结合router的方式
17、vue.config.js配置解决跨域问题
浏览器的同源策略:就是两个页面具有相同的协议/主机/端口号
请求同一个接口时,出现Access-Control-Allow-Origin等,就说明跨域了
vue中解决跨域,配置vue.config.js文件,如果没有就自行新建一个
原理:
1)将域名发送给本地的服务器(localhost:8080)
2)再由本地的服务器去请求真正的服务器
3)因为请求是从服务器端发出的,所以不存在跨域问题
注意:修改vue.config.js 文件,需要重启服务
module.exports = {
devServer: {
// 跨域
proxy: {
"/api": {
// 目标路径
target: "https://www.123.com/index",
// 是否跨域
changeOrigin: true,
// 重写路径
pathRewrite: {
"^/api": "",
},
},
},
},
chainWebpack: (config) => {
// 发行或运行时启用了压缩时会生效
config.optimization.minimizer("terser").tap((args) => {
const compress = args[0].terserOptions.compress;
// 非 App 平台移除 console 代码(包含所有 console 方法,如 log,debug,info...)
compress.drop_console = true;
compress.pure_funcs = [
"__f__", // App 平台 vue 移除日志代码
//'console.dug' // 可移除指定的 console 方法
];
return args;
});
},
};
17、Vue.extend()创建动态组件
动态创建组件:只在事件触发的时候,才产生某组件。平时它并不存在
Vue.extend() extend创建的是一个组件的构造器,而不是一个具体的组件实例
v-if 只在为true的时候被渲染,根据表达式的值在DOM中生成或移除HTML元素
v-show 更具表达式的值来,显示或者隐藏HTML元素
使用场景:
1)动态渲染组件
2)类似于window.alert() 提示组件
1)先创建一个组件模板 toast.vue ,也是以组件的方式创建,动态组件模板是没有data对象的
2)在extend…js文件中
【补充】
效果为:
toast.vue文件
<!--
* @Author: Null
* @Date: 2022-04-21 10:55:48
* @Description: 动态通知组件
-->
<template>
<div>
<div v-if="isShow" class="dynamic-toast">
{{ text || '动态通知组件' }}
</div>
</div>
</template>
<script>
export default {
name: 'Toast'
}
</script>
<style lang="scss" scoped>
.dynamic-toast{
position: fixed;
padding: 10px;
border: solid 1px #f00;
z-index: 10000;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
</style>
toast-extend.js文件
/*
* @Author: Null
* @Date: 2022-04-21 10:58:21
* @Description: 动态通知组件----toast-extend.js
*/
import Vue from 'vue'
import Toast from './index.vue'
// 生成构造函数构造器
const ToastConstructor = Vue.extend(Toast)
export function showToast (text, time = 2000) {
const dom = new ToastConstructor({
el: document.createElement('div'),
data () {
return {
text,
isShow: true
}
}
})
document.body.appendChild(dom.$el)
setTimeout(() => {
dom.isShow = false
}, time)
}
页面中使用
<el-button @click="testClick">
测试点击
</el-button>
import { showToast } from '@/components/Toast/toast-extend'
testClick () {
console.log('显示文本')
showToast('显示文本')
},
【补充】Vue.extend() 内使用computed属性失效 , 这个只接受静态的vue模板,如下图
18、javascript的闭包机制
闭包:父函数中,返回子函数
this , this的指向是,由它所在函数调用上下文【执行环境】决定的,而不是,由它所在函数定义的上下文决定的
var 和 let的区别
var 创建的变量会挂载在window对象上,而let 、 const创建的变量不会挂载在window对象上
【补充】const声明引用类型数据,不改变引用地址就不会报错
当我们在单独创建好的js文件中,声明的 const 变量 = 变量值
,必须是先声明,再引用。也就是 const 变量 = 变量值
,声明先在前,引用在后
打印结果为 : 我是老尚
如果我将var 更改为 let ,那么打印值为undefined,因为let 不会将变量挂载到window对象上
19、backdrop-filter(实现毛玻璃效果)
CSS效果为:
知识点:
样式为:
20、如何使用匿名自执行函数创建独立命名空间
匿名自执行函数,IIFE 。它被称之为,立即执行函数
格式:
(function (i) {
console.log("匿名自执行函数IIFE");
})(i);
优点:1)避免变量名的重复;2)独立的作用域;3)提高一些性能,减少对作用域链的查找;4)单独的js模块
()圆括号在js中的作用:1)调用函数表达式;2)对表达式求值
函数有两种创建的方式:1)通过function声明函数:function fun(){} ; 2)函数表达式:var fun = function(){}
示例:
(function (win) {
win.myFun = function () {
console.log("添加属性");
};
win.abc = "123";
console.log("封装好的实例", win); // 打印出 {abc:123,myFun:f() }
})((window.fun = window.fun || {})); // 传入的值相当于独立的命名空间,是在window对象下挂载fun命名空间,
// IIFE表达式的属性和方法不直接挂载到window对象下;
21、v-if和v-for为什么不能同时使用?
原因:1)v-for优先级更高;2)会造成性能浪费
解决方案:1)把v-if写在外层dom中;2)把v-if写在外层template中;3)把v-if写在v-for内
【注意只要保证不同级就可以,因为v-for和v-if都是虚拟dom,都要经过vue的diff算法的,生成真实节点】
22、Promise对象的resolve与pending状态的区别?
知识点:
1)async 函数,返回一个Promise对象,如果在函数中return 一个直接量,async会把这个直接量通过Promise.resolve() 封装成Promise对象
2)Resolved,完成状态
3)Pending,进行状态,表示延迟(异步)操作正在进行
23、await 和 .then方法的区别
示例代码1:
<script>
// async 会返回一个Promise对象
async function fn2() {
return 1;
}
function fn3() {
console.log(33333);
}
let _fn2 = fn2(); // _fn2得到的是fn2()函数返回的Promise对象
console.log(_fn2); // 打印为 Promise {<resolved>: 1}
const fn1 = () => {
// Promise.then()方法会在js引擎的微任务中执行回调
// 换句话说,当同步代码执行完毕之后,再调用微任务中的回调函数。
//当同步代码_fn2()函数执行完毕,返回 Promise {<resolved>: 1},那么fn1()函数的作用域已经没有了
// 当fn2()或者fn3报错了,那么堆栈信息就会额外会记录fn1的信息,这样就会导致一个问题
// 这中操作会降低性能,占用内存
_fn2.then(() => {
fn3();
});
};
fn1();
</script>
在IE浏览器中: fulfilled状态
在谷歌浏览器中: resolved状态
与await相比
<script>
async function b() {
return 1
}
function c() {
console.log('cccccc')
}
const a = async ()=>{
// await关键字后面必须跟随Promise对象,并且它会暂停async函数的执行,也就是阻塞await后面的代码执行
// 等到异步执行函数b()执行完毕之后,在执行下面的c()函数
await b()
console.log(await b()) // 打印为 1
c()
}
a()
</script>
24、hash模式和history模式
这个hash值就是我们访问链接#号之后的东西,
示例: https://www.123.com/hash.html#xx
我们可以通过window.location.hash能够访问到‘#xx’ 这个hash值,这个hash值不会带到服务器去
两个比较重要的参数,一个是newURL【当前路由】和oldURL【上一个路由】
我们可以根据hash值去显示不同的div,从而实现路由跳转
示例为:
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.router {
display: none;
}
</style>
</head>
<body>
<div>
<a href="#/">首页</a>
<a href="#/detail">详情</a>
</div>
<div class="router" id='index'>首页首页首页首页首页首页首页</div>
<div class="router" id='detail'>详情详情详情详情详情详情详情详情详情详情详情详情详情</div>
</body>
<script>
window.onhashchange = (e) => {
// console.log('e:', e)
showRouter()
}
function getDom(id, str = 'none') {
let dom = document.getElementById(id)
dom.style.display = str
}
function showRouter() {
let hash = window.location.hash
console.log('hash:', hash)
let hashVal = hash.replace('#/', '')
let hashValue = hashVal ? hashVal : 'index'
if (hashValue == 'index') {
getDom('index', 'inline')
getDom('detail', 'none')
} else {
getDom('index', 'none')
getDom('detail', 'inline')
}
}
</script>
</html>
history模式的实现
主要是通过history.pushState()
添加切换路由 ,window.onpopstate()
监听浏览器路由的变化
25、防抖和节流
防抖是通过setTimeOut 和 clearTimeOut实现的
使用场景:一些搜索下拉框等高频率请求的场景,就需要使用这个,只请求超出输入时间1.5s的请求
<script>
var timer;
function search() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
console.log("延迟请求");
}, 1500);
}
</script>
节流是节事件流,节约连续输入,每隔3s执行一次请求
<script>
var lastTime
function search() {
console.log('输入')
var newTime = new Date()
if(!lastTime || newTime - lastTime > 3000){
console.log('输入超过3s,节流事件触发')
lastTime = newTime
}
}
</script>
【补充】防抖节流函数:
/**
* @desc 函数防抖---"立即执行版本" 和 "非立即执行版本" 的组合版本
* @param func 需要执行的函数
* @param wait 延迟执行时间(毫秒)
* @param immediate---true 表立即执行,false 表非立即执行(默认)
**/
debounce(func, wait, immediate) {
let timer;
return function () {
let that = this;
let args = arguments;
if (timer) clearTimeout(timer);
// 立即执行
if (immediate) {
var callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait)
if (callNow) func.apply(that, args)
} else {
// 非立即执行
timer = setTimeout(function () {
func.apply(that, args)
}, wait);
}
}
},
/**
* @desc 函数节流
* @param func 需要执行的函数
* @param delay 规定时间内不再触发(毫秒)
**/
throttle(func, delay) {
let oldDate = Date.now()
return function () {
let that = this
let args = arguments
let newDate = Date.now()
if (newDate - oldDate > delay) {
func.apply(that, args)
oldDate = Date.now()
}
}
},
使用方式为:
26、Vue中按钮级别的权限控制
1)通过vuex中注册相应的按钮状态,显示或隐藏;在src/store文件夹下的index.js文件夹中
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
buttonPermission: {
add: true,
edit: false,
delete: false
}
}
})
export default store
2)在src/direcitive文件夹下的has.js文件夹中通过注册指令的方式
export default {
inserted(el, binddings, vnode) {
console.log("dom元素插入了")
console.log(vnode)
// 获取绑定指令上的值
let btnPermissionValue = binddings.value
// 获取vuex中的state的相应状态
let bool = vnode.context.$store.state.buttonPermission[btnPermissionValue]
!bool && el.parentNode.removeChild(el) // 如果vuex中的状态为false,则移除此节点
}
}
3)在相应的页面中使用此指令
<template>
<div>
<button type="primary" v-has="'add'">管理</button>
<button type="primary" v-has="'edit'">管理1</button>
<button type="primary" v-has="'delete'">管理2</button>
</div>
</template>
<script>
import has from '../direcitive/has.js'
export default {
name: "text",
directives: {
has
},
data() {
return {}
}
}
</script>
<style lsng="scss" scoped></style>
效果为:
27、filters的使用
数据过滤
示例:
<template>
<div>价格:{{ price | currency(price)}}</div>
</template>
<script>
import has from "../direcitive/has.js"
export default {
name: "text",
data() {
return {
price: 10
}
},
filters: {
currency: function(value) {
return `¥${value}元`
}
}
}
</script>
<style lsng="scss" scoped></style>
28、Vue异步组件的使用
就是需要加载该组件的时候才加载
正常来说,我们直接通过import 组件名 from ‘组件相对路径’ ,这样会把组件全部加载出来。
效果为:
/* webpackChunkName:“list” */ 是给默认的1.js 重命名为 list.js 【未成功】
通过Vue ui可以实现,以前的具体问题处在那里,不清楚
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
示例代码:
<template>
<div>
<div>Vue异步加载组件</div>
<button type="primary" @click="change()">切换</button>
<div v-if="show">
<list />
</div>
</div>
</template>
<script>
export default {
name: "textBox",
components: {
list: () => import(/* webpackChunkName:"list" */ '../components/list')
},
data() {
return {
show: false
}
},
methods: {
change() {
this.show = !this.show
}
}
}
</script>
<style lsng="scss" scoped></style>
创建异步组件工程函数
<template>
<div>
<div>Vue异步加载组件</div>
<button type="primary" @click="change()">切换</button>
<div v-if="show">
<AsyncList />
</div>
</div>
</template>
<script>
import loadingCom from "../components/loading.vue"
import errorLoading from "../components/errorLoading.vue"
const AsyncList = () => ({
component: import(/* webpackChunkName:"list" */ "../components/list"),
loading: loadingCom ,
error: errorLoading,
delay:400,
timeOut: 3000
})
export default {
nme:'text',
components: {
AsyncList
},
data() {
return {
show: false
}
},
methods: {
change() {
this.show = !this.show
}
}
}
</script>
<style lsng="scss" scoped></style>
怎么样才能看见loading的加载效果,将网络更改为slow 3G,就能够看见loading…加载组件了
29、Vue路由懒加载
为什么采用路由懒加载,是因为减小首次加载的包下载,提高页面加载速度
import Vue from 'vue'
import VueRouter from 'vue-router'
import Hellowworld from '../components/HelloWorld.vue'
import index from '../pages/index.vue'
// import text from '../pages/text.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
redirect: '/index'
},
{
path: '/index',
name: 'index',
component: index
}, {
path: '/Hellowworld',
name: 'Hellowworld',
component: Hellowworld
}, {
path: '/text',
name: 'text',
// 这就是路由懒加载的方式
component: () => {
import(/* webpackChunkName:"text" */ '../pages/text.vue')
}
}
]
const router = new VueRouter({
routes
})
export default router
30、什么是Vue Mixins(混入)?
作用:复用我们的代码
适用于场景:我现在有两个组件,组件内的方法和data属性都是可以公用的,那么就可以采用混入mixins的方式
示例:
componentA.vue
<template>
<div>
<div>
<div>我是组件componentA</div>
<div v-show="isShow">显示或隐藏的文本内容</div>
</div>
<button @click="changShow()">切换A</button>
</div>
</template>
<script>
export default {
name: "componentA",
data() {
return {
isShow: false
}
},
methods: {
changShow() {
this.isShow = !this.isShow
}
}
}
</script>
componentB.vue
<template>
<div>
<div v-show="isShow">
<div>我是组件componentB</div>
<div>显示或隐藏的文本内容</div>
</div>
<button @click="changShow()">切换B</button>
</div>
</template>
<script>
export default {
name: "componentB",
data() {
return {
isShow: false
}
},
methods: {
changShow() {
this.isShow = !this.isShow
}
}
}
</script>
在src/mixins/show.js文件中写入
export const showMixins = {
data() {
return {
isShow: false
}
},
methods: {
changShow() {
this.isShow = !this.isShow
}
}
}
我们可以将componentA和componentB简写为:
<template>
<div>
<div>
<div>我是组件componentA</div>
<div v-show="isShow">显示或隐藏的文本内容</div>
</div>
<button @click="changShow()">切换A</button>
</div>
</template>
<script>
import {showMixins} from "../mixins/show.js"
export default {
name: "componentA",
mixins: [showMixins]
}
</script>
<template>
<div>
<div v-show="isShow">
<div>我是组件componentB</div>
<div>显示或隐藏的文本内容</div>
</div>
<button @click="changShow()">切换B</button>
</div>
</template>
<script>
import {showMixins} from "../mixins/show.js"
export default {
name: "componentB",
mixins: [showMixins]
}
</script>
【补充】混入其实跟把页面中的公共部分提取出来,再通过模板字符串分别复制到对应页面中
31、Vue3中计算属性computed跟watch的区别
computed:
支持缓存,只有依赖数据发生改变,才会重新进行计算;不支持异步,当computed内有异步操作时无效,无法监听数据的变化;
如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
watch:
- 不支持缓存,数据变,直接会触发相应的操作;
2.watch支持异步;
3.监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值; - 当一个属性发生变化时,需要执行对应的操作;一对多;
- 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
immediate:组件加载立即触发回调函数执行,
deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
32、组件之间的通信
1)父子之间的通信
props+ $emit ;使用回调函数的方式【跟react的useState的改变方法函数相似】; 组件实例上的 $parent + $children ; provide+inject , 给组件绑定ref,获取组件实例,操作组件属性和方法
provide+inject 示例 【向下传递的props好说,单向传递,孙向父上传递得借助中央事件总线或Vuex】
1)父组件
2)子组件 【父到孙之间的数据传递,是通过v-bind=‘$attrs’ , $listeners.changeName 可以监听父组件的事件】
3)孙子组件
33、深拷贝函数
一项一项重赋值给另一个变量
<script>
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
return obj
}
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 判断是否是原来对象内的属性
if (obj.hasOwnProperty(key)) {
// 如果对象的属性下还有对象或者数组,就再深入拷贝
result[key] = deepClone(obj[key])
}
}
return result
}
</script>
34、数组去重
1)利用es6的set去重
function unique(arr) {
return Array.from(new Set(arr))
}
2)利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) { //第一个等同于第二个,splice方法删除第二个
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
3)利用indexOf去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i])
}
}
return array;
}
4)利用reduce+includes
function unique(arr) {
return arr.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], []);
}
5)[…new Set(arr)]
[...new Set(arr)]
//代码就是这么少----(其实,严格来说并不算是一种,相对于第一种方法来说只是简化了代码)
35、Vue的生命周期
创建前/创建后,挂载前/挂载后,更新前/更新后,销毁前/销毁后
36、动态路由中的参数传递
1)我们首先会创建相应路由
2)我们会根据访问的/user/:id的参数路由不同,显示对应的id值
上面这个data属性里的 id:this.$route.params.id
只会渲染一次,所以我们动态更换路由场景,不会刷新id
1)通过watch监听
watch: {
// to代表是新的路由 , from代表是老的路由
$route(to, from) {
this.id = to.params.id
}
},
2)这个时候我们就必须借助一个钩子函数 beforeRouteUpdate
beforeRouteUpdate(to , from){
this.id = to.params.id
},
37、使用addRoute动态添加路由
1)创建两类路由,一类是普通路由,一类是带权限的路由
2)就拿登录来说,我们可以在Vuex中保存一个登录状态
3)我们可以在相应的页面中,通过this.$store.commit('login')
来改变store中的state状态 ,也可以通过this.$router.addRoutes(asyncRoutes)
动态添加路由
38、递归中的闭包
什么是递归?
递归是函数是直接或间接地调用自己
闭包是什么?
父函数里包含子函数,子函数被return 到父函数之外,这个子函数就是闭包
下面这样的写法,每次return的,都是一个全新的函数
...
return {
fun:function(){
// 函数体
}
}
看下述示例:
<script>
// 首先看一下两个示例
var o = {
// 此时的fn 是在对象里为一个属性名出现的,环境为对象,对象内是没有作用域链的,所以找不到fn
fn: function () {
console.log(fn); // 会报错 undefined
},
};
o.fn(); // 报错undefined , 执行环境为window , window下没有fn 所以报错
var fn1 = function () {
console.log(fn1);
};
fn1(); // 变量提升,var会将fn1挂载到window下 ,所以我们在window环境下执行会找到fn1()
</script>
请看题:
<script>
// 什么是作用域? 是变量的活动空间。变量查找的过程为作用域链
// js引擎的回收机制是 标记清楚和引用计数,当一个变量持续被引入的时候,那么该变量就不会被清除
// 这就是闭包所带来的问题,占内存;清除闭包可以通过 赋值为null的方式实现
function fn(n,o){
console.log(o)
return {
fn:function(m){ // 这个地方就是闭包,一个父函数下有一个子函数,与其词法环境绑定,换句话理解
return fn(m,n) // 就是最外层的fn(n ,o)的形参n 与最内层的fn(m ,n) 都是通过n绑定,当fn(n,o)执行完毕,理应被销毁
} // 但是最外层fn(n ,o)的形参被最内层引用着,所以未被销毁,形参上次赋的值也未被销毁
}
}
var a = fn(0)
console.log(a) // 当第一次执行fn(0)时,第二个形参未被赋值,console.log(o)所以打印undefined
a.fn(1) // 执行最外层函数返回的对象的fn属性下的方法,因为在执行var a = fn(0)的时候,上述方法的 return fn(m,n) 中的n已经赋值为0了,一直被引用
a.fn(2) // 所以之后的 console.log(o) 一直都是0
a.fn(3)
</script>
打印结果为:
39、forEach()和map()方法的区别
共同点:1)都循环数组中的每一项;2)每次循环的匿名函数都有三个参数(当前项item , 索引值index, 原始数组arr); 3)匿名函数中的this指向window;4)只能循环数组
区别:1)forEach()没有返回值;2)map()有返回值,返回新数组,原数组不变
用途:forEach() 不改变数据,只能用一些数据做一些事情
map() 你需要返回一个新数组
40、vuex实现状态持久化
主要是利用locaStorage缓存实现,在刷新页面的前一刻存至本地缓存中,页面刷新完毕,更新vuex,并清空本地缓存
41、AJAX是个啥?如何使用AJAX获得数据?
知识点:
1)AJAX是在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容
2)核心为:XMLHttpRequest对象, 用于在后台与服务器之间交换数据
3)open( method , url , async ) , 规定请求的类型,URL , 以及是否异步处理请求
4)send() , 将请求发送到服务器
5)readyState属性 , 保存XMLHttpRequest的状态,从0到4的发生变化;0:请求未初始化;1:服务器已建立连接;2:请求已接受;3:请求处理中;4:请求已完成,且响应已就绪
6)onreadystatechange事件,每当readyState属性发生改变时,就会调用该函数;
7)status: 响应状态 , 200:‘OK’ , 400:‘未找到页面’
<script>
main()
function main(params) {
let url = 'http://localhost:2244/get_data'
getRequest(url , 'get' , true)
}
/**
* XMLHttpRequest封装ajax请求
* url String 请求路径
* method String 请求方式, get,post
* crossOrigin Boolean 是否跨域 ,默认为true
*/
function getRequest(url , method , crossOrigin = true) {
let xhr = new XMLHttpRequest()
xhr.open(method , url , crossOrigin) // xhr.open( 请求方式 , 请求路径, 是否跨域 )
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
// 此时请求完成
if(xhr.status ===200){
// xhr.responseText 以字符串的形式返回结果,这个地方只能使用JSON.parse()转
console.log('xhr.responseText' , xhr.responseText)
let resData = JSON.parse(xhr.responseText)
}
}
}
xhr.send(null) // 因为我们是get请求不需要传递数据,所以用null就可以了
}
</script>
未能够访问到nodeJs内的端口数据
因为跨域的问题,访问不到【未解决】不到,请看上文12、jsonp跨域
我们用nodeJs写一个GET请求,下为app.js文件。通过node app.js启动该接口
// nodeJs自带的http请求模块
const http = require('http')
// 自带的querystring模块,作用是将url问号后的参数以对象的形式输出
const querystring = require('querystring')
// req 为请求头,请求头自带method/url ,不带有query属性的
// res 为响应数据
const server = http.createServer((req,res)=>{
const method = req.method
console.log('nethod' , method)
const url = req.url
console.log('url' , url)
// 我们在req 请求头对象下新增加属性query
req.query = querystring.parse(url.split('?')[1])
console.log('req.query' , req.query)
// 这个发送数据地方一定要转为JSON字符串
res.end(JSON.stringify(req.query))
})
server.listen(5000,()=>{
console.log('server run at port 5000!')
})
成功在本地端口号5000启动
访问路径: http://localhost:5000/?id=1
接口创建成功
42、数据结构与算法—栈
数据结构和算法在规划存取和计算效率上很重要。
栈是一种线性的、后进先出(LIFO)的数据结构,线性表示与数组一样,只有一维呈线性排列,后进先出指的是最后添加到栈中的元素先出去
例如
1)JavaScript函数调用栈就是通过栈来实现的
2)计算带小括号的数学表达式
操作栈的方法有:
push() 将元素加至栈顶,栈大小+1; pop()将元素从栈顶抛出,栈大小-1
isEmpty() 判断栈是否为空 ; size() 获取栈的大小 ; peek()查看栈顶元素
我们可以通过JavaScript中的数组实现栈操作
class Stack {
stack = [];
// 入栈
push(item) {
this.stack.push(item);
}
// 出栈
pop() {
return this.isEmpty() ? "栈为空" : this.stack.pop();
}
// 判断栈是否为空
isEmpty() {
return this.size() === 0;
}
// 返回栈的大小
size() {
return this.stack.length;
}
// 返回栈顶元素,并不会从栈顶弹出
peek() {
return this.stack[this.stack.length - 1];
}
}
let statck = new Stack();
stack.push(1);
stack.push(5);
stack.push(8);
stack.peek(); // 栈顶为8
stack.size(); // 长度为3
stack.pop();
stack.size(); // 栈为2
stack.pop();
stack.pop();
stack.pop(); // 输出栈为空