这是我参与「第四届青训营 」笔记创作活动的第3天
今天听月影老师讲解了Javascript,下面来分享一下我觉得比较有意思的内容。
写好前端的一些原则
各司其职
让HTML、CSS和JS职能分离
深夜食堂的案例教会我们:
-
应该避免不必要的由JS直接操作样式
-
可以用class来表示状态
-
纯展示类交互寻求零JS方案
组件封装
好的UI组件具备正确性、扩展性和复用性
-
组件设计的原则:封装性、正确性、扩展性、复用性
-
实现组件的步骤:结构设计、展现效果、行为设计
-
三次重构:插件化 模块化 抽象化(组件框架)
过程抽象
应用函数式编程思想
这里主要想分享一下函数的节流和防抖,这也是在面试过程中经常会被问到的
对节流与防抖的理解
- 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
- 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
防抖函数的应用场景:
- 按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次
- 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤lodash.debounce
节流函数的适⽤场景:
- 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动
- 缩放场景:监控浏览器resize
- 动画场景:避免短时间内多次触发动画引起性能问题
应用场景:1.提交表单 2.高频监听事件
函数防抖的实现:
function debounce(fn, wait) {
var timer = null;
return function() {
var context = this,
args = [...arguments];
// 如果此时存在定时器的话,则取消之前的定时器重新记时
if (timer) {
clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
函数节流的实现:
// 时间戳版
function throttle(fn, delay) {
var preTime = Date.now();
return function() {
var context = this,
args = [...arguments],
nowTime = Date.now();
// 如果两次时间间隔超过了指定时间,则执行函数。
if (nowTime - preTime >= delay) {
preTime = Date.now();
return fn.apply(context, args);
}
};
}
// 定时器版
function throttle (fun, wait){
let timeout = null
return function(){
let context = this
let args = [...arguments]
if(!timeout){
timeout = setTimeout(() => {
fun.apply(context, args)
timeout = null
}, wait)
}
}
}
然后来介绍一些算法题
判断是否是4的幂
这个题目很简单,我们先来分析一下:
1、如果一个数是4的幂,则一定是2的幂,那么2的幂要如何判断呢?2的幂有一个特点就是二进制只有一个1。比如2的二进制是10,8的二进制是1000...
我们借助这个特性,就可以通过(num & (num - 1))是否为0来进行判断了。
相信大家和我一样,刚看到这个式子的时候很懵。我们前面说过了2的幂的二进制的特点是只有一个1,而(num & (num - 1))可以做到让num二进制中1的个数减去1。这个证明也很简单,我们只考虑num的最后两位,比如是01,那么减去1之后这两位会变成00,&之后是00(个数少了1)....这里我们不做过多证明。
2、当然只是这样肯定不够的,比如8是2的幂却不是4的。在此条件的基础上还要保证1的位数一定在奇数位
(num&0x55555555) == num
(或者这里我们可以根据4的特性,4 % 3 == 1)
所以最终的代码:
//版本1
function isPowerOfFour(num){
num = parseInt(num);
return num > 0 &&
(num & (num - 1)) === 0 &&
(num & 0xAAAAAAAAAAAAA) === 0;
}
//版本2
function isPowerOfFour(num) {
nun = parseInt(num);
return num > 0 && (num & (num - 1)) == 0 && num % 3 == 1;
}
洗牌问题
网上常见的版本:
const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function shuffle(cards) {
return [...cards].sort(() => Math.random() > 0.5 ? -1 : 1);
}
但是这样的问题是index越小排在前面的概率越大。这显然不是我们想要的结果。所以下面介绍一种改进的算法
正确的写法:
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;
}
思路是每次随机抽出一张牌与倒数第i位互换(i为抽牌的次数)
这个算法还可以进行改进:
采用生成器
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]);
分红包问题
切西瓜法
主要的思路是类似于切西瓜一样,每次都是切最大的那块。(通过Math.max寻找最大的)
切的实现也是利用随机数,会得到两块。
这里需要注意的是我们需要从数组中删除切的这一块,并把切好的两块添加进数组。
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;
}
const amountEl = document.getElementById('amount');
const countEl = document.getElementById('count');
const generateBtn = document.getElementById('generateBtn');
const resultEl = document.getElementById('result');
generateBtn.onclick = function(){
let amount = Math.round(parseFloat(amountEl.value) * 100);
let count = parseInt(countEl.value);
let output = [];
if(isNaN(amount) || isNaN(count)
|| amount <= 0 || count <= 0){
output.push('输入格式不正确!');
}else if(amount < count){
output.push('钱不够分')
}else{
output.push(...generate(amount, count));
output = output.map(m => (m / 100).toFixed(2));
}
resultEl.innerHTML = '<li>' +
output.join('</li><li>') +
'</li>';
}
但是这样抽出来的红包相对来说比较均匀,不够刺激呀~~~~
下面我们介绍第二种方法
抽牌法
这里思路利用的就是我们前面介绍的洗牌问题。
时间复杂度O(n),缺点是空间复杂度比较高。
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];
}
}
function generate(amount, count){
if(count <= 1) return [amount];
const cards = Array(amount - 1).fill(0).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;
}
const amountEl = document.getElementById('amount');
const countEl = document.getElementById('count');
const generateBtn = document.getElementById('generateBtn');
const resultEl = document.getElementById('result');
generateBtn.onclick = function(){
let amount = Math.round(parseFloat(amountEl.value) * 100);
let count = parseInt(countEl.value);
let output = [];
if(isNaN(amount) || isNaN(count)
|| amount <= 0 || count <= 0){
output.push('输入格式不正确!');
}else if(amount < count){
output.push('钱不够分')
}else{
output.push(...generate(amount, count));
output = output.map(m => (m / 100).toFixed(2));
}
resultEl.innerHTML = '<li>' +
output.join('</li><li>') +
'</li>';
}