写好 JavaScript 的三个准则:各司其责 组件封装 过程形象
各司其责
-
HTML/CSS/JavaScript 各司其责
- HTML -> Structural ; CSS -> Presentational ; JavaScript -> Behavioral
- 该当防止不必要的由 JS 间接操作款式
- 能够用 class 来示意状态
- 纯展现类交互应寻求零 JS 计划
组件封装
组件是指 Web 页面上抽出来的一个个蕴含模块(HTML)、款式(CSS)和性能(JS)的单元。好的组件具备封装性、正确性、扩展性、复用性。实现组件的步骤:结构设计、展示成果、行为设计,三次重构:插件化重构、模板化重构、抽象化重构。
- 结构设计:HTML
- 展示成果:CSS
-
行为设计:JS
- API(性能),API 设计应保障原子操作,职责繁多,满足灵活性。
- Event(控制流),应用自定义事件来解耦。
-
插件化重构,即解耦
- 将管制元素抽取成插件
- 插件与组件之间通过依赖注入形式建立联系
-
模板化重构
- 将 HTML 模板化,更易于扩大
-
抽象化重构(组件框架)
- 将通用的组件模型形象进去
过程形象
过程形象是指用来解决部分细节管制的一些办法,是函数式编程思维的根底利用。
-
高阶函数
- 以函数作为参数
- 以函数作为返回值
- 罕用于函数装璜器
// 零阶高阶函数,等价于间接调用函数
function HOF_0 (fn){return function(...args){return fn.apply(this.args);
}
}
- 结构 once 高阶函数,为了可能让“只执行一次”的需要(例如一些异步操作、一次性的 HTTP 申请)笼罩不同的事件处理,咱们能够将这个需要利用闭包剥离进去。这个过程称为过程形象。
function once (fn) {return function (...args){const res = fn.allpy (this.args);
fn = null;
return res;
}
}
-
防抖函数,在第一次触发事件时,不立刻执行函数,而是给出一个期限值,如果在期限值内没有再次触发滚动事件,那么就执行函数,如果在期限值内再次触发滚动事件,那么以后的计时勾销,从新开始计时
const debounce = (fn, delay) => { let timer = null; // 借助闭包 return function () {clearTimeout(timer); timer = setTimeout(fn, delay); } }
-
节流函数,相似管制阀门一样定期凋谢的函数,也就是让函数执行一次后,在某个时间段内临时生效,过了这段时间后再从新激活。成果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的工夫期限内不再工作,直至过了这段时间才从新失效。
const myThrottle = (fn, delay) => { let flag = true; return function () {if (!flag) {return false;} else { flag = false; setTimeout(() => {fn(); flag = true; }, delay); } } }
-
为什么要应用高阶函数?
- 缩小零碎内非纯函数的数量,进步零碎的可测试性和稳定性。
写好 JavaScript
写代码应该关注
- 效率
- 格调
- 应用场景
- 约定
- 设计
具体的代码实现要因场景而定,不同的场景重视点不一样,例如在某些比拟底层的场景,可能更重视效率,而在多人合作的时候可能更关注约定。
判断是否是 4 的幂
-
惯例操作
const isPowerOfFour = (num) => {num = parseInt(num); while(num > 1){if(num % 4) return false; num /= 4; } return true;}
-
优化版本 1,利用 4 的幂的二进制数最高位为 1,低位为偶数个 0
const isPowerOfFour = (num) => {num = parseInt(num); return num > 0 && (num & (num-1)) === 0 && (num & 0XAAAAAAAA) === 0; }
- 优化版本 2,利用正则表达式
const isPowerOfFour = (num) => {num = parseInt(num).toString(2);
return /^1(?:00)*$/.test(num);
}
实现交通灯切换成果
-
版本一,利用 setTimeout,可能呈现回调天堂
const traffic = document.getElementById('traffic'); (function reset(){ traffic.className = 's1'; setTimeout(function(){ traffic.className = 's2'; setTimeout(function(){ traffic.className = 's3'; setTimeout(function(){ traffic.className = 's4'; setTimeout(function (){ traffic.className = 's5'; setTimeout(reset,1000) },1000) },1000) },1000) },1000) })();
-
优化版本,利用 async/await
const traffic = document.getElementById('traffic'); function wait(time){return new Promise(resolve => setTimeout(resolve,time)) } function setState(state){traffic.className = state;} async function start(){while(1){setState('wait'); await wait(1000); setState('stop'); await wait(3000); setState('pass'); await wait(3000); } } start();
洗牌算法
-
谬误示例
看似能够正确洗牌,但实际上较小的牌放到后面的概率更大,较大的的牌放到前面的概率更大const cards = [0,1,2,3,4,5,6,7,8,9]; const shuffle = (cards) => {return [...cards].sort(() => { Math.random() > 0.5 ? -1 : 1 }); }
- 正确示例
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[i - 1]] = ,c[pIdx]];
yield c[i - 1];
}
return c;
}
分红包问题
-
版本一,相似于切蛋糕,取最大的持续切分,得出的后果会比拟均匀
function generate(amount,count){let ret = [amount]; while(count > 1){ // 挑出最大的进行切分 let cake = Math.max(...ret), idx = ret.indexOf(cake), part = 1 + Math.floor((cake / 2) * Math.random()), rest = cake - part; ret.splice(idx,1,part,rest); count --; } return ret; }
- 版本二,随机性较大,可能会有较大的红包呈现
function *draw(cards){const c = [...cards];
for(let i = c.length ; i > 0 ; i --){const pIdx = Math.floor(Math.random()*i);
,c[i-1]] = ,c[pIdx]];
yield c[i-1]
}
return c;
}
function generate(amount,count){if(count <= 1) return [amount];
const cards = Array(amount - 1).fill(null).map((_,i) => i+1);
const pick = draw(cards);
const result = [];
for(let i = 0 ; i < count; i++){result.push(pick.next().value);
}
result.sort((a,b)=>a-b);
for(let i = count - 1 ; i > 0 ; i--){result[i] = result[i] - result[i - 1];
}
return result;
}