这是我参与第五届青训营伴学笔记创作活动的第1天。
如何写好JS
本课重点内容
- 写好JS的一些原则:各司其职、组件封装、过程抽象。
- 代码质量优化:可读性与性能提升。
详细知识点介绍
JS编程原则之各司其职
在网页的渲染和交互逻辑中,让html、css、js各司其职:html只负责页面结构,css只负责页面样式,js只负责页面交互逻辑。
深夜食堂小案例:写一段JS,控制一个网页,让它支持浅色和深色两种浏览模式
未遵循各司其职原则:通过dom事件触发,在js代码块中直接修改html元素的样式,代码冗杂度高。
优化1:通过dom事件触发,但不直接修改元素样式,而是通过更换元素类名的方式,将样式都放入css中,降低了js,html与css的耦合度。
优化2(最终版):用隐藏的checkbox作为开关,通过css的伪类实现元素状态改变时页面样式的变更,实现了零js的纯展示类交互。
<!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>深夜食堂</title>
<style>
body, html {
width: 100%;
margin: 0;
padding: 0;
font-weight: 700;
}
body {
box-sizing: border-box;
}
.content {
height: 100%;
padding: 10px;
}
div.pic img {
width: 100%;
}
.btn {
float: right;
font-size: 2rem;
border: none;
background: transparent;
}
#modeCheckBox {
display: none;
}
#modeCheckBox:checked + .content {
background-color: black;
color: white;
transition: all 1s;
}
.btn {
font-size: 2rem;
float: right;
}
.btn::after {
content: '🌞';
}
#modeCheckBox:checked + .content .btn::after {
content: '🌜';
}
</style>
</head>
<body>
<input type="checkbox" id="modeCheckBox">
<div class="content">
<header>
<label for="modeCheckBox" class="btn"></label>
<h1 class="title">深夜食堂</h1>
</header>
<main>
<div class="pic">
<img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
</div>
<div class="description">
<p>
这是一间营业时间从午夜十二点到早上七点的特殊食堂。
这里的老板,不太爱说话,却总叫人吃得热泪盈眶。
在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;
轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;
乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;
一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。
食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。
每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返。
</p>
</div>
</main>
</div>
</body>
</html>
小结
- html/css/js各司其职
- 应当避免不必要的由JS直接操作样式
- 可以用class来表示状态
- 纯展示类交互寻求零JS方案
JS编程原则之组件封装
组件是指Web页面上抽出来的一个个包含模板(html)、功能(js)、样式(css)的单元。好的组件具备封装性、正确性、扩展性、复用性。
案例 用原生JS写一个电商网站的轮播图
结构:
<div id="my-slider" class="slider-list">
<ul>
<li class="slider-list__item--selected">
<img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png"/>
</li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg"/>
</li>
<li class="slider-list__item">
<img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg"/>
</li>
<li class="slider-list__item">
<img src="https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg"/>
</li>
</ul>
<a class="slide-list__next"></a>
<a class="slide-list__previous"></a>
<div class="slide-list__control">
<span class="slide-list__control-buttons--selected"></span>
<span class="slide-list__control-buttons"></span>
<span class="slide-list__control-buttons"></span>
<span class="slide-list__control-buttons"></span>
</div>
</div>
样式:
#my-slider{
position: relative;
display: inline-block;
width: 790px;
height: 340px;
}
.slider-list ul{
list-style-type:none;
position: relative;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.slider-list__item,
.slider-list__item--selected{
position: absolute;
transition: opacity 1s;
opacity: 0;
text-align: center;
}
.slider-list__item--selected{
transition: opacity 1s;
opacity: 1;
}
.slide-list__control{
position: relative;
display: table;
background-color: rgba(255, 255, 255, 0.5);
padding: 5px;
border-radius: 12px;
bottom: 30px;
margin: auto;
}
.slide-list__next,
.slide-list__previous{
display: inline-block;
position: absolute;
top: 50%;
margin-top: -25px;
width: 30px;
height:50px;
text-align: center;
font-size: 24px;
line-height: 50px;
overflow: hidden;
border: none;
background: transparent;
color: white;
background: rgba(0,0,0,0.2);
cursor: pointer;
opacity: 0;
transition: opacity .5s;
}
.slide-list__previous {
left: 0;
}
.slide-list__next {
right: 0;
}
#my-slider:hover .slide-list__previous {
opacity: 1;
}
#my-slider:hover .slide-list__next {
opacity: 1;
}
.slide-list__previous:after {
content: '<';
}
.slide-list__next:after {
content: '>';
}
.slide-list__control-buttons,
.slide-list__control-buttons--selected{
display: inline-block;
width: 15px;
height: 15px;
border-radius: 50%;
margin: 0 5px;
background-color: white;
cursor: pointer;
}
.slide-list__control-buttons--selected {
background-color: red;
}
行为:API设计应保证原子操作,职责单一,满足灵活性
class Slider{
constructor(id, cycle = 3000){
this.container = document.getElementById(id);
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
this.cycle = cycle;
const controller = this.container.querySelector('.slide-list__control');
if(controller){
const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
controller.addEventListener('mouseover', evt=>{
const idx = Array.from(buttons).indexOf(evt.target);
if(idx >= 0){
this.slideTo(idx);
this.stop();
}
});
controller.addEventListener('mouseout', evt=>{
this.start();
});
this.container.addEventListener('slide', evt => {
const idx = evt.detail.index
const selected = controller.querySelector('.slide-list__control-buttons--selected');
if(selected) selected.className = 'slide-list__control-buttons';
buttons[idx].className = 'slide-list__control-buttons--selected';
})
}
const previous = this.container.querySelector('.slide-list__previous');
if(previous){
previous.addEventListener('click', evt => {
this.stop();
this.slidePrevious();
this.start();
evt.preventDefault();
});
}
const next = this.container.querySelector('.slide-list__next');
if(next){
next.addEventListener('click', evt => {
this.stop();
this.slideNext();
this.start();
evt.preventDefault();
});
}
}
getSelectedItem(){
let selected = this.container.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
return Array.from(this.items).indexOf(this.getSelectedItem());
}
slideTo(idx){
let selected = this.getSelectedItem();
if(selected){
selected.className = 'slider-list__item';
}
let item = this.items[idx];
if(item){
item.className = 'slider-list__item--selected';
}
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
}
slideNext(){
let currentIdx = this.getSelectedItemIndex();
let nextIdx = (currentIdx + 1) % this.items.length;
this.slideTo(nextIdx);
}
slidePrevious(){
let currentIdx = this.getSelectedItemIndex();
let previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
this.slideTo(previousIdx);
}
start(){
this.stop();
this._timer = setInterval(()=>this.slideNext(), this.cycle);
}
stop(){
clearInterval(this._timer);
}
}
组件封装基本方法结构设计、展示效果、行为设计。
重构:
1.插件化:解耦,将控制元素抽取成插件,插件与组件之间通过依赖注入方式建立联系。
2.模板化:将html模板化,更易于扩展。
3.组件框架:抽象,将通用的组件模型抽取出来。
小结
- 组件设计的原则:封装性、正确性、扩展性、复用性。
- 实现组件的步骤:结构设计、展现效果、行为设计。
- 三次重构:插件化、模板化、抽象化(组件框架)。
JS编程原则之过程抽象
过程抽象即用来处理局部细节的一些方法,是函数式编程的基础应用。
高阶函数即是用过程作为参数,实现过程抽象。定义:以函数作为参数,以函数作为返回值,常用于作为函数装饰器。
常用高阶函数
Once保证传入的函数只执行一次。
function once(fn) {
return function(...args) {
if(fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
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){
dur = dur || 100;
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, dur);
}
}
编程范式
命令式告诉计算机如何做
switcher.onclick = function(evt){
if(evt.target.className === 'on'){
evt.target.className = 'off';
}else{
evt.target.className = 'on';
}
}
声明式告诉计算机你想要什么
function toggle(...actions){
return function(...args){
let action = actions.shift();
actions.push(action);
return action.apply(this, args);
}
}
switcher.onclick = toggle(
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
);
代码质量优化
写代码应该关注:风格,效率,约定,设计,使用场景。
当年Left-pad事件:代码效率低,复杂
优化:代码更简洁,效率提升
总结
本次课程主要从代码角度对JS的编程原则和编程规范作了举例和讲解,同时涉及到一些重要的编程思想。初次创作,文章若有错误和不足,欢迎大家批评指正哈!