>[success] # 方法封装 1. **函数的封装性**是指把函数相关的数据和行为结合在一起,对调用者隐藏内部处理过程 1.1. 提取函数中可变的,将其变为参数 1.2. 考虑**副作用**,将副作用代码从封装中剥离出来,**注副作用说明**把改变外部状态的部分叫做代码叫做**副作用**,**副作用危害**所有的外部交互都有可能带来副作用,**副作用**也使得方法通用性下降**不适合扩展**和**可重用性**,同时副作用会给程序中带来**安全隐患给程序带来不确定性**,但是副作用不可能完全禁止,**尽可能控制**它们在可控范围内发生。 1.3. 代码可读性质,**代码是给人读的,只是偶尔让计算机执行一下** >[info] ## 案例说明 模拟交通灯信号,分别以5秒、1.5秒、3.5秒来循环切换绿灯(pass状态)、黄灯(wait状态)和红灯(stop状态)。也就是,默认是绿灯,过5秒后显示黄灯,过1.5秒后显示红灯,再过3.5秒后又回到绿灯,然后以这样的方式继续循环下去 >[danger] ##### 没经过考虑只为实现的写法 * **下面代码存在问题** 1. 代码写死了,如果后续红绿灯交替时间需要变更不在是**5秒、1.5秒、3.5秒** 2. 函数内部调用了外部` const traffic = document.querySelector('.traffic')`,让函数产生了副作用(可以理解为如果通过其他方式改变了`traffic`变量函数执行产生的对应结果也会发生变化,导致函数不在是纯函数) 3. `traffic.className = signal`他的改变完全影响是外部元素,而不再是函数内部,因此产生副作用 4. 最重要代码可读性不高 ~~~ <!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> <style> .traffic { display: flex; flex-direction: column; align-items: center; } .traffic .light { width: 100px; height: 100px; border-radius: 50%; background-color: #999; } /* 设置不同灯颜色 */ .traffic.pass .light:nth-child(1) { /* 绿灯 */ background-color: green; } .traffic.wait .light:nth-child(2) { /* 黄灯 */ background-color: yellow; } .traffic.stop .light:nth-child(3) { /* 红灯 */ background-color: red; } </style> </head> <body> <div class="traffic pass"> <div class="light"></div> <div class="light"></div> <div class="light"></div> </div> </body> <script> // 红绿灯展示 -- 写死 // 问题是函数中多了一个来源不明的变量`traffic` // 产生问题这个函数复用到其他地方,我们还得在那个地方重建这个traffic对象(副作用) // `traffic.className = signal`他的改变完全影响是外部元素,而不再是函数内部,(副作用) // 就是函数体内部不应该有完全来自外部环境的变量,除非这个函数不打算复用 // 可变的数据作为变量,将这些变量规整为数据的结构化 // const traffic = document.querySelector('.traffic') // function loop() { // setTimeout(() => { // 来源不明不是函数内部定义也不是通过参数传入 // traffic.className = 'traffic pass' // setTimeout(() => { // traffic.className = 'traffic wait' // setTimeout(() => { // traffic.className = 'traffic stop' // loop() // }, 1500) // }, 3000) // }, 5000) // } // loop() const traffic = document.querySelector('.traffic') function loop() { setTimeout(() => { // 来源不明不是函数内部定义也不是通过参数传入 traffic.className = 'traffic pass' setTimeout(() => { traffic.className = 'traffic wait' setTimeout(() => { traffic.className = 'traffic stop' loop() }, 1500) }, 3000) }, 5000) } loop() </script> </html> ~~~ >[danger] ##### 改版代码 1. 先分析出代码中可变的部分将其按照合理数据规则去规划传参,其中**定时器时间**,定义显示不同灯**颜色的css**,下一步就是靠采用什么结构作为参数,是六个参数(定时器时间和颜色×3),还是三组(定时器时间和颜色为一组对象 × 3),还是采用更加合理数组形式(只要一个参数 显然这个更加合理) 2. 下一步解除两个副作用,一个是`const traffic = document.querySelector('.traffic')`,可以将其变成参数传入到函数中,`traffic.className = signal` 他的改变完全影响是外部元素,而不再是函数内部,因此考虑把函数体内部有副作用的代码剥离出来,这往往能提升函数的通用性、稳定性和可测试性 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> <style> .traffic { display: flex; flex-direction: column; align-items: center; } .traffic .light { width: 100px; height: 100px; border-radius: 50%; background-color: #999; } /* 设置不同灯颜色 */ .traffic.pass .light:nth-child(1) { /* 绿灯 */ background-color: green; } .traffic.wait .light:nth-child(2) { /* 黄灯 */ background-color: yellow; } .traffic.stop .light:nth-child(3) { /* 红灯 */ background-color: red; } </style> </head> <body> <div class="traffic pass"> <div class="light"></div> <div class="light"></div> <div class="light"></div> </div> </body> <script> // 代码是给人读的,只是偶尔让计算机执行一下 // 将setTimeout 封装到一个wait函数 function wait(ms) { return new Promise((resolve) => { setTimeout(resolve, ms) }) } const traffic = document.querySelector('.traffic') async function signalLoop(subject, signals = [], onSignal) { const signalCount = signals.length // 也可使用 while(true) 循环 for (let i = 0; ; i++) { const { signal, duration } = signals[i % signalCount] await onSignal(subject, signal) await wait(duration) } } const signals = [ { signal: 'pass', duration: 5000 }, { signal: 'wait', duration: 3500 }, { signal: 'stop', duration: 1500 }, ] signalLoop(traffic, signals, (subject, signal) => { subject.className = `traffic ${signal}` }) </script> </html> ~~~ >[info] ## 参考 [第三天:代码的封装性、可读性和正确性](https://juejin.cn/book/6891929939616989188/section/6891930943884689421)