js 深度学习 -类型 与 文法

js 深度学习 -类型 与 文法

You-Dont-Know-JS(你不知道的 js 这本书的开源版本)

You-Dont-Know-JS(你不知道的 js 这本书的开源版本)

github 国内翻译

https://github.com/JoeHetfield/You-Dont-Know-JS

掘金中文

https://juejin.cn/post/6844903478813261831

github 原帖

https://github.com/getify/You-Dont-Know-JS

Number

非常大或非常小的number将默认以指数形式输出,与toExponential()方法的输出一样,比如:

1
2
3
4
5
6
7
8
9
var a = 5e10;
a; // 50000000000
a.toExponential(); // "5e+10"

var b = a * a;
b; // 2.5e+21

var c = 1 / a;
c; // 2e-11

因为number值可以用Number对象包装器封装,number值可以访问内建在Number.prototype上的方法。举个例子,toFixed(..)方法允许你指定一个值在被表示时,带有多少位小数:

1
2
3
4
5
6
7
var a = 42.59;

a.toFixed(0); // "43"
a.toFixed(1); // "42.6"
a.toFixed(2); // "42.59"
a.toFixed(3); // "42.590"
a.toFixed(4); // "42.5900"

要注意的是,它的输出实际上是一个numberstring表现形式,而且如果你指定的位数多于值持有的小数位数时,会在右侧补0

toPrecision(..)很相似,但它指定的是有多少 有效数字 用来表示这个值:

1
2
3
4
5
6
7
8
var a = 42.59;

a.toPrecision(1); // "4e+1"
a.toPrecision(2); // "43"
a.toPrecision(3); // "42.6"
a.toPrecision(4); // "42.59"
a.toPrecision(5); // "42.590"
a.toPrecision(6); // "42.5900"

你不必非得使用持有这个值的变量来访问这些方法;你可以直接在number的字面上访问这些方法。但你不得不小心.操作符。因为.是一个合法数字字符,如果可能的话,它会首先被翻译为number字面的一部分,而不是被翻译为属性访问操作符。

// 不合法的语法:

1
42.toFixed( 3 ); // SyntaxError

// 这些都是合法的:

1
2
3
(42).toFixed(3); // "42.000"
(0.42).toFixed(3); // "0.420"
(42).toFixed(3); // "42.000"

42.toFixed(3)是不合法的语法,因为.作为42.字面的一部分被吞掉了,因此没有.属性操作符来表示.toFixed访问。

42..toFixed(3)可以工作,因为第一个.number的一部分,而第二个.是属性操作符。但它可能看起来很古怪,而且确实在实际的 JavaScript 代码中很少会看到这样的东西。实际上,在任何基本类型上直接访问方法是十分不常见的。但是不常见并不意味着 或者

number 还支持科学计数法,以及二进制,八进制,十六进制

var onethousand = 1E3;                        // 代表 1 * 10^3

var onemilliononehundredthousand = 1.1E6;    // 代表 1.1 * 10^6

1
2
3
4
5
6
例如:19971400000000=1.99714×10^13// 计算器或电脑表达10的幂是一般是用E或e
,也就是1.99714E13=19971400000000

0x 开头 代表16进制
0o 开头 代表8进制
0b 开头 代表2进制

关于 number 一个大坑

就是 0.1 + 0.2 不等于 0.3 问题

其实并不是 javascript 独有的问题。 而是使用 IEEE 754 语言标准的通病

**可以通过  ** “机械极小值(machine epsilon)” 来判断,就是说两个相比的数,相减小于机械极小值,就是相等了。

在 ES6 中,使用这个容差值预定义了Number.EPSILON,所以你将会想要使用它,你也可以在前 ES6 中安全地填补这个定义:

1
2
3
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2, -52); // 2^-52
}

我们可以使用这个Number.EPSILON来比较两个number的“等价性”(带有错误舍入的容差):

1
2
3
4
5
6
7
8
9
function numbersCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON; // Math.abs方法为取绝对值
}

var a = 0.1 + 0.2;
var b = 0.3;

numbersCloseEnoughToEqual(a, b); // true
numbersCloseEnoughToEqual(0.0000001, 0.0000002); // false

可以被表示的最大的浮点值大概是1.798e+308(它真的非常,非常,非常大!),它为你预定义为Number.MAX_VALUE。在极小的一端,Number.MIN_VALUE大概是5e-324,它不是负数但是非常接近于 0!

Error(..)

就是抛出 错误哈 可以用 new Error() 构造器写法。 也可以直接 Error()

一般 模拟 真实报错 用 throw 关键字抛出错误, return 的话就如同 console 上打印一般,没得劲

提示: 技术上讲,除了一般的Error(..)原生类型以外,还有几种特定错误的原生类型:EvalError(..)RangeError(..)ReferenceError(..)SyntaxError(..) TypeError(..),和URIError(..)。但是手动使用这些特定错误原生类型十分少见。如果你的程序确实遭受了一个真实的异常,它们是会自动地被使用的(比如引用一个未声明的变量而得到一个ReferenceError错误)。

=

Symbol(..)

在 ES6 中,新增了一个基本值类型,称为“Symbol(标志)”。Symbol 是一种特殊的“独一无二”(不是严格保证的!)的值,可以作为对象上的属性使用而几乎不必担心任何冲突。它们主要是为特殊的 ES6 结构的内建行为设计的,但你也可以定义你自己的 symbol。

语法

Symbol( [description] )

参数

description** **可选可选的,字符串类型。对 symbol 的描述,可用于调试但不是访问 symbol 本身。

Symbol 可以用做属性名,但是你不能从你的程序中看到或访问一个 symbol 的实际值,从开发者控制台也不行。例如,如果你在开发者控制台中对一个 Symbol 求值,将会显示Symbol(Symbol.create)之类的东西。

在 ES6 中有几种预定义的 Symbol,做为Symbol函数对象的静态属性访问,比如Symbol.createSymbol.iterator等等。要使用它们,可以这样做:

obj[Symbol.iterator] = function(){ /../ };

要定义你自己的 Symbol,使用Symbol(..)原生类型。Symbol(..)原生类型“构造器”很独特,因为它不允许你将new与它一起使用,这么做会抛出一个错误。

1
2
3
4
5
6
7
8
9
10
var mysym = Symbol("my own symbol");
mysym; // Symbol(my own symbol)
mysym.toString(); // "Symbol(my own symbol)"
typeof mysym; // "symbol"

var a = {};
a[mysym] = "foobar";

Object.getOwnPropertySymbols(a); // 该方法返回 参数对象所有 Symbols 所组成的数组
// [ Symbol(my own symbol) ]

虽然 Symbol 实际上不是私有的(在对象上使用Object.getOwnPropertySymbols(..)反射,揭示了 Symbol 其实是相当公开的),但是它们的主要用途可能是私有属性,或者类似的特殊属性。对于大多数开发者,他们也许会在属性名上加入_下划线前缀,这在经常在惯例上表示:“这是一个私有的/特殊的/内部的属性,别碰!”

注意: Symbol 不是 object,它们是简单的基本标量。它是一个抽象的符号

不是值的值

  • null是一个空值
  • undefined是一个丢失的值

或者:

  • undefined还没有值
  • null曾经有过值但现在没有

null**是一个特殊的关键字,不是一个标识符,因此你不能将它作为一个变量对待来给它赋值(为什么你要给它赋值呢?!)。然而,**undefined**(不幸地)**是 一个标识符。可以被赋值。

NaN

Nubmer 类型 真正的意思应该为: 不合法的数字、坏掉的数字、失败的数字。

在 ES6 中,:Number.isNaN(..) 不像 window.isNaN() 那样 会把字符串, 就是除了数字外的都理解为 NaN.

实际上,通过利用NaN与它自己不相等这个特殊的事实,我们可以更简单地实现

判断是否为 NaN 最好的方法就是 自己不等于自己的数 即是 NaN

1
2
3
4
5
if (!Number.isNaN) {
Number.isNaN = function (n) {
return n !== n;
};
}

=

特殊等价

当使用等价性比较时,值NaN和值-0拥有特殊的行为。NaN永远不会和自己相等,所以你不得不使用 ES6 的Number.isNaN(..)(或者它的填补)。相似地,-0撒谎并假装它和普通的正零相等(即使使用===严格等价)

在 ES6 中,有一个新工具可以用于测试两个值的绝对等价性,而没有任何这些例外。它称为Object.is(..): -0 能等于 -0 NaN 能等于 NaN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var a = 2 / "foo";
var b = -3 * 0;

Object.is(a, NaN); // true
Object.is(b, -0); // true

Object.is(b, 0); // false
// 对于前ES6环境,这是一个相当简单的Object.is(..)填补:
if (!Object.is) {
Object.is = function (v1, v2) {
// 测试 `-0`
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// 测试 `NaN`
if (v1 !== v1) {
return v2 !== v2;
}
// 其他情况
return v1 === v2;
};
}

Object.is(..)可能不应当用于那些=====已知 安全 的情况(见第四章“强制转换”),因为这些操作符可能高效得多,并且更惯用/常见。Object.is(..)很大程度上是为这些特殊的等价情况准备的。

=

值与引用

在 JavaScript 中,没有指针,并且引用的工作方式有一点儿不同。你不能拥有一个从一个 JS 变量到另一个 JS 变量的引用。这是完全不可能的。引用都是值(数据)的引用

基本数据类型就是除了 Object 外的 他们的赋值本质都是拷贝**
**而被拥有引用数据类型值的变量赋值则是 对 Object 值本身的引用,而非对赋值变量的引用。**
**就是说 你可以通过存有某对象引用的变量,来修改这个对象本身,因为不是拷贝,是引用(共享)。**
**修改后的值会共享到同样引用此对象的变量上面。**
**但是你对这个变量重新赋值时,并不代表对引用的对象赋值,只是单纯的给这个变量赋值。**
**引用的对象** **还是 原样一点没变,只是重新赋值后的变量的值 变成了其他的值,**
**其他引用这个对象的变量,值依然是那个对象,不会改变的。**
**这很重要,不要被骗了。


a 赋值给 b** 实质上是把 [1, 2, 3] 这个数组对象的引用赋值给了 b,而并非 a 对象本身**

  • a 变量 可以访问 [1, 2, 3] 这个数组对象的引用,也就可以调用 push 修改这个数组对象, 同样拥有[1, 2, 3]数组对象引用的 b 变量自然的会共享到修改后的值

  • 但是 对 a 变量本身重新赋值, 跟 b 变量完全没有一点关系,b 变量 还是存有 修改后的[1, 2, 3] 数组对象的引用

你可以修改 变量引用的一部分 比如 object 的键值, arr 的元素。这些都会

– 实例分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function foo(x) {
x.push( 4 ); // 对引用的数组对象本身进行push, a也能响应到
x; // [1,2,3,4]

// 稍后
x = [4,5,6]; // x变量 重新赋值,不管a的事了。因为原引用的数组对象被覆盖,他们的羁绊被斩断了。
x.push( 7 );
x; // [4,5,6,7]
}

var a = [1,2,3];

foo( a );

a; // [1,2,3,4] 不是 [4,5,6,7]
-- 如果你想 影响到 a的值可以这样写
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] 不是 [1,2,3,4]

正如你看到的,x.length = 0x.push(4,5,6,7)没有创建一个新的array,但是修改了现存的共享array

操作符

**1. || ** && 操作符

表达式中 :

  • || 和 && 操作符号 都是 判断第一个参数,会把第一个参数强转为 一个 Boolean 值 。
  • || 是 第一个参数正确就取第一个参数, 第一个参数错误则取第二个参数
  • 而 && 是 第一个参数正确就取第二个参数,第一个参数错误,就取第一个参数。 刚开始会感觉挺无厘头的,但是这种机制完全跟 if 如出一辙,很多时候都可以 用 && 取代 if 语句 。
  • 需要注意 && 与传统 if 语句的不同, 就是当 第一个参数错误时,&& 会执行第一个参数本身,而非强转后的布尔值。

if 的条件中

  • 就是单纯返回 true false 了
  • || 表示任意一个正确就返回 true, && 表示都要正确 才返回 true
  • 但是本质上 也会是表达式中那样运转 只是最后返回的参数 被 if 再一次强转为了 boolean 值

1
2
3
4
5
6
7
8
9
a || b;
// 大体上等价于:
a ? a : b;

a && b;
// 大体上等价于:
a ? b : a;
// 大体上等价于:
if (a) b;

2. 优先级问题

==

三元表达式 ?后的 一项 也可以是一个三元表达式

&& 优先级比 || 高,而 || 优先级比 ? : 高。 if 语句中 && || 也高于 = 。所以赋值语句需要用小括号框住。

3. {}

js 文法上 {} 除了**被*做运算 (+{}、-{} 、{}.. 但不包括 = {})。

本质上都是空的代码块的意思, 不是空对象。

1
2
3
4
[] + {}; // "[object Object]"
{
}
+[]; // 0

在第一行中,{}出现在+操作符的表达式中,因此被翻译为一个实际的值(一个空object[]被强制转换为“”因此{}也会被强制转换为一个string“[object Object]”

但在第二行中,{}被翻译为一个独立的{}空代码块儿(它什么也不做)。块儿不需要分号来终结它们,所以这里缺少分号不是一个问题。最终,+ []是一个将[]明确强制转换 number的表达式,而它的值是0

4. 逻辑操作符

ECMAScript 中的所有数值都以 IEEE-754 64 位格式存储,但位操作符并不直接操作 64 位的值。而是先将 64 位的值转换成 32 位的整数,然后执行操作,最后再将结果转换为 64 位。就是只要使用操作符的 两个 数值还是字符串或是其他数据类型,都会被转为 32 位的 整数

“ | “  ( 按位或 or )

相或的两位数 只要其中一位是 1 就是 1 。两位同时为 0 才为 0

任何数 和 0 ‘|’ 都得那个数本身

但特殊数字转为 32 位 整数 都得 0

“ ^ “  (  位异或   )

相位异或的两位数 只要其中一位是 1 就是 1 。两位同时为 1 则为 0, 两位同时为 0 也为 0

“ & “  ( 按位与 and )

相与的两位数 只要其中一位是 0 就是 0 。两位同时为 1 才为 1

任何数 和 0 ‘&’ 都得 0

“ ~ “  ( 按位取反  )

  • 第 1 步:把运算数转换为 32  位的二进制整数。
  • 第 2  步:逐位进行取反操作。
  • 第 3  步:把二进制反码转换为十进制浮点数。

其实本质的结果就是 取一个数的相反数 然后 -1

~ 12 ———> -12 - 1 ———> -13

~ -12 ———> 12 -1 ———> 11

最佳可以搭配 indexOf() 使用。 因为 indexOf() 索引不存在的下标会返回 -1 而 ~-1 等于 0 也是就是一个 false,而且其他任何值都会返回个 true 。所有完全避免了写 比较判断。

还可以使用 ‘~~’ 双~  把一个数的小数位给去掉。


“ !! “  ( 否定之否定  )

就是把一个 值 强转为 Boolean 类型

数值转 二进制方法

5. ‘==’  的 坑

不要在任何情况下,使用== true== false。永远。

因为 == 布尔 逻辑判断中 布尔值 都是 默认转 number 类型来判断比较。其他数值不会转成 Boolean 的。

但时要记住,我在此说的仅与==有关。=== true=== false不允许强制转换,所以它们没有ToNumber强制转换,因而是安全的。

  1. 如果比较的任意一边可能出现**true**或者**false**值,那么就永远,永远不要使用**==**。
  2. 如果比较的任意一边可能出现**[]**,**“”**,或**0**这些值,那么认真地考虑不使用**==**。

考虑如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var a = "42";

// 不好(会失败的!):
if (a == true) {
// ..
}

// 也不该(会失败的!):
if (a === true) {
// ..
}

// 足够好(隐含地工作)隐式转换:
if (a) {
// ..
}

// 更好(明确地工作)明转:
if (!!a) {
// ..
}

// 也很好(明确地工作):
if (Boolean(a)) {
// ..
}

如果你在你的代码中一直避免使用== true== false(也就是与boolean的宽松等价),你将永远不必担心这种真/假的思维陷阱。

6. 关系型比较

JS 更准确地将 **<font style="background-color:#F3F4F4;"><= </font>** 考虑为“不大于”

a <= b 即是 !(a>b)

>=考虑为“不小于”

a >=b 即是 !(a<b)

不幸的是,没有像等价那样的“严格的关系型比较”。换句话说,没有办法防止a < b这样的关系型比较发生 隐含的 强制转换,除非在进行比较之前就明确地确保ab是同种类型。

使用与我们早先=====合理性检查的讨论相同的推理方法。如果强制转换有帮助并且合理安全,比如比较42 < “43”就使用它。另一方面,如果你需要在关系型比较上获得安全性,那么在使用<(或>)之前,就首先 明确地强制转换 这些值。

var a = [ 42 ];

var b = “043”;

a < b; // false – 字符串比较!

Number( a ) < Number( b ); // true – 数字比较!


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!