跟着月影学 JavaScript | 青训营笔记
这是我参与「第五届青训营 」笔记创作活动的第 3 天
详细知识点介绍:
写好 JS 的一些原则
- 各司其责:让 HTML、CSS 和 JavaScript 职能分离。
- 组件封装:好的 UI 组件具备正确性、扩展性、复用性。
- 过程抽象:应用函数式编程思想。
例子 深夜食堂
写一段JS,控制一个页面,让它支持浅色和深色两种浏览模式。如果是你来实现,你会怎么做?
方法一:JS直接操作样式
const btn = document.querySelector('#modeBtn')
btn.addEventListener('click', (e) => {
const body = document.body
if (e.target.innerHtml === '☀') {
body.style.backgroundColor = 'black'
body.style.color = 'white'
e.target.innerHtml === '🌙'
} else {
body.style.backgroundColor = 'white'
body.style.color = 'black'
e.target.innerHtml === '☀'
}
})
JS直接操作样式,操作的是页面元素中的 style 属性,操作这个属性就是写的内联样式,这样书写不仅不能使样式和结构相分离,还不容易后期的修改。
方法二:JS引用class样式
const btn = document.querySelector('#modeBtn')
btn.addEventListener('click', (e) => {
const body = document.body
if (body.className === 'night') {
body.className = 'night'
} else {
body,className = ''
}
})
做到了样式与行为分离,直接引用之前已经写好的样式。我一般也会这样写。
方法三:纯CSS实现
<body>
<input id="modeCheckBox" type="Checkbox">
<div class="content">
<header>
<label id="modeBtn" for="modeCheckBox"></label>
<h1>深夜食堂</h1>
</header>
<main>
...
</main>
</div>
</body>
#modeCheckBox:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
纯 css 实现的浅色系和深色系切换,利用 checkbox 的选中进行切换,利用 label 标签来绑定切换。很巧妙的运用,这个方法不需要使用 JS 来编写。完全的实现了各司其职。
深夜食堂--结论
- HTML/CSS/JS 各司其责
- 应当避免不必要的由 JS 直接操作样式
- 可以用 class 来表示状态
- 纯展示类交互寻求零 JS 方案
例子 组件封装 轮播图
用原生 JS 写一个电商网站的轮播图,应该怎么实现?
轮播图一:
表现 CSS
- 使用 CSS 绝对定位将图片重叠在同一个位置
- 轮播图切换的状态使用修饰符(modifier)
- 轮播图的切换动画使用 CSS transition
行为 JS
- Slider
- +getSelectedItem()
- +getSelectedItemIndex()
- +slideTo()
- +slideNext()
- +slidePrevious()
这个代码可以实现一个具有简单功能的轮播图
通过封装轮播图,书写轮播图原型上的API
直接调用实例里面的方法,可以做到切换图片
在外面加了一个定时器就可以实现自动轮播
很多功能还不完善
轮播图二:控制流
添加了控件小圆点和前后按钮来切换图片
轮播图的所有功能基本实现
但是constructor里面的内容非常的沉重
而且独立性不好,不可以选择性添加控件
轮播图三:插件化
constructor里面只放必要的属性
其他的通过插件的形式注册进来
通过依赖注入的方式将this传参到插件函数中,使其能访问class显式原型上的方法
可以很好的控制哪些控件需要哪些控件不需要
但是不需要的控件还是会显示在页面上
轮播图四:模板化
HTML页面非常的干净,跟平时用vue框架写一样非常的干净,只有一个展位的容器
轮播图五:抽象
抽象之后,有一个组件的框架,后续可以在里面添加各种plugin来完善组件
高阶函数
Once
为了能够让“只执行一次”的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。
这个过程我们称为过程抽象
function once(fn) {
return function(...args) {
if (fn) {
const ret = fn.apply(this, args)
fn = null
return ret
}
}
}
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
function HOF0(fn) {
return function(...args) {
return fn.apply(this, args)
}
}
常用的高阶函数 HOF
- Once 调用一次
- Throttle 节流函数
function throttle(fn, time = 500) {
let timer
return function(...args) {
if (timer === null) {
fn.apply(this, args)
timer = setTimeout(() => {
timer = null
}, time)
}
}
}
- Debounce 防抖函数
function debounce(fn, dur = 500) {
var timer
return function(...args) {
clearTimeout(timer)
timer = setTimerout(() => {
fn.apply(this, args)
}, dur)
}
}
- Consumer 延迟函数
function consumer(fn, time) {
let tasks = [], timer
return function(...args) {
tasks.push(fn.bind(this, ...args))
if (timer === null) {
timer = setInterval(() => {
tasks.shift().call(this)
if (tasks.length <= 0) {
clearInterval(timer)
timer = null
}
}, time)
}
}
}
- Iterative 可迭代方法
function iterative(fn) {
return function(subject, ...rest) {
if (isIterable(subject)) {
const ret = []
for(let obj of subject) {
ret.push(fn.apply(this, [obj, ...rest]))
}
return ret
}
return fn.apply(this, [subject, ...rest])
}
}
纯函数
- 确定的输入产生确定的输出(不使用作用域链上的数据)
- 不会产生副作用(不修改入参参数的状态,比如对象中的值)
编程范式
- 命令式(怎么做)
let list = [1, 2, 3, 4]
let mapl = []
for (let i = 0; i < list.length; i++) {
mapl.push(list[i] * 2)
}
- 函数式(做什么)
let list = [1, 2, 3, 4]
const double = x => x * 2
list.map(double)
声明式编程比命令式编程更具有扩展性