<你不知道的Javascript>笔记

来源:互联网 发布:淘宝仿真金属狙击枪 编辑:程序博客网 时间:2024/06/05 17:41

前言

Javascript本身是一门非常灵活的语言,如果是先有写过编译型语言的再来看看它,简直是灵活到你可以接近”瞎写”的程度还不会出错.

然而凡事皆有双面性,它的灵活性背后是无数的坑.Javascript设计的时候,估计也没有考虑到会发展成今天这个规模,更是没有想到它居然可以用在后端.

很多在前端设计中无所谓的坑,放在后端就是灾难.这本书非常详细的介绍了那些Javascript中看似古怪的设计及其背后的深层次原因,值得一读.

这份笔记里将记录一些我认为比较有实践意义的东西.对于那些实际代码中应该抛弃的奇怪写法,不做深究.

类型转换

js本身规定了六个基本类型,在必要的时候,会发生类型转换.

let a = 42;let b = a + ""; let c = String(a);

第二和第三个赋值操作时,都发生了类型转换,前者是隐式,将a强制转换为string类型,后者是显式.

转为string

基础类型转化规则定义如下:
* null: “null”
* undefined: “undefined”
* boolean: “true”/”false”
* number: 通常和常规数学表达一致.有个特殊情况,在数字很小或很大的时候,将会转换出指数形式的结果,比如”1.07e21”

JSON.stringify()

所有JSON安全的值都可以用这个方法转为字符串.以下情形不属于JSON安全的对象:undefined, function, symbol以及循环引用.遇到前三者时会自动忽略,而循环引用会报错.
以下代码可以创建一个具有循环引用的JSON对象:

let a = {"prop0": "safe"};a.prop1 = a;

JSON.stringify()还具有一个可选的参数,叫做replacer.这个参数可以传两种类型的值进去,数组和函数.
* 传入数组的时候,这个数组里元素必须是字符串.它指出了JSON对象中要处理的键.未在该数组中列出的键会被忽略.如:

let a = {    "b": 0,    "c": 1,    "d": 2};JSON.stringify(a, ["c", "d"]);
  • 传入函数的时候,对象的每个键会调用一次,同时对象本身也会调用一次.

转为number

true转换为1,false转换为0(反过来好像也是这样).undefined转换为NaN,null转换为0.

转为boolean

ES5中定义了转换为boolean类型时的规则.转换结果为false的如下:
* undefined
* null
* false
* +0 -0 NaN
* ""

除此以外,其它所有的东西,转换结果都是true.包括空对象,空数组这些.

显式转换

string & number

字符串转数字,如果要用类型转换的方式来做,如下:

let c = "3.14";let d = 5+ +c;

这其实是一种可读性很差的写法,我个人并不推荐这么写.书里还举了一个疯狂的例子:

1 + - + + + - + 1;  //  What the fuck?

还可以调用构造函数Number()进行转换.

另外,用的比较多的parseInt()也是一个办法,不过它的准确定义应该是解析字符串,而非类型转换.parseInt()函数还有第二个参数,指定了转换数字的目标进制.比如要字符串是十六进制表示,如下:

let a = "100";parseInt(a, 16);

注意,这里是将100作为一个十六进制数来看待,而并不是将100作为一个十进制数看待,返回转十六进制的结果.

与之相反的,是toString()方法,它也可以附带一个参数,将Number类型的数据转成字符串并以指定的进制输出.一个有趣的应用是产生随机字符串:

(Math.random()*100000000000).toString(36).replace('.', "");

这段代码的原理就是36进制的表示恰好是0-9和a-z的完整集合,转成字符串之后,字符串里只会包含数字和小写字母.

| 和 ~运算符

~运算符会对一个32位有符号整数执行按位取反操作,包括符号位.如果目标参数不是整数,则先强制转换为整数,再执行取反.

|会对一个32位有符号整数执行按位或操作.如果参数不是整数,同样转换为整数再运算.

如下两个应用会实现取整的效果:

let a = 1.123;let b = 0 | a;

由于0和任何数按位或都是另一个数本身,因此这里返回的就是a取整的结果.

let a = 1.1415926;let b = ~~a;

第一个~首先将a取整后取反.a取整的结果是0x00000001,按位取反后是0xfffffffe,即-2的补码表示.第二个~对0xfffffffe进行按位取反,就得到了取整的结果0x00000001.

这种写法看似简洁,并且使用了位运算.一个通常的说法是使用位运算效率会高很多.对于编译型语言,结论成立.对于解释型语言,打问号.看如下的代码:

let a = [];for (let i = 0; i < 100000; i ++)   {    a.push(Math.random() * 1000000)}let ts = Date.now();for (let i = 0; i < 100000; i ++)   {    ~~a[i];}console.log(Date.now() - ts);ts = Date.now();for (let i = 0; i < 100000; i ++)   {    Math.floor(a[i]);}console.log(Date.now() - ts);

在我的机器上跑下来的结果是,前者26ms,后者3ms.当然,这个结果会随着不同的node版本会有差异,但已经足够证明一点:不要迷信那些在传统编译型语言中惯用的优化手段.