Secbone 发布的文章

PS1/PS2/PS3/PS4 及 PROMPT_COMMAND 的意义

  • PS1: 命令行提示符
  • PS2: 延续命令行提示符
  • PS3: 脚本中 select 语句提示符
  • PS4: 调试模式下脚本命令提示符 (set -x 提示所执行命令)
  • PROMPT_COMMAND: Bash shell 在显示 PS1 提示符前所执行的命令

提示符转义码

Bash shell 允许用户在提示符中使用以下转义符号:

  • \a: 响铃 (ASCII 0x07)
  • \d: 日期 (格式: 星期 月 日)
  • \D{format}: 以指定格式显示当前日期 (格式字符串将传递给 strftime() 函数,若为空则使用本机标准格式)
  • \e: ESC, (ASCII 0x1B)
  • \h: 本机名称 (从左至右直到遇到 '.')
  • \H: 本机名称
  • \j: shell 当前正在处理任务号
  • \l: shell 终端设备基本名称
  • \n: 换行 (ASCII 0x0A)
  • \r: 回车 (ASCII 0x0D)
  • \s: shell 名, $0 的基本名
  • \t: 当前时间, 24小时制, 格式为 HH:MM:SS
  • \T: 当前时间, 12小时制, 格式为 HH:MM:SS
  • \@: 当前时间, 12小时制, 格式为 HH:MM am|pm
  • \A: 当前时间, 24小时制, 格式为 HH:MM
  • \u: 当前用户名
  • \v: Bash 版本
  • \V: Bash 发行号 (版本 + 补丁级别)
  • \w: 当前路径, $HOME 将会被缩写为 ~
  • \W: 当前路径的基本名 (最后一级文件夹), $HOME 处理同上
  • \!: 当前命令的命令历史记录编号
  • \#: 当前命令的编号
  • \$: 如果当前用户 ID 为 0 (超级用户), 则显示 #; 否则显示 $
  • \nnn: 八进制数值 nnn 对应的 ASCII 字符
  • \\: 反斜杠 \
  • \[: 标识不打印字符串的开始
  • \]: 标识不打印字符串的结束

在shell中使用不同颜色输出文字

在 shell 中,可以通过转义序列 [<attr>;<fgcolor>;<bgcolor>m 设置文字的显示属性 (可选择分别或组合设置显示属性/前景色/背景色)。如果要还原字符显示方案 (显示属性/前景色/背景色) 为系统默认,可以使用 \e[m 完成。

attr 控制文字的修饰效果 (终端并一定全部支持)

  • 0: 默认
  • 1: 加亮
  • 2: 变暗
  • 4: 下划线
  • 5: 闪烁
  • 7: 反色显示
  • 8: 隐藏文字
  • 9: 删除线

fgcolor 控制文本颜色

  • 30: 黑色
  • 31: 红色
  • 32: 绿色
  • 33: 黄色
  • 34: 蓝色
  • 35: 紫色
  • 36: 青色
  • 37: 白色

bgcolor 控制文本颜色

  • 40: 黑色
  • 41: 红色
  • 42: 绿色
  • 43: 黄色
  • 44: 蓝色
  • 45: 紫色
  • 46: 青色
  • 47: 白色

显示属性/前景色/背景色定义在不同的数值区间,这样作的好处是不用显示指明颜色的作用域,系统也会从颜色数值推断出用户想要设置的究竟是属性/前景色/背景色。例如,\e[9;41m 是要设置显示属性和背景色,保持前景色不变; \e34m 仅设置前景色。

转义字符ESC的输入:

  • shell 中,如果想输入 ESC (转义) 字符,可以先按 ctrl+v 键后,再按下 ESC 键,系统会显示出 ^[ 表示转义字符输入完成
  • echo 命令中,使用 -e 选项允许转义字符后,可以输入\033\e 表示转义符 ESC
  • printf 命令中,直接使用 \033 即可表示转义符 ESC

光标控制

设置 PS1

PS1 控制着终端中系统默认提示符的格式 (一般系统默认为 \u@\h:\w \$ )。我们可以在PS1控制字符串使用 Bash 预定义的转义符号加入如用户/当前路径/主机名等信息,还可以使用转义字符串控制提示符的颜色。

export PS1='\[\e[0;32m\][\u@\h \w \$]\[\e[m\]'

设置 PS2

PS2 控制终端中命令延续行的格式,一般系统默认为单字符>。和 PS1 一样,可以使用字符/预定义转义字符/转义序列等进行定制。

export PS2='\[\e[0;32m\]~>\[\e[m\]'

设置PS3

PS3 用于控制 shell 中提示用户进行选择的提示符,一般系统默认为 #?

[user @ dir] $ cat ps3test.sh
select i in mon tue wed thu fri exit
do
case $i in
mon) echo "Monday";;
tue) echo "Tuesday";;
wed) echo "Wednesday";;
thu) echo "Thusday";;
fri) echo "Friday";;
exit) exit;;
esac
done
[user @ dir] $ ./ps3test.sh
1) mon
2) tue
3) wed
4) thu
5) fri
6) exit
#? 6
[user @ dir] $ export PS3='Enter your choice: '
[user @ dir] $ ./ps3test.sh
1) mon
2) tue
3) wed
4) thu
5) fri
6) exit
Enter your choice: 6

设置 PS4

调试模式下脚本命令提示符,用以将命令和命令执行结果区分开。系统默认为 +。同样,可以使用各种字符/预定义符号/转义控制序列去设置 PS4,此外,系统还提供了两个可用于 PS4 的变量 $0$LINENO,分别表示脚本名和当前行号。

[user @ dir] $ cat ps4test.sh
#!/bin/bash
export PS4='\[\e[0;37;44m\]$LINENO@$0:\[\e[m\]'
set -x
ls -l ~ | wc -l
du -s ~/Music
 
[user @ dir] $ ./ps4test.sh
5@./ps4test.sh:ls -l /home/user
5@./ps4test.sh:wc -l
13
6@./ps4test.sh:du -s /home/user/Music
4 /home/user/Music

PROMPT_COMMAND

PROMPT_COMMAND 是 Bash shell 在显示 PS1 提示符前所执行的命令字符串。

export PROMPT_COMMAND="echo -n [$(date +%H:%M:%S)]"

显示:[23:25:38] [user@~/workspace/temp] $

---- 整理自 Bash prompt basics

その日、人类は思い出した。
やつらに支配されていた恐怖を…
鸟かごの中に囚われていた…屈辱を…

故事发生在某个东方神秘国度,这里的人们由于一些神秘力量的影响,他们每天吃的东西不是普通是食物,而是数据信息。他们生活的区域被一堵看不见摸不着的墙包围着。你可能会问:既然看不见也摸不到,那人们是怎么知道有这样一堵墙呢? 关于这个问题,并没有文字资料可以证明它的存在,不过生活在其中的人们却分明感受到,它,一直在...

起源

据一个不存在的网站记载,这堵墙起步建造于 1998 年,然后一直到 2002 年的时候,有一名西方记者在一篇文章中描述了这堵墙,文章中用了 Great Firewall 来称呼它,后来就有了 GFW 这个缩写来代指它并广为流传。

像其他所有的城墙一样,GFW 的主要作用也是防御外部的威胁和对来往的旅人进行盘查。不过不同的是,GFW 防御和盘查的不是人,而是信息,也就是人们每天吃的粮食。它主要过滤一些 政治敏感内容、色情 和 青少年犯罪 等内容,当然还包括一些国家敏感信息的监控。而这些数据如果一不小心没有被过滤到被墙内的人误食了,可能会导致人们 情绪异常、身体不适 或者其他更大的危险。

那么这样一堵庞大精细的墙究竟是怎么样工作的呢?

主要技术手段

因为之前提到的,因为 GFW 是一种看不见摸不到的存在,并且并没有任何官方数据,所以目前并没有很确切的数据来解释它的工作原理。不过根据一些外在的信息表现,我们可以推测总结出几种工作手段。为了方便解释和理解,我们把他们抽象成我们现在网络体系下存在的技术进行说明,请勿对号入座哦~

IP 地址封锁

第一种最常见的技术就是 IP 地址封锁,可以对需要通过网关的数据包进行 ACL(Access Control List)检查来实现对指定 IP 地址的数据包进行拦截和丢弃。

例如,当我们跟踪访问某 G 站的数据包时,可以拿到类似下面这样的数据:

$ traceroute -d 218.189.25.131
traceroute to 218.189.25.131 (218.189.25.131), 64 hops max, 52 byte packets
 1  192.168.1.1 (192.168.1.1)  1.545 ms  1.860 ms  1.278 ms
 2  100.64.0.1 (100.64.0.1)  52.488 ms  4.257 ms  3.784 ms
 3  202.105.153.181 (202.105.153.181)  3.798 ms  4.238 ms  3.979 ms
 4  183.56.65.54 (183.56.65.54)  4.992 ms
    183.56.65.50 (183.56.65.50)  6.833 ms
    183.56.65.54 (183.56.65.54)  9.435 ms
 5  202.97.94.126 (202.97.94.126)  7.965 ms * *
 6  202.97.94.90 (202.97.94.90)  13.816 ms
    202.97.94.98 (202.97.94.98)  17.447 ms
    202.97.94.94 (202.97.94.94)  11.026 ms
 7  202.97.63.106 (202.97.63.106)  13.678 ms  14.872 ms  15.898 ms
 8  202.97.62.110 (202.97.62.110)  115.336 ms  113.077 ms  113.486 ms
 9  * * *
10  * * *
11  * * *
12  * * *
13  * * *
14  * * *
15  * * *
16  * * *

从结果中我们可以看到,数据包在经过 202.97.62.110 这个地址以后就没有了响应,也就是这个地址上很有可能做了针对数据包的过滤处理,从而使得数据包没有正确的被投递。

同样的,不只是 IP 地址,也可以针对某些特殊端口进行 ACL 过滤。

当然,作为这个神秘国度的神秘项目,GFW 也在不断的升级,现在对于 IP 封锁的手段已经替换为 黑洞路由 技术。简单点解释就是说,网关不再是把需要封锁的 IP 地址写在 ACL 里面,而是写在正常的转发列表里,只是通过动态路由技术为其指向一个错误的地址。也就是说,当墙内的人们向外投递一张含有违禁品的食物订单时,订单经过 GFW 的转发,会随机投递给一条错误的供应链,而这条链路上找不到订单上的东西,这张订单就会被当做废纸,被遗弃在世界的某个角落。对于 GFW 来说,它做的只是一个正常的转发动作,所以效率比 ACL 要高很多。

DNS 污染

我们知道,当我们访问一个网址的时候,首先需要向 DNS 服务器获取这个域名所对应的 IP 地址,才能再发出正式的请求。DNS 污染就是修改或者删除 DNS 服务器上某些特定域名的记录,从而使得数据不能发出或发向错误的地址。

比如我们查询某 G 站的 IP 的时候:

$ nslookup www.google.com
Server:        114.114.114.114
Address:    114.114.114.114#53

Non-authoritative answer:
Name:    www.google.com
Address: 93.46.8.89

记录返回 IP 地址为 93.46.8.89,而我们查询后发现这并不是一个正确的 G 站的地址,而是一个错误的地址。所以我们发出去的包并不会正确的到达 G 站。

而对于墙内的人们来说,他们的情况也是这样的。而且很巧合的是,在这个神秘国度一共 3 台 DNS 镜像服务器,一台在王城,一台在特政区,还有一台在宝岛。由于一些历史原因,后两台并不受 GFW 管理,所以只有在王城的那一台会被污染。所以当人们需要购买某些食材的时候,会被王城的回包告知 “并没有这种食材”。

TCP 连接重置

TCP 重置是指在建立 TCP 加密连接的时候,在握手阶段伪装成对方向连接的两段发送 RESET 数据包,从而导致连接无法创建。

正如墙内的人们所遇到的问题一样,当人们想创建一份秘密的订单来订购违禁品时,GFW 会在双方协商密码的时候伪装成对方,并说:“对方不想和你说话,并向你扔了一个 RESET 包!”,从而导致无法创建秘密订单。

据记载从 2015 年升级以后,这种手段已经开始被 黑洞路由 技术所代替。

关键词阻断

关键词阻断是指当数据信息中包含某些特殊关键词时,会触发阻断机制,中断本次连接。

所以当人们的订单中含有某些特定关键词的时候,GFW 就会伪装成对方发送 RESET 包,来中断本次连接。这里并不是直接丢弃订单或者使用 黑洞路由 技术的原因是,如果丢弃数据包,人们会以为是交通的问题导致订单没有送到,从而不断的发送重试,导致压力增加。而使用 RESET 包的话,人们会认为是对方终止了交易,就不会再重试了。

加密连接干扰 和 深度包检测

GFW 除了我们常见的一些网络手段之外,还有一些比较特殊的技巧。比如通过发送一些探针对各个供应商进行刺探,来确定是否是一个正常的供应商,还是一个代理供应商。同时还会对某些特殊的加密订单进行干扰,使订单成功率降低,从而降低违禁食品交易的成功率。

当然,随着 GFW 的不断升级,它还引入了一些机器学习的方法,对每天经过的大量的信息进行审查、训练,从而提取出异常信息的特征,比如握手特征、连接时长、数据量、时间、地点等等,当这些特征再次出现时,就可以快速有效的进行拦截和过滤。

也就是说,GFW 正在变得越来越精准,越来越智能。

墙内

回到我们刚才的故事。由于 GFW 的日渐强大,墙内的人们也开始越发得难以吃到那些墙外的食品,因为说实话,那些 “过审的食品” 有时候确实不怎么营养。那些偷运食物的人一个接一个的被卫兵带走,送进来的食物种类也越来越少。

由于长期的营养不良,很多人对于颜色的感知逐渐退化,变得只能分辨红光,红红的旗子,红红的眼睛,红红的血,红红的心...

还有一些还没有退化的人们,每天盼望着,盼望着更营养的食物到来的那天...

今天在写一个倒计时的时候,突然想起一个问题,“对于 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 替换成别的标签,暂时避免使用它喽,等苹果修复好再说。。。

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

晚安~

话说最近比较大的是应该就是 vim 更新出了一个时隔 N 年的版本 8.0,其实我不用 vim 也好长时间了,也只有在登录远程服务器或者一些比较特殊的情况下才用用 vim,但是这依然不影响 vim 在我心目中的地位,我认为它依然是一个很棒的编辑器。那么它这次的更新自然也不会错过,忍不住赶紧更新一下自己的 vim。

mac 是默认装有 vim 的,我的貌似是 7.4 版本,那么下面就开始我的神奇的更新之旅。。。

在 mac 下更新这种命令行工具,最自然想到的就是 homebrew,赶紧来试一下

brew update
brew install macvim

嗯嗯,貌似一切很顺利,但是,这样并不行,你再次查看 vim 版本的时候发现,还是 7.4

这是因为系统自带的 vim 是在 /usr/bin/vim 这里,而 homebrew 安装的是在 /usr/local/bin/vim 这里,所以并没有覆盖掉系统的 vim,所以现在调用的还是系统版本的 vim

那么,要怎么办呢?

通常就是两种办法,一个是在 .bashrc 或者 .bash_profile 里面覆盖一下环境变量,像这样

export PATH=/usr/local/bin:$PATH

但是我个人总觉得这样不够优雅,那么第二种办法呢,就是把旧文件改下名字,这样系统在 /usr/bin 下找不到,就会自动再到 /usr/local/bin 下来找,比如

mv /usr/bin/vim /usr/bin/vim74

但是,当我执行重命名的时候,会提示我没有权限,sudo 也不行,好吧,看样子也是费了。。。

那就没有办法更新了么?

肯定是可以的,可以用 homebrew 的 --with-override-system-vim 来安装

然后试一下

brew install macvim --with-override-system-vim

是的,它喵的又报错了!

提示需要 Xcode。。。。

EXCUSE ME?! 我要是想装 Xcode 我还要 vim 干嘛!

原因是要覆盖系统自带的 vim,需要重新编译,而在 mac 上编译文件,需要 Xcode.....

所以,你是要我为了装一个 4M 的 vim 而去安装一个 4G 的 Xcode ?!

呵呵

正常人怎么可能会去装?!!

反正最后

...

我装了....

事情起源于一次 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);

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

今天就先这样吧,拜拜~