导航
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[封装03-设计模式] Decorator 装饰器模式在前端的应用
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] koa
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[源码-react01] ReactDOM.render01
[源码-react02] 手写hook调度-useState实现
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber
[深入25] Typescript
[深入26] Drag
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon
复习笔记-01
复习笔记-02
复习笔记-03
复习笔记-04
前置知识
(1) 一些单词
decorator 装饰器
custom 定制 习惯
edition 版本
reversion 反转 逆转
(2) 自定义事件 - CustomEvent
- 概念:CustomEvent 用来生成自定义的事件实例
- 特点
要在触发事件的同时,传入指定的 (数据),就可以使用 CustomEvent 接口生成自定义事件对象- 浏览器预定义事件的缺点
- 那些浏览器预定义的事件,虽然可以手动生成,但是往往不能在事件上绑定数据
- 对比
new Event(type, options)new CustomEvent(type, options)EventTarget.dispatch(event事件实例)
前置知识
---
1
EventTarget.dispatchEvent(event)
- 作用
- 在 ( 当前节点 - EventTarget ) 上触发 ( 指定的事件 - event事件实例 ),从而 ( 触发监听函数 ) 的执行
- event事件实例如何生成 - 通过 new Event() 生成
- 参数
- 是一个 Event 对象的实例
- 返回值
- 返回一个boolean值
- Event.preventDefault() 返回false,否则返回true
2
Event 对象 | 构造函数
- 概念
- 浏览器提供了原生的Event对象
- (1) 所有的 ( 事件 ) 都是 ( Event ) 对象的 ( 实例 )
- (2) ( 事件实例对象 ) 继承了 ( Event.prototype对象 )
- (3) 事件实例 可以通过 new Event() 来生成
- 语法
- const event = new Event(type, options)
- 参数
- type是一个字符串,表示事件名称
- options是一个对象,表示事件配置对象
- bubble
- 表示事件是否可以冒泡,( boolean值,可选,默认是false )
- cancelable
- 表示事件是否可以通过 Event.preventDefault() 被取消;( boolean值,可选,默认是false )
- 一旦事件被取消,就好像从来没有发生过,不会触发浏览器对该事件的默认行为
3
案例
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<div id="one">通过EventTarget.dispatch(event)来触发事件</div>
<button id="two">触发click事件的button</button>
<script>
// 效果
// - 1. 点击 button 触发 click 事件,执行click事件绑定的监听函数
// - 2. 在上面 1 的监听函数中,执行 EventTarget.dispatch(event) 触发 div上绑定的hello1事件,从而触发 hello1 事件的监听函数 打印hello
const one = document.getElementById("one");
const hello2 = new Event("hello1", {
// ---------------------------------------- 生成 hello2 事件,hello2事件的名称是 'hello1'
bubbles: true, // 是否冒泡
cancelable: true, // 是否可以通过 Event.preventDefault() 来取消
});
one.addEventListener("hello1", (e) => {
// ---------------------------------------- 监听事件名称是 'hello1' 的事件,事件触发时,会执行监听函数
console.log("hello");
});
const two = document.getElementById("two");
two.addEventListener("click", () => {
console.log("click事件回调触发");
one.dispatchEvent(hello2);
// 触发 one 节点上的 hello2 事件
// EventTarget.dispatchEvent(event事件实例),在 one 节点上触发 hello2 事件,hello2事件通过 new Event() 生成,指定的事件名事hello1,hello1事件被addEventListener监听
});
</script>
</body>
</html>
1
new CustomEvent(type, options)
- 参数
- type 表示事件名称一个字符串,必传参数
- options 事件的配置对象 可选
- detail:该属性表示 ( 事件附带的数据 ),默认值是null
- 作用
- Custom- CustomEvent 构造函数用来生成 customEvent 事件实例Event 构造函数用来生成 customEvent 事件实例、
- 案例
const button3 = document.getElementById("three");
const customEvent = new CustomEvent("hello3", {
bubbles: false,
cancelable: false,
detail: {
effect: "customEvent可以携带数据", // new CustomEvent() 相对于 new Event() 在第二个参数配置对象中,多了 detail 属性,表示 ( 事件附带的数据 )
},
});
button3.addEventListener("click", () => {
button3.dispatchEvent(customEvent);
});
button3.addEventListener("hello3", (e) => {
console.log(`e.detail`, e.detail);
});
(3) HOC 高阶组件
- HOC本质上不是组件,而是一个 ( 函数 ),该函数接收一个组件作为参数,返回一个新的组件
- 实现高阶组件的方式
属性代理 props proxy----------->代理反向继承 inhert inversion----->继承
- ( 属性代理 ) 实现高阶组件
- 本质:使用组合的方式,通过容器组件对组件的包装,在容器组件中实现功能
- 特点:因为是组件对组件的嵌套,即存在父子组件生命周期执行顺序问题和state,props状态改变带来的影响
- 多种属性代理的实现方式:
操作props:在外层容器组件中拦截,处理props,然后传入被包装的组件抽象state:在容器组件中,定义state和操作state的方法,然后传给子组件ref的引用:ref只能用于类组件,不能用于函数组件,但是hooks提供了useRef,forwardRef,useImperativeHandle属性
- ( 反向继承 ) 实现高阶组件
反向继承 - 实现HOC高阶组件的原理- 1.通过 extends 的方式继承被包装的组件
- 2.然后在高阶组件的render函数中,通过 super 对象获取到被装组件原型上的 render 方法
- 3.通过以上12,能获取到被包装组件的原型上的所有属性和方法,比如render,生命周期,state等,那么就能实现不同的功能:比如条件渲染,获取|重写生命周期,修改state等
- super
- super可以作为函数,也可以作为对象
- super作为函数时
- 表示的是父类的构造函数,只能用于构造函数中,this指向子类实例
- super作为对象时
- 在普通方法中:表示父类的原型,this指向子类实例
- 在静态方法中:表示父类,this指向当前子类
- super作为函数时
- 注意
- es6的class是存在两条原型链的
- 1.A 的原型对象是 B
- 2.A.prototype 的原型对象 B.prototype
- super可以作为函数,也可以作为对象
- 属性代理 和 反向继承 的对比
属性代理:是通过 ( 组合-装饰器模式 ) 的方式,从 ( 外部 ) 去操作 props 来实现不同的功能反向继承:是通过 ( 继承 ) 的方式,从 ( 内部 ) 去获取组件内部的属性和方法,比如render,state,生命周期等
(4) 处理URL
1
const url = new URL(url [, base])
- 参数
- url
- 一个表示 ( 绝对或者相对 ) URL 的 DOMString
- 绝对URL:base参数将被忽略
- 相对URL:会把 base 参数作为基准URL
- base
- 一个基准URL的DOMString
- base参数生效的前提是:第一个参数url是相对URL
- 返回值
- 新生成的url实例对象
- 如果给定的基本 URL 或生成的 URL 不是有效的 URL 链接,则会抛出一个`TypeError`
- !!!!!! ---> url.searchParams() 和 new URLSearchParams() 的返回值一样
- !!!!!! ---> new URL('https://.../?s=url').searchParams.get('s') === new URLSearchParams('?s=url').get('s')
- 例子
const url = new URL('/api/?author=woow_wu7&age=20#head', 'http://www.baidu.com')
// 等同于 const url = new URL('http://www.baidu.com/api/?autho=woow_wu7&age=20#head')
// href: "http://www.baidu.com/api/?author=woow_wu7&age=20#head"
// 注意:这里 query 一定要在 hash 前面,不然取不到query,会把query部分也作为hash
2
const URLSearchParams = new URLSearchParams(init)
- 参数
- init:一个url信息的string
- 返回
- URLSearchParams 实例
- 实例对象上的属性
- append(key, value) 添加search键值对
- delete(key) 删除
- entries() 返回迭代器对象 - 可以被 for...of 遍历
- forEach() 遍历
- get(key) 获取
- getAll(key) 因为可以存在相同key的情况,对应不同的value值
- has(key) 是否存在
- keys()
- set(key, value)
- sort()
- toString()
(5) vue -> vm.attrs 和 vm.$slots
- vm.$listeners
- 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
- 可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用
this.$listeners.click获取v-on:click="go"指定的go方法
- vm.$attrs
- 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定,除了 ( class 和 style )
- 可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用
- vm.$slots
- 用来访问被 ( 插槽分发 ) 的内容
v-slot:foo中的内容将会在vm.$slots.foo中被找到默认插槽中的内容可以通过vm.$slots.default中被找到- 请注意插槽不是响应性的
- 例子
(一) 装饰器模式定义
- 类型:装饰器模式是
结构型设计模式,即不改变原有结构 - 别名
wrapper- 表示在原有的基础上再包装上一层
特点不修改原来的 对象|类|函数 ,所以满足 - 开放封闭原则,对扩展开放,对修改封闭不使用 继承动态的添加新功能
- 常用的装饰器类型
- 常用的装饰器有 ( 类装饰器 ) ( 属性装饰器 ) ( 方法装饰器 ) ( 参数装饰器 )
- 举例1
- 圣诞节我们做了一个圣诞树,然后又加了彩灯,添加彩灯就是装饰器模式
- 举例2
- 需求:一张照片,现在的需求:需要使照片更结实,并能挂在墙上去
- 实现:我们添加一个相框,那么现在除了照片本身,多了相框之后就具有挂在墙上和保护等新的功能,我们并没有去改变照片本身,而是在外面加了相框
- 扩展:以后我们还要晚上也能看到,还可以继续进行包装,加上灯等
(二) 装饰器模式在前端中的应用 - 数据上报
1
需求:
1. 点击按钮登录
2. 现在我们点击按钮,除了能登录,还要做一些其他的事情,比如上报一些数据
2
未使用Decorator装饰器模式前的实现:
缺点:我们直接在原来的函数中添加了逻辑,违反了 ( 单一职责原则 和 开放封闭原则 )
单一职责原则:一个函数只包含一个任务逻辑
开放封闭原则:可扩展,不可修改;对扩展开放对修改封闭
function login() { // 违反了单一职责原则和开放封闭原则
console.log('登录')
console.log('数据上报')
}
3
函数版本 - 使用Decorator实现
<!DOCTYPE html>
<html lang="en">
<body>
<button id="button">按钮</button>
<script>
// 装饰器模式
// 1. 没有直接修改login的逻辑
// 2. 而是通过 after 函数 使用装饰器模式 对函数进行包装,实现了功能的扩展
const login = (params) => {
console.log("login");
};
const report = (params) => {
console.log("report");
};
const after = (...fns) => {
return (...rest) => {
// 这里返回了一个函数,所以你还可以对after继续使用Decorator装饰器模式进行函数功能的扩展
fns.forEach((fn) => fn(...rest));
};
};
const loginDecorator = after(login, report); // 类比一下redux中的compose函数,看看是否需要 执行结果迭代 和 每个函数参数的迭代
const handleClick = () => {
loginDecorator("data");
};
const button = document.getElementById("button");
button.addEventListener("click", handleClick, false);
</script>
</body>
</html>
4
class版本 - 使用Decorator实现
<!DOCTYPE html>
<html lang="en">
<body>
<button id="button">点击</button>
<script>
class Login {
login() {
console.log("login");
}
}
class Report {
report() {
console.log("report");
}
}
class After {
constructor(Login, Report) {
this.login = new Login();
this.report = new Report();
}
after() {
this.login.login();
this.report.report();
}
}
const handleClick = () => {
const after = new After(Login, Report);
after.after();
};
const button = document.getElementById("button");
button.addEventListener("click", handleClick, false);
</script>
</body>
</html>
(三) 装饰器模式在前端中的应用 - 表单校验
- 在提交表单前做校验工作
/**
* 借助装饰器模式,很容易衍生出 AOP 面向切面编程的概念
* - 场景:典型场景就是对表单的验证,我们将把表单校验逻辑 validate 函数融入到 before 逻辑当中
* - 具体:
* 1. 在提交表单时,执行 ( submit.before ) 函数,因为在 ( Function.prototype.before ) 挂载了 ( before ) 函数,被所有 ( 函数实例 ) 所继承
* 2. 执行 ( submit.before ) 从而在 submit 之前执行验证函数 ( validate ) 从而在执行submit之前做表单验证功能
*/
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<button id="button">submit</button>
<script>
function submit(params) { // ---- 提交
console.log("submit ->", params);
}
function validate(params) { // -- 校验
console.log("validate ->", params);
return true;
}
Function.prototype.before = function (validateFn) { // ---- before
const self = this; // this -> submit
return function () {
const isValidate = validateFn.apply(this, arguments);
return isValidate ? self.apply(this, arguments) : "未通过校验";
};
};
const button = document.getElementById("button");
const handle = () => {
submit.before(validate)("校验的数据和提交的数据都在这里"); // 这里也可以像案例1中那样单独写 before 函数
};
button.addEventListener("click", handle, false);
</script>
</body>
</html>
(四) 装饰器模式在前端中的应用 - HOC
- 需求
- 在vue中所有页面的按钮,都添加debounce防抖功能
<!DOCTYPE html>
<html lang="en">
<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>
<script src="./dist/vue.js"></script>
</head>
<body>
<!-- 在vue中使用HOC实现给每个button添加debounce功能 -->
<div id="root">
<hoc-button @click="clickFather">HocButton</hoc-button>
</div>
<script>
Vue.component("HocButton", {
props: {},
// template: `
// <button @click="clickChild">
// HocButton
// </button>
// `,
// 使用上面的 template 也可以,这里使用 render 函数来渲染,因为 template和render最终都会转成render函数
render(createElement) {
return createElement(
"Button",
{
on: {
click: this.clickChild,
},
props: this.$props,
attrs: this.$attrs, // 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外),配置上面props,能获取到所有组件上绑定的属性
scopedSlots: this.$scopedSlots, // 透传 scopedSlots
},
this.$slots.default // 默认default
);
},
methods: {
debounce(fn, delay) {
clearTimeout(fn.timer);
fn.timer = setTimeout(fn, delay);
},
clickChild() {
console.log("clickChild");
this.debounce(this.$listeners.click, 1000);
// 获取父作用域中的 click 事件的监听函数 clickFather
// vm.$listeners
// - 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
},
},
});
new Vue({
el: "#root",
data() {
return {};
},
methods: {
clickFather() {
console.log("clickFather");
},
},
});
</script>
</body>
</html>
资料
- 精读设计模式 juejin.cn/post/690078…
- react高阶组件 juejin.cn/post/684490…
- url www.zhangxinxu.com/wordpress/2…
- 奇技淫巧Vue HOC juejin.cn/post/684490…