这是我参与「第四届青训营 」笔记创作活动的的第2天
一、重点内容:
- 写好JS的三大原则:各司其职,组件封装,过程抽象
- 写代码的依据
- 常见问题的几种算法
- 写JS的逻辑与思维
二、详细知识点介绍:
学习书籍
犀牛书和红宝书
写好JS的一些原则
各司其职:HTML、CSS、JavaScript职能分离
组件封装:好的UI组件具有正确性、扩展性、复用性
过程抽象:应用函数式编程思想
例子
如何切换浅色和深色模式
直接操作CSS
直接操作HTML
纯CSS:checkBox,for,伪类,相邻选择器
轮播图组件
结构设计:编写HTML
展现效果:CSS
行为设计:新建类,添加API和Event控制流,使用自定义事件来解耦。
重构:插件化
将控制元素抽取成插件,插件与组件之间通过依赖注入方式建立联系
function pluginController(slider) {
const controller = slider.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) {
slider.slideTo(idx);
slider.stop();
}
});
controller.addEventListener('mouseout', evt => {
slider.start();
});
slider.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';
});
}
}
function pluginPrevious(slider) {
const previous = slider.container.querySelector('.slide-list__previous');
if (previous) {
previous.addEventListener('click', evt => {
slider.stop();
slider.slidePrevious();
slider.start();
evt.preventDefault();
});
}
}
function pluginNext(slider) {
const next = slider.container.querySelector('.slide-list__next');
if (next) {
next.addEventListener('click', evt => {
slider.stop();
slider.slideNext();
slider.start();
evt.preventDefault();
});
}
}
重构:模板化
将HTML模板化,更易于扩展
类似React的render
class Slider {
constructor(id, opts = { images: [], cycle: 3000 }) {
this.container = document.getElementById(id);
this.options = opts;
this.container.innerHTML = this.render();
this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
this.cycle = opts.cycle || 3000;
this.slideTo(0);
}
render() {
const images = this.options.images;
const content = images.map(image => `
<li class="slider-list__item">
<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');
pluginContainer.className = `.${name}__plugin`;
pluginContainer.innerHTML = plugin.render(this.options.data);
this.container.appendChild(pluginContainer);
plugin.action(this);
});
}
render(data) {/* abstract */
return ''
}
}
过程抽象
- 用来处理局部细节控制的一些方法
- 函数使编程思想的基础应用
高阶函数
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
function HOF0(fn) {
return function(...args) {
return fn.apply(this, args);
}
}
常用高阶函数:
- 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);
}
}
- 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)
}
}
}
function add(ref, x){
const v = ref.value + x;
console.log(`${ref.value} + ${x} = ${v}`);
ref.value = v;
return ref;
}
let consumerAdd = consumer(add, 1000);
const ref = {value: 0};
for(let i = 0; i < 10; i++){
consumerAdd(ref, i);
}
- Iterative 迭代
const isIterable = obj => obj != null
&& typeof obj[Symbol.iterator] === 'function';
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]);
}
}
const setColor = iterative((el, color) => {
el.style.color = color;
});
const els = document.querySelectorAll('li:nth-child(2n+1)');
setColor(els, 'red');
为什么使用高阶函数
输入一定时,输出一定
编程范式
命令式:怎么做
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);
写代码需要关注什么?
使用场景
快速幂
while (n) {
if (n % 2 == 1) {
result += string;
}
if (n > 1) {
string += string;
}
n >>= 1;
}
切换交通灯
数据抽象,状态数组
过程抽象
异步+函数式
判断4的幂
常规操作:
- 从1循环幂,判断是否相等;
- 位运算,不断右移2位后,判断是否等于1;
还有两个NB操作:
- 位运算的巧妙应用:num & (num - 1)会使二进制数的1少一个;4的幂的1会在奇数位
- 把数字转为二进制字符串,使用正则匹配
function isPowerOfFour(num){
num = parseInt(num);
return num > 0 &&
(num & (num - 1)) === 0 &&
(num & 0xAAAAAAAAAAAAA) === 0;
}
function isPowerOfFour(num) {
num = parseInt(num).toString(2);
return /^1(?:00)*$/.test(num);
}
tips:js的整数位最多53位
洗牌
正确写法:先抽出一张放在上面
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(cards) {
const c = [...cards];
for(let i = c.length; i > 0; i--) {
const pIdx = Math.floor(Math.random() * i);
[c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
}
return c;
}
洗牌生成器
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function * draw(cards){
const c = [...cards];
for(let i = c.length; i > 0; i--) {
const pIdx = Math.floor(Math.random() * i);
[c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
yield c[i - 1];
}
}
const result = draw(cards);
console.log([...result]);
分红包
- 切西瓜法:选一块最大来切
- 栅栏法:将红包看为数轴,随机将其插入一道栅栏,进行切分
三、课后个人总结:
今天的课程干货满满,十分实用,月影老师不仅教授了JS的三大原则,还为我们一一举例,使我们的理解更加深刻,在以后实践的过程中还能参照着前进。下午月影老师在解决常见问题的过程中,将编写JS的逻辑与思维一并传授,受益匪浅。