分类 前端技术 下的文章

今天在写一个倒计时的时候,突然想起一个问题,“对于 js 的小数取整,那种方法效率最高呢?”

然后我就把我能想到的取整的方法做了一下测试,大概是这个样子

Math.floor(-100/3)
parseInt(-100/3)
~~(-100/3)
(-100/3 >> 0)
(-100/3 | 0)
( -100 - (-100%3) ) / 3

然后我把每种方法跑了 10^7 次,取了一下平均值,然后发现:

  • Math.floor 耗时 0.0000197 ms
  • parseInt 耗时 0.0000225 ms
  • ~~(-100/3)(-100/3 >> 0)(-100/3 | 0) 三个耗时基本相同,大概都是 0.0000029ms 的样子
  • ( -100 - (-100%3) ) / 3 耗时 0.0000155ms

基本可以看出,三个位运算的方法耗时最少,而且少很多,那么是不是我们就可以使用这三种方法了呢?

在说答案之前,我们先来看看他们都干了什么吧

首先,位运算为什么能取整?

因为 js 是弱类型的语言,在对一个数进行位运算的时候,实际上是先对其进行 ToInt32 操作,转换成整数,然后再进行位运算。那么这里就有一个问题,原来的数被 ToInt32 之后,会变成一个 32 位长的整数,多余的部分会被截掉,当然,还包含一个符号位,那么也就是说,我们实际有效的数字只有 31 位,也就是 2147483647-2147483648

所以,当你的数字超过这个范围的时候,使用位运算的时候,就会计算出一些很奇怪的数字,其实因为 ToInt32 的时候被截掉了

那么现在来看答案就比较明显了, 如果你的数字会超过 32 位,那么就不能使用位运算。

然后再来看看 Math.floor,它是向下取整,也就是说,如果你的数字是负数的话,向下取整就是这样

Math.floor(-1.5)   // -2

剩下的两个 parseInt(-100/3) 比较稳定,功能也强大,支持不同进制转换,但是耗时也最长。

( -100 - (-100%3) ) / 3 这一坨就不说了,速度还可以,但是不易维护,如果不是情况特殊的话,我应该不会用这种办法吧

最后,就看你的实际情况来选择喽~

今天就到这里啦,如果有什么不对的地方,请留言告诉我哈~

大家晚安~

前言

在 ES6 中增加了对类对象的相关定义和操作(比如 classextends ),这就使得我们在多个不同类之间共享或者扩展一些方法或者行为的时候,变得并不是那么优雅。这个时候,我们就需要一种更优雅的方法来帮助我们完成这些事情。

Python 中的装饰器

decorators 即 装饰器,这一特性的提出来源于 python 之类的语言,如果你熟悉 python 的话,对它一定不会陌生。那么我们先来看一下 python 里的装饰器是什么样子的吧:

A Python decorator is a function that takes another function, extending the behavior of the latter function without explicitly modifying it.

装饰器是在 python 2.4 里增加的功能,它的主要作用是给一个已有的方法或类扩展一些新的行为,而不是去直接修改它本身。

听起来有点儿懵,“show me the code !”

def decorator(f):
    print "my decorator"
    return f

@decorator
def myfunc():
    print "my function"

myfunc()

# my decorator
# my function

这里的 @decorator 就是我们说的装饰器。在上面的代码中,我们利用装饰器给我们的目标方法执行前打印出了一行文本,并且并没有对原方法做任何的修改。代码基本等同于

def decorator(f):
    def wrapper():
        print "my decorator"
        return f()
    return wrapper

def myfunc():
    print "my function"

myfunc = decorator(myfuc)

通过代码我们也不难看出,装饰器 decorator 接收一个参数,也就是我们被装饰的目标方法,处理完扩展的内容以后再返回一个方法,供以后调用,同时也失去了对原方法对象的访问。当我们对某个应用了装饰以后,其实就改变了被装饰方法的入口引用,使其重新指向了装饰器返回的方法的入口点,从而来实现我们对原函数的扩展、修改等操作。

引入到 Javascript 中

那么我们了解到了装饰器在 python 中的表现以后,会不会觉得其实装饰器其实蛮简单的,就是一个 wrapper 嘛,对于 Javascript 这种语言来说,这种形态不是很常见吗,干嘛还要引入这么一个东西呢?

是的,在 ES6 之前,装饰器对于 JS 来说确实显得不太重要,你只是需要加几层 wrapper 包裹就好了(虽然也会显得不那么优雅)。但是正如文章开头所说,在 ES6 提出之后,你会发现,好像事情变得有些不同了。当我们需要在多个不同的类之间共享或者扩展一些方法或行为的时候,代码会变得错综复杂,极其不优雅,这也就是装饰器被提出的一个很重要的原因。

话说从装饰器被提出已经有一年多的时间了,同时期的很多其他新的特性已经随着 ES6 的推进而被大家广泛使用,而这货现在却还停留在 stage 2 的阶段,也很少被人提及和应用。那么,装饰器到底是在 Javascript 中是怎样表现的呢?我们下面来一起看一下吧!

Javascript 中的装饰器

先来看一下装饰器在代码中是长成什么样子吧

@decorator
class Cat {}

class Dog {
    @decorator
    run() {}
}

嗯,代码中的 @decorator 就是 JS 中的装饰器,看起来基本和 python 中的样子一样,以 @ 作为标识符,可以作用于类,也可以作用于类的属性。那么接下来,我们就来看看它具体的表现及运行原理吧。

ES6 中的类

首先我们先来看一下关于 ES6 中的类吧

class Cat {
    say() {
        console.log("meow ~");
    }
}

上面这段代码是 ES6 中定义一个类的写法,其实只是一个语法糖,而实际上当我们给一个类添加一个属性的时候,会调用到 Object.defineProperty 这个方法,它会接受三个参数:targetnamedescriptor ,所以上面的代码实际上在执行时是这样的:

function Cat() {}
Object.defineProperty(Cat.prototype, "say", {
    value: function() { console.log("meow ~"); },
    enumerable: false,
    configurable: true,
    writable: true
});

好了,有了上面这段代码以后,我们再来看看装饰器在 JS 中到底是怎么样工作的吧!

作用于类的装饰器

当一个装饰器作用于类的时候,大概是这个样子的:

function isAnimal(target) {
    target.isAnimal = true;
      return target;
}

@isAnimal
class Cat {
    ...
}

console.log(Cat.isAnimal);    // true

是不是很像之前我们在 python 中看到的装饰器?

(๑•̀ㅂ•́)و✧

所以这段代码实际上基本等同于:

Cat = isAnimal(function Cat() { ... });

那么我们再来看一下作用于类的单个属性方法的装饰器

作用于类属性的装饰器

比如有的时候,我们希望把我们的部分属性置成只读,以避免别人对其进行修改,如果使用装饰器的话,我们可以这样来做:

function readonly(target, name, descriptor) {
    discriptor.writable = false;
    return discriptor;
}

class Cat {
    @readonly
    say() {
        console.log("meow ~");
    }
}

var kitty = new Cat();

kitty.say = function() {
    console.log("woof !");
}

kitty.say()    // meow ~

我们通过上面的代码把 say 方法设置成了只读,所以在我们后面再次对它赋值的时候就不会生效,调用的还是之前的方法。

在上面的代码中我们可以看到,我们在定义装饰器的时候,参数是有三个,targetnamedescriptor

诶?等一下,这里怎么这么眼熟?⊙_⊙

没错,就是我们上文提到过的关于类的定义那一块儿的 Object.defineProperty 的参数,所以其实装饰器在作用于属性的时候,实际上是通过 Object.defineProperty 来进行扩展和封装的。

所以在上面的这段代码中,装饰器实际的作用形式是这样的:

let descriptor = {
    value: function() {
        console.log("meow ~");
    },
    enumerable: false,
    configurable: true,
    writable: true
};

descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;

Object.defineProperty(Cat.prototype, "say", descriptor);

嗯嗯,是不是这样看就清楚很多了呢?这里也是 JS 里装饰器作用于类和作用于类的属性的不同的地方。

我们可以看出,当装饰器作用于类本身的时候,我们操作的对象也是这个类本身,而当装饰器作用于类的某个具体的属性的时候,我们操作的对象既不是类本身,也不是类的属性,而是它的描述符(descriptor),而描述符里记录着我们对这个属性的全部信息,所以,我们可以对它自由的进行扩展和封装,最后达到的目的呢,就和之前说过的装饰器的作用是一样的。

当然,如果你喜欢的话,也可以直接在 target 上进行扩展和封装,比如

function fast(target, name, descriptor) {
    target.speed = 20;

    let run = descriptor.value;
    descriptor.value = function() {
        run();
        console.log(`speed ${this.speed}`);
    }

    return descriptor;
}

class Rabbit {
    @fast
    run() {
        console.log("running~");
    }
}

var bunny = new Rabbit();

bunny.run();
// running~
// speed 20

console.log(bunny.speed);   // 20

小结

OK,让我们再来看一下 JS 里对于装饰器的描述吧:

Decorators make it possible to annotate and modify classes and properties at design time.

A decorator is:

  • an expression
  • that evaluates to a function
  • that takes the target, name, and decorator descriptor as arguments
  • and optionally returns a decorator descriptor to install on the target object

装饰器允许你在类和方法定义的时候去注释或者修改它。装饰器是一个作用于函数的表达式,它接收三个参数 targetnamedescriptor , 然后可选性的返回被装饰之后的 descriptor 对象。

现在是不是对装饰器的作用及原理都清楚了呢?

最后一点就是,现在装饰器因为还在草案阶段,所以还没有被大部分环境支持,如果要用的话,需要使用 Babel 进行转码,需要用到 babel-plugin-transform-decorators-legacy 这个插件:

babel --plugins transform-decorators-legacy es6.js > es5.js

如果你感兴趣的话,也可以看一下转码以后的代码,我这里就不做详细介绍了,很有帮助哦~

如果本文描述的有错误的地方,欢迎留言~ ヾ(o◕∀◕)ノ

参考文献:

上个月苹果更新的系统版本,包括 iOS 10 和 MacOS,伴随更新的还有新的 safari。 新版本上来必然会多多少少存在一些坑。还好上个月有段时间在休假,并没有踩到坑。

但是,就在昨天,终于被我踩到了。。。。

大概是这样的:

公司的项目会使用 rem 来做不同屏幕的适配,然后昨天做完一个页面以后,发现在 chrome 下调试没有任何问题,自己的安卓手机也没有问题,但是到了 iphone 上就会出现:上半部分样式正常,下半部分 rem 布局的样式全部都缩小了!

从来没有遇到过同一个页面里面,一部分样式没问题,另一部分样式有问题的情况。。

切换各种浏览器测试发现,只有 safari 10 下会出现这个问题,包括 iOS 下、MacOS 下 和使用 safari 10 的微信浏览器,都会出现这样的问题。。。

好吧,经过了一晚上蛋疼的排查问题,终于找到了原因:

原来是因为一个 summary 标签!!!!

最终发现在 summary 标签前面的样式都没有问题,后面的元素全部缩小了,而把 summary 标签一去掉,样式就全部正常了!

然后自己也写了个小例子,大概是这样的:

<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no" />
        <style>
            html{
                font-size: 20px;
            }
            .test-box{
                margin: 20px auto;
                height: 4rem;
                width: 4rem;
                background-color: red;
            }
        </style>
    </head>
    <body>
        <div class="test-box"></div>
    </body>
</html>

这样是完全没有问题的,test-box 显示正常的大小

但是,如果我在它前面加一行 summary,比如这样:

...
<body>
    <summary>123</summary>
    <div class="test-box"></div>
</body>
...

在 safari 10 下面,你就会惊奇的发现,它的 rem 计算出错了!!!

到网上搜了很久,并没有找到相关的资料,估计是 safari 的 bug 吧。 昨天也给 safari 提了 bug report,但是并没有得到回应(不晓得那个提交系统是不是真的好用...)

好吧,目前暂时的解决方法就是把 summary 替换成别的标签,暂时避免使用它喽,等苹果修复好再说。。。

嗯嗯,今天就这样吧,主要是好久没更新,写篇文章表示,我还活着!

晚安~

事情起源于一次 github 的改样式...

某一天安然君告诉我:“你看 github 是不是改字体大小了?”,然后我就兴冲冲地去看了一眼,噫!丑死了!

但是毕竟是作为一条职业前端,我还是很自然的点开了它的样式代码,看了一眼是这样儿的:

font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 
    Helvetica, Arial, sans-serif, 
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"

哎?好像好多没见过的?那个 -apple-system 是个什么东西,好像没有见过?

遇到不认识的为什么不去问一下神奇海螺呢?

于是,我就去搜索了一下,然后事情大概是这样的:

从名字上就可以看出来,-apple-system 是表示使用苹果系统默认的字体,那么,它具体是指的什么字体呢?

大概了解了一下苹果的默认字体,大概是这样的:

  • IOS 9.0- 以及 OSX 10.5- 中文是 STXihei/STHeiti (华文黑体),英文/数字 使用 Helvetica
  • OSX 10.6-10.10 中文字体改为 Heiti SC (黑体-简),算是华文黑体的进化版
  • IOS 9.0+ 以及 OSX 10.11+ 中文改为 PingFang SC (苹方),英文/数字 改为 San Francisco

所以,也就是说 -apple-system 会在不同版本的系统下,使用与之对应的系统字体

哇!是不是感觉世界一下子好多了,不用再写好几个字体来兼容不同版本了~ 点赞!一下子简便了好多!

然后说起简便,其实呢,在 IOS 8.4+ 的版本,还支持字体的动态样式,也算是一种简便的写法,大概是这样的

font: -apple-system-body
font: -apple-system-headline
font: -apple-system-subheadline
font: -apple-system-caption1
font: -apple-system-caption2
font: -apple-system-footnote
font: -apple-system-short-body
font: -apple-system-short-headline
font: -apple-system-short-subheadline
font: -apple-system-short-caption1
font: -apple-system-short-footnote
font: -apple-system-tall-body

这样设置的时候,会使用系统预置的字体样式,注意的是,这里不光会设置使用哪种字体,还会设置字体大小、字体粗细等属性,其实就是一整套的字体样式全部打包,可以方便的来使用。

这个算是番外篇,然后我们接着往下看, BlinkMacSystemFont 又是什么呢?

其实和上面说的 -apple-system 说的事情基本一样,就是 在 mac 上用 Chrome 访问页面时,会使用系统的默认字体,应该算是对不支持 -apple-system 的一个降级吧

像这种简便的写法其实还有很多,比如 caption, icon, menu, message-box, small-caption, status-bar,还有 W3C 还在讨论要不要加一个 system 来表示系统字体,这边就不细说了,可以单独查一下

再后面的就比较常见了,简单说一下

  • Segoe UI 是给 Windows 和 Windows Phone 的字体
  • Roboto 是给 Android 和 最新的 Chrome OS 的字体
  • Helvetica 上面也说过,是兼容旧版本的字体
  • Arial 微软经典的兼容的无衬线字体
  • sans-serif 无衬线类型的字体(表示一个字体类型)
  • Apple Color Emoji 苹果的 emoji 表情
  • Segoe UI Emoji Windows 的 emoji 表情
  • Segoe UI Symbol Windows 的各种图标符号

这样一看,是不是 github 的字体设置的很科学呀,毕竟是 “最大的同性交友网站”

我们以后写页面的时候也可以参考这样的设置来写。

不过,等等,是不是没有写 Linux 的字体?这样让我 "一大批" Linux 用户怎么办?(谁要管你们?!哼~

还好,我在另外一个地方看到了这样的设置:

font-family: -apple-system, BlinkMacSystemFont, 
    "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", 
    "Fira Sans", "Droid Sans", "Helvetica Neue", 
    sans-serif;

把几个不一样的拿出来说一下

  • Oxygen 是 KDE 桌面环境下的字体
  • Ubuntu 是... 你猜?
  • Cantarell 是 Gnome 环境下的字体
  • Fira Sans 是 Firefox OS 的字体 (我都忘记这货还有个系统了!
  • Droid Sans 是兼容旧版本的 Android 的字体

嗯嗯,大概就是这样子,Linux 党们有没有一本满足?

哈哈哈哈哈哈,好吧,今天大概就是这个样子了,这个周末可能会多发几篇文章,把之前的补一下,但是应该都比较简单,主要是备忘,毕竟还有好多事情要做 QAQ

晚安~

参考文献:

前几天在写一个活动页的时候发现一个你一样问题,先还原一下现场

页面中有一个地方要做一个一直旋转的动画,类似转盘的效果,很简单是不是?是的!

.rotate{
    animation: rotate 1s linear infinite;
}


@keyframes rotate{
    from{ transform: rotate3d(0,0,1,0deg) }
    to{ transform: rotate3d(0,0,1,360deg) }
}

简洁明了,有木有,还带 3D 加速,完美是不是?
然而。。。
为什么 safari 下并不动。。。。 = =!,老夫可是最新的 safari 9.1.1 呀!

额,这就尴尬了~

请教一下同事,然后试着改成了这样

@keyframes rotate{
    0%   { transform: rotate3d(0,0,1,0deg) }
    50%  { transform: rotate3d(0,0,1,180deg) }
    100% { transform: rotate3d(0,0,1,360deg) }
}

然后,诶~ 转了!
等等!!! 为什么转的姿势这么奇怪!
你知道来回摇头是什么姿势么?没错!就是这样!转到 180 度的时候,它又返回去了!!!
你怎么还来回的转呢?!!!

看来这样子不行!
再换一个姿势试一下,google 一下,有人说是开始和结束的位置是一样的(毕竟转了 360 度嘛),所以 safari 会以为没有动,所以就不转了。

好吧,那我们换成不一样的试试

@keyframes rotate{
    from{ transform: rotate3d(0,0,1,0deg) }
    to{ transform: rotate3d(0,0,1,359deg) }
}

改成 359 度呢?

答案是: 还 TM 不行!!!

WTF!!!

然后,还有人给出答案说,再多加几个关键帧,因为加了 50% 的时候它确实转了,不过转的后半段的姿势不是我们想要的,那我们如果把它加一个关键帧,是不是就不会再转回去了呢?

@keyframes rotate{
    0%   { transform: rotate3d(0,0,1,0deg) }
    50%  { transform: rotate3d(0,0,1,180deg) }
    75%  { transform: rotate3d(0,0,1,270deg) }
    100% { transform: rotate3d(0,0,1,360deg) }
}

然后你会发现,它转起来,还是那么奇怪!!!
呵呵呵呵呵呵呵呵呵呵。。。。。。

然后,老夫又试了几种办法,最终发现有两种方法可以正常转动

一种就是不用 3D 加速,直接变成这样

@keyframes rotate{
    from{ transform: rotate(0deg) }
    to{ transform: rotate(360deg) }
}

呵呵,这样竟然。。。。。

但是我知道有些强迫症肯定还是要用 3D 加速,那么,你可以试试这样

@keyframes rotate{
    0%   { transform: rotate3d(0,0,1,0deg) }
    25%  { transform: rotate3d(0,0,1,90deg) }
    50%  { transform: rotate3d(0,0,1,180deg) }
    100% { transform: rotate3d(0,0,1,360deg) }
}

是的!就是这么奇葩!加上一个 25% 的关键帧,它喵的就正常了?!!!

至于具体是设么原因造成的,额。。。。 我真的没查到 QAQ~
我猜是 safari 的 bug 吧。。。

如果有知道的朋友,希望能留言告诉我一下,多谢了~

OK,今天就先这样吧! 好想吸毒~ QAQ

补一篇文章,攒了好几篇想写的内容,今天先补一篇,然后慢慢补

单元测试这种东西大家都很熟悉了,它可以很方便的帮助你进行自检,增加代码的稳定性,同时可以帮助我们完成持续集成。mocha 大家也比较熟悉了,这里也不说了

先说一下 istanbul 这货吧,没错,就是伊斯坦布尔,其实就是一个检测代码覆盖率的一个工具,然后 Github 长这样儿 istanbul

这货可以帮你检测你各个地方的 js 代码,服务端、浏览器端都可以,也可以生成多种格式的检测报告。好吧,我知道你们对这个不感兴趣,那就直接来试试喽~

npm install -g istanbul

安装完成后,我们就可以去我们的代码目录试一下啦~

istanbul cover test.js

如果你在使用 mocha 也可以这样:

istanbul cover mocha

然后你就会发现,你的文件夹下出现了一个 coverage 的文件夹。对了!这就是我们的检测报告~

报告里会标注你的测试覆盖率以及那些代码没有被覆盖到,是不是感觉一下子就清楚了要完善什么了呢,哈哈哈哈哈

然后有小伙伴会问,那么,如果我想给 mocha 设置参数怎么办? 因为根据我们的常识可以知道,命令后面的空格分隔的参数是会传给这个命令的,也就是说如果我们直接在后面这样跟参数:

istanbul cover mocha --reporter spec

后面的参数是会传递给 istanbul 而不会传递给 mocha,那么我们该怎么办呢?

很简单,只要加上一个 -- 就好了~

istanbul cover mocha -- --reporter spec

istanbul 会把 -- 后面的参数传递给 mocha,这样就可以啦~

等等,话说你叨叨了这么大半天,不是说好测试 Babel 应用的么?怎么连一句话都没有提到?

好吧,下面就聊一下测试 Babel 的坑,首先是 mocha 测试 Babel 应用,这个很简单,Babel 官网就直接有文档,就先不说了,地址在这里 Babel + Mocha

然后就是 istanbul 加进来的时候你就会发现,特喵的根本不行!

是的,没办法,因为它现在还不支持 Babel 的检测,不过还好,istanbul 已经在做这方面的工作了,所以我们可以用它的测试版来完成我们的覆盖率检测~

npm install -g [email protected]

alpha.2 了,感觉离正式版也不太远了~

然后你就可以愉快的和 Babel 玩耍了~ ( 切~ 感觉好失望~

好吧,其实就是这样。 那么如果我想要再好玩一点儿,我们想在 travis 帮我们做持续集成的时候,顺便我我的检测报告发送到我的 codecov 上面去,我该怎么办呢?

嘿嘿~ follow me ~

travis.yml

...

before_script:
    - npm install [email protected]

script:
    - node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --compilers js:babel-register --require babel-polyfill

after_success:
    - npm install codecov
    - ./node_modules/.bin/codecov

嗯嗯,就是这样喽,很简单吧~

为了美好的(gai)明(xu)天(qiu),快去把你的代码加上测试吧~

晚安喽~

好吧,一如既往的短篇记录性文章,记下坑供查阅

原因大概是这样的,公司有很多内网的服务系统,同属于同一个主域,但是是不同的子域,然后呢,当在一个系统需要调用另一个系统的时候,就会出现跨域的问题。所以呢,我们打算写一个通用代理程序来做中转,然后呢,先简单贴一下代码吧

var server = http.createServer(function(request, response) {
    ...

    var options = {
        hostname: address.hostname,
        port: address.port || 80,
        path: address.path,
        method: request.method,
        headers: request.headers,
    };

    options.headers.host = address.host;

    var proxy = http.request(options, function(res) {

        for(var key in res.headers) {
            response.setHeader(key, res.headers[key]);
        }

        response.setHeader("Access-Control-Allow-Origin", "*");

        res.pipe(response);
    });

    request.pipe(proxy);
});

嗯嗯,大概就是这样的

但是,马上我们就发现了问题,是的,事情总不会这么顺利。接下来我们发现,因为这个代理 Server 和 web 也不是同一个子域,所以虽然我们添加了允许跨域的头,但是会发现,发送到后端的 POST 跨域请求并没有携带 cookie 信息,然后也就没有办法得到另一个服务系统的认证。

OK,当我们手动在 ajax 请求中把 cookie 添加到头信息中的时候,像下面这样

$.ajax({
    ...
    headers: {"Cookie": document.cookie},
    ...
});

但是浏览器会提示这是一个不安全的操作,然后就。。。。 华丽丽的拒绝掉了

然后我们试着用 withCredentials 带上 cookie,大概是这个样子

$.ajax({
    ...
    xhrFields: {
        withCredentials: true
    }
});

然后呢,我们需要在我们的 Server 返回的时候加上一条头信息

response.setHeader("Access-Control-Allow-Credentials", true);

然后,是的,你会发现浏览器又给你报了一条错误,提示你 Access-Control-Allow-Origin 不能用 * 通配符。

听起来好像很合理,如果你想用一个域的 cookie 来认证的话,就指定一下这个域,很合理的要求,那么,当我们的想写一个通用的代理程序,希望任何一个域名都可以通过它的 cookie 来认证它要代理的 API 接口时,要怎么办?

听上去好像不是一个合理的需求,当时貌似,还真的有办法,就是再返回的时候,把请求的域填写进头信息里,就是谁访问我,我允许谁,代码大概就是这样的

response.setHeader("Access-Control-Allow-Credentials", true);
response.setHeader("Access-Control-Allow-Origin", request.headers.origin);

嗯嗯,就是这样啦,虽然感觉最后并不是一个很正确的方式,但是也算是解决了我们奇葩的需求了

今天就先这样吧,拜拜~

小问题,简单记一下

今天用 marquee 标签做滚动的时候,在 Chrome 下看效果没有问题,但是后来发现在手机微信上,每当内容滚动过 marquee 宽度的距离以后,就会重置重新从头滚动,这一点让我着实蛋疼了一下。。。。

然后后来排查的时候发现,是因为我在样式里给 marquee 设置了宽度(因为不想整个屏幕宽度滚动,所以就设置了一下宽度),然后就出现了问题,这个宽度被继承到 marquee 里面的滚动块上了,所以导致当滚动过这个距离以后,marquee 会以为滚动内容结束了,从而又重新滚动。

那么,当我们确实需要改变 marquee 的宽度的时候怎么办呢?

哈哈,给它外面加上一个 div,然后给 div 指定宽度就可以了

<style>
#marquee{
    width: 300px;
    ...
}
</style>
<div id="marquee">
    <marquee behavior="scroll" direction="left">
        我是内容我是内容我是内容我是内容
    </marquee>
</div>

嗯嗯,大概就是这样啦~

--------- 分割线诈尸 -----------

目前微信已经更新了 X5 内核的版本,已经不再支持 <marquee> 标签,所以做这种滚动的效果还是使用 Animation 来做喽~

是的,我又用上 sass 了,新公司在用,所以就又写 sass 了。

然后今天遇到的小问题,随手记一下

当我们用 sass 写如下代码的时候

.class{
    $padding: 5px;
    width: calc(100% - $paddding * 2);
}

嗯嗯,看上去好像很正常。。。

错!! 编译完了之后,你会发现这段代码是这样的

.class{
    width: calc(100% - $padding * 2);
}

然后浏览器就无情的忽略掉这一行谜一样的样式了

这尼玛是什么呀?! 摔!

好吧,看样子 sass 在编译的时候,把括号里面的变量当做字符串处理了。 所以我们该怎么办呢?

哈哈哈,对!还记得 sass 怎么在字符串里拼接变量么? 就是它!

.class{
    $padding: 5px;
    width: calc(100% - #{$paddding * 2});
}

OK,解决!

事件起因源于对我之前写的一个小工具的再优化,工具遇到的场景大概是这样的:

工具需要在用户点击完按钮后做大量的循环操作(这里暂时没有算法层的优化),大概有 6 万次,然后与此同时,界面上还需要显示出当前循环进行的程度,百分比。大概是这样的:

setInterval(function() {
    // trigger progress...
}, 16);

for(var i=0; i<60000; i++) {
    // do something...
}

这里在之前就遇到一个问题,就是当 js 去在一个函数里进行循环操作时,js 会被卡住,因为是一个同步过程,所以界面的更新事件会在循环进行完以后再触发执行,这当然不是我们想要的。然后后来,我就把它变成了下面这种递归模式:

function loop() {
    // do something ...
    
    setTimeout(loop, 0);
}

这样的话,就把每次循环变成了一个异步事件,等上次循环执行完后,再把下次循环的事件挂到事件队列上,等待下次 event loop 执行。这样就使得界面更新事件可以正常在循环进行中的时候触发了。

然而这样又出现了另一个问题,就是循环执行速度极慢(详情可脑补《疯狂动物城》的树懒。。。)

好吧,其实分析一下就可以得知,当我执行完一次函数的时候,才把下次循环挂到事件队列上,也就是下次循环的执行时间最短是下次 event loop 取事件队列的间隔时间,所以就导致速度很慢。

OK,那我把所有循环都做成异步事件,一次性都挂到事件队列上呢?

for(var i=0; i<total; i++) {
    setTimeout((function(count){
        return function() {
            // do something...
        }
    })(i), 0);
}

然后呢?我得到的结果是并不能到达我们想象的样子。这些事件会被快速执行,但是界面更新事件只会触发一次,然后就等全部循环执行完成后再次触发,有点儿类似同步循环的阻塞,但是我试了一下,要比同步的阻塞写法慢一些。

好吧,其实这里想一些也就明白了,我这里是将所有的异步事件一次性挂到事件队列上面,导致这一次的事件队列特别长,所以 js 会等这一次的事件全部处理完成后,再去取下次的队列,也就变成了类似同步的写法,只是中间应该会耗费一些事件去切换事件处理吧。

那么再然后呢?怎么样才能做到既能快速进行运算,有不影响到其他事件的触发呢?

最后我试着把循环拆成了若干个小循环去完成,大概是这样:

function loop() {
    for(var i=current; i<total && i<current+200; i++) {
        // do something...
    }
    current = i;
    
    setTimeout(loop, 0);
}

这样就是每次异步函数只执行 200 次,然后把下次的循环挂到下次的事件队列里,这样界面更新事件就有机会出现在下次的事件队列中,从而得到触发。

事情就此解决,也从这件事情中,更明白了一些关于异步和 event loop 的东西:

  • 单个函数内部的执行是同步的,影响到后面函数的执行
  • js 会在全部执行完一次 event loop 的异步事件后,再去取下次的队列,也就是单次队列里面的异步函数如果耗费大量时间的话(不管是因为单个函数耗费大还是因为所有函数个数耗费大),都会影响到下次取队列的时间
  • js 是单线程的,即使你是异步写法,到后面也是同步执行,所以要更好的优化性能的话,就尽量的让 js 在每次的 event loop 的间隔时间里,尽可能多的进行运算。如果运算时间实在太长,就合理的拆成小段儿来填充进每一个 event loop 里面

最后这里记篇文章:

How JavaScript Timers Work
是安然君推荐给我的,把 event loop 的运行机制讲的很明白,个人感觉很好,有空儿尝试翻译一下~

嗯嗯,大概就是这样吧,本文其实少了很多图示,(好吧,其实是因为我太懒了。。。),大家可以去看推荐的文章,讲的很清楚。

本文中如果有不对的地方,欢迎指正,一起学习~

嗯嗯,就这样吧,晚安~