当前位置: 首页 > wzjs >正文

做淘宝需要知道什么网站吗网站开发软件

做淘宝需要知道什么网站吗,网站开发软件,哪些网站是用织梦做的,小程序游戏搭建文章目录 1 起因2 换一种思路3 填坑之旅4 复盘与小结 写在前面 都说念念不忘,必有回响。写过的文章也好,看过的视频也罢,其实只要用心积累,不必刻意去死记硬背,这些看似分散的碎片都会在未来某个不经意的瞬间串联起来—…

文章目录

    • 1 起因
    • 2 换一种思路
    • 3 填坑之旅
    • 4 复盘与小结

写在前面
都说念念不忘,必有回响。写过的文章也好,看过的视频也罢,其实只要用心积累,不必刻意去死记硬背,这些看似分散的碎片都会在未来某个不经意的瞬间串联起来——人的大脑就是如此神奇。本篇分享就是源于本专栏的一次分享,以及几天前的一个教学视频。函数式编程的思想和实践其实离我们的日常工作和生活也并没有想象中的那么遥远。

1 起因

前几天看到一个讲 JavaScript 函数式编程的系列视频,虽然内容质量还不错,总体感觉可以打到 8 分,但评论区的网友们似乎并不怎么买账,尤其是当大家看到视频中为了解释某个重要概念(比如函数柯里化)而生搬硬套某些写法的时候,更是忍不住在弹幕区疯狂吐槽。这再次印证了李笑来反复强调的 精心挑选演示案例的极端重要性,同时也成功勾起了我对相关话题的兴趣,想着什么时候遇到合适的应用场景了再来分享也不迟。好巧不巧今天就遇到了。

如下图所示,这是一个要求用原生 JavaScript 实现的倒计时特效,每隔一秒就会触发一次上翻动画:

图 1 利用原生 JavaScript 实现的一个前端倒计时特效_1

图 1 利用原生 JavaScript 实现的一个前端倒计时特效_2

【图 1 利用原生 JavaScript 实现的一个前端倒计时特效】

2 换一种思路

由于之前接过类似的项目,所以第一版很快就搞定了,用的是 WebAPI 中的原生方法 parent.appendChild(firstElem),对于已有的元素节点,浏览器会按剪切操作执行该方法。

但是这样一来,每个时间数字上都要提前安插 09 不等的图片元素,显得十分臃肿和冗余,例如:

<div class="time-item"><ul><li><img src="images/5.png" /></li><li><img src="images/4.png" /></li><li><img src="images/3.png" /></li><li><img src="images/2.png" /></li><li><img src="images/1.png" /></li><li><img src="images/0.png" /></li></ul>
</div>
<div class="time-item"><ul><li><img src="images/9.png" /></li><li><img src="images/8.png" /></li><li><img src="images/7.png" /></li><li><img src="images/6.png" /></li><li><img src="images/5.png" /></li><li><img src="images/4.png" /></li><li><img src="images/3.png" /></li><li><img src="images/2.png" /></li><li><img src="images/1.png" /></li><li><img src="images/0.png" /></li></ul>
</div>
<div class="time"></div>

难道就没有其他更简洁的方式了吗?仔细一想,还真找到一个:利用 transition 来监听 margin-top 属性,并且控制过渡效果的开关,也能打到同样的效果。这样一来,每个时间位上的数字卡片总数就从三个(十位的小时)到十个(个位的分钟和秒)变为统一的两个(当前的、后续的)了:

<figure><section class="second"><div class="s1"><ul><li class="num5"></li><li class="num4"></li></ul></div><div class="s2"><ul><li class="num9"></li><li class="num8"></li></ul></div></section><figcaption class="label"></figcaption>
</figure>

然后再用 JavaScript 控制每组 li 上的 CSS 样式类就行了。这是第一次重写后的 JavaScript 逻辑:

let timer = null;
function countDown() {if(timer) {return;}console.log('Start counting down...');timer = setInterval(countDownS2, 1000);
}function stopCountDown() {if(timer) {clearInterval(timer);timer = null;}console.log('Stop counting down...');
}

其中 countDownS2 是一个控制个位的秒上翻一页的函数。结果一测就出 Bug:最最重要的自动停止倒计时的功能忘写了。

于是开启了今天的“套娃”模式……

3 填坑之旅

说起来这个 Bug 并不难修复,就是在秒的个位数每次回 0 时,需要同步看看前面的所有数位是否都已经变为 0:如果是,则停止计时,否则继续翻页。但这个案例的特殊性就在于,每一个靠右的时间单位都以类似递归的方式影响着相邻左边单位的翻页,且彼此间的换算关系还不一样:

图 2 各数位的不同标识及各自的进制换算示意图

【图 2 各数位的不同标识及各自的进制换算示意图】

按照这个思路,有了下面的改进版:

function countDown() {if(timer) {return;}console.log('Start counting down...');timer = setInterval(() => {if(prevDigitsAllZero(digits)) {stopCountDown();showMessage('时间到!!!');return;}countDownS2();}, 1000);
}// 2nd digit of seconds
function countDownS2() {countDownNext(second2);if(comeToZero(second2)) {if(prevDigitsAllZero([hour1, hour2, minute1, minute2, second1])) {return;}countDownS1();}
}// 1st digit of seconds
function countDownS1() {countDownNext(second1);if(comeToZero(second1)) {if(prevDigitsAllZero([hour1, hour2, minute1, minute2])) {return;}countDownM2();}
}// 2nd digit of minutes
function countDownM2() {countDownNext(minute2);if(comeToZero(minute2)) {if(prevDigitsAllZero([hour1, hour2, minute1])) {return;}countDownM1();}
}// 1st digit of minutes
function countDownM1() {countDownNext(minute1);if(comeToZero(minute1)) {if(prevDigitsAllZero([hour1, hour2])) {return;}countDownH2();}
}// 2nd digit of hours
function countDownH2() {countDownNext(hour2);if(comeToZero(hour2)) {if(prevDigitsAllZero([hour1])) {return;}countDownH1();}
}// 1st digit of hours
function countDownH1() {countDownNext(hour1);if(comeToZero(hour1)) {return;}
}

可以看到,这里的每一个子函数都出现了严重的冗余,基本流程都是一致的:

  • 看看当前单位是否为 0——
    • 若不为 0:则翻动一次左侧相邻的卡片;
    • 若为 0:则看看前面所有的单位是否也都为 0 ——
      • 若都为 0:则中止执行;
      • 若不全为 0:则正常执行后续逻辑。

怎样简化这样的代码呢?我想到了之前更新 JS 函数式编程专栏文章时提过的 延续传递风格(Continuation-passing style,即 CPS 风格),重新构建了一个中间函数:

// before:
function countDownS2() {countDownNext(second2);if(comeToZero(second2)) {if(prevDigitsAllZero([hour1, hour2, minute1, minute2, second1])) {return;}countDownS1();}
}// after:
function _countDown(currUnit, prevUnits, nextFn) {countDownNext(currUnit);if(comeToZero(currUnit)) {if(prevDigitsAllZero(prevUnits)) {return;}nextFn();}
}
const digits = [hour1, hour2, minute1, minute2, second1, second2];
const countDownS2 = _countDown(second2, digits.slice(0, 5), countDownS1);

但是问题似乎并没有解决:countDownS2 的定义要看 countDownS1,而 countDownS1 又是左边的 countDownM2 决定的……一直要递推到最右端的小时十位数翻页逻辑 countDownH1 的确定,整个过程才算结束。这样的重构无非是回调地域的另一种形式:

const countDownS2 = _countDown(second2, digits.slice(0, 5), function() {countDownNext(second1);if(comeToZero(second1)) {if(prevDigitsAllZero(digits.slice(0, 4))) {return;}countDownM2();}
});

貌似只能简化到这一步了,因为第 7 行的 countDownM2() 是一个函数的执行,而非函数引用本身,无法像简化 countDownS2 那样将 countDownM2 作为参数传递。如何将这段选择性执行的代码逻辑以传递函数引用的形式重构呢?

答案是将其封装到一个新的回调函数中,整个逻辑需要从右向左重新梳理:

// 小时的首位逻辑保持不变
const countDownH1 = () => {countDownNext(hour1);if(comeToZero(hour1)) {return;}
};
const digits = [hour1, hour2, minute1, minute2, second1, second2];
// 用中间函数重构后续的处理逻辑
const countDownH2 = () => _countDown(hour2, [hour1], countDownH1);
const countDownM1 = () => _countDown(minute1, digits.slice(0, 2), countDownH2);
const countDownM2 = () => _countDown(minute2, digits.slice(0, 3), countDownM1);
const countDownS1 = () => _countDown(second1, digits.slice(0, 4), countDownM2);
const countDownS2 = () => _countDown(second2, digits.slice(0, 5), countDownS1);

这样不仅可以将内部逻辑选择性地封装起来,还可以像写 async-await 那样处理异步函数调用,而最终的主逻辑丝毫不受影响:

function countDown() {if(timer) {return;}console.log('Start counting down...');timer = setInterval(() => {if(prevDigitsAllZero(digits)) {stopCountDown();showMessage('时间到!!!');return;}countDownS2();}, 1000);
}

可以看到,第 12 行的函数调用和改造前继续保持一致,唯独多了一块判定暂停的逻辑(这是为了修复 Bug 必须引入的)。至于中间的判定逻辑 comeToZero()prevDigitsAllZero(),可以放到最后来实现:

const container = document.querySelector(".container");
const $ = (selector, parentDom = container) => parentDom.querySelector(selector);const comeToZero = digit => {const currentLi = $('li:first-of-type', digit);const index = currentLi.className.slice(-1);return parseInt(index, 10) === 0;
};const prevDigitsAllZero = digits => digits.every(comeToZero);

正所谓擒贼先擒王,重构代码时 一定要分清主次,集中精力解决核心逻辑,其他旁枝末节锦上添花的部分作为支线任务放到最后完成。千万不要本末倒置。

这是修复 Bug 后,最终停止计时的效果图:

图 3 修复 Bug 后最终的页面效果截图

【图 3 修复 Bug 后最终的页面效果截图】

4 复盘与小结

完整代码后续我会免费放到网盘中,敬请留意!

此次代码重构创新引入了函数式编程中的 CPS 风格,将后续可能执行的业务逻辑通过封装成一个新的回调函数、并作为工具函数的参数传入,成功解决了代码冗余和书写 回调地域 式代码的问题,同时也让整个业务逻辑更加简洁、紧凑。

对于函数式编程这种十多年来仍无法顺利走进每个程序员撸码日常的“异类”而言,不结合具体业务场景而空谈其各种好处的内容创作,在我看来就是在炫技、自嗨。

http://www.dtcms.com/wzjs/222371.html

相关文章:

  • 网上接网站项目网上有免费的网站吗
  • 河源网站搭建费用中国域名注册官网
  • 如何做电影网站不违法网上接单平台
  • 网站建设优化方案搜索引擎优化是做什么的
  • 优化型网站的好处互联网公司排名2021
  • 广告网站建设及推广google ads
  • 网站开发多语言查询网址域名ip地址
  • 中国住房和城乡建设部查询网站6帮人推广注册app的平台
  • 青海小学网站建设腾讯会议价格
  • 企业网站建立意义何在jsurl转码
  • 在线直播网站开发软件排名优化
  • 哪些网站可以做ppt赚钱刷赞网站推广ks
  • h5易企秀官网南京百度seo
  • WordPress为什么卡关键词排名优化江苏的团队
  • 江苏省政府网站集约化建设深圳百度seo优化
  • 自媒体专用网站免费做小程序要多少钱
  • 后端开发需要掌握哪些知识seo网站优化教程
  • 来雁新城建设投资公司官方网站seo常用优化技巧
  • wordpress架构的网站专业关键词优化平台
  • 网站怎么做来流量自己如何做链接推广
  • 广告传媒建设网站微信推广方式有哪些
  • 网站建设需要哪些技术代写文案的软件
  • 如何做楼盘网站广告策划公司
  • 网站开发最合适的搭配优化是什么意思?
  • 番禺网站建设怎么样企业如何进行网络推广
  • 做网站排版网络营销推广策划案例
  • 昆明网站开发推广seo知识培训
  • 自己怎么做微网站刷移动关键词优化
  • 在线客户服务系统优化关键词的正确方法
  • 路由器当服务器做网站云浮网站设计