题目合集持续更新ing~
- 前端基础github地址。
README.md
可以下载到typora
中打开,会有整个大纲目录显示(github中markdown目录快捷生成方式不现实,之后可能会想办法生成贴过来,暂时不做相关处理) - 前端基础gitbook地址。
README.md
中会实时更新进度内容。gitbook!gitbook
版本可建议后期碎片化时间进行复习使用。 - 前端基础csdn地址。
CSDN
博客专栏前端自我修养进阶中,也会一篇一篇实时更新相关知识点。 - 前端基础一掘金地址、前端基础二掘金地址
⭐️就是最好的鼓励哦wink~💗
5.20
1、作用域和值类型引用类型的传递
作用域
var num1 = 11;
var num2 = 22;
function fn(num,num1){
//num,num1会计作用域中变量,分别传入时候只为11,22
num = 100;
num1 = 100;
num2 = 100; //没有用var声明,不是块作用域中变量,那么修改的就是全局的num2
console.log(num) //100
console.log(num1) //100
console.log(num2) //100
}
fn(num1,num2)
console.log(num1) //11
console.log(num2) //100
console.log(num) //undefined报错
值类型、引用类型的传递
function Person(name,age,salary){
this.name = name;
this.age = age;
this.salary = salary;
}
function f1(person){
//执行f1(p)的时候person的地址和p的地址指向同一个内存地址,因此修改person.name会修改p.name
person.name = "ls"
person = new Person('aa',18,10) //此时重新将person指向另外一个引用地址,所以跟p无关,不修改p值
}
var p = new Person('zs',18,10000)
console.log(p.name) //zs
f1(p)
console.log(p.name) //ls
2、封装函数进行字符串驼峰命名
- 已知有字符串类似
get-element-by-id
格式,写一个function将其转化成驼峰命名表示法getElementById
function fn(str){
var arr = str.split('-');
for(let i = 1;i<arr.length;i++){
arr[i] = arr[i].charAt(0).toUpperCase()+arr[i].substr(1,arr[i].length-1)
}
return arr.join(''); //数组拼接成字符串
}
3、冒泡排序
function bubble(arr){
for(let i = 0;i < arr.length;i++){
for(let j = 0;j < arr.length - 1 -i;j++){
if(arr[j]>arr[j+1]){
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
}
}
}
return arr;
}
4、反转数组
- 示例:比如数组
[1,2,3,4,5,6,7,8]
反转数组之后的结果是[8,7,6,5,4,3,2,1]
- 类似于首位交换
0 len-1
1 len-1 -1
2 lem-1 -2
function fn(arr){
for(let i = 0;i < arr.length/2;i++){
[arr[i],arr[arr.length -1 -i]] = [arr[arr.length -1 -i],arr[i]]
}
return arr
}
5、数组去重
- Set
function fn(arr){
return Array.from(new Set(arr))
}
- 一项一项去拿,然后和其后面形成的数组进行对比
function fn(arr){
for(let i = 0;i < arr.length;i++){
let val = arr[i],
compareArr = arr.slice(i); //取出i之后的所有项组成的数组
if(compareArr.indexOf(val)>-1){ //如果后面存在这个值
arr.split(i,1)
arr.length--;
i--;
}
}
return arr;
}
function fn(arr){
for(let i = 0;i < arr.length;i++){
let val = arr[i],
compareArr = arr.slice(i); //取出i之后的所有项组成的数组
if(compareArr.indexOf(val)>-1){ //如果后面存在这个值
arr[i] = null;
}
}
arr = arr.filter((item)=>{return item != null})
}
- 先排序再去重
function fn(arr){
arr = arr.sort((a,b)=>{return a-b}); //升序
for(let i = 0;i < arr.length-1;i++){ //倒数第一项不用去跟后面一项进行对比
if(arr[i] === arr[i+1]){
arr.split(i,1)
arr.length--;
i--;
}
}
return arr;
}
6、js综合面试题
- 变量提升,函数提升。变量提升:变量名提升;函数提升:函数整体提升
- this指向问题
- 变量常用规则,变量沿着作用域链进行查找
- 运算符优先级关系:点运算优先级最高,但是遇到
()
没有办法进行运算,所以会记那个前面整体先进性运算 - 实例对象属性规则,原型原型链
//涉及变量提升、函数提升;
function Foo(){
getName = function(){alert(1)} //修改了全局的
return this;
}
Foo.getName = function(){alert(2)}
Foo.prototype.getName = function(){alert(3)}
var getName = function(){alert(4)}
function getName(){alert(5)}
//输出如下结果
Foo.getName() //2
getName() //4
Foo().getName() //1 this指向window,window.getName()
getName() //1
new Foo.getName(); //2
new Foo().getName(); //3
new new Foo().getName(); //3
7、nodejs事件轮询
-
libuv
-
事件轮询机制
- 定时器阶段:计时到点的计时器
- pending callback:做系统操作,tcp错误等
- idle、prepare准备工作
- 轮询阶段:轮询队列
- 轮询队列不为空:一次去除回调函数中的第一个函数,先进先出。知道轮询队列为空或者达到最大限制(轮询队列为空:设置过SetImmidiate函数直接进入下一个check阶段;没有设置过,那么就在当前poll等待,直到轮询队列添加进来新的函数,就会去第一个情况执行。如果定时器到点,那么也会去执行下一个阶段)
- check查阶段:执行setImmediate设置的回调函数
- close callback:关闭阶段。close事件的回调函数在这个阶段进行。
-
process.nextTick
能在任意阶段优先执行
setTimeout(function(){
console.log(1)
},0)
setImmediate(function(){
console.log(2)
})
process.nextTick(function(){
console.log(3)
})
//nextTick timeout immediate
8、从url输入
-
DNS解析。域名解析成IP。先读取浏览器DNS缓存,读取系统DNS缓存,读取路由器DNS缓存,网络运营商缓存,递归搜索
-
TCP三次握手:
- 浏览器告诉服务器要发送请求
- 服务器告诉浏览器准备接收
- 浏览器告诉服务器马上就发送请求
-
发送请求:请求报文
-
接受响应:响应报文
-
渲染页面
- 遇见HTML标记,浏览器调用HTML解析器解析解析并且构建成dom树
- 遇见style/link,浏览器调用css解析起解析并且构建成cssom树
- 遇到script会调用js解析器,处理script代码(绑定事件,修改dom树/cssom树)
- 合并dom树和cssom树成渲染树
- 根据渲染树进行计算布局,计算每个节点的几何信息(布局)
- 将各个节点颜色绘制到屏幕上(渲染 )
- 注意:这五个不一定按照顺序执行,如果dom树或者cssom树被修改了,可能会执行多次布局和渲染。
-
断开链接:四次挥手。
- 浏览器发送给服务器:我请求报文完了,关闭
- 服务器告诉浏览器:我准备关闭
- 服务器告诉浏览器:响应报文发送完了,准备关闭
- 浏览器告诉服务器:客户端准备关闭
9、闭包
-
函数嵌套
-
内部函数使用外部函数的局部变量
-
优点:延长外部函数局部变量的生命周期;缺点:内存泄漏
-
合理使用闭包,及时销毁
function fun(n,o){
console.log(o) //执行var a = fun(0)时候o没有定义,undefined
return {
fun:function(m){
return fun(m, n); //这个fun是window的fun也就是全局的fun
}
}
}
//a中存的是同一个返回方法,是同一个fun(m,n),此时存的n都是第一次的0,后面也只是改m所以n都是0,o打印都为0
var a = fun(0) //结果给a赋值返回一个对象{fun:function(){...}}
a.fun(1) //0
a.fun(2) //0
a.fun(3) //0
//注意这里区别于上面,这里每次执行完之后保存的n都是上一次返回的n
var b = fun(0).fun(1).fun(2).fun(3) //undefined 0 1 2
//同理:
var d = fun(0).fun(1).fun(2).fun(3).fun(67).fun(45) //undefined 0 1 2 3 67
var c = fun(0).fun(1) //undefined 0 此后c上的n保存变成了1
c.fun(2) //1
c.fun(3) //1
10、变量提升&上下文
变量提升
- js引擎在代码执行之前会做预处理工作
- 收集变量:var声明的变量定义,但是不赋值
- 收集函数:function声明的函数就提前定义函数
console.log(username) //undefined
var username = 'smileyqp'
fun(); //正常执行函数
function fun(){
console.log('inner func')
}
执行上下文(excute context)
- 理解:代码执行的环境
- 时机:代码正式执行之前会进入到执行环境
- 创建变量对象(变量提升)
- 变量
- 函数及函数的参数
- 全局:window
- 局部变量对象
- 确认this指向
- 全局window
- 局部:调用其的对象
- 创建作用域链
- 父级作用域链+当前的变量对象
- 扩展:执行上下文可以认为是一个大的对象,这个对象中主要包含
- 变量对象:变量、函数、函数的形参
- 作用域链Scopechain:父级作用链+当前作用域对象
- this:window||调用其的对象
- 创建变量对象(变量提升)
11、宏任务和微任务
js是单线程的,所以我们可以称之为主线程,也就是我们的js代码都是在主线程上完成的。祝不过我们区分任务是同步还是异步,如果是异步的话,那么它会有一个回调函数。 回调函数:定时器的回调、ajax请求的回调、对应事件的回调(比如点击事件)。不同的任务又会交给不同的模块去处理,比如:定时器模块、网络请求模块、事件处理模块。
js事件轮询机制,js是单线程的
- 宏任务:setTimeout 、setInterval、requestAnimationFrame
- 宏任务所处的队列就是宏任务队列
- 第一个宏任务队列中只有一个任务、执主线程的js代码
- 宏任务队列可以哟欧多个
- 微任务:New Promise().then(两个回调函数 )、process.nextTick
- 微任务所处的队列就是微任务队列
- 只有一个微任务队列
- 上一个宏任务执行完成之后如果有微任务就会执行微任务队列中所有任务
- 当宏任务重的任务执行完成之后会此案去查看微任务队列中是否有任务,如果有就先执行微任务队列中的任务,如果没有的话就继续执行下一个宏任务
//主线程宏任务执行打印1-5 微任务队列执行打印then中console setTimeout宏任务执行
console.log('start')
setTimeout(()=>{
console.log('setTimeout')
},0)
new Promise((resolve,reject)=>{
for(var i = 0;i<=5;i++){
console.log(i)
resolve()
}
}).then(()=>{
console.log('promise实例成功回调')
})
console.log('end')
//输出:
// start 1 2 3 4 5 end promise实例成功回调 setTimeout
12、React和Vue
相同点
- 都是组件化开发
- 虚拟dom
- 都支持通过属性的方式去进行父子组件之间通信
- 数据驱动:更新状态数据,界面会自动更新,不用直接操作dom
- 支持服务器渲染
- 支持原生应用方案:react native(react)、weex(vue)
不同点
-
数据绑定:vue中是双向数据绑定,也就是界面和内存的数据可以双向交流;而react只支持从内存到界面,不支持从界面到内存
-
组件书写方式不一样:react采用JSX(JSX是js的扩展语法,react提出all in js)而vue使用的是模版(也就是在html中去写js、css再通过webpack的loader去对他们进行打包)
-
状态变化:react中调用
setState
;vue中直接通过this.xxx
去更新data中的数据 -
虚拟dom的底层实现不是完全一样:vue会去跟踪每一个组件的依赖关系、不需要重新渲染整个组件树;而对于react而言,一旦状态改变,全部组件都会重新去渲染所以react中需要
shouldComponentUpdate
这个生命周期方法去进行控制 -
react严格上来说是MVC的view层,而view则是mvvm模式的
13、Redux状态管理机制
基本理解:
- redux是一个独立的状态管理js库,不是react的插件库
- 可以用在react、vue、angular等项目中,但是大多情况与react配合使用
- 作用:集中管理react组件中共享数据的状态
Redux 使用扩展
- react-redux简化编码
- redux-thunk实现redux的一步编程。以及redux-saga
- redux-devtools实现chrome中redux的调试
5.21
14、vue组件间通信的方式
- 父向子通信
- 子向父通信
- 兄弟组件通信
- 隔代组件通信
实现通信方式
- props
- 标一般属性(父向子)、函数属性(子向父)
- 隔代通信比比较麻烦:需要逐级传递;兄弟组件通信需要通过父组件传递
- Vue自定义事件:
- 绑定监听(子胥见在父组件中调用的时候绑定监听)
<My-component @eveName="callback"/>
- 触发(分发)事件
this.$emit('eveName',data )
- 只适合子组件向父组件通信
- 绑定监听(子胥见在父组件中调用的时候绑定监听)
- 消息订阅与发布(例如:pubsub-js )
- 订阅消息
- 发布消息
- 适用于任意关系的组件通信
- Vuex(vue的状态管理的插件库)
- slot:专门用来父向子传递带数据的标签
15、vuex状态管理机制
16、Vue的MVVM的实现
- 模板解析
- 解析大括号表达式
- 解析指令
- 数据绑定
- 更新显示实现:数据劫持
17、重绘、重排(回流)
- 先把dom节点生成dom树(解析dom)
- 生成cssom(解析css)
- 布局:构成渲染树render tree(从根节点递归,布局)
- 绘制:绘制div等到页面上
重绘(repaint):一个元素外观的改变所触发的浏览器的行为,浏览器会根据元素的新属性重新绘制,使元素呈现新外观
回流(重排、重构reflow):当渲染树的一部分因为元素的尺寸、布局、隐藏等改变而需要重新构建称之为重排
重绘和重排的关系:在重排的过程中,浏览器会使渲染树中收到影响的部分失效,并且重新去构造这部分渲染树,完成重排之后,浏览器会重新绘制受影响的部分到屏幕中,该部分称为重绘。
重排(回流)一定会重绘,但是重绘不一定会重排。(重排=>布局 重绘=>样式)
触发重排的条件
- 页面渲染初始化
- 添加删除可见dom元素
- 元素位置改变或者使用动画
- 元素尺寸的改变——大小、外边距、边框
- 浏览器窗口尺寸的变化(resize事件发生时)
- 填充内容的改变,比如文本的改变导致的计算值的宽高改变
- 读取某些元素属性(读取宽高offsetTop/Top/Height/width等)
重绘或者重排的代价:耗时、导致浏览器卡顿
重绘重排优化
- 浏览器自己的优化:浏览器会维护一个队列,把所有会引起重绘或者重排的操作放入这个队列,等队列中的操作到了一定的哦时间间隔或者一定数量的时候,浏览器就会flush队列进行一个个处理,这样就会让多次回流和重绘便车个一次
- 我们也可以合并多次对dom的操作以及对样式的修改为一次,并且减少对style的样式请求
- 直接改变元素的className
- 先设置元素的
display:none
然后进行页面布局等操作,设置完成之后再将display:block
这样的话就之后出现两次重绘和重排 - 使用cloneNode(ture or false)和replace技术只引发一次重绘和重排
- 将需要毒刺重排的元素的
position
设置成absolte或者fixed,使其脱离文档流,那么它的变化不会影响到其他元素 - 如果要插入多个dom节点可以创建一个documentFragment创建完成之后一次性加入document
var fragment = document.createDocumentFragment()
for(let i = 0;i<=1000;i++){
var li = document.createElement('i')
li.innerHTML = i + '</br>'
fragment.append(li)
}
document.getElementById('container').appendChild(fragment)
5.22
18、ES6的class
//class就是构造函数的一种新写法,可以视作语法糖
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log(`${this.name}${this.age}岁`)
}
}
let p = new Person('smileyqp',18);
p.say()
console.log(p.name)
class Teacher extends Person{
constructor(){
super()
}
hello(){
console.log('say hi')
}
}
19、async、await使用与原理
- 异步情况:定时器、ajax、事件处理、nodejs、读取文件也有异步
let p = new Promise((resolve,reject)=>{
resolve(1);
})
//这里的function就是promise中调用的resolve。那么上面掉哟哦那个resolve(1),data参数就是1
p.then(function(data){
console.log('success',data) //success 1
})
-
Promise减少嵌套关系,是一个链式编程的结果
-
axios就是把ajax用promise封装了一下
-
async、await最简单的使用就是省去then的步骤,改成类似同步的过程,等待成功才会执行下面。方便、可读性强、异步改成类似同步写法
-
async和await其实是generator和yield的语法糖。async、await使得代码清晰。
-
Generator里面的代码是分段执行。看到yield就分段暂停。
function *helloGenerator(){
yield 'hello';
yield 'world';
yield 'ending';
}
var hw = helloGenerator();
console.log(hw); //这个函数的结果并不是摁钉,因为代码是暂停的。是一个暂停标记,标记指向'hello'等结果
console.log(hw.next()) //hello
console.log(hw.next().next()) //world
console.log(hw.next().next().next()) //ending
console.log(hw.next().next().next().next()) //undefined
- 拓展
Promise.all
和Promise.race
Promise.all
必须数组里面的所有promise执行完毕,才成功。用在要很多结果都执行成功的情况。Promise.race
只要数组里面的一个成功,整个race就成功执行
Promise.all([p1,p2,p3...],function)
Promise.race([p1,p2,p3...],function)
20、ts在项目中的使用
- ts是js的一个超集,支持ES6标准
- ts比js有更严格的类型要求。有类型的约束,减少大型项目中的bug
- 示例:
msg!:string
msg是非空字符串;msg?:string
msg有可能有欲呕可能没有
21、ES6装饰器的使用
- 装饰器:是一种与类相关的语法,用来注释和修改类和类相关的方法和属性。许多面向对象的语言都有这个功能。一般和类class 相关,普通的方法不要去使用
- 装饰器是一种函数,写法是
@函数名
,它可以放在类和类的方法定义前。装饰器就是执行函数,给类或者类下面的属性方法加一些控制条件 - 装饰器
- 给类或者类属性驾驶一些其他代码
- 可以代码复用
@decorator
class A(){
}
//等同于
class A(){
//decorator是一个函数,相当于调用它给A类可以加上一些其他代码,加上属性等
A = decorator(A)
}
//实例.在类或者类属性方法上写上@函数名,就相当于调用这个函数
function testfunc(target){
target.isok = true; //相当于Myclass.isok = true
console.log('i am test func',target)
}
@testfunc
class Myclass{
}
function readonly(target,name,descriptor){
console.log(target) //当前的class,即Person
console.log(name) //装饰的属性或方法名
//configuerable修改、enumerable枚举是否可for in循环、writeable、value对象属性默认值
console.log(descriptor)
return descriptor;
}
class Person{
@readonly
abc(){
console.log('add func ')
}
}
5.23
22、事件循环
异步:定时器、ajax、onclick、promise(new Promise立刻执行,then异步)
- 宏任务:定时器(setTimeout、setInterval)、requestAnimationFrame、I/O
- 微任务:process.nextTick、Promise、Object.observe、MutationObserver
- 宏任务队列和微任务队列,执行主线程任务,宏任务放到宏任务队列,微任务放到微任务队列。只有在微任务队列中的微任务全部执行完成之后才会去执行下一个宏任务。注意:执行宏任务的时候,也会产生微任务,继续执行上面过程
console.log('1')
setTimeout(function(){
console.log('2')
new Promise(function(resolve){
console.log('3')
resolve()
}).then(function(){
console.log('4')
})
})
new Promise(function(resolve){
console.log('5')
resolve()
}).then(function(){
console.log('6')
})
setTimeout(function(){
console.log('7')
new Promise(function(resolve){
console.log('8')
resolve()
}).then(function(){
console.log('9')
})
console.log('10')
},0)
console.log('11')
//1 5 11 6 2 3 4 7 8 10 9
23、浏览器缓存原理
-
浏览器本身有缓存,浏览器可能会把上次代码缓存起来,再去访问不是去拿新代码,而是直接使用缓存
-
浏览器的缓存分成两种,强制缓存和协商缓存
- 强缓存:不会向服务器发送请求,直接从缓存中读取资源,每次访问本地资源直接验证看是否过期。强缓存可以通过设置两种http header实现:expire过期时间和cache-control缓存控制。
- 协商缓存(Last-Modified/If-Modefied-Since和E-tag/If-None-Match):发请求到服务器,服务器会告诉浏览器去拿缓存还是新的代码
拓展:
- 网站优化
- 雪碧图
- 懒加载
- 减少http请求(缓存:浏览器有缓存,现在h5的manifest也可以进行自定义缓存,优化网站)
24、h5离线存储manifest
Html5提出的一个新特性:离线存储。通过离线存储,我们可以吧需要离线存储在本地的文件夹列举在manifest配置文件中,这样即使在离线的情况下,用户也可以正常的看见网页
- 在需要离线缓存的页面的html标签上加上
manifest='cache.manifest'
<html lang='en' manifest='cache.manifest'>
...
</html>
- 在根目录创建新文件名称为
cache.manifest
的文件,并写上代码
5.24
25、移动端兼容问题
26、混合开发
混合开发
- 一部分原生一部分js
- 内嵌浏览器壳
- web手机端网页:手机操作比较困难,一般没有手机操作权限
混合开发框架
- weex:采用vue框架,可打包成app
- react-native:采用react框架
- react语法加上自己特定的标签
<Text></Text>
<View></View>
等
- react语法加上自己特定的标签
- uniapp:采用vue框架
补充:如何将vue项目打包成app?1、vue项目build 2、将build文件夹下面文件复制到新的hbuild创建的心项目中,覆盖原文件 3、直接打包
27、一次完整的http请求过程
题目
当web浏览器输入www.baidu.com
具体发生了什么?
- DNS域名解析,得到对应的IP
- 根据这个IP,找到对应的服务器,发起TCP三次握手
- 建立TCP链接之后发起HTTP请求
- 服务器响应HTTP请求,浏览器获取html代码
- 浏览器解析html代码,并且请求html代码中的资源(css、js、图片、视频等。得到html后才能去湖区这些资源)
- 浏览器对页面进行渲染并且呈现给用户
- 服务器关闭TCP链接(四次挥手)
详细解析
-
怎样进行域名解析,DNS怎样找到域名的?
- DNS域名解析是采用递归查询的方式,过程是:先去找DNS缓存=〉缓存找不到就去找根域名服务器=〉根域名又会去找下一级,这样递归查找之后,找到了就给我们的浏览器
- 浏览器自身DNS缓存
- 操作系统DNS缓存
- 路由器DNS缓存(host文件中查找)
- 递归去各个域名服务器查找
- DNS域名解析是采用递归查询的方式,过程是:先去找DNS缓存=〉缓存找不到就去找根域名服务器=〉根域名又会去找下一级,这样递归查找之后,找到了就给我们的浏览器
-
为什么HTTP要基于TCP来实现?
- TCP是一个端到端的可靠的面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误的时候会重传)
-
浏览器对页面是如何进行渲染的?
- 解析html获得dom树
- 解析css生成cssom树
- dom树和cssom合成渲染树
- 边解析边渲染(布局:计算位置和尺寸;渲染:渲染样式)
- JS单线程运行的,js困难修改dom的结构
拓展:重排(回流):修改布局;重绘:修改样式。重排一定会重绘,重绘不一定重排。
28、http缓存控制
- 缓存作用范围
- 第一次响应后到第二次请求
- 缓存分类
- 缓存命中率:从缓存中获取数据的请求与所有请求的比率。理想状态是越高越好
- 过期内容:超过设置的过期时间,被标记为陈旧的内容,必须重新向服务器请求新的内容或者验证缓存的内容是否仍然准备
- 验证:验证缓存中的内容是否仍然有效,验证通过的话就刷新过期时间
- 失效:把内容从缓存中清除,内容发生改变时就必须清除失效内容
- http缓存实现技术
浏览器缓存分类
- 强缓存:不用发请求到服务器就能拿到缓存(减少服务器压力)
- expire和cache-control两个请求头设置。两个字端都有以
cache-control
为主,因为expire绝对时间,服务器和浏览器可能有差异 - expire:定义过期时间,绝对时间,由服务器发给浏览器的绝对时间
- cache-control:定义过期时间,相对时间,一个时间段
- expire和cache-control两个请求头设置。两个字端都有以
- 协商缓存:请求到服务器,询问服务器缓存是否过期,没有就直接从缓存加载资源。返回304状态码。
- Last-Modified/If-Modefied-Since
- E-tag/If-None-Match
#####
拓展
-
一台服务器,提高并发?可以使用浏览器缓存,直接使用浏览器缓存减少请求,提高并发。
- http缓存,能够帮助服务器提高并发,资源不需要重复请求,能够直接从浏览器中获取
- http缓存分类分为轻缓存和协商缓存。
- 强缓存通过cache和cache-control来进行控制。协商缓存通过last-modefied以及etag来进行控制
-
为什么有expire需要cache-control ?
- 因为expire是绝对时间,有可能存在浏览器和服务器的时间不同步的问题
- cache-control 是相对时间,一定程度上弥补了expire导致的时间不同步问题
-
last-modify和etag
- last-modify存在精度问题,到秒
- e-tag没有精度问题,只要文件改变,e-tag值就会改变
5.25
29、Promise
题目类型
- Promise概念
- 解决ajax回调地狱问题,使得代码更加简洁易懂
- 说Promise题目输出
const first = () =>(new Promise((resolve,reject)=>{
console.log(3)
let p = new Promise((resolve,reject)=>{
console.log(7)
setTimeout(()=>{
console.log(5)
resolve(6) //一个promise值能执行一个resolve;这个在定时器中,比下面后执行,因此是执行下面的
},0)
resolve(1)
})
resolve(2)
p.then((arg)=>{
console.log(arg)
})
}))
first().then((arg)=>{
console.log(arg)
})
console.log(4)
//3 7 4 1 2 5
- 如何实现链式调用
//每次then的时候返回一个promise
b.then().then().then()的链式调用如何实现的?
//方法一:返回this
class Test{
then(){
console.log('then')
return this;
}
}
var t = new Test();
t.then().then().then()
//方法二:返回改对象实例
class Test1{
then(){
console.log('then')
return new Test1();
}
}
var t1 = new Test1();
t1.then().then().then()
-
手写实现简易Promise
-
分析promise特点
- Promise参数函数会立即执行
- promise在then的回调函数中可以拿到resolve参数
- promise可以有多个then并且可以依次执行
- promise可以嵌套多个then,then回调中可以返回promise
- promise可以嵌套多个then,then的回调中可以返回普通值
- resolved状态的promise,调用then方法会立即执行的
- 二次调用resolve不会产生影响
-
实现思路
- promise是一个对象,一般是通过
new Promise
来实例化的 - promise的then是可以链式调用的,所以需要有链式调用的实现
- 逐个根据列出的promise的特点来实现
- 主要实现promise的构造方法和then方法
- promise是一个对象,一般是通过
-
//不完整,之后补充
let State = {
pending:'pending',
resolve:'resolved',
reject:'rejected'
}
const noop = () => {}
class MyPromise{
constructor(exclutor){ //回调函数,promise传入的回调函数exclutor
exclutor(this._resolve.bind(this),_this.reject.bind(this)); //回调函数立即执行
}
_state = State.pending;
_value;
_resolve(val){
this._value = val;
this._state = State.resolved;
//执行then传入进来的onRes;执行then传入进来的回调函数
while(_resArray.length>0){
const item = _resArray.shift();
item(this._value);
}
};
_reject(){
this.state = State.rejected;
};
_resArray = [];
then(onRes,onRej = noop){
const newPromise = new MyPromise(()=>{})
this._resArray.push(onRes); //存储then中回调方法
return newPromise;
}
}
export default MyPromise;
30、React
redux
- redux是一个独立的数据状态管理库,在angular、vue也都可以使用redux,只不过常与react一起使用
- redux解决数据状态管理,跨层级问题。
- redux就是一个经典的发布订阅器。事件绑定的过程其实也是一个发布订阅的过程。
react题目
-
redux是如何将State注入到React组件中去
- redux使用方法
- 调用ctreateStore创建store对象
- 用Provide包裹根组件
- 使用connect获取链接
- redux使用方法
-
redux在实际项目中的使用问题
-
React中的hooks
5.26
30、React
redux
-
Redux帮我们用一个变量存储所有的state,并且提供发布的功能来修改我们的数据,以及提供订阅的功能来触发回调
-
redux是一个独立的数据状态管理库,在angular、vue也都可以使用redux,只不过常与react一起使用
-
redux解决数据状态管理,跨层级问题。
-
redux就是一个经典的发布订阅器。事件绑定的过程其实也是一个发布订阅的过程。
-
redux使用方法
- 调用ctreateStore创建store对象
- 用Provide包裹根组件
- 使用connect获取链接
react-redux
-
提供两个api
Provider
传递store到每个组件中去。Provider实际上是一个组件。getChildContext创建store。Provider就是通过React的context API把数据往下传。
拓展:
函数柯里化
:函数返回函数;高阶组件
:组件返回组件
react题目
-
redux是如何将State注入到React组件中去
- 明确react与redux的联系是react-redux这个库(Provider、connect)
- redux的原理其实就是一个发布订阅器,帮我们用一个变量存储所有state,并且提供了发布来修改数据,提供了订阅功能来触发回调
- react-redux的功能是订阅store中的数据更新,它包含两个重要的元素Provider和connect
- Provider的作用是通过context api把store对象注入到react组件中去
- connect方法就是一个高阶组件,在高阶组件中通过订阅store的更新,调用setState方法来触发组件更新
-
redux在实际项目中的使用问题
-
redux痛点是什么?
- 增加代码的复杂性。需要经过dispatch、调用reducer、触发回调、更新数据。redux在使用中最大的弊端就是样板代码(action、reducer)太多,修改数据链路较长
-
为什么还是要使用redux?
- redux可以解决跨组件间数据传递问题并且修改数据十分清晰。在复杂的大型项目中,状态数据较多,redux的映入可以较好对数据进行管理,使得数据流向组件状态变更更为清晰
-
redux在使用时候,有哪些比较好的实践方式呢?(可以使用一些手段减少模板代码从而简化redux api)
- 使用redux-action,在初始化reducer和action构造器时候减少样板代码
- 减少创建action时候创建的一堆固定的写法
- 减少创建reducer时候写的一堆固定的switch(封装)
- 使用cli工具,帮我们生成模板代码。比如yeoman工具
总结:redux最大的弊端是样板代码太多,修改数据链路太长
解决方式:可以借助一些工具来减少创建样板代码的过程
- 使用redux-action减少书写固定不变的代码使得我们代码更加清晰
- 使用cli工具自动生成模板文件和代码
- 使用redux-action,在初始化reducer和action构造器时候减少样板代码
-
redux的异步问题怎样处理(为什么redux处理不了异步问题?)
- dispatch默认只能接受一个object类型的action,因为reducer中要接收action.type来处理不同的数据
- 那怎样解决redux不能处理异步的问题呢?
- redux异步问题可以用中间件来解决
- redux-saga:让异步成为中架构中独立的一层
- redux-thunk
- redux异步问题可以用中间件来解决
-
redux总结:
- redux会增加代码的复杂性,使用前需要考虑当前项目的规模和要求
- 可以使用redux-action或者cli工具来帮我们减少模板代码的书写
- redux无法处理异步action是因为dispatch只能接收一个object类型的action,但是可以通过中间件的方式来解决异步问题
-
React中的hooks
- React Hooks是一个新的API,可以用函数来写所有组件
- 可以让函数也拥有自己的状态管理(包括state和生命周期)
- 可以通过创建自定义的hooks来抽离可服用量的业务组件
-
React组件类型
- 函数组件
- 一个函数就是一个组件
- 一个函数必须有return
- return的是一个react元素
- 类组件
- 一个Class声明就是一个类组件
- 所有的类组件都是继承于React.Component
- React.Component类有自带属性和方法,比如render、componentDidMount等等
- 函数组件
- React Hooks的作用以及有哪些特性
- react hooks是v16.8版本才引入的全新API,它算是一个颠覆性的变革
- 所有的React组件都可以是一个函数组件,再也不需要写类组件了
- 再也不需要记住react有哪些生命周期了
创建使用自定义hooks
5.27
31、性能优化
初始阶段(加载优化)
-
首页加载优化
-
解答
- 对于首页加载过慢的问题,一般是由于首页加载资源过多并且资源过大导致,所以应对的策略一般是减少资源的数量以及减少资源的大小
- 对于图片可以进行懒加载,减少首屏图片加载量,以及对于小图标和小图片分别可以使用Iconfont和雪碧图来解决,最大程度的减少首屏图片渲染量,提高首屏加载速度
- 对于其他资源可以通过打包(nginx combo资源合并或者webpack打包)来合并资源,或者通过路由懒加载的方式来减少首页js加载的数量
- 同时可以在服务器配置nginx开启gzip打包来最大化压缩静态资源体积,达到优化首页加载的目的
-
问题分析(资源多、大)
-
1、首页加载图片过多
-
1、总结:
- 通过懒加载的方式处理非首屏图片
- 对于小图标可以使用Iconfont的方式来解决
- 对于小图片可以使用雪碧图的方式来解决
-
Q&A
- Q:首页加载图片过多怎样处理
- 懒加载:监听滚动条事件,如果滚动条的高度距离浏览器顶部的高度等于或者接近于图片到浏览器顶部的高度,那么就将data-src的属性赋值到src上
- Q:首页设置的小图标很多,比如有很多的小icon怎么办
- 对于纯色小图标可以用Iconfont来解决(减少资源请求)
- 设置font-famliy的css属性
- 对于一些彩色的小图标可以使用雪碧图
- 把所有小图标拼接到一张大图片上(减少资源请求)
- 并使用background-position的css属性来修改图片坐标
- 对于纯色小图标可以用Iconfont来解决(减少资源请求)
- Q:首页加载图片过多怎样处理
-
-
2、首页请求过多
-
2、总结(首页请求量过多,可以通过一些手段来减少资源的请求量)
- 通过nginx服务器来做静态资源的合并或者通过webpack等打包工具进行物理的打包
- 在代码层面,对于u 一些需要引入大型的第三方库进行按需加载,比如可以按照babel来进行
- 还可以通过react lazy等动态导入方案进行前端路由层面的动态加载,从而减少首页的js和css加载的大小
-
可以通过减少资源请求量
- 通过nginx服务器(可用来做CDN,处理静态资源)用来做文件合并combo—将多个js、css合并成一个。逻辑上打包,通过拼接请求链接,将多个资源链接合并到一起
- 通过打包工具(webpack)来做资源文件的物理打包。没有第一种灵活。
-
Q&A
-
Q:只有合并资源的方式才能减少资源请求吗?
-
对于引用一些大型的第三方库,比如antd、elementui等,可以使用按需加载的方式进行解决。一般都是使用babel插件来实现
-
针对SPA单页应用,在路由层面,可以使用前端路由懒加载的方式,从而减小首页js和css的大小
- 使用React.lazy进行路由的加载(react16.6以上才可以使用,16.6以下版本可以使用react-loadable)
-
-
Q:为什么React lazy可以进行路由懒加载?
- 首先react lazy是使用了动态加载(dynamic import)的一个标准,webpack只要遇到了动态加载就会把import的内容单独打一个包
- 由于动态加载返回的是一个promise,所以可以利用promise的流程来做渲染流程的控制
- 如果当前promise是pending状态,那么就渲染loading组件,如果是resolve状态那么就渲染动态导入的组件
动态导入:代码执行到import这一行的时候才开始去下载组件。并且webpack会将其单独打包成一个文件
lazy懒加载
- 结论:
- import(‘xxx’)返回的是一个promise
- webpack只要遇到了import(‘xxx’)就会把括号里引入的内容单独打一个包
-
-
-
3、首页请求的静态资源(html、css、js)过大
- 分析
- 要分资源文件、js、css等分开处理
- css和js可以通过webpack进行混淆和压缩
- 混淆:将js代码进行字符串加密(最大程度减少代码,比如将变量名称程度单个字母等)
- 压缩:去除注释空行以及console.log等调试代码
- 图片的压缩
- 自动化工具来压缩图片
- 图片进行转码,转成base64格式
- 使用webP格式
- 通过开启gzip进行全部资源的压缩
- gzip是一种压缩文件资源的格式,可以对任何文件进行压缩(类比于文件压缩),通过nginx服务器配置开启
- 分析
-
-
-
优化图片的做法
-
解答:图片优化业主要是从两个方面来进行,太多和太大
- 通过懒加载减少加载图片请求,或者通过雪碧图来合并图片,以及将小图转化成base64格式减少图片请求
- 图片过大问题可以通过图片自动化压缩工具或者使用webp格式的图片
-
问题分析:
- 减少图片加载请求
- 减少图片大小
-
Q&A
-
Q:用什么自动化工具对图片进行压缩?
- 熊猫站(https://tinypng.com/):无损压缩。熊猫站是通过相似颜色的量化技术来减少颜色的数量,并且可以将24位的png文件转化成8位的彩色图片,同时可以将不必要的元素进行剥离。并且它提供了npm包tinify,可以进行批量压缩
-
Q:还有什么其他方式吗
- 将图片转码为base64,会增大图片体积,因此不建议把大图片转成base64格式,但是建议把小图片转成base64格式,因为它直接写在代码中,可以减少一个图片的请求
- 使用webp格式
-
-
-
实现webpack打包优化
-
解答:多&大
- 可以设置mode=production来默认实现webpack对代码的混淆和压缩,从而最大程度减少代码体积
- 使用webpack+dynamic import(动态加载)并结合路由的入口文件动态加载做拆包处理
- 并且可以设置一定的打包策略(分包压缩,node_modules、常改动、不常改动公共组件),配合网络缓存(cache-control等)进行加载性能优化
-
问题分析
- 少:使用webpack进行物理打包
- 小:使用webpack进行混淆和压缩,所有与webpack相关的配置都在optimization这个配置项进行管理
-
Q&A
-
Q:打包怎样小怎样少?
- A:使用webpack对代码进行混淆和压缩,并且可以使用react lazy进行拆包,结合路由进行按需加载
-
Q:对文件进行拆包处理,那么文件肯定会增多,会不会跟减少资源请求数量矛盾呢?
- A:并不矛盾,因为我们按需加载之后,拆包的文件不可能同时加载,所以不会造成同一时间请求过多的问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SW9325Ze-1622195066706)(/Users/yqp/Library/Application Support/typora-user-images/image-20210528022556530.png)]
-
-
打包策略
-
实现CDN加速
-
解答:
- CDN服务器主要是用来做静态资源的服务器,可以用来加速静态资源的下载
- CDN之所以能够加速是因为在很多地方都部署了CDN服务器,如果用户需要下载静态资源,会自动选择最近的资源节点进行下载
- 同时由于CDN的服务器地址一般都跟主服务器不同,所以可以破除http1.0中对同一个域名同时发送的请求的限制问题
-
问题分析
-
什么叫做CDN(内容分发网站)
- 放静态资源服务器(JS、CSS、图片、字体…)。
-
为什么CDN可以实现加速?
- 因为里我们近。CDN服务器就是在里用户较近的地方放置一台服务器,把所有的静态资源放到这台服务器上,以后访问优先从这个网站上访问。CDN是一种解决方案,一般是用nginx实现。
-
为什么要进行CDN呢?
- HTTP1.1。因为对于同一个协议、同一个域名、同一个端口,浏览器允许最多同时打开六个TCP连接(最多同时发送六个请求)。通过CDN可以绕过浏览器对这个请求的限制
- http2:引入了多路复用的机制,可最大化发送请求数量。(没有了http1的六个TCP请求限制)
-
-
运行阶段(渲染优化)
-
思路:
- 导致卡顿的远影一般都是dom操作太多太频繁
- 如果想要渲染哦很多数据又不造成浏览器卡顿,那么肯定是要减少dom的操作。比如react创建虚拟dom,本质上是用js来模拟真实的dom,从而减少dom的操作
- 还有在插入多个dom节点时候,可以使用
document.createDocumentFragment
先创建虚拟节点,再一次性插入 - 也可以采取分段式渲染的方式
requestAnimation
来进行逐帧渲染
-
渲染十万条数据不造成卡顿?
- 结论:
- 可以使用
document.createDocumentFragment
创建虚拟节点来避免不必要的渲染 - 当所有的li都创建完成之后,再一次吧虚拟节点中的li全部渲染出来
- 可以采用分段渲染的方式,比如一次只渲染一屏的数据
- 最后使用
- 可以使用
- 结论:
普通方式:
优化渲染(分段渲染 )
补充:
requestAnimation
逐帧渲染。1000/60=16,也就是16ms渲染一次。requestAnimation要保证浏览器是60帧,所以默认是16ms一次渲染。
拓展:服务器类别
- 应用服务器:弹性计算,存放运行后端代码等
- 存储服务器:存储文件
- CDN服务器:处理静态资源,做资源文件的合并。做静态资源分发
- 数据库服务器
5.30
32、MVC、MVVM、MVP
MVC
- M:model代表数据模型,主要任务是操作数据。将新的数据发送到view改变视图
- V:view代表的是视图,主要任务是将数据模型转化成UI视图。传送指令到controller
- C:controller控制器,主要任务是负责处理业务逻辑。完成业务逻辑之后要求model改变状态
33、函数防抖与节流
解决性能问题,可降低函数调用频率。事件频繁触发的时候,比如window.resize
以及window.onmousemove
等等,触发频率十分高,会造成浏览器性能问题。以及输入框搜索的时候会实时去后台请求数据,对服务器造成不必要的压力。
函数节流(间隔一定时间执行一次)
function throttle(fn,delay=500){
let pre = 0;
return function(event){
const cur = Date.now();
if(cur - pre > delay){ //只有与上次调用的时间差大于delay
fn.call(this,event)
pre = cur;
}
}
}
函数防抖(一定时间内,只触发最后一次)
function debounce(fn,delay = 500){
let timer = null;
return function(e){
if(timer){clear(timer)}
timer = setTimeout(()=>{ //这里需要用箭头函数;因为需要改变this指向
fn.call(this,e)
},delay)
timer = null;
}
}
补充:高阶函数:1、参数是函数 2、返回值是函数。满足其中之一的就是高阶函数。
6.1
34、数组声明式系列方法的使用
(1)产生一个每个元素都比原来大10的新数组
function addTen(arr){
return arr.map((item,index)=>{
return item+10;
})
}
(2)得到数组中所有奇数的和
function getAdd(arr){
arr.reduce((pretotal,item,index)=>{
return item%2 === 1 ? pretotal+item:pretotal;
},0)
}
(3)得到值大于8且为偶数的
function filterArr(arr){
return arr.filter((item,index)=>{
return item>8&&item%2===0;
})
}
(4)找出一个值大于8且下标是偶数的元素
function getArr(arr){
return arr.find((item,index)=>{
return item>8&&index%2===0;
})
}
(5)找出一个值大于8且下标是偶数的元素的下标
function getArrindex(arr){
return arr.findIndex((item,index)=>{
return item>8&&index%2===0;
})
}
(6)判断下标为偶数的元素是否都是奇数
funcrion getConfirm(arr){
return arr.every((item,index)=>{
//下标为奇数或者下标为偶数值都是奇数的元素
return index%2 === 1 || (index%2 === 0 && item % 2 === 1)
})
}
(7)是否有下标为偶数的元素值为奇数
funcrion getConfirm(arr){
return arr.some((item,index)=>{
//下标为奇数或者下标为偶数值都是奇数的元素
return index%2 === 1 || (index%2 === 0 && item % 2 === 1)
})
}
自定义数组声明式方法
- map
Array.prototype.map = function(callback){
const arr = [];
//遍历当前数组,调用callback得到一个结果数据,添加到arr
for(let i = 0;i < this.length;i++){ //this就是原数组
const element = this[i];
arr.push(callback(element,i))
}
return arr;
}
- reduce
Array.prototype.redece = function(callback,initialVal){
let result = initialVal;
for(let i = 0;i < this.length;i++){
//recude三个参数,上一次的累加结果,value,index;上一次累加结果由callback决定
result = callback(result,this[i],i)
}
return result;
}
- filter
//调用callback得到一个boolean值,如果为true那么就将值push到结果中去
Array.prototype.filter = function(callback){
const result = [];
for(let i = 0;i < this.length;i++){
if(callback(this[i],i)){
result.push(this[i]);
}
}
return result;
}
- find
//遍历当前数组,调用callback,返回boolean值,如果为true那么就返回这个值
Array.prototype.find = function(callback){
for(let i = 0;i < this.length;i++){
const element = callback(this[i],i)
const result = this[i];
if(element){
return result;
}
}
return undefined; //没有匹配的返回undefined
}
- findIndex
//遍历当前数组,调用callback,返回boolean值,如果为true那么就返回这个值
Array.prototype.findIndex = function(callback){
for(let i = 0;i < this.length;i++){
if(callback(this[i],i)){
return i;
}
}
return -1; //没有匹配的返回undefined
}
- every
//遍历,一点callback返回false,那么返回false,都不为false,返回true
Array.prototype.every = function(){
let result = true;
for(let i = 0;i < this.length;i++){
if(!callback(this[i],i)){
result = false
}
}
return result;
}
- some
//一旦callback为true返回true否则返回false
Array.prototype.some = function(){
let result = false;
for(let i = 0;i < this.length;i++){
if(callback(this[i],i)){
result = true
}
}
return result;
}
35、算法基础
数组
数组相关声明式方法都是高阶函数,都没有改变原数组
- 数组静态方法
- from
- of
- isArray
//from 从一个类数组,或者可迭代对象创建一个新的,浅拷贝的数组实例
const set = new Set(['foo','smile','yqp'])
console.log(Array.from(set)) //['foo','smile','yqp']
//of 创建一个可变数量参数的数组实例,所有参数列表作为元素,创建一个新数组
console.log(Array.of(3)) //[3]
console.log(Array.of(3,5,7,8)) //[3,5,7,8]
//isArray 判断是否是数组
console.log(Array.isArray([1,2,3])) //true
console.log(Array.isArray({1,2,3,4})) //false
- 数组更新的方法
- push
- pop
- unshift
- shift
- sort
- reverse
- splice
const arr = [1,2,4,7,9,3]
//push
//1、数组最后添加6
arr.push(6) //[1,2,4,7,9,3,6]
//2、数组最后一次添加8和9
arr.push(8,8) // [1,2,4,7,9,3,8,9]
//2、数组最后添加包含8和9的数组
arr.push([8,9]) // [1,2,4,7,9,3,[8,9]]
//pop 删除数组最后一个元素
arr.pop() //9 pop方法返回的是删除的最后一个元素的值 原数组变成[1,2,4,7,9]
//unshift
//1、在数组最前面添加2
arr.unshift(2) //[2,1,2,4,7,9,3]
//2、在数组最前面添加4和3
arr.unshift(4,3) //[4,3,1,2,4,7,9,3]
//shift 删除数组的第一个元素
arr.shift() //1 返回被删元素值 原数组变成[2,4,7,9,3]
//sort 数组降序排列;sort的回调函数返回值为数值,分为dayu0,等于0,小于0;大于0item2放在左边
arr.sort((item1,item2)=>{return item2 -item1}) //sort改变原数组;返回值也是改变之后排序的数组
//splice
//1、删除第三个元素;两个参数,第一个index第二个是个数
arr.splice(2,1)
//2、删除第将第三个元素替换为10
arr.splice(2,1,10)
//3、将11插入为数组的第三个元素
arr.splice(2,0,11)
- 数组遍历相关的声明式方法
- forEach
- map
- reduce
- filter
- find
- findIndex
- some
- every
- slice返回一个新的数组,包含从 start 到 end。不会修改数组,而是返回一个子数组。
arrayObject.slice(start,end)
- 数组的其他方法
- length
- join
- contact被合并的可以是数组也可以是元素。
arr.concat([1,3],4)
,如果是数组就把数组中的元素取出来 - indexOf,找到元素下标
- lastIndexOf
6.2
自定义栈Stack
- 栈的几个功能方法
- 进栈(压栈)push
- 出栈pop
- 产看栈顶peek
- 栈中元素个数size
- 是否空栈isEmpty
function Stack(){
//用于保存元素数据的数组
const arr = [];
this.push = function(element){
arr.push(element)
}
this.pop = function(){
arr.pop()
}
this.peek = function(){
return arr[arr.length-1]
}
this.size = function(){
return arr.length
}
this.isEmpty = function(){
return arr.length === 0 ?true:false
}
}
export default Stack;
- 栈应用(栈十进制转二进制:除2取余,倒序排列,高位补0)。数组实现十进制转二进制。
function edc2bin(decNum){
//创建一个用于保存二进制的array。Array改为Stack就是栈应用
const arr = new Array();
while(decNum>0){
const remainder = decNum % 2; //取余
arr.push(remainder);
decNum = Math.floor(decNum / 2); //向下取整数
}
//依次
let result = ''
while(arr.length !== 0){
result = result + arr.pop()
}
return result;
}
队列Queue
function Queue(){
const arr = [];
this.enqueue = function(element){
arr.push(element)
}
this.dequeue = function(){
arr.shift() //删除数组的第一个元素并且返回
}
this.front = function(){ //查看队列第一个
return arr[0]
}
this.size = function(){
return arr.length
}
this.isEmpty = function(){
return arr.length === 0 ?true:false
}
}
export default Queue;
- 队列击鼓传花(队列头依次放到后面去;并且删除指定的第几个 )
- 创建一个空queue
- 将数组中的所有元素依次放入queue
- 将队列中num-1元素依次转移到队列最后,将队头部元素删除(只要队列的size>1就不断做,直到剩下最后一个,就是目标元素 )
function passGame(names,num){
//创建一个空queue
const queue = new Queue();
//将数组中的所有元素(name和index)依次放入queue
names.forEach((name,index)=>{
console.log(name,index)
queue.enqueue({name,index})
})
while(queue.size() > 1){
for(let i = 0;i < num;i++){
queue.enqueue(queue.dequeue())
}
//删除头部元素
queue.dequeue(queue.front())
}
const {name,index} = queue.front()
return `${name}是最后一个,它的index是第${index}个`
}
var names = ['a','b','c','d','e']
passGame(names,3)
- 优先级队列
- 传入值优先级。标记优先级,优先级高的先出
链表(单项链表->只有next、双向链表->有front和next)
树(dom树)
二叉树BT:一个节点有最多两个子节点
二叉搜索树BST:满足二叉树的基础上,值左边小,右边大
集合
元素唯一不能重复。ES6中集合的实现是用数组实现的。当然集合也可以用Object实现
字典
哈希列表/散列表
图
6.3
36、webpack
webpack基础知识
- webpack:静态模块打包工具。webpack中的一切资源都是文件。
- webpack是通过从入口进行递归查询的方式直接或者间接相互依赖的文件,最后生成打包文件。
- webpack-cli处理打包命令,解析webpack命令。webpack包做文件打包工作
- webpack不用全局下载,只用局部下载即可。因为不同项目可能使用的webpack版本不一样
- webpack本身能解析打包各种模块规范的js代码
yarn init -y
yarn add -D webpack webpack-cli
//创建src/index.js因为webpack打包默认会找src目录下面的index.js文件
//此时执行webpack命令,可以看到webpack将其打了个基础包,在dist目录下
//package.json中配置打包命令,之后使用npm run build进行webpack打包
"scripts": {
"build":"webpack"
},
-
创建
webpack.config.js
文件,写入相关配置 -
clean-webpack-plugin@1.0.1
上一次打包的文件清除 -
html-webpack-plugin
解决手动引入 -
webpack-dev-server
解决自动打包不需要手动打包
yarn add -D webpack-dev-server html-webpack-plugin clean-webpack-plugin@1.0.1
cnpm install @babel/core @babel/plugin-transform-runtime @babel/polyfill @babel/preset-env @babel/runtime autoprefixer babel-eslint babel-loader copy-webpack-plugin css-loader eslint eslint-friendly-formatter eslint-loader file-loader html-webpack-plugin --save-dev
webpack核心概念,常用10个配置
- mode:none、development、production。声明一些模式就是内部为我们预先加上一些配置
- 入口entry:一个或者多个(单页打包多页打包)
- 出口output:指定打包的文件夹
- modules模块加载器,用来指定loader:webpack本身只能打包js,不能处理css、图片等资源。因此就需要模块加载器。将css打包到js,引用地址
- 插件plugins:需要打包js之外做的事情,比如说压缩、拷贝、清除、引入页面等操作。插件名称一般是
xx-webpack-plugin
形式- clear-webpack-plugin清除插件
- html-webpack-plugin
- devtools开发工具,用来制定sourcemap
- devServer开发服务器。比如说配置代理、以及不带#路由(Broswer路由)404问题
- resolve解析别名和路径
- optimization指定优化处理的。由模式确定。生产环境和开发环境指定的优化方式不一样。
- externals外部某个包。配置了某个包,比如说用了lodash,不用npm下载也可以,在这里配置也是可以的
webpack常用包
- webpack-dev-server开发服务器的包
- webpack-merge合并包
- cross-env
- css-loader
- style-loader
- postcss-loader
- autoprefixer自动浏览器适配。适配浏览器
- postcss-px2em写的px实际上自动转为rem,适配移动端
- less-loader
- styles-loader
- sass-loader
- file-loader
- url-loader
- image-webpack-loader压缩图片,不影响显示效果
- babel-loader
- @babel/core
- @babel/preset-env
- @babel/preset-react
- @babel/polyfill
- @babel/plugin-transform-runtime
- @babel/runtime
- vue-loader
- eslint-loader语法检查
- MiniCssExtractPlugin.loader单独提取打包css。
- thread-loader多线程打包
- html-webpack-plugin把打包的css或者js自动引入到界面中去
- clean-webpack-plugin删除文件
- mini-css-extract-plugin单独提取打包css
- optimize-css-assets-webpack-plugin压缩css
- copy-webpack-plugin拷贝文件
- terser-webpack-plugin压缩js
- add-asset-html-webpack-plugin
- webpack-bundle-analyzer分析打包文件的
- webpack.ProgressPlugin显示打包进度的
- webpack.HotModuleReplacementPlugin热模块低缓
- webpack.HashedModulesPlugin模块id哈希值
- webpack.DllPlugin多进程打包
- webpack.DllReferencePlugin多进程打包
- new webpack.ProvidePlugin
- 处理
- loader处理:js资源以外的资源、包括jsES6转ES5
- plugin插件处理
- devTool开发工具:
- devServer开发服务器:
版本号
babel打包处理
babel本身是不能进行ES6编译到ES5的,它是提供了一个平台babel-core
,靠babel-core
去组织,每一个语法都有对应的插件去解析。
@babel/preset-env
预设包,是很多插件包的集合包,这里指的是很多js的ES6到ES5语法转换的插件包的集合包。并且不是插件能解决所有的语法转化问题,因为有些新语法是提供函数方法API,有些新语法是提供操作符。
@babel/polyfill
补丁包,处理一些浏览器不兼容的语法。
@babel/plugin-transform-runtime
处理
打包图片
file-loader
url-loader
:处理图片主要是用这个,但是它是需要file-loader
做基础。并且不仅图片、音视频、字体文件、打包样式、格式处理都可以使用这个文件
配置样式打包加载模块
- 基本css
- css预编译器
- less
- sass
- stylus
- postcss
- 抽取/单独打包css
- 压缩css
css预编译器
cnpm install style-loader css css-loader less less-loader node-sass sass-loader stylus stylus-loader --save
PostCss
- postcss.config.js
module.exports = {
plugins: [
// require('precss'),
require('autoprefixer')()
]
}
- config.webpack.js中postcss配置
//模块加载器
module:{
rules:[
//处理css
{
test: /\.css$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader', //预处理css的,要在css前进行,因此由于loader加载顺序,放在css-loader的下面或者右边
]
},
//处理less
{
test: /\.less$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'less-loader',
]
},
//处理stylus
{
test: /\.(styl|stylus)$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'stylus-loader'
]
}
]
},
样式单独打包
- mini-css-extract-plugin
npm install --save-dev mini-css-extract-plugin
webpack中配置
const MiniCssExtracPlugin = require('mini-css-extract-plugin')
//webpack中配置
plugins:[
//从js中抽取css单独打包;一旦抽取了css就不需要cssloader了,需要换成这个插件里面的loader。单独打包css
new MiniCssExtracPlugin({
filename:'css/[name].css'
})
]
//webpack中loader配置。使用MiniCssExtracPlugin.loader代替style-loader即可
{
test: /\.css$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader, //代替style-loader
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader', //预处理css的,要在css前进行,因此由于loader加载顺序,放在css-loader的下面或者右边
]
}
css样式压缩
npm install --save-dev optimize-css-assets-webpack-plugin
- webpack配置
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
//优化配置
optimization:{
minimizer:[new OptimizeCssAssetsPlugin()]
}
webpack基础配置
/**
* webpack配置要向外暴露一个对象,用commonjs的规范进行,因为webpack内部是基于node来进行的处理
*/
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const MiniCssExtracPlugin = require('mini-css-extract-plugin')
const webpack = require('webpack');
const { optimize } = require('webpack');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
function resolve(dir){ //传入一个目录名称,就返回当前目录名称所在的绝对路径
return path.resolve(__dirname,dir)
}
module.exports = {
//模式
mode:'production',
//入口:入口可以是字符串、对象、数组,都是可以的
entry:{
main:resolve('src/index.js')
},
//出口
output:{
path:resolve('dist'),
filename:'bundle.js',
publicPath:'/' //解决图片路径问题;所有生成的URL链接左侧用/开头;即相对路径
},
//模块加载器
module:{
rules:[
//处理ES6到ES5
{
test: /\.m?js$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
{
loader: 'babel-loader', //babel也可以专门用配置文件babel.config.js或者.babelrc进行配置,也可以这两个配置文件都不屑直接在wbpack的这个地方进行配置即可
// options: {
// presets: ['@babel/preset-env'] //这边的配置也可以在babel.config.js中写
// }
}
]
},{
//处理图片
test: /\.(jpe?g|png|webp|gif)$/, //指定对哪些文件进行处理,正则
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
{
loader: 'url-loader', //babel也可以专门用配置文件babel.config.js或者.babelrc进行配置,也可以这两个配置文件都不屑直接在wbpack的这个地方进行配置即可。处理图片、字体、音视频等
options: {
limit: 1024*15, //把小于15kb的进行base64处理
name:'img/[name].[ext]' //文件路径;相对于所有文件下面的;[name]文件名[ext]后缀扩展名
}
}
]
},
//处理css
{
test: /\.css$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader, //代替style-loader
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader', //预处理css的,要在css前进行,因此由于loader加载顺序,放在css-loader的下面或者右边
]
},
//处理less
{
test: /\.less$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader, //代替style-loader
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'less-loader',
]
},
//处理stylus
{
test: /\.(styl|stylus)$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader,
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'stylus-loader'
]
}
]
},
//插件
plugins:[
//向页面中引入打包的js或者css代码
new HtmlWebpackPlugin({
template:'public/index.html' //指定以哪个为模板
}),
//清除打包文件夹
new CleanWebpackPlugin(['dist']),
// new webpack.ProvidePlugin({
// jQuery: "jquery", //配置jquery
// $: "jquery"
// })
//从js中抽取css单独打包;一旦抽取了css就不需要cssloader了,需要换成这个插件里面的loader。单独打包css
new MiniCssExtracPlugin({
filename:'css/[name].css'
}),
],
//开发服务器
devServer:{
open:true //自动打开浏览器访问
},
//优化配置
optimization:{
minimizer:[new OptimizeCssAssetsPlugin()]
}
}
6.7
37、设计模式
设计模式:针对特定的场景,在软件设计过程中常见的代码范式(通用解决方案,与语言无关)。
- 观察者模式(麦当劳点餐)
- 代理模式(花店送花)
- 装饰器:可以允许向一个对象添加新的功能,却不改变原有对象。比如:
- 给文件上传功能添加日志输出功能
- react中redux的
@connect
- 职责链模式
代理模式
- 特点:代理对象与真实对象有相同的行为
- 真实对象->代理对象->用户
- 花店送花:男孩让花店送花给女孩
//定义女孩对象
var Girl = function(name){
this.name = name;
}
var Boy = function(girl){
//女同学
this.girl = girl;
//送花
this.sendGift = function(gift){
console.log(this.girl.name+gift) //花店下订单,花店人去送花
}
}
//代理对象 花店员工
var ProxyObj = function(girl){
this.girl = girl; //需要知道女孩信息
this.sendGift = function(gift){
(new Boy(this.girl)).sendGift(gift) //替人送花;直接调用
}
}
//调用 不能看出是谁送花
var girl = new Girl('Vicky')
var proxy = new ProxyObj(girl)
proxy.sendGift("flowers")
- 代理模式的应用:图片懒加载
- 真实图片:大、加载慢;代理图片:小,加载快
//常用图片懒加载
window.onload = function(){
var myImage = (function(){ //自执行函数
var imgNode = document.createElement('img') //创建真实图片节点
document.body.appendChild(imgNode) //图片节点加到body
var img = new Image() //代理对象 先展示代理图片 接着拉取实际图片
img.onload = function(){ //真实图片加载完毕后触发
imgNode.src = this.src; //把真实图片url给真实图片节点;替换等待图片;this指向代理对象
}
return { //返回一个对象
setSrc:function(src){
imgNode.src = 'https://th.bing.com/th/id/R20d0cd9f696181bbead3b250782239fc?rik=SozVXnglW3zBxg&riu=http%3a%2f%2fbpic.588ku.com%2felement_pic%2f01%2f35%2f08%2f79573bd17084b13.jpg&ehk=UPH3Ji9JG4ZMqBzH9qKIJwuFG2R31XoHzvp4NkK5vXc%3d&risl=&pid=ImgRaw'; //代理图片url给到真实对象;先展示等待图片
img.src = src; //把src给到代理对象
}
}
})()
//调用:把真实图片给到对象
myImage.setSrc("https://himg.bdimg.com/sys/portrait/item/pp.1.d64a7775.reheWDHF3vrjPLK3n-YgKw.jpg?tt=1623053822499"); //真实图片
}
使用代理模式重构图片懒加载
window.onload = function(){
var myImage = (function(){
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc:function(src){
imgNode.src = src;
}
}
})()
//代理对象:先展示等待图片,再拉取真实图片
var ProxyImage = (function(){
var img = new Image() //内存中的代理
img.onload = function(){ //加载完之后将触发这个方法
myImage.setSrc(this.src) //this指向img;将真实图片给真实对象展示
}
return {
setSrc:function(src){
myImage.setSrc('https://th.bing.com/th/id/R20d0cd9f696181bbead3b250782239fc?rik=SozVXnglW3zBxg&riu=http%3a%2f%2fbpic.588ku.com%2felement_pic%2f01%2f35%2f08%2f79573bd17084b13.jpg&ehk=UPH3Ji9JG4ZMqBzH9qKIJwuFG2R31XoHzvp4NkK5vXc%3d&risl=&pid=ImgRaw') //代理图片
img.src = src;
}
}
})()
//调用;直接给代理对象设置真实图片 ProxyImage.setSrc('https://himg.bdimg.com/sys/portrait/item/pp.1.d64a7775.reheWDHF3vrjPLK3n-YgKw.jpg?tt=1623053822499') //真实图片
}
观察者模式(发布订阅模式)
场景:双十一购买商品。购买者:观察者;商品:被观察者;商家(商品价格修改):发布者。发布者发布,关注着会收到改变。
观察者、被观察者、发布者
//观察者
var shopObj = {}
//商品列表 ['huawei':['订阅人1','订阅人2'],'apple':['订阅人3','订阅人2'],'oppo':['订阅人1']]
shopObj.list = []
//订阅;根据特定的商品推给特定人
shopObj.listen = function(goodskey,fn){ //商品订阅方法;fn订阅的行为; 建立商品和观察者的联系
if(!this.list[goodskey]){
this.list[goodskey] = []
}
shopObj.list[goodskey].push(fn) //往特定商品列表中添加订阅
}
shopObj.publish = function(){
var goodskey = arguments[0]
var fns = this.list[goodskey]
for(var i = 0,fn;fn = fns[i++];){
//执行订阅的fn
fn.apply(this,arguments)
}
}
//用户1:添加订阅
shopObj.listen('huawei',function(brand,model){
console.log('user 1'+brand,model)
})
//用户2:添加订阅
shopObj.listen('apple',function(brand,model){
console.log('user 2'+brand,model)
})
//商家发布订阅
shopObj.publish('huawei','P40')
观察者模式优化
//订阅发布放一起;之后再初始化
var event = {
list:[],
listen:function(key,fn){
if(!this.list[key]){
this.list[skey] = []
}
shopObj.list[key].push(fn) //往特定商品列表中添加订阅
},
publish:function(){
var goodskey = arguments[0]
var fns = this.list[goodskey]
for(var i = 0,fn;fn = fns[i++];){
//执行订阅的fn
fn.apply(this,arguments)
}
}
}
//观察者对象初始化
var initEvent = function(obj){
for(var i in event){
obj[i] = event[i];
}
}
//定义空发布者
var shopObj = {} //空对象
initEvent(shopObj) //将我们定义的方法属性都给它
//用户1:添加订阅
shopObj.listen('huawei',function(brand,model){
console.log('user 1'+brand,model)
})
//用户2:添加订阅
shopObj.listen('apple',function(brand,model){
console.log('user 2'+brand,model)
})
//商家发布订阅
shopObj.publish('huawei','P40')
6.8
装饰器
-
给对象添加额外行为但是没有改变原有对象
-
简单装饰器的实现
class Circle{
draw(){
console.log('draw a circle')
}
}
//使用装饰器添加一个边框
class Decorator{
constructor(circle){
this.circle = circle;
}
draw(){
this.circle.draw() //圆自己绘制
this.setBorder() //增加绘制方法
}
setBorder(circle){ //装饰器方法
console.log('draw border')
}
}
var circle = new Circle()
var dec = new Decorator(circle)
dec.draw()
- 装饰器–注解形式
class Boy {
@run
speak(){
console.log('speak')
}
}
function run(target,key,descriptor){ //装饰器参数。
//target是Boy对象;key是被装饰的方法speak;descriptor是描述方法(writeable可写;enumerable可枚举,即是否可以for循环出来;configuerable可配置)
console.log('run')
}
var boy = new Boy()
boy.speak()
需要配置babel
,使用transform-decoarors-legacy
将@
转化成ES5代码;安装之后使用babel xxx.js -o /build/xxx.js
进行转化。
cnpm i -S babel-cli babel-preset-env babel-plugin-transform-decoarors-legacy
//babel-preset-env用于ES6转ES5
//babel-plugin-transform-decoarors-legacy用于注解翻译的
- 装饰器参数
- target 对象
- key被装饰的方法
- descriptor 描述对象
//给方法添加日志输出功能;不改变原有功能,添加新功能
class Math{
@log
add(a,b){
return a+b;
}
}
//日志装饰器
function log(target,name,descriptor){
var oldValue = descriptor.value; //descriptor.value是被修饰的方法,add
descriptor.value = function(){
console.log(`调用${name}`参数是`${arguments}`) //arguments是js的内置对象,包含所有实参的类数组
return oldValue.apply(target,arguments)
}
return descriptor;
}
var math = new Math();
math.value(1,3)
装饰器添加参数
//给方法添加日志输出功能,并且带上参数,加到a、b上;
class Math{
@log(100)
add(a,b){
return a+b;
}
}
//日志装饰器
function log(num){
return function log(target,name,descriptor){
var _num = num || 0;
var oldValue = descriptor.value;
descriptor.value = function(...args){
arg[0] += _num; //改变原有参数值
arg[1] += _num;
console.log(`调用${name}`参数是`${args}`)
return oldValue.apply(target,args)
}
return descriptor;
}
}
var math = new Math();
math.value(1,3)
职责链模式
- 场景:充值抽奖
开闭原则:原有代码对现有需求时关闭的,对扩展时开放的
充值500抽100rmb;充值200抽20rmb;否则无奖
function(orderType,isPay,count){ //订单类型;是否支付成功;金额
if(orderType === 1){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
if(count>0){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
}else if(orderType === 2){ //200rmb
if(isPay){
console.log('中奖20rmb')
}else{
if(count>0){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
}else if(orderType === 3){
console.log('谢谢参与')
}
}
- 使用职责链模式重构
function order500(orderType,isPay,count){
if(orderType === 1 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
order200(orderType,isPay,count)
}
}
function order200(orderType,isPay,count){
if(orderType === 2 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
orderdefault(orderType,isPay,count)
}
}
function orderdefault(orderType,isPay,count){
if(count>0 && isPay){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
- 使用职责链再次重构
function order500(orderType,isPay,count){
if(orderType === 1 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
return "nextSuccessor"
}
}
function order200(orderType,isPay,count){
if(orderType === 2 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
return "nextSuccessor"
}
}
function orderdefault(orderType,isPay,count){
if(count>0 && isPay){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
//创建职责链关系对象
function Chain(fn){
this.fn = fn;
this.successor = null;
}
//设置下一个关系对象
Chain.prototype.setnextSuccessor =function(successor){
this.successor = successor;
}
//传递给下一个
Chain.prototype.passRequest = function(){
var ret = this.fn.apply(this,arguments)
if(ret === 'nextSuccessor'){ //传给下一个
this.successor && this.successor.passRequest.apply(this.successor,arguments)
}
}
//实例化
var chain500 = new Chain(order500)
var chain200 = new Chain(order200)
var chaindefault = new Chain(orderdefault)
chain500.setnextSuccessor = chain200;
chain200.setnextSuccessor = chaindefault;
//调用
chain500.passRequest(1,true,100) //类型1 支付成功 金额500
面向对象:抽象、多态、继承、封装