关于梦想

每个人对梦想的定义可能是不一样的,也不分高低贵贱。
私以为梦想是美好的愿景,但又是遥不可及,可以让人为之奋斗一辈子。
而欲望,无穷无尽,永远无法满足。
特斯拉?我所欲也!别墅?我所欲也!…?我所欲也!但我知道,这些欲望,只要财务自由,大多可满足。
但财务自由也是欲望啊,在年入十万的时候,觉得千万可能财务自由了,当有千万的时候,可能数十亿才能算财务自由。
吾生也有涯,而”欲”也无涯 。以有涯随无涯,殆已!(庄子的棺材板怕是压不住了)。
记得读书那会儿,老师经常会问:同学们的梦想是什么啊?我们大多清一色地回答科学家、医学家。因为压根就没考虑真的去实现,吹牛逼谁不会啊。
而步入向社会后,我慢慢认为梦想应该是厚重的,所以不再敢谈及自己的梦想。我朋友圈的签名,在毕业一年极度迷茫的时候改成了:丢了梦想的年轻人。
《I have a dream》,这篇不知多少年前学过的课文,依然振聋发聩。马丁路德金为之奋到生命的最后一刻。
思考了很久,我觉得自己没有梦想的原因,就是怕自己一旦有了梦想,从此就要走出作为一条咸鱼所呆的舒适区。
但没有梦想,不代表我没有目标,目标是短期的,计划未来多长时间学习到什么技术,并运用到项目,计划学习大公司的经验,在未来一段时间,把我们的前端团队进一步正规化,把项目做好的同时,帮助每一位小伙伴提升自我。
虽然,暂时,我没有梦想,但大多数情况下,还是个有目标的、正向的人,我依然在向着”be a better man”方向走。
没有梦想?只是我懒的托词。

Vue中loading状态管理

为了提升用户体验,我们一般会在做耗时操作时显示loading效果,缓解用户等待的焦虑.通常情况下,我们的代码可能是这样子:

<div v-show="isLoading">
   loading...
</div>
new Vue({
  data:{
    isLoading:false
  },
  methods:{
    async fetchFn(){
        this.isLoading = true
        try{
          await getSomething()
        }catch(e){
          //
        }finally{
          this.isLoading = false
        }
    }
  }
})

这样,我们在调用fetchFn时,即可显示loading效果了。
这种实践不是不可以,只是每次都需要要用户手动的、命令式的来维护loading的状态,很容易出现漏写的情况,虽然通过事后的测试可能发现问题,但总归是麻烦的。
我们能否想到一种自我状态管理的情况,给每个异步的method都生成一个loading状态,然后在需要用到loading的地方,在视图里面订阅指定method的loading变化即可。
我们通过Vue插件来实现该功能:

var VueLoadingPlugin = {};
VueLoadingPlugin.install = (Vue,loadingNameSpace='tzLoading') => {
  Vue.mixin({
    data() {
      return {
        [loadingNameSpace]: {},
      };
    },
    beforeCreate() {
      const methods = this.$options.methods || {};
      Object.keys(methods).forEach(key => {
        let fn = methods[key];
        this.$options.methods[key] = function(...args) {
          this.$set(this[loadingNameSpace], key, true);
          let ret = fn.apply(this, args);
          if (ret && typeof ret.catch === "function") {
            return ret.finally(() => (this[loadingNameSpace][key] = false));
          } else {
            this[loadingNameSpace][key] = false;
            return ret;
          }
        };
      });
    },
  });
};

这样,当我们有如下代码时:

new Vue({
  methods:{
    async fetchFn(){
      await getSomething()
    }
  }
})

只需要在模版里面适当的地方订阅fetchFn的loading状态,然后,在每次fetchFn开始时都会自动出现加载效果,然后在fetchFn正常结束或出错后关闭加载效果:

<div v-show="tzLoading.fetchFn">
loading...
</div>

Vue中异步错误处理

一般在一个项目开始之前,我们一般会对现有的框架做一定功能上的丰富,比如对ajax请求功能的二次封装,封装的功能可能包含了:通用错误处理,请求过滤,响应过滤等等。如果我们封装的函数叫request,那么业务中触发一个ajax请求的流程大致如图:
正常ajax请求流程图
通常,这样的流程处理能满足需求,然而,更多的情况,我们希望request的返回数据,经过request预处理后,首先交由业务代码这边自行判断是否合法,是否需要处理错误,如果不合法,且自己不打算处理错误,则再抛出错误,这样的话,就符合很多后端框架的流程了,业务层的错误,先自己catch,要么处理,要么往框架抛,框架统一处理,流程大致如图:
改造后的request请求图

这样的话,所有的错误,业务层有了优先处理的权利,不需要自行处理的情况才交由框架做通用处理。
当然,在javascript中,异步错误的处理不能简简单单地通过window.onerror可以搞定的,在vue下改造,具体的思路是将我们需要按新的流程处理的函数处理成async函数,这样,我们就能用promise的那套错误处理机制来处理了。
具体实现看代码:


const install = (Vue, { handler = () => {} }) => { Vue.mixin({ beforeCreate() { const options = this.$options; const { methods = {}, catchAsyncError } = options; if (!catchAsyncError) return; Object.keys(methods).forEach(key => { const fn = methods[key]; options.methods[key] = function(...args) { const ret = fn.apply(this, args); if (ret && typeof ret.catch === "function") { return ret.catch(handler); } return ret; }; }); } }); }; export default { install };
// 使用插件

Vue.use(asyncErrorCatch, {
  handler(err) {
    console.log("catch async error:", err.message);
  }
});

在组件内我们这样使用:


export default{ name: "MyPage", catchAsyncError: true, methods: { async fn() { // Will catch by plugin throw new Error("this is an async error") }, test() { // Will catch by Vue.config.errorHandler throw new Error('sync error') } } }

插件放GitHub了 :https://github.com/Elity/VueGlobalError

待解决问题:

如下代码中,通过dom事件触发a方法,a方法内部中调用异步的b方法,而b方法中的错误没到a方法内就被处理了,理想状态是a方法内应该可以catchb方法抛出的错误

{
    methods:{
        async a(){
            this.b()
        },
        async b(){
            throw new Error("Error")
        }
    }
}

javascript软绑定

说道软绑定,那么不得不首先提到什么是硬绑定,如Function.prototype.bind,就是硬绑定,有如下代码:

let obj1 = {
    id:'obj1'
},
obj2 = {
    id: 'obj2'
};

function print(){
    console.log(this.id)
}
let p1 = print.bind(obj1);
p1() // 输出 obj1
let p2 = p1.bind(obj2);
p2() // 依然输出 obj1 

这就是硬绑定的演示。一个函数一旦使用bind绑定过上下文,那么之后再绑定就不会生效了。
我这里要实现的软绑定,则就是为了某些特别需求而实现可以重复绑定的bind

Function.prototype.softBind = function(ctx,...cur){
  let fn = this
  function ret(...arg){
    return ret.fn.apply(ctx,[...ret.arg,...arg])
  }
  ret.fn = this.fn || this
  // 不要忽视对参数的绑定
  ret.arg = [...(this.arg || []),...cur]
  return ret
}

但这种实现方式会对外暴露fn和arg,存在被污染的可能性

比特币问答录

1. 为什么如此热衷比特币?

因为他是一种最接近我理想中货币形式的货币:不依靠任何政府或者中央银行机构,完全按照一套固定的生成规则发行。尽管它在我看来可能还有一些缺陷,但我更愿意把它看成是一种自由货币的发端,从这个意义上讲,我把整个比特币经济圈都当成是一场社会实验。实验当然存在失败的可能性,可人类社会也是在不断的试错中发展的。但为了自己的理想做实验是一件幸福的事情,我已经为实验失败做了充足的准备。我的所有关于比特币的看法都是基于这个前提的,所以请不要把我这个系列的文章当作投资建议。非要听投资建议的话,我想说,高风险高收益。比特币的市值几千倍的涨,对应前面那句话,想想风险有多高。
2. 如果没有政府担保,凭什么相信这种货币?

一个理性的人应该对任何事情都持怀疑态度,一个有人情味的理性的人至少对骗过他的人要持怀疑态度。所以说在问这个问题之前,建议翻翻历史,看看当权者骗过我们多少次呢?我想比你十个前女友加起来都多。对于我来说,宁愿相信一个看得明白的规则,也不愿相信一个曾经几次三番骗过我的人的承诺。

当然你可以说为了爱,我不在乎她骗我多少次。塞浦路斯那些和你一样想法的人眼睁睁的看着自己手里的钞票变成了银行的股票,而且还是被套牢的股票,这就是爱的代价。

3. 既然你觉得政府不靠谱,你的意思是该把所有前都换成比特币?

这个,我再说一遍,「一个理性的人应该对任何事情都持怀疑态度,一个有人情味的理性的人至少对骗过他的人要持怀疑态度。」你应该在很多地方听到过这样一句话:「不要把所有的鸡蛋全放在一个篮子里。」比特币完全可以成为你放鸡蛋的一个篮子。当然,如果你真的想把自己所有的鸡蛋全砸在一个篮子里以博取高收益的话,也是你个人的自由选择,而且确实有人这么做,看看瑞典海盗党的创始人 Rickard Falkvinge 就知道了。

4. 那比特币到底是一种用什么担保的货币呢?

如果说非要有担保的话,就是用信用担保的货币。有人愿意接受这种货币,那么你就能用这种货币来买东西。

5. 那如果背后的信用体系崩塌了,岂不就变成一堆无用的二进制数字了?

说得好,津巴布韦的货币已经接近一堆白纸和数字了。

6. 你是否觉得比特币在未来某一天会消亡?

肯定会有这么一天,但我觉得不太可能是因为信用崩塌。我觉得愿意接受它的人的群体一旦形成规模,就很难倒退回去,毕竟没有什么机构在那里疯狂印比特币让你手里的资产贬值。我更愿意相信比特币消亡的那一天是另一种更好的货币取代比特币的那一天。不过还是那句话,既然是社会实验,就存在实验失败的可能性。

7. 照你这么说,比特币会威胁到政府的货币发行权,难道政府不会出台措施封禁掉比特币么?

比特币是一种基于 P2P 技术的货币,政府要关停一个基于 P2P 技术的东西的难度和封禁全网差不多,所以哪怕真的有一天比特币威胁到了政府对金融的控制,政府也要权衡封禁这种虚拟货币的代价。何况,要撼动政府货币发行权没有想象的那么容易。

8. 如果大家都用比特币了,政府的税收怎么解决啊?(一个法国人问我的问题)

我觉得吧,别替政府瞎操心了,政府想收你钱有的是办法,还是先关心一下自己的养老金能否取出来比较靠谱。

9. 有人说比特币交易市场有「庞氏骗局」的某些特征,你怎么看?

你只是说有「某些特征」,那我承认,其实绝大多数投资都有「庞氏骗局」的某些特征:你都要交钱给别人,别人都会承诺给你回报,权益可交易,都存在低买高卖的情况……不是吗?但你要说这就是「庞氏骗局」,建议先查查「庞氏骗局」的定义,相信我,虽然这个术语很唬人,但定义你肯定能看明白。

10. 现在大家对比特币的狂热怎么看着像 17 世纪荷兰的郁金香花球泡沫呢?

其实和上一个问题一样,很多东西都有相似的地方。一些人很狂热的做事情,无论具体在做什么,看他们的样子都差不多。但是类比的时候除了要关注相同的地方,更要看看不同的地方,比如郁金香花球会腐败变质(就像我上一段爱情一样),但基于信用的二进制数字却天长地久。:

11. 挖矿是什么意思?

挖矿就是比特币发行新货币的方式。简单来说,比特币交易的 P2P 网络上每一台电脑都在做交易确认,其中涉及到了大量的运算,如果你能率先计算出来并广播出去,就可以凭空获得一些比特币,这个过程有一点像你在金矿里挖出金子的感觉,因此称之为挖矿。

12. 为什么挖矿越来越难?

挖矿难的原因主要有两方面:1)从长期来看,比特币的创始人为了解决传统货币为人所诟病的通货膨胀问题,把比特币的生成速率以及未来总量都写死在算法当中了。比特币生成的速率越来越慢,并且是时间的函数,与参与挖矿的机器数量以及总计算能力无关。目前的生成速率是大约每十分钟 25 个比特币。2)短期来看,即使生成速率不变,由于参与挖矿的人原来越多,分到每个人手上的比特币自然也会变少。

13. 什么是矿机?

专门用来挖比特币的计算机。如前面所说,由于投入到挖矿的计算机越来越多,即使单位时间产生的比特币数量固定,分到每个人手上的比特币也变得越来越少,而具体分给每个人的份额取决于你在挖矿过程中所贡献的算力的百分比。普通的电脑的 CPU 以及显卡 GPU 的计算速度在挖矿中已经很难获得收益,因此有人专门为挖矿设计了芯片,并基于这种芯片组装了专门用来挖矿的机器,也就是矿机。这种矿机的计算速度可以达到原来 CPU 和 GPU 的数倍甚至数十倍。

14. 如果有人用公家电脑挖矿,岂不是空手套白狼?

这确实算是空手套白狼,公家电脑自己不用买,电费也不用自己出,如果能挖出比特币,收益全是自己的。之前也确实有人这么干过,不过如前面所说现在 CPU 和 GPU 基本上挖不出什么东西来了。

15. 既然有这种空手套白狼的机会,是不是说明比特币本身有漏洞呢?

这只能说是公家的监管有漏洞,与比特币本身无关。好比淘金热的时候,你可以用政府的挖掘机去给自己挖金子,这也算是空手套白狼。事实上各行各业都存在空手套白狼的情况,这并不能说明相关产品本身有漏洞。

16. 既然挖矿难度越来越大,先挖矿的人可以获得更多比特币,这岂非对后进入者不公平?

就持有比特币的数量以及比特币资产的市值而言,先挖矿的人确实获得了更多的收益。但任何一个新兴行业似乎都有这样的现象存在,腾讯首先进入了即时通讯领域,利用 QQ 赚取了大量的利润,现在要仿造一个类似 QQ 的软件太简单了,可以你却无法取得和腾讯一样的收益,这似乎也不怎么公平。其实,货币本身的功能就没有维持社会公平这一项。

当然谈到公平,必须要明确两个概念:结果平等和机会平等。先行者可以获得更多的收益,从结果而言,看上去并不平等;但从机会上讲,任何人都可以在那个时候挖矿,没有人阻止过你。比特币就结果而言确实不公平(事实上,在现实生活中,没有任何一直分配机制可以做到结果平等),但是所有人都有平等进入这个领域的机会。而且先行者有很大的风险,看看马化腾创业的心路历程,回顾一下那段历史你就知道当时倒掉多少家即时通讯工具,伴随高收益总是着高风险。如果比特币没有像今天这样受到人们的认可的话,早期比特币矿工的投入就全部浪费了,即便是今天这样的风险依然存在。

17. 可是三年前我不知道比特币这个东西啊,也不知道它会这么值钱,我怎么知道要去挖矿?

这个问题分两方面来看:首先,别人利用你不知道的一些信息赚取了比你更多的钱,这是这个社会的常态。最典型的例子是,咨询机构和培训班,就是利用这种信息不对称挣钱的。其次,没有人可以预知未来,如果你今天能知道三天后的股市行情,无论涨与跌,你都有办法挣钱。

18. QQ 提供了交流的平台,我没有一天不在用,创造了大量的价值,比特币怎么能比?

比特币正受到越来越多人的认可,即使是现在比特币也可以用来购买很多商品,支付各种款项,同时它也可以算是目前结算(尤其是大宗结算)最为方便的货币。这些难道不能算是价值么?你没有一天不聊 QQ,我想你就是今天不买东西,明天也会买的吧。

19. 挖矿耗费了大量的电力和计算机运算能力,不是浪费资源吗?

先扯一句别的,用专门的机器挖金矿,锻造成形,切割成块也耗费了大量的资源;同样,砍树,化成纸浆,添加各种辅料,制成特种纸张,印刷并添加各种防伪标识也耗费了大量的资源。比起这些来,比特币所耗费的资源要少的多。更重要的是,要把耗费的资源与产生的价值相比较,如果耗费的资源能够产生更大的价值,那么我想这种资源消耗就是值得的。比特币挖矿过程有如下价值:1)解决了结算过程需要的大量计算的问题;2)就挖矿者个人而言,可以获得一些有支付价值的货币;3)就整个比特币流通网而言,保证了一种相对公平相对透明的货币发行方式,可以对未来货币的总量做更为理性的预期——毕竟没有某个中央发行机构开动印刷机就可以神不知鬼不觉得从你口袋里掏钱。

20. 为什么说比特币的发行方式更公平?

比特币的产生是在全网均匀分布的,也就是说你参与了挖矿,就参与了货币的发行,而你行的那部分就落在了你自己的手里。而且发行的速率和规则都是透明的,即使不挖矿的人也可以对市场上的比特币数量有一个更好的预期。

21. 比特币与 Q 币有什么区别?

虽然比特币与 Q 币目前都被称为「虚拟货币」,但这两种东西有本质的不同。

首先,腾讯公司掌握着 Q 币的发行权,理论上腾讯想发行多少 Q 币就可以发行多少 Q 币;而比特币的发行权不属于任何一个组织,而是由全网生成的,其生成速率和总量也被写死在算法中,从根本上遏止了通货膨胀。这也就是为什么比特币的出现是一次金融革命,而 Q 币的出现并没有引起太多关注的原因。

其次,Q 币只能用来购买腾讯公司内部发行的一些产品,而比特币的价值已经获得了很多人的认可,甚至全球最大的比特币交易市场 Mt.Gox 已经获得美国财政部金融犯罪执法网络处颁发的货币服务事务许可,至少说明比特币的「货币」属性已经受到了美国的官方的认可。一种可以与法币自由兑换的货币,理论上可以买到任何任何法币能够买到的东西。

最后,最重要的一点是,我更喜欢比特币。

22. 比特币狂热怎么看着有点像传销?

把对比特币的狂热说成是传销,是目前我见过的对比特币最无厘头的指控。简单来说,比特币最大的创新在于去中心化,而各类非法传销组织的核心架构都是一个中心化极其明显的金字塔型的结构。稍微了解一下这两种东西,相信不会把二者混淆在一起,如果想了解更多,可以点击这里查看我专门针对这一问题撰写的文章。

23. 如何说服楼下卖冰棍的大妈接受比特币支付呢?

一种货币的流通性是逐渐建立起来的,比特币虽然已经可以算作是一种货币,但它还是一种成长中的货币。等到比特币得到更多人的认可时,大妈也会知道这玩意有价值,可以用来买东西,这时她自然就愿意接受了。

更重要的是比特币现在是一种类似外汇的金融资产,你拿美元在中国买冰棍,大妈也会觉得兑换不方便,可能也不愿意接受,那怎么办?很简单,把美元换成人民币。自然,你也可以把比特币换成人民币去购买冰棍和煎饼菓子。货币的价值并不是只能用购买冰棍来体现,古人喝杯茶也不会动不动就掏一锭金子出来。

24. 卖矿机的人为什么自己不挖矿呢?

旧金山淘金热的时候也有卖铁锹和挖掘机的。每个人擅长的东西不同,对风险的偏好程度不同,对一件东西的价值判断不同,正是有这样的不同才促成的各种各样的交易。我想你不会问一个厨子为什么不去缝衣服。

25. 中本聪骰子靠不靠谱?
我不知道你说的靠谱具体指什么?如果说指赢钱的话,那么我必须说十赌九输。尽管赌场获胜概率只比玩家高一点点,但这一点点足以使赌场赚翻,请相信概率论。如果你说的靠谱指无暗箱操作(现实生活中大多数赌场都有暗箱操作),那么中本聪骰子可以说是最靠谱的赌博游戏了。但终归是赌博游戏,别指望通过赌博来赚钱。

26. 有人说比特币就是电子黄金,你怎么看?

在我看来,把比特币比作黄金是目前为止最为接近的一种比喻。二者都没有担保方,总量都是固定的(至少相对固定),难以伪造(比特币几乎不可伪造),易于分割(事实上比特币更易于分割)。当然,既然是比喻,二者肯定不可能完全相同,因此也不能把适用于黄金的理论直接套在比特币上。

27. 那么比特币究竟哪里和黄金不一样?

首先,比特币是一组二进制数字,黄金具有实物形态(尽管其实物形态已经越来越不重要了);其次,黄金本身作为金属有其实用价值(尽管大部分人购买黄金不是冲着这部分价值去的),而比特币显然不能锻造金属制品,不能打造首饰;第三,比特币交易更加方便。个人觉得一个比较恰当的说法是,比特币类似于黄金靠信用支撑的那部分价值单独提取出来而形成的一种货币。

28. 你说比特币易于分割,那么最小能分割到什么程度?

目前比特币的最小单位是「聪(satoshi)」(1 聪=0.00000001BTC)。如果有需要的话,还可以考虑引进更小的单位,数字是可以无限分割的。当然,就目前看来没什么必要,聪这个单位已经足够小了。

29. 比特币交易需要大量算力来确认交易,因此有了所谓的挖矿,可是到 2140 年以后,比特币总量不会再增多了,还有人愿意用自己的算力为别人确认交易吗?

其实挖矿所得除了增发的货币之外,还有一部分是交易手续费。事实上现在小额交易的话也会产生一部分手续费,这部分手续费都给了「矿工」。等到货币不再增发之时,你帮别人确认交易依然可以赚取手续费。

30. 手续费那么少,会有人愿意去为手续费而挖矿吗?

多与少取决于市场供求关系,当没有人愿意挖矿时,你去挖了,手续费就都是你的,这时你就不会嫌少了。

 

文章来源 http://shengmingzhiqing.com/blog/Bitcoin-QnA.html/

该文写于2014.04.23,当时比特币一枚约为人民币5000,尔后受中国ZF政策影响一路跌至1000以下。

今天,2017.12.09,比特币单枚价格已经突破100000人民币。

行内可替换元素设置100%高度后导致页面出现滚动条的原因探究及解决

今天同事在项目上碰到一个问题,HTML及CSS如下,就是这样一个看似正常的页面,在chrome下居然出现了纵向滚动条:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>TEST</title>
  <style>
    *{
      margin:0;
      padding:0;
    }
    html,body{
      height:100%;
    }
    img{
      height:100%;
      width:100%;
    }
  </style>
</head>
<body>
  <img />
</body>
</html>

诡异的滚动条哪里来的?
一番搜索后,得到了一个看似简单,但极易被人忽略的点:img是行内元素中特殊的一种,叫行内可替换。
什么是行内可替换元素呢?可以配合着翻译看w3c对它的定义,简单来说就像img,iframe,select,video等等这些元素,其内容是超出CSS格式化模型范围的元素。相比于一般的行内元素,这些可替换行内元素最大的特点就是可以设置宽度和高度。
当然,以上这些其实并不重要,重要的是行内元素、inline-block元素,在一行内连续排版的时候是不会忽略标签之间的空白符的,很早之前看张鑫旭的《去除inline-block元素间间距的N种方法》一文,已然对这个特性有所了解。
然而,今天碰到这个滚动条的问题,一开始压根就没往这方面去找原因,毕竟若有行内空白符的存在应该是会导致页面出现横向滚动条,而非纵向。
当然,要解决这个问题,到这里已经很简单了,比如给父元素设置font-size:0line-height:0都可解决,或者直接设置当前元素display:block

然而,作为一名程序员,必须得有刨根问底搞清原理的精神,究竟是什么原因导致了纵向滚动条的出现呢?
再次动用谷歌搜索大法,发现有人给出另外的修复方案:设置当前元素的css属性vertical-align: bottom;
说实话,看到这个方案,我是一脸懵B的。但几秒钟懵B完成后,我记得原来看到过关于vertical-align这个属性对齐参考线的一张图:
vertical-align
再查阅MDN关于vertical-align属性的说明,发现其默认值是:baseline。对照上图,很明显,baseline下面其实还有一大截,而baseline与这个img元素的底部对齐时,正是baseline底下这一大截导致了整个页面被“撑高”出现了纵向的滚动条。

其实,到了这里,我们发现vertical-align属性导致滚动条的出现并不是空白符的原因,因为如果是空白符的话,水平方向应该也会出现滚动条。而且,如果用开发者工具查看的话就会发现:就算页面出现了滚动条,而页面的实际高度依然和100vh是相等的,也就是说,虽然出现了滚动条,但是页面高度依然和原来没有滚动条时是一样的。那这又是为什么呢? 我的理解是当对img,iframe等等这些可替换的行内元素以及其它的display:inline-block元素设置vertical-align属性时,就算该元素两边并无文字可参考,浏览器渲染引擎也会参考一个看不见摸不着的姑且称之为“空白元素”的元素去排版,而这个看不见摸不着的“空白元素”实际却不影响正常的文档流(不参与包含块的宽高计算)。至于导致最外层滚动条的出现,w3c貌似没有做明确的规定,另外我测试了下在Firefox 43.0.1版本下是没有滚动条的。

另外,想彻底领悟vertical-align属性的话
参考这篇文章 http://christopheraue.net/2014/03/05/vertical-align/
对应的译文:https://segmentfault.com/a/1190000007663895

Vue下优雅实现事件频率控制

该方式在vue2.4以后不再可用,之后的版本,在绑定事件的时候,内部给函数有包装了一层,我们无法在指令内部移除事件监听。源码见 ->->->这里

在web开发中,对DOM事件做频率限制随处可见,特别是涉及网络请求。这样的需求下,我们希望事件在第一次触发后立即响应,然后不接受响应,接下来在特定的时间内没再次收到该事件,则解除该限制。

借鉴debouncethrottle的思路,我们第一次可能想到的是自己定义一个频率控制的高阶函数(比如oneTime),接受我们传入的事件响应函数作为参数,得到一个新的函数传入@click里面,就像:

<button @click="oneTime(myFn)>测试</button>

经过实验,发现这样不行。查看vue.js源码

export function createFnInvoker (fns: Function | Array<Function>): Function {
  function invoker () {
    const fns = invoker.fns
    if (Array.isArray(fns)) {
      const cloned = fns.slice()
      for (let i = 0; i < cloned.length; i++) {
        cloned[i].apply(null, arguments)
      }
    } else {
      // return handler return value for single handlers
      return fns.apply(null, arguments)
    }
  }
  invoker.fns = fns
  return invoker
}

可发现,我们在模版里面传入的部分,也就是函数中的fns,包裹在invoker中每次都会被执行,也就是绑定的过程每次点击都会进行。所以,这样行不通。
那么,我们只能在data里面单独定义一个变量来保存生成的函数,然后把这个变量塞入@click里面。这样就导致每次需要使用该特性都得单独定义一个变量来保存新的函数,更优雅地实现方式是咋样的呢?我首先想到的是指令,想象下,在需要控制频率的地方使用自定义的指令,传入需要被控制的事件及时间,一切都和谐多了。附上指令实现方式:

function oneTime(method, duration) {
    let timer = null,running = false;
    return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(()=>{
            running = false
        }, duration);
        if(running)return;
        running = true;
        method.apply(this, args);
    }
}

Vue.directive('freq', { 
    inserted(el,bd,vnode) { 
            console.log('here',vnode.data.on)
        let all = {};
        if(typeof bd.value === 'string'){
            all[bd.value] = 500
        }else if(Array.isArray(bd.value)){
            bd.value.forEach(val=>all[val] = 500)
        }else{
            all = bd.value
        }
        for(let ev in all){
            if(!vnode.data.on[ev])continue;
            el.removeEventListener(ev,vnode.data.on[ev]);
            vnode.data.on[ev] = oneTime(vnode.data.on[ev],all[ev])
            el.addEventListener(ev,vnode.data.on[ev])
        }
    }
})

然后我们就可以这样使用了:

<button @click="fn" v-freq="{click:500}">
    测试
</button>

或者这样:

<button @click="fn" v-freq="'click'">
    测试
</button>

甚至这样:

<button @click="fn" v-freq="['click','mouseenter']">
    测试
</button>

我们的网站为什么需要分页?

昨天晚上看到某微信公众号推荐一本书:《集装箱改变世界》,这本书我在几年前看过。记得当时有媒体公布了比尔盖茨一年看过的50本书籍,其中比尔盖茨首推的就是这本书。我当时脑子里钻进的第一个想法是:一个破箱子有啥好说的。我得承认,这是我惯常的思维,喜欢透过第一印象或表面现象看事物。但是作为崇拜者,还是怀着一颗好奇心去读了。

集装箱发明以前的码头,来来往往的是各类货车和劳工,一车车卸货然后然后一船船装货,这是我们会在各种时间线在二十世纪初的电视剧中看到的场景。以一个身处上百年后的现代人的眼光来评价:拥挤、低效。当然后来人们逐渐发现,干嘛不在每次运输到达的时候把车厢整个装到船上呢?省去了大量装卸货物的时间,也节省了运输车辆等待的时间。这样集装箱的雏形就出来了。之后一系列的反集装箱运动都是另外的话题了,毕竟经济全球化的发展,是无法阻止更高效更经济的集装箱占领各大码头的。

扯了这么久,其实都是题外话,告诫自己不要通过表象来给某事物下结论,或想当然的去设想他的必要性。

事情起因是我们正在做的作业考试系统,对于考试页面,理论上来说顶多一百题,我觉得根本不需要分页,而产品给的原型做了分页。当时我跟产品商量说在这个版本先不做分页吧,巴拉巴拉说了一些我认为合理的理由,说句实话,最主要的原因是我们前端要做分页也会费事很多,而且后台接口设计的同事当时也是没考虑这块分页的,现在需要都改的话会拖慢项目进度。产品也妥协了。

后来,我心里总存在个疙瘩,感觉这样推掉产品需求不好。直到重新回忆起《集装箱改变世界》的一些读后感。我开始思索:我们为什么需要分页?

搜集了相关的一些资料,发现“分页”的发明在展示类的网站未出现的时候就有了,那时候的网络环境很差、服务商的流量也很贵,一篇很长的文章加载可能需要很长时间,包含图片的会更慢,为不需要的人加载一篇很长文章是种浪费。所以那时候分页的大部分用途就是为了节省网络流量,加快首页加载速度。

到后来网站内容越来越多,各种地方可能需要分页才能容下。商品(或其它)展示、文章列表、搜索结果……分页变得理所当然,后面为了增加用户体验出现的滚动到底部自动加载也是分页一个变种。

到了对分页习以为常的今天,我们对于分页的第一感觉可能就是网站内容达到我假想的长度就应该分页。

站在用事实说话的角度,我们重新来考量下传统分页的益处:

  1.   节省网站流量
  2.   划分数据量巨大的列表
  3.   在2的基础上方便用户迅速定位到相应的位置
  4.   SEO需要,搜索引擎会收录更多的网站页面
  5.  网站PV值提示,广告展示更多
  6.  切分长文章的核心点,避免造成用户阅读疲劳

对于第1点:除了移动端节省用户流量,PC端一般是没什么意义了,所以应该少有网站会从这个角度考虑来使用分页;

对于第2点:就像淘宝或百度这样,单个搜索词的结果可能就有成千上万条,不分页确实是无法展示的;

对于第3点:还是淘宝或百度那样,如果采用用户体验更好的滚动加载,会导致用户滚动位置无法被记录,当需要向另外一个人推荐关键词搜索的某项结果时,没法量化的描述该怎么定位。更甚者,搜索排名靠后的可能永无展示的机会。(谁会无聊到不停地滚动下滑?并且并没有谁会告诉你到底需要滚动多久才算完);

对于第4点:把一个文章分成几页展示,搜索引擎可能会收录几次,但到计算机运算能力显著加强的今天,搜索引擎可能会自动“去重”,进而导致网站被降权;

对于第5点:投放展示类广告确实会带来更好的收益。

对于第6点:仁者见仁智者见智了

“分页”对于用户体验来说在一定层面上肯定是有所降低的,毕竟用户需要靠多一次点击来切换到下一页内容。而体验更好的滚动加载的局限性又太大。

综合“分页”的优缺点来看,对于我们这个项目的考试页面,例举的6项优点,在这里似乎毫无用武之地。增加分页功能不但导致技术层面上的复杂(不是只单纯的分页复杂,而是如何分页的问题,基于题目类型分页?题目数量分页?页面高度分页?在这里实现的技术方案都不同,每样都有其明显的缺点),用户体验变差,而且带来的好处微乎其微。那么,在这里,我们真的需要分页吗?

反柯里化

多年前看腾讯前端的一篇技术文章讲反柯里化的,原来只是觉得很神奇,完全看不懂。今天尝试着自己去实现一下uncurry这个函数,反而一下就想明白了。
所谓反柯里化对应于柯里化的概念,就是说,把原来已经固定的参数或者this上下文等当作参数延迟到未来传递。比如我们经常有这样的需求(把ArrayLike转为Array):

var arr = Array.prototype.slice.call(document.querySelectorAll('script'))

如果需要每次都写这么一长串Array.prototype.slice.call必然不是做技术的该有的态度。
我们可能期望的是有slice这么个函数,然后直接这样使用:

var slice = Array.prototype.slice.uncurry();
var arr = slice(document.querySelectorAll('script'))

uncurry实现如下:

Function.prototype.uncrrying = function(){
    let self = this;
    return function(...arg){
        return Function.prototype.call.apply(self,arg)  
        //return self.apply(arg.splice(0,1)[0],arg)   //这样亦可
    }
}

其实如果在支持Function.prototype.bind的环境还有更快得到slice的方式:

var slice = Function.prototype.call.bind(Array.prototype.slice);

解决部分安卓机下QQ内置浏览器rem不准确的问题

部分安卓机由于rem计算不准确,导致页面在这些机型下出现了水平滚动条。
主要解决思路是:
1.设置1rem与px的对应关系,即html元素的font-size,我这里设置为屏幕视口宽度的1/10;
2.然后设置body的宽度为10rem;
3.取body的计算宽度,是否与html的宽度一致,不一致则说明rem计算有误,将html的font-size按比例缩放至正确大小

function fixRem () {
    var html = document.documentElement,
        body = document.body,
        bodyW = body.style.width;
    body.style.width = '10rem'; // 假如页面的视口宽度为10rem
    var bodyWidth = parseInt(window.getComputedStyle(body, null).width),
        htmlWidth = parseInt(window.getComputedStyle(html, null).width);
    if (bodyWidth != htmlWidth) {
        var size =  parseInt(html.style.fontSize)* htmlWidth/bodyWidth;
        html.style.fontSize = size + 'px';
    }
    body.style.width = bodyW;
}

这里要注意,document的DOMContentLoaded事件,在某些浏览器上触发时,html元素可能还没显示,此时去取其计算高度可能为auto,所以fixRem应当放在window.onpageshow事件回调里面。

nw.js调用dll

忙乎了一天半,踩了N多坑,总算把https://github.com/essa/nw_native_dll_sample这个nw.js调用dll的示例跑起来了。
跑起来后想想,踩坑的很大原因是自己看文档不仔细和之前安装的各种新版本(有些可能还是预览版本)的依赖比如node.js v7、python3.6。
一一例举下这些坑,及我的解决办法:
1.自己最开始没有装c的编译环境,便把示例里的.c和.h文件发给c++开发的同事编译,发给我后,测试代码始终出错,后发现同事编译的是x64版本,而我这边node是32位的。 = =! 后台干脆自己装MinGw编译了.nodejs调用DLL的测试总算跑通了。

2.然后在示例里面跑nw.js去调用dll始终没反应,然后发现无论是F12还是win.showDevTools()都无法打开控制台。。。再次跑去nw.js官网查文档,发现原来nw.js的build方式分为normal和SDK。只有SDK版本才带控制台。npm install nw –nwjs_build_type=sdk 和cnpm install nw –nwjs_build_type=sdk都巨慢,挂着下载一晚上第二天来还是个报错。。只得自己去github下载压缩包 然后新建一个目录www,在www目录下新建一个以下载的安装包版本号命名的文件夹,比如我的0.21.4,然后把压缩包原封不动(不要改名)放到文件夹内。 然后在www内 用http-server -p 9999 开启个本地服务,在项目根目录新建文件 .npmrc,内容为:
nwjs_build_type=sdk
NWJS_URLBASE=http://localhost:9999/
然后在根目录npm install nw –save;

3.项目目录nw app 运行程序,控制台终于出来了,发现错误来源于require(‘ffi’)与require(‘ref’); 照着错误一番搜索,发现直接npm install 这样安装的方式并不适应于nw.js调用(node.js直接调用是没得问题的)。需要用nw-gyp 在对应的模块文件文件夹内rebuild,见官方doc。于是乎,来到ffi模块的目录内,执行命令:
nw-gyp rebuild –target=0.21.4 –arch=ia32
十分钟,二十分钟,半小时。。都没得反应,一小时后终于有反应了,说服务器无响应了。。哔了狗了
照着错误提示的地址,用迅雷把压缩包拉下来:http://node-webkit.s3.amazonaws.com/v0.21.4/nw-headers-v0.21.4.tar.gz
然后在原来的www目录新建目录v0.21.4,把下载的压缩包放到该文件下,然后nw-gyp rebuild –target=0.21.4 –arch=ia32 –dist-url=http://localhost:9999/ (target是nw.js版本 arch是cpu位,ia32或x64)。 然后报错找不到nw.lib、node.lib、x64/nw.lib、x64/node.lib。同样去下载下载,然后再次执行nw-gyp rebuild –target=0.21.4 –arch=ia32 。successful!!然后去ref目录同样操作。

4.再次回到项目目录 nw app 总算跑起来了。

HTML标签select在移动端下多选问题

默认情况下:

<select name="test"multiple="multiple">
    <option value="1">aa</option>
    <option value="2">bb</option>
    <option value="3">cc</option>
    <option value="4">dd</option>
    <option value="5">ee</option>
</select>

点击多选条触发多选,然后直接点完成,而不去实际选择某项,这时系统会默认把第一项选上(实际显示未被选择)。
然后在不刷新的情况下再点开多选菜单去选择其它项目,选择界面上第一项仍显示未被选择,提交给后台的数据包含了第一项。
这样的情况,在最开始的option前面插入一项隐藏的,不可用的分组就OK了:

<select name="test"multiple="multiple">
    <optgroup disabled hidden></optgroup>
    <option value="1">aa</option>
    <option value="2">bb</option>
    <option value="3">cc</option>
    <option value="4">dd</option>
    <option value="5">ee</option>
</select>