这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
以上为今日课上笔记内容
一.如何写好 JS
写好JS 的一些原则
1.各司其责: 让HTML、CSS和JavaScript职能分离。
2.组件封装: 好的UI组件具备正确性、扩展性、复用性。
3.过程抽象: 应用函数式编程思想。 应用函数式编程思想。
1.各司其责
HTML负责结构 CSS负责表象 JavaScript负责行为
例子
写一段JS,控制一个网页,让它支持浅色和深色两种浏览模式。如果是你来实现,你会怎么做?
版本一:操作body.style
const btn = document .getElementById( '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 ='太阳';
}
});
版本二:操作body.className //比一更能展现各司其职
const btn = document .getElementById( 'modeBtn');
btn.addEventListener('click', (e) => {
const body= document .body;
if(body.className !== 'night') {
body .className = 'night';
} else {
body.className =' ';
}
});
版本三:
<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 is;
}
结论:
- HTML/CSS/JS 各司其责
- 应当避免不必要的由 JS 直接操作样式
- 可以用 class 来表示状态
- 纯展示类交互寻求零 JS 方案
2.组件封装
组件是指Web页面上抽出来一个个包含模版 (HTML)、功能 (JS) 和样式 (CSS)的单元。好的组件具备封装性、正确性、扩展性、复用性。
例子: 用原生 JS 写一个电商网站的轮播图,应该怎么实现?
- 结构设计:HTML
结构:HTML
轮播图是一个典型的列表结构我们可以使用无序列表ul元素来实现。
<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/t0ladbe3351db853eb3.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>
</div>
- 展现效果: CSS
表现: CSS
①使用 CSS 绝对定位将图片重叠在同一个位置
②轮播图切换的状态使用修饰符 (modifier)
③轮播图的切换动画使用 CSS transition
#my-slider{
position:relative;
width:790px;
}
.slider-list ul{
list-style-type:none;
position:relative;
padding: 0:
margin:0
}
.slider-list__item,
.slider-list_item--selected{
position:absolute;
transition:opacity ls;
opacity: 0;
text-align: center;
}
.slider-list item--selected{
transition:opacity 1s;
opacity: 1;
}
- 行为设计: API
行为: JS
满足灵活性API 设计应保证原子操作,职责单一
Slider
+getSelectedltem()
+getSelectedltemIndex()
+slideTo()
+slideNext()
+slidePrevious()
class Slider{
constructor(id){
this.container =document.getElementById(id);
this.items=this.container
.querySelectorAll(".slider-list item,.slider-list item--selected');
}
getSelectedItem(){
const selected =this.container
.querySelector('.slider-list__item--selected');
return selected
}
getSelectedItemIndex(){
return Array.from(this.items).index0f(this.getSelectedItem());
}
slideTo(idx){
const selected = this.getSelectedItem();
if(selected){
selected.className ='slider-list__item';
}
const item =this.items[idx];
if(item){
item.className ='slider-list_item--selected';
}
}
slideNext(){
const currentIdx = this.getSelectedItemIndex();
const nextIdx = (currentIdx + 1)% this.items.length;
this.slideTo(nextIdx);
}
slidePrevious(){
const currentIdx = this.getSelectedItemIndex():
const previousIdx =(this.items.length + currentIdx - 1)
%this.items.length;
this.slideTo(previousIdx);
}
行为设计:控制流(HTML)
行为:控制流
使用自定义事件来解耦
<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>
const detail = {index: idx}
const event = new CustomEvent( 'slide', {bubbles:true, detail})
this .container.dispatchEvent(event)
总结:
基本方法
①结构设计
②展现效果行为设计
③API (功能)
④Event(控制流)
改进
①重构:插件化
解耦
将控制元素抽取成插件
插件与组件之间通过依赖注入方式建立联系
function pluginController(subject){
//...
}
function pluginNext(subject){
//...
}
function pluginPrevious(subject){
//...
}
②解耦
将HTML模板化,更易于扩展
class Slider{
constructor(id, opts ={images:[],cycle:3000}){
...
}
render(){
const images = this.options.images;
const content =images.map(image =>
<li class="slider-listitem">
< img src="${image}"/>
</li>
`.trim());
return `<ul>${content.join( '')}</ul>`;
}
...
}
③重构:组件框架
抽象
将通用的组件模型抽象出来
class Component{
constructor(id, opts = {name,data:[]}){
this.container =document.getElementById(id);
this.options =opts;
this.container.innerHTML=this.render(opts.data);
}
registerPlugins(...plugins){
plugins.forEach(plugin => {
const pluginContainer =document.createElement('div');
pluginC ontainer.className=`.${name}__plugin`;
pluginContainer.innerHTML =plugin.render(this.options.data);
this.container.appendChild(pluginContainer);
plugin.action(this);});
});
}
render(data){
/* abstract */
return''
}
}
总结
①组件设计的原则:封装性、正确性、扩展性、复用性
②实现组件的步骤:结构设计、展现效果、行为设计
③三次重构
插件化
模板化
抽象化 (组件框架)
3.过程抽象
过程抽象用来处理局部细节控制的一些方法函数式编程思想的基础应用
例子
操作次数限制
一些异步交互一次性的HTTP请求
const list = document querySelector( 'ul' );
const buttons = list.querySelectorAll( button');
buttons.forEach((button) => {
button.addEventListener('click',(evt) => {
const_target = evt .target;
target .parentNode.className = ' completed';
setTimeout(() => {
list .removeChild( target .parentNode);
},2000);}
});
});
Once
为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来这个过程我们称为过程抽象
function once(fn){
return function(...args){
if(fn){
const ret = fn.apply(this, args);
fn = null;
return ret;
}
};
}
button.addEventListener('click', once((evt) => {
const target = evt.target;
target.parentNode.className ='completed';
setTimeout(( ) => {
list.removeChild(target.parentNode);
},2000);
}));
高阶函数
①以函数作为参数
②以函数作为返回值
③常用于作为函数装饰器
常用高阶函数Once
①Throttle 一次
②Debounce 节流,防抖
③Consumer /2 每隔一段时间调用,延时调用
④Iterative 循环调用
function HOFo( fn) {
return function( ...args ) {
return fn.apply( this, args );
}
}
思考与讨论
编程范式
命令式与声明式
//命令式
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);
例子
Toggle - 命令式
Toggle - 声明式
Toggle - 三态
总结
①过程抽象 / HOF /装饰器
②命令式 / 声明式
二.Leftpad 这个代码好不好
//判断一个mat2d矩阵是否是单位矩阵
function isUnitMatrix2d(m) {
return m[0] === 1 & m[l] === 0 && m2) === 0 && m[3] === 1 && m4] === 0 && m[5] === 0:
}
那放在这里呢?
get layerTransformInvert(){
if(this[_layerTransformInvert]) return this[_layerTransformInvert];
const m =this.transformMatrix;
if(m[0]===1&&m[1] === 0 &&m[2]=0 && m[3] === 1 && m[4] === 0 && m[5] === 0) {
return null;
}
this[_layerTransformInvert] = mat2d.invert(m);
return this[_layerTransformInvert];
}
- 写代码最应关注什么?
风格约定
效率
使用场景
设计
- 当年的 Left-pad 事件
事件本身的槽点
NPM 模块粒度
代码风格
代码质量/效率
原本代码
function leftpad (str, len, ch) {
str = String(str);
var i = -l;
if (!ch && ch !== 0) ch = " "
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
重构代码
代码更简洁 效率提升
function leftpad (str, len, ch) {
str = String(str);
var i = -l;
if (!ch && ch !== 0) ch = " "
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
性能更好
for (;;){
if ((count & 1) == 1) {
rpt += str;
}
count >>>=1;
if (count == 0) {break;
}
str += str;
}
性能的再次优化
var rpt = “ ”;
do {
rpt += str;
str += str;
count &= count - 1;
} while(count);
例子
交通灯:状态切换
实现一个切换多个交通灯状态切换的功能
版本一:
版本二:
版本三:(过度抽象)
版本四:(异步+函数式)
例子:判断是否为4的幂
版本一
function isPower0fFour(num){
num = parseInt(num);
while(num > 1) {
if(num % 4) return false;
num /= 4;
}
return true;
}
版本二
function isPower0fFour(num){
num = parseInt(num);
while(num > 1) {
if(num & 0bl1) return false;
num >>>= 2;
}
return true;
}
版本三
a&&(a-1) 会使二进制中的1少一个
4的幂和2的幂里二进制只有一个1
function isPower0fFour(num){ num = parseInt(num);
{
return num >0 &&
(num &(num-1))=== 0 &&(num & 0XAAAAAAAA)=== 0;
}
版本四
function isPower0fFour(num){
num = parseInt(num).toString(2);
return /^1(?:00)*$/.test(num);
}
例子:
洗牌
版本一:(错误写法)
版本二:(数学归纳法)
版本三:(生成器)
洗牌思路
版本四:
例子:分红包
版本一:(切西瓜法)
版本二:(抽牌法)