你不知道的javascript————类型和语法

类型

七个内置类型

  • 空值 (null)
  • 未定义 (undefined)
  • 布尔值 (boolean)
  • 数字 (number)
  • 字符串 (string)
  • 对象 (object)
  • 符号 (symbol)

检测各个类型

1
2
3
4
5
6
7
8
9
typeof undefined		=== "undefined" 	// true
typeof true === "boolean" // true
typeof 42 === "number" // true
typeof "42" === "string" // true
typeof {"life": 42} === "object" // true
typeof Symbol === "symbol" // true
typeof null === "object" // true
typeof function(){} === "function" // true
typeof [0,1] === "object" // true

本身null对象里面代表空值,所以其为object也是合理。但应该typeof null 返回是 null才符合常理。由于这个bug在许多代码中已经这样做了,所以重新修回会导致更严重的bug。因此被修回的可能性很小。
因此,对null应该采用复合查询语句

1
2
var a = null;
(!a && typeof a === "object") // true

而对于typeof function(){} === “function” 而言,因为本身function是object的一个子类型,具体的说,函数是一个可调用对象。
且typeof [0,1] === “object”,因为数组也是object的一个子类型

ps:由于所有的typeof都会返回一个string,所以 typeof typeof 42会是”string”,因为typeof 42首先会变成一个“number”,是一个string类型,所以typeof “number”是一个string

undefined && undeclared && typeof undeclared

1
2
3
4
5
6
7
8
9
var a;
typeof a; // "undefined"
var b = 42;
var c;
b=c;
typeof c // "undefined"
typeof b // "undefined"
a // "undefined"
d // "VM422:1 Uncaught ReferenceError: d is not defined at <anonymous>:1:1"

从上述可以看出,undefined 和 undeclared 是两码事。undefined意思是定义但未赋值,或者赋值为undefined,而undeclared是未定义。因此两者不能画等号

typeof undeclared

1
typeof a			// "undefined"

出现这种原因因为typeof有一个特殊的安全防范机制,因为多个脚本文件会在共享的全局变量命名空间中加载变量。如果typeof一个未命名的报错,会导致整段程序停止运行。对于typeof来检查undeclared变量,有时是一个不错的办法。

数组

js的数组可以容纳任何的值,甚至可以是另一个数组,因此多维数组就是这种方式来实现的。
由于数组本身就是一个特殊的对象,所以数组也可以包含字符串键值和属性,但是这并不计算在数组长度内。

1
2
3
4
5
6
7
8
var a = [];
a[0] = 1;
a["foobar"] = 1;
a.length // 1
a["foobar"]; // 1
a.foobar // 1
a["13"] = 42;
a.length // 14

为什么会这样呢?由于本身数组就是一个对象的子集合,因此在[]中,使用十进制字符串数字会直接强制类型转化成数字。a[“13”]就变成了a[13],因此在数组内会直接将其长度变化成14。同理,在属性给foobar加到a数组中,因为数组的界定是有数字来确定下标位置,而length是最后一个下标数字+1,因而加入的非数字就不在长度里面了。

类数组

对于es5而言,可以用slice,concat来实现类数组转数组,对于es6而言,可以用Array.from()来实现从类数组转换成数组。

字符串

字符串的一些方法indexOf(),concat(),toUpperCase(),reverse()等等。
字符串的一些方法:
|方法|描述|
|—|—|
|charAt() | 返回指定索引位置的字符|
|charCodeAt() | 返回指定索引位置字符的 Unicode 值|
|concat() | 连接两个或多个字符串,返回连接后的字符串|
|fromCharCode() | 将 Unicode 转换为字符串|
|indexOf() | 返回字符串中检索指定字符第一次出现的位置|
|lastIndexOf() | 返回字符串中检索指定字符最后一次出现的位置|
|localeCompare() | 用本地特定的顺序来比较两个字符串|
|match() | 找到一个或多个正则表达式的匹配|
|replace() | 替换与正则表达式匹配的子串|
|search() | 检索与正则表达式相匹配的值|
|slice() | 提取字符串的片断,并在新的字符串中返回被提取的部分|
|split() | 把字符串分割为子字符串数组|
|substr() | 从起始索引号提取字符串中指定数目的字符|
|substring() | 提取字符串中两个指定的索引号之间的字符|
|toLocaleLowerCase() | 根据主机的语言环境把字符串转换为小写,只有几种语言(如土耳其语)具有地方特有的大小写映射|
|toLocaleUpperCase() | 根据主机的语言环境把字符串转换为大写,只有几种语言(如土耳其语)具有地方特有的大小写映射|
|toLowerCase() | 把字符串转换为小写|
|toString() | 返回字符串对象值|
|toUpperCase() | 把字符串转换为大写|
|trim() | 移除字符串首尾空白|
|valueOf() | 返回某个字符串对象的原始值|

如果需要经常一字符数组的方式来处理字符串的话,倒不如直接用数组。这样就不用在字符串和数组之间来回折腾。可以在有需要的时候使用join(“”)来将字符串数组转换为字符串

数字

和大部分编程语言一样,js中的数字是基于IEE754标准来实现的。该标准通常也被称为“浮点数”。而js使用的是双精度单位(64位)格式。所以也会有iee754标准的通病,即浮点数之间相加会有奇妙的现象。
数字的一些方法:
toExponential()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = 5E10			// 可以通过这种方式赋值
a // 50000000000
a.toExponential() // "5e+10"
var b = a * a;
b // 2.5e+21
```
toFixed() // 精度
```bash
var a = 42.59
a.toFixed(1) // "42.6"
// 无效
42.toFixed(3) // Uncaught SyntaxError: Invalid or unexpected token
// 有效
42..toFixed(3) // "42.000"
(42).toFixed(3) // "42.000"
0.42.toFixed(3) // "0.420"
42 .toFixed(3) // "42.000"

因为.被视为常量42.的一部分。所以没有.属性访问运算符来调用toFixed()
toPrecision() // 执行有效位数的显示位数

1
2
3
4
5
6
7
8
9
10
var a = 42.59
a.toPrecision(1) // "4e+1"
a.toPrecision(2) // "43"
a.toPrecision(3) // "42.6"
```
es6支持新格式
```bash
0B 0b // 二进制
0O 0o // 八进制
0X 0x // 十六进制

EPSILON // 最小精度

1
2
3
if(((0.1 + 0.2) - 0.3)<Number.EPSILON){
}else{
}

MAX_VALUE
MAX_SAFE_INTEGER

1
2
Number.MAX_VALUE						// 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991

isInteger

1
2
3
Number.isInteger(1)					// true
Number.isInteger(1.1) // false
Number.isInteger(1.0) // true

特殊数值

undefined
1
2
var undefined = 2
undefined // 2

ps:永远不要重新定义undefined

void 运算符

在不需要返回值的时候,可以void掉

1
2
3
if(ready){
return void setTimeout(..)
}

这样做可以将setTimeout返回的id给void掉

NaN

NaN是一个数值型。意思指的是不是一个数值,并且NaN != NaN。可以使用isNaN来判断是否是NaN

1
2
Number.isNaN(NaN)				// true
Number.isNaN(1) // false

0值

加法和减法运算永远不会有-0
使用toString和JSON.stringify()会将-0变成0

1
2
3
4
5
6
0/-1			// -0
0/1 // 0
var a = -0
a // -0
a.toString() // 0
JSON.stringify(a) // 0

特殊等式

Object.is
Object.is 可以判断是+0还是-0,而且可以判断是否为NaN

1
2
Object.is(+0, -0)	// false
Object.is(NaN, NaN) // true

值和引用

null,undefined,字符串,数字,布尔,symbol都是简单值
对象,函数都是复杂值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function foo(x) {
x.push(4);
x; // [1,2,3,4]
x = [4,5,6];
x.push(7);
x; // [4,5,6,7]
}
var a = [1,2,3];
foo(a);
a; // [1,2,3,4]
由于一开始是引用赋值,然后x是a对应数组的一个引用,x在push一个4之后,重新引用一个新的数组,4.5.6,而a引用的数组变化成了[1,2,3,4];
function foo(x){
x.push(4);
x; // [1,2,3,4]
x.length = 0;
x.push(4,5,6,7)
x; // [4,5,6,7]
}
var a = [1,2,3]
foo(a)
a; // [4,5,6,7]
和上面一开始一样,只是后面在x.length=0后,再push进去了4,5,6,7。所以x的引用没变,还是和a引用的一样。所以a和x一同变化

ps:我们无法自行决定使用值赋值还是引用赋值,一切由值的类型决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo(warpper){
warpper.a = 42
}
var obj = {
a: 1
}
foo(obj)
obj.a //42
function foo (x) {
x = x+1;
x; // 3
}
var a = 2;
var b = new Number(a);
foo(b)
console.log(b) // 2
前者是引用赋值,后者是值赋值

原生函数

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Function
  • RegExp
  • Date
  • Error
  • Symbol

内部属性[[Class]]

所有typeof返回值为“Object”的对象(如数组)都包含一个内部属性[[Class]],这个属性通常无法直接访问,一般通过Object.prototype.toString查看

1
2
3
4
Object.prototype.toString.call([123])			// "[object Array]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"

虽然Null和undefined这样的原声构造函数不存在,但是内部Class属性值仍然是Null和Undefined。基本类型值被各自的封装对象自动包装,所以他们的内部[[Class]]属性值为Boolean。

封装对象包装

1
2
3
4
5
var a = "abc";
console.log(a); // "abc"
a.length // "3"
var b = new String("abc")
console.log(b) // String {[[PrimitiveValue]]: "abc"} 0:"a" 1:"b" 2:"c" length:3 __proto__:String [[PrimitiveValue]]:"abc"

只是创建字面量基本值的时候,并没有其他的方法。当在使用其对象方法时,需要通过封装对象才能访问,此时js会自动为基本类型值包装(box或者wrap)一个封装对象。
但是为经常用到的.length方法直接new一个对象也不是一个好办法,因为浏览器对.length这样的常见情况做了优化,直接使用封装对象来“提前优化”反而会降低执行效率。

封装对象的释疑

例如:

1
2
3
4
var a = new Boolean(false);
if (!a){
console.log(...) // 执行不到这里
}

因为建立一个a之后,这个对象得到的是真值,得到的结果和使用false相反

自行封装可以使用Object

1
2
3
4
5
6
7
8
9
10
var a = "abc"
var b = new String (a);
var c = Object(a);
typeof a // "stirng"
typeof b // "object"
typeof c // "object"
b instanceof String // true
c instanceof String // true
Object.prototype.toString.call(b); // "[object String]"
Object.prototype.toString.call(c); // "[object String]"

一般不直接使用封装对象,但是他们偶尔也会派上用场

拆封

如果想得到封装对象里面的值,可以使用valueOf函数,隐式拆封也是调用了valueOf函数:

1
2
3
4
5
6
7
8
9
10
var a = new String("abc")
var b = new Number(11)
var c = new Boolean(true)
a.valueOf() // "abc"
b.valueOf() // 11
c.valueOf() // true
var d = a + "";
console.log(d) // "abc"
typeof a // "object"
typeof d // "string"

原生函数作为构造函数

四种方式创建应该尽量避免构造函数,除非十分必要

  • array 数组
  • object 对象
  • function 函数
  • RegExp 正则表达式

    ARRAY(..)

    调用Array构造函数时,可以不需要加上new,效果一致。)且Array构造函数纸袋一个数字作为参数的时候,这个参数会当作数组的预设长度,而不是充当其中的一个元素
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 效果一致
    var a = new Array(1,2,3);
    a // [1,2,3]
    var b = Array(1,2,3)
    b // [1,2,3]
    var c = [1,2,3]
    c // [1,2,3]
    // 不同方式创建出来空数组效果不一致
    var d = new Array(3);
    console.log(d) // chrome上: (3) [empty × 3]
    d.length // 3
    var e = [undefined,undefined,undefined];
    console.log(e) // (3) [undefined, undefined, undefined]
    var f = []
    f.length = 3;
    console.log(f); // chrome上: (3) [empty × 3]
    // 直接以,创建。虽然长度是3令人费解,但是可以更好的复制粘贴
    var g = [,,,]
    console.log(g) // chrome上: (3) [empty × 3]

由于创建方式不同,导致在chrome下不一致的显示,但是更难过的是,他们有时相同,有时呵呵

1
2
3
4
5
6
var a = new Array(3)
var b = [undefined,undefined,undefined]
a.join("-") // "--"
b.join("-") // "--"
a.map(function(v,i){return i}) // (3) [empty × 3]
a.map(function(v,i){return i}) // [0, 1, 2]

a.map之所以执行失败,是因为a中是没有元素的,而b里面有undefied。
而join首先假定数组不为空,然后通过length属性值来便利其中的元素,而map并不做这种假定
可以通过这种方式来创建包含undefined单元的数组

1
2
var a = Array.apply(null, {length:3});
console.log(a) // (3) [undefined, undefined, undefined]

PS:永远不要创建和使用空单元数组

OBJECT、FUNCTION、REGEXP

除非玩不得已,尽量不要使用他们

1
2
3
4
5
6
7
8
9
10
var c = new Object();
c.foo = "bar";
c // {foo:"bar"}
var d = {foo:"bar"}
d // {foo:"bar"}
var e = new Function("a","return a * 2");
var f = function(a){return a*2};
function g(a){return a*2}
var h = new RegExp("^a*b+","g");
var i = /^a*b+/g

javascript对常量形式的代码会对他们进行预编译和缓存!

DATE、ERROR

相较于其他原生构造函数,Date、Error的用处比其他的更多,因为没有其他对用的常量形式来作为他们的替代
引入生成当前时间戳,使用

1
2
3
4
Date.now()
// 使用new来生成时间
new Date()
// Thu Jan 04 2018 06:47:59 GMT+0800 (CST)

错误对象通常与throw一起使用

1
2
3
4
5
6
function foo(x){
if(!x){
throw new Error("///");
}
// -
}

SYMBOL

Symbol可作为私有属性是一种简单标量基本类型

强制类型转换

抽象值操作

如果对象有自己的toString()方法,字符串化就会调用该方法并使用其返回值。
数组的默认toString方法经过了重新定义

1
2
var a = [1,2,3]
a.toString() // "1,2,3"

JSON 字符串化

JSON.stringify(42) // “42”
JSON.stringify(“42”) // “”42””
JSON.stringify(null) // “null”
JSON.stringify(true) // “true”

JSON.stringify()在对象中遇到undefined、function和symbol时会自动将其忽略,在数组中则会返回null

1
2
3
4
JSON.stringify(undefined)			// undefined
JSON.stringify(function(){}) // undefined
JSON.stringify([1,undefined, function(){},4]) // "[1,null,null,4]"
JSON.stringify({a:2, b: function(){}}) // "{"a":2}"

循环引用会出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var o = {};
var a = {
b:42,
c:o,
d:function(){}
}
o.e = a
JSON.stringify(a)
a.toJSON=function(){
return {b:this.b}
}
JSON.stringify(a)
# Uncaught TypeError: Converting circular structure to JSON
# at JSON.stringify (<anonymous>)
# at <anonymous>:8:6