Leon82 发布于 04月06, 2020

谈谈小程序自动化

浏览器领域,我们有如selenium和puppeteer这样的库,可以自动化控制浏览器执行自动化脚本,以完成自动化端对端测试、定时自动化任务等。随着持续集成、持续部署也就是CI/CD的需求日益增长,自动化也成为必不可少的一环。

对于日益增长的小程序开发需求,我们能不能自动化控制小程序呢,进而达成自动测试、自动发布等任务呢?

阅读全文 »

Leon82 发布于 01月07, 2020

谈谈农历

还有不到20多天,2020年的农历春节就要到了。对于中国人来讲,农历春节是一年最隆重的节日。笔者曾经编写过农历计算的程序。为此调研过一段农历的算法。我国现行的农历,经过了若干次迭代,形成了如今的版本。农历,精确地反映了太阳、地球、月亮的天体运动的相对位置。2016年11月30日,农历中的二十四节气被正式列入联合国教科文组织人类非物质文化遗产名录。那么,农历是如何定义的,年份又是如何计算的,农历的月是如何划分的,二十四节气的天文学意义是什么以及如何用计算机推算农历?这篇文章将给大家做比较详细的解答。

阅读全文 »

Leon82 发布于 12月11, 2019

中不中奖,都是抽奖程序的锅?

转眼年关切近,很多业务都会考虑做一些抽奖活动,而各大公司在年底也会考虑开办年会。年会中,一般会安排抽奖环节。抽奖程序一般接收一个抽奖人员列表,通过算法产生一个随机数,再通过某种对应关系,将列表中的某个人与这个随机数匹配。这个匹配的人就是中奖的幸运儿了。

不出意外的话,年底这一个多月,通常是一年中随机函数被调用最频繁的一段时间。

阅读全文 »

Leon82 发布于 11月12, 2019

把你的NodeJS程序给没有NodeJS的人运行

标题很绕口,不过确实是一个很常见的需求。

众所周知,NodeJS程序开发简便且容易实现跨平台。但是,当你开发了一个NodeJS程序,想要分发给其他人运行的时候,你会发现,你往往需要对方也来安装一个NodeJS环境。理想的方式是,我们可以把我们的程序打包成一个可执行文件,这样,就可以直接在对方的电脑上运行你的程序了;同时,我们将代码打成二进制形式,可以在一定程度上保护源代码以及API等处理的逻辑。

阅读全文 »

Leon82 发布于 10月16, 2019

浅谈Shadow Dom

为什么会有Shadow DOM

你在实际的开发中很可能遇到过这样的需求:实现一个可以拖拽的滑块,以实现范围选择、音量控制等需求。

除了直接用组件库,聪明的你肯定已经想到了多种解决办法。如在数据驱动框架React/Vue/Angular下,你可能会找到或编写对应的组件,通过相应数据状态的变更,完成相对复杂的交互;如在小快灵的项目下,用jQuery的Widget也是一个不错的选择;再或者,你可以点开你的HTML+JavaScript+CSS技能树,纯手工打造一个。这都是不难完成的任务。

当然,在完成之后,你可能会考虑对组件做一些提炼,下次再遇到同样的需求,你就可以气定神闲地“开箱即用”。

这里是Clair组件库对这个需求的封装。

我们不妨从这个层面再多想一步。其实由于HTML和CSS默认都是全局可见的,因此,尤其是纯手工打造的组件,其样式是很容易受到所在环境的干扰的;由于选择器在组件层没有统一的保护手段,也会造成撰写时候的规则可以被随意修改;事件的捕获和冒泡过程会和所在环境密切相关,也可能会引起事件管理的混乱。

根据一般意义上“封装”的概念,我们希望相对组件来讲,DOM和CSS有一定的隐藏性;如非必要,外部的变化对于内部的有一定的隔离;同时,外界可以通过且仅可以通过一些可控的方法来影响内部,反之亦然。

针对这些问题,其实浏览器提供了一种名叫Shadow DOM的解决方案。这个方案目前与 Custom Elements、HTML Templates、CSS changes和JSON, CSS, HTML Modules并列为Web Components标准

阅读全文 »

Leon82 发布于 08月12, 2019

源自babel的多包管理工具:Lerna

多包合作的烦恼

在开发需要多个密切协作的软件包时候,我们往往将独立的功能块进行划分,使得各个功能独立的模块分别完成,以减少相互影响,完成有效的多人合作。但是,在模块协作时,经常会遇到一些问题:

  1. 依赖处理繁琐。
  2. 依赖的模块,尚处在开发之中,通行的npm install、yarn等无法从安装源中获得。
  3. 被依赖的模块版本升级,模块其他版本需要手动管理相关的版本。
  4. 有循环依赖的风险

阅读全文 »

Leon82 发布于 07月09, 2019

WSL:在Windows下优雅地玩Linux

引子

一直以来,Windows的命令行的体验都不是特别的友好。由于Windows以图形界面交互为主,同时微软在一段时间内对命令行程序发展并不积极,以及Windows系统底层与*nix系列的不一致。造成Windows下的命令行开发与图形开发体验相去甚远:一方面工具链的不完整,一方面终端字体不甚美观,甚至默认的终端色域相对于Mac都是精简的。

阅读全文 »

Leon82 发布于 05月22, 2019

谈谈StorageEvent

纷纷红紫已成尘,布谷声中夏令新。夹路桑麻行不尽,始知身是太平人。 ——宋.陆游 《初夏绝句》

我们在开发多Tab应用时候,常常会遇到多个Tab状态同步的问题。

想象如下场景:用户主界面,显示用户购物车内待结算的商品总数。此时,用户可能打开多个Tab。当用户添加新商品到购物车的时候,需要更新购物车的数量。

此时,当前页面需要向服务器发起请求,在得到添加成功响应的时候,可以更新用户界面。为了兼顾体验和可靠性,如果确信添加成功概率比较高的时候,也可以先更新界面,当多数返回错误的时候,可以给用户界面做状态回滚。为了下次展示方便,我们还会把这个数据写到LocalStorage里面。用户再次打开时候,可以优先从localStorage中取值。

当前页面解决了,那么如果同时打开多个Tab该如何解决呢?这里使用StorageEvent可能是一种代价较小的解决方案。

StorageEvent是什么呢?

  1. 是一种Event,可以通过标准的Event监听器操作。
  2. 当storage变化时候,事件会被派发到所有同域下的其他页面。
  3. 触发变化的当前页面,没有事件派发。

这里有一个简单的示例可以展示这个API的用法。

const STORAGE_KEY = "cartlist"

const getStorage = () => {
    try {
        let rets = window.localStorage.getItem(STORAGE_KEY)

        if (rets === null) {
            return []
        }
        return JSON.parse(rets)
    }
    catch(e){
        return []
    }
}

const addCart = (value) => {
    let rets = getStorage()

    rets.push(value)

    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(rets))

    return rets    
}

const minusCart = (value) => {
    let rets = getStorage()
    let idx = rets.indexOf(value)

    if (idx !== -1){
        rets.splice(idx, 1)
        window.localStorage.setItem(STORAGE_KEY, JSON.stringify(rets))
    }


    return rets    
}

const render = () => {
    let rets = getStorage()

    if (rets.length){
        $("#num").html(rets.length).show()
    }
    else {
        $("#num").hide()
    }

    $(".list li").each((i,el) => {
        if (rets.includes(i)){
            $(el).find("a:nth-child(1)").css("visibility", "hidden")
            $(el).find("a:nth-child(2)").css("visibility", "visible")

        }
        else {
            $(el).find("a:nth-child(1)").css("visibility", "visible")
            $(el).find("a:nth-child(2)").css("visibility", "hidden")

        }
    })
}

$(".list a").on("click", (e)=> {
    let opIdx = $(e.target).parent().find("a").index(e.target)
    let line = $(e.target).parent().parent()
    let idx = $(".list li").index(line)

    opIdx === 0 ? addCart(idx) : minusCart(idx)

    render()

    return false
})

window.addEventListener('storage', (e) => {
    render()
})

render()

其中,下面这行代码是实现的关键:

window.addEventListener('storage', (e) => {
    render()
})

当我们注释掉这个语句,我们的页面同步就不能运行了。

读者可以打开多个Tab并观察页面的变化https://jsbin.com/radekilosu/1/edit?html,css,js,output。

实际上,这个事件e上还带有很多信息,方便编程时,对于事件做精确的控制。

字段 含义
key 发生变化的storageKey
newValue 变换后新值
oldValue 变换前原值
storageArea 相关的变化对象
url 触发变化的URL,如果是frameset内,则是触发帧的URL

上述各值都是只读的。

还有一点没有解决掉,就是触发storage变化的本页面,不能接收这个值,这个一般情况下是没问题。当然,为了一致性,我们可以自行new一个事件,在发生时候主动触发它。

此时我们可以包装一个新的Storage对象:

var Storage = {
    setItem : function(k,v){
      localStorage.setItem(k,v);
    },
    removeItem : function(k){
      localStorage.removeItem(k);
    },
    clear: function (){},
    getItem: function(k){}
}

此时,我们再包装一个函数:

function dispatchMe(key, oldval, newval, url, storage){
    var se = document.createEvent("StorageEvent");
    se.initStorageEvent('storage', false, false, key, oldval, newval, url, storage);
    window.dispatchEvent(se);
}

此时,我们只需要在setItem、removeItem、clear中获取对应的值,并手动调用一下dispatchMe,同时把和localStorage打交道的地方改为调用我们包装的Storage即可。

参考资料

  1. https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
  2. https://www.cnblogs.com/cczw/p/3196195.html

阅读全文 »

Leon82 发布于 05月21, 2019

从谷歌华为暂停合作说起

引子

2019年5月20日,科技界最火爆的消息莫过于谷歌暂停支持华为部分业务。关于这个事件将产生的影响,大家可以从相关的科技媒体上找到详细的分析。比较一致的看法是:谷歌的这个做法,对于国内的华为用户,影响不大;对于海外的华为用户,尤其是在上游应用生态环境上,会有一定的影响。

这是由于,谷歌暂停合作的服务,都是在用户层面的服务GMS。用户层与安卓底层内核遵从不同的协议,因此不必开源,进而导致替换成本高企。基于现有生态,在海外市场构建应用生态环境难度非常大。

这篇文章将尝试从这则新闻涉及的开源协议来分析一下,这个做法背后相关的逻辑,并籍此,跟各位读者聊一聊主流的开源协议。

阅读全文 »

Leon82 发布于 04月21, 2019

Puppeteer PK 滑动验证码

天街小雨润如酥,草色遥看近却无。最是一年春好处,绝胜烟柳满皇都。 —— 唐.韩愈 《早春呈水部张十八员外二首》

引子

几个月之前,chunpu小编曾经在《震惊! 滑动验证码竟然能这样破解》一文中给大家展示了使用支持WebDriver标准的Firefox破解滑动验证码的示例,脑洞之大,构思之巧,笔法幽默,叹为观止,也极大的燃起了笔者对此的兴趣。

这篇文章就带大家使用目前较为流行的Puppeteer完成这件事情。

阅读全文 »