JavaScript的“神奇”之处

news2025/1/13 10:19:30

JavaScript 是一门很棒的语言。它的语法简单,生态系统也很庞大,最重要的是,它拥有最伟大的社区力量。我们知道,JavaScript 是一个非常有趣的语言,但同时也充满了各种奇怪的行为。让我们一起来看一下吧~

example

数组等于一个数组取反:

[] == ![]; // -> true 

解释

抽象相等运算符会将其两端的表达式转换为数字值进行比较。尽管这个例子中,左右两端均被转换为 0,但原因各不相同。[]的值比较总是true,因此右值的数组取反后总是为 false,然后在抽象相等比较中被类型转换为 0。左值是另一种情形,空数组没有被转换为布尔值的话,尽管在逻辑上是true,但在抽象相等比较中,会被类型转换为数字 0

表达式的运算步骤如下:

+[] == +![]; // true;
0 == +false; // true;
0 == 0; // true; 

true 不等于 ![],也不等于 [];

数组不等于 true,但数组取反也不等于 true;数组等于 false,数组取反也等于 false

true == []; // -> false
true == ![]; // -> false

false == []; // -> true
false == ![]; // -> true 

解释

true == []; // -> false
true == ![]; // -> false

// 根据规范
true == []; // -> false

toNumber(true); // -> 1
toNumber([]); // -> 0

1 == 0; // -> false

true == ![]; // -> false

![]; // -> false

true == false; // -> false 
false == []; // -> true
false == ![]; // -> true

// 根据规范
false == []; // -> true

toNumber(false); // -> 0
toNumber([]); // -> 0

0 == 0; // -> true

false == ![]; // -> true

![]; // -> false

false == false; // -> true 

true 是 false

!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true 

解释

// true 是真值,并且隐式转换为数字1,而字符串 'true' 会被转换为 NaN。
true == "true"; // -> false
false == "false"; // -> false

// 'false' 不是空字符串,所以它的值是 true
!!"false"; // -> true
!!"true"; // -> true 

NaN !== NaN

NaN === NaN; // -> false 

解释

规范严格定义了这种行为背后的逻辑:

  • 如果 Type(x) 不同于 Type(y),返回 false
  • 如果 Type(x) 数值, 然后* 如果 x 是 NaN,返回 false。* 如果 y 是 NaN,返回 false

根据 IEEE 对 NaN 的定义:

有四种可能的相互排斥的关系:小于、等于、大于和无序。

当比较操作中至少一个操作是 NaN 时,便是无序的关系。换句话说,NaN 对任何事物包括其本身比较都应当是无序关系。

Object.is() 和 ===

Object.is() 用于判断两个值是否相同,和 === 操作符像作用类似,但它也有一些不同。

Object.is(NaN, NaN); // -> true
NaN === NaN; // -> false

Object.is(-0, 0); // -> false
-0 === 0; // -> true

Object.is(NaN, 0 / 0); // -> true
NaN === 0 / 0; // -> false 

解释

在 JavaScript “语言”中,NaN 和 NaN 的值是相同的,但却不是严格相等。NaN === NaN 返回 false 是因为历史包袱,记住这个特例就行了。基于同样的原因,-0 和 0 是严格相等的,但它们的值却不同。

一个隐式类型转换的天花板例子

(![] + [])[+[]] +(![] + [])[+!+[]] +([![]] + [][[]])[+!+[] + [+[]]] +(![] + [])[!+[] + !+[]]; // -> 'fail' 

解释 我们先分解成片段拆解来看,以下表达式出现:

+![]// -> 0
+!![] // -> 1
!![]// -> true
![] // -> false
[][[]]// -> undefined
+!![] / +![]// -> Infinity
[] + {} // -> "[object Object]"
+{} // -> NaN 
![] + []; // -> 'false'
![]; // -> false 

所以我们尝试将 [] 和 false 加起来。但是因为一些内部函数调用(binary + Operator - >ToPrimitive - >[[DefaultValue] ]),我们最终将右边的操作数转换为一个字符串:

![] + [].toString(); // 'false' 

将字符串作为数组,我们可以通过[0]来访问它的第一个字符:

"false"[0]; // -> 'f' 

剩下的部分以此类推,不过此处的 i 字符是比较巧的。fail 中的 i 来自于生成的字符串 falseundefined,通过指定下标 ['10'] 取得的。

null 是假值,但又不等于 false

尽管 null 是假值,但它不等于 false

!!null; // -> false
null == false; // -> false 

但是,别的被当作假值的却等于 false,如 0 或 ''

0 == false; // -> true
"" == false; // -> true 

document.all 是一个 object,但又同时是 undefined。

尽管 document.all 是一个类数组对象(array-like object),并且通过它可以访问页面中的 DOM 节点,但在通过 typeof 的检测结果是 undefined

document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined' 

同时,document.all 不等于 undefined

document.all === undefined; // -> false
typeof document.all; // -> 'undefined' 

但是同时,document.all 不等于 undefined

document.all === undefined; // -> false
document.all == null; // -> true 

不过。

document.all == null; // -> true 

解释

document.all 作为访问页面 DOM 节点的一种方式,在早期版本的 IE 浏览器中较为流行。尽管这一 API 从未成为标准,但被广泛使用在早期的 JS 代码中。

当标准演变出新的 API(例如 document.getElementById)时,这个 API 调用就被废弃了。因为这个 API 的使用范围较为广泛,标准委员会决定保留这个 API,但有意地引入一个违反 JavaScript 标准的规范。

这个有意的对违反标准的规范明确地允许该 API 与 undefined 使用[严格相等比较]得出 false,而使用[抽象相等比较]得出 true

数组相加

如果你尝试将两个数组相加:

[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6' 

解释

数组之间会发生串联。步骤如下:

[1, 2, 3] +[4, 5, 6][// 调用 toString()(1, 2, 3)].toString() +[4, 5, 6].toString();
// 串联
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6"); 

数组中的尾逗号

假设你想要创建了一个包含 4 个空元素的数组。如下所示,最终只能得到一个包含三个元素的数组,原因在于尾逗号:

let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,' 

解释

尾逗号 (trailing commas,有时也称为“最后逗号”(final commas)) 在向 JavaScript 代码中添加新元素、参数或属性时非常有用。

如果您想添加一个新属性,若前一行已经有尾逗号,你无需修改前一行,只要添加一个新行并加上尾逗号即可。

这使得版本控制历史较为干净,编辑代码也很简单。

数组的相等性是深水猛兽

数组之间进行相等比较,是 JS 中的深水猛兽,看看这些例子:

[] == '' // -> true
[] == 0// -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == ''// -> false
[''] == 0// -> true

[null] == ''// true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0// true

[[]] == 0// true
[[]] == '' // true

[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0// true

[[[[[[ null ]]]]]] == 0// true
[[[[[[ null ]]]]]] == '' // true

[[[[[[ undefined ]]]]]] == 0// true
[[[[[[ undefined ]]]]]] == '' // true 

undefined 和 Number

无参数调用 Number 构造函数会返回 0。我们知道,当函数没有接受到指定位置的实际参数时,该处的形式参数的值会是 undefined。因此,你可能觉得当我们传入 undefined 时应当同样返回 0。然而实际上传入 undefined 返回的是 NaN

Number(); // -> 0
Number(undefined); // -> NaN 

解释

根据规范:

  • 若无参数调用该函数,n 将为 +0
  • 否则,n 将为?Number(value)
  • 如果值为 undefinedNumber(undefined) 应该返回 NaN

parseInt 它的奇怪之处

parseInt("a*s"); // -> NaN
parseInt("a*s", 16); // -> 10 

解释 这是因为 parseInt 会持续解析直到它解析到一个不识别的字符,'a*s' 中的 a 是 16 进制下的 10

parseInt 解析 Infinity 到整数也很有意思

//
parseInt("Infinity", 10); // -> NaN
// ...
parseInt("Infinity", 18); // -> NaN...
parseInt("Infinity", 19); // -> 18
// ...
parseInt("Infinity", 23); // -> 18...
parseInt("Infinity", 24); // -> 151176378
// ...
parseInt("Infinity", 29); // -> 385849803
parseInt("Infinity", 30); // -> 13693557269
// ...
parseInt("Infinity", 34); // -> 28872273981
parseInt("Infinity", 35); // -> 1201203301724
parseInt("Infinity", 36); // -> 1461559270678...
parseInt("Infinity", 37); // -> NaN 

也要小心解析 null

parseInt(null, 24); // -> 23 

解释

它将 null 转换成字符串 'null',并尝试转换它。对于基数 0 到 23,没有可以转换的数字,因此返回 NaN。

而当基数为 24 时,第 14 个字母“n”也可以作数字用。

当基数为 31 时,第 21 个字母“u”进入数字的行列,此时整个字符串都可以解析了。

而当基数增加到 37 以上,已经超出了数字和字母所能表达的数字范围,因此一律返回 NaN

解析八进制:

parseInt("06"); // 6
parseInt("08"); // 8 如果支持 ECMAScript 5
parseInt("08"); // 0 如果不支持 ECMAScript 5 

解释

当输入的字符串以“0”开始时,根据实现的不同,会被解释为八进制或十进制。

ECMAScript 5 明确表示应当使用十进制,但有部分浏览器仍不支持。

因此推荐在调用 parseInt 函数时总是传入表示基数的第二个参数。

parseInt 会先将参数值转换为字符串:

parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2
Number({ toString: () => 2, valueOf: () => 1 }); // -> 1 

解析浮点数的时候要注意:

parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5 

解释

parseInt 接受字符串参数并返回一个指定基数下的整数。

parseInt 会将字符串中首个非数字字符(字符集由基数决定)及其后的内容全部截断。

如 0.000001 被转换为 "0.000001",因此 parseInt 返回 0

而 0.0000001 转换为字符串会变成 "1e-7",因此 parseInt 返回 1

1/1999999 被转换为 5.00000250000125e-7,所以 parseInt 返回 5

true 和 false 的数学运算

做一下数学计算:

true + true; // -> 2
(true + true) * (true + true) - true; // -> 3 

解释 我们可以用 Number 构造函数将值强制转化成数值。很明显,true 将被强制转换为 1 :

Number(true); // -> 1 

一元加运算符会尝试将其值转换成数字。它可以转换字符串形式表达的整数和浮点数,以及非字符串值 truefalse 和 null。如果它不能解析特定的值,它将转化为 NaN。这意味着我们可以有更简便的方式将 true 转换成 1

+true; // -> 1 

当你执行加法或乘法时,将会 ToNumber 方法。根据规范,该方法的返回值为:

如果参数是 true,返回 1。如果参数是 false,则返回  +0

因此我们可以将布尔值相加并得到正确的结果。

神奇的数字增长

999999999999999; // -> 999999999999999
9999999999999999; // -> 10000000000000000

10000000000000000; // -> 10000000000000000
10000000000000000 + 1; // -> 10000000000000000
10000000000000000 + 1.1; // -> 10000000000000002 

解释

这是由 IEEE 754-2008 二进制浮点运算标准引起的。极大的数字会被四舍五入到最近的偶数。可以参考阅读:

  • 6.1.6 数字类型
  • 维基百科上的 IEEE 754

精度计算

来自 JavaScript 的知名笑话。0.1 和 0.2 相加是存在精度错误的

0.1 + 0.2; // -> 0.30000000000000004
0.1 + 0.2 === 0.3; // -> false 

解释

程序中的常量 0.2 和 0.3 是最接近真实值的近似值。

最接近 0.2 的 double 大于有理数 0.2,但最接近 0.3 的 double 小于有理数 0.3

0.1 和 0.2 的和大于有理数 0.3,因此在程序中进行常量比较会得到假。

这不仅仅是 JavaScript 特有的问题,在其他采用浮点计算的语言中也广泛存在。

扩展数字的方法

你可以向包装对象添加自己的方法,比如 Number 或 String

Number.prototype.isOne = function() {return Number(this) === 1;
};

(1.0).isOne(); // -> true
(1).isOne(); // -> true
(2.0).isOne(); // -> false
(7).isOne(); // -> false 

解释

显然,在 JavaScript 中扩展 Number 对象和扩展其他对象并无不同之处。但是,扩展不符合规范的函数行为是不推荐的。

三个数字的比较

1 < 2 < 3; // -> true
3 > 2 > 1; // -> false 

解释

为什么会这样呢?其实问题在于表达式的第一部分。以下是它的工作原理:

1 < 2 < 3; // 1 < 2 -> true
true < 3; // true -> 1
1 < 3; // -> true

3 > 2 > 1; // 3 > 2 -> true
true > 1; // true -> 1
1 > 1; // -> false 

我们可以用 大于或等于运算符(>=

3 > 2 >= 1; // true 

有趣的数学

通常 JavaScript 中的算术运算的结果可能是难以预料的,考虑这些例子:

 3- 1// -> 2
 3+ 1// -> 4
'3' - 1// -> 2
'3' + 1// -> '31'

'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'

'222' - -'111' // -> 333

[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN 

解释

前四个例子发生了什么?你可以参考此处的给出的关于 JavaScript 中的加法的对照表:

Number+ Number-> 加法
Boolean + Number-> 加法
Boolean + Boolean -> 加法
Number+ String-> 串联字符串
String+ Boolean -> 串联字符串
String+ String-> 串联字符串 

在相加之前,[] 和 {} 隐式调用 ToPrimitive 和 ToString 方法。

  • 不过需要注意此处的 {} + [],这是一个例外。
  • 你可以发现它的求值结果与 [] + {} 不同,这是因为当我们不加括号时,它被当作是一个空的代码块和一个一元加法运算符,这个运算符会把其后的 [] 转换为数字。具体如下:
{// 代码块
}
+[]; // -> 0 

当我们加上括号,情况就不一样了:

({} + []); // -> [object Object] 

正则表达式的加法

你知道可以做这样的运算吗?

// Patch a toString method
RegExp.prototype.toString =function() {return this.source;} /7 /-/5/; // -> 2 

字符串不是 String 的实例

"str"; // -> 'str'
typeof "str"; // -> 'string'
"str" instanceof String; // -> false 

解释

String 构造函数返回一个字符串:

typeof String("str"); // -> 'string'
String("str"); // -> 'str'
String("str") == "str"; // -> true 

再试试 new

new String("str") == "str"; // -> true
typeof new String("str"); // -> 'object' 

用反引号调用函数

我们来声明一个返回所有参数的函数:

function f(...args) {return args;
} 

你肯定知道调用这个函数的方式应当是:

f(1, 2, 3); // -> [ 1, 2, 3 ] 

但是你知道你还可以使用反引号调用任意函数吗?

f`true is ${true}, false is ${false}, array is ${[1, 2, 3]}`;
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// -> true,
// -> false,
// -> [ 1, 2, 3 ] ] 

解释

在上面的例子中,f 函数是模板字面量的标签。

以定义这个标签以使用函数解析模板文字。

标签函数的第一个参数是包含字符串的数组,剩余的参数与表达式有关。例:

function template(strings, ...keys) {// 操作字符串和键值
} 

到底 call 了谁

console.log.call.call.call.call.call.apply(a => a, [1, 2]); // -> 2; 

解释

不用看它call了几遍,看你最后一个call就好。

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/145685.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Weston 窗口管理(2)

窗口管理(2) 本文基于 weston 分支 10.0.2 进行描述. 五、概述 本文为窗口管理(1)的续章,更多站在开发者角度,以 weston 的代码实现讲解窗口管理(1)中所实现的部分业务场景. 六、数据结构 在窗口管理(1)中曾经描述过 weston 具体的分层逻辑,如下: 再进一步可以把 WESTON_LAY…

关于grpc的第一次尝试

自己瞎琢磨的&#xff0c;有错勿怪。 一、rpc理解 微服务会在不同的端口使用不同的语言提供相异的服务&#xff0c;端口之间的通信就使用rpc。这边的grpc的“g”我原先意味是golang&#xff0c;后来发现是google。 在golang关于rpc的官方包中&#xff0c;rpc主要有使用http/…

(7)Qt中的自定义槽(函数)

目录 槽函数定义的规则 关联信号与槽错误的示例 类成员函数函数做槽函数使用 静态类成员函数做槽函数使用 全局函数做槽函数使用 lambda表达式做槽函数使用 使用lambda表达式的注意事项 注意事项一&#xff1a; 注意事项二&#xff1a; 槽函数定义的规则 1.槽函数返回值…

多图解析KMP算法原理

KMP是什么 KMP是一种字符串匹配算法&#xff0c;能够判断字符串s2&#xff0c;是否为字符串s1的子串 例如&#xff1a;s1 "abd123def"&#xff0c;s2 "123"&#xff0c;KMP会返回4&#xff0c;代表s2是s1的子串&#xff0c;第一个匹配的下标为3 假设s…

线程池及源码分析

目录 1 java构建线程的方式 2 线程池的7个参数 3 线程池属性标识&线程池的状态 3.1 核心属性 3.2 线程池的状态 4 线程池的执行流程 5 添加工作线程的流程 6 Worker的封装&后续任务的处理 1 java构建线程的方式 一般就3~4种&#xff1a; 继承Thread&#xff…

迎接新年,暂且用Python绘制几个中国结吧

前言 今天就来分享几个用python绘制的图案吧 马上就要迎来新年了 就绘制了几个中国结&#xff0c;嘿嘿 话不多说&#xff0c;直接展示一下代码和效果图吧 更多学习资料与源码点击文章末尾名片领取 1. 效果图&#xff1a; 代码展示 import turtle turtle.screensize(600,…

GPDB插件安装工具之gppkg

gppkg命令gppkg是一个python3编写的打包脚本&#xff0c;在整个集群中安装.gppkg格式的Greenplum数据库扩展&#xff08;例如PL/Java、PL/R和MADlib&#xff09;及其依赖项&#xff0c;位于/usr/local/cloudberry-db/bin/gppkg(自己安装的gpdb目录)&#xff0c;安装到$GPHOME里…

1个寒假能学多少网络安全知识?

现在可以看到很多标题都声称三个月内就可以转行网络安全领域&#xff0c;并且成为月入15K的网络工程师。那么&#xff0c;这个寒假的时间能学多少网络安全知识&#xff1f;是否能入门网络安全工程师呢&#xff1f; 答案是肯定的。 虽然网络完全知识是一门广泛的学科&#xff…

ccc-sklearn-13-朴素贝叶斯(1)

朴素贝叶斯 一种直接衡量标签和特征之间概率关系的有监督学习算法&#xff0c;专注分类的算法&#xff0c;基于概率论和数理统计的贝叶斯理论。在计算的过程中&#xff0c;假设特征之间条件独立&#xff0c;不进行建模&#xff0c;采用后验估计。 sklearn中的朴素贝叶斯 类含…

1-选择题练手

1.采用递归方式对顺序表进行快速排序&#xff0c;下列关于递归次数的叙述中&#xff0c;正确的是 A.每次划分后&#xff0c;先处理较长的分区可以减少递归次数 B.递归次数与初始数据的排列次序无关 C.每次划分后&#xff0c;先处理较短的分区可以减少递归次数 D.递归次数与…

DaVinci:键 - 外部蒙版

调色页面&#xff1a;键Color&#xff1a;Key在调色页面&#xff0c;可以轻松地从媒体池将某个片段拖至节点面板中&#xff0c;以作为外部蒙版。或者&#xff0c;在节点上右击选择“添加蒙版” Add Matte。若无附加&#xff0c;则可以选择本节点片段的明度信息作为外部蒙版。当…

hbase2.x orphan regions on filesystem(region丢失)问题修复

问题描述&#xff1a;orphan regions on filesystem 可以通过主master web页面的HBCK Report查看 也可以通过hbck2工具查看 # 查看指定表 hbase hbck -j $HBASE_HOME/lib/hbase-hbck2-1.3.0-SNAPSHOT.jar addFsRegionsMissingInMeta default:tableName # 查看命名空间下所有…

Yolov5+TensorRT-生成dll-python/c++调用dll

YOlov5-6.0TensorRTdllpython/c调用简介1.项目环境2.TensorRT验证1.在tensorrtx-yolov5-v6.0\yolov5目录下新建build目录2.编写CMake.txt,根据自己目录更改2&#xff08;OpenCV_DIR&#xff09;、3&#xff08;TRT_DIR&#xff09;、10&#xff08;Dirent_INCLUDE_DIRS&#xf…

LabVIEW网络服务器何使用,有哪些不同

LabVIEW网络服务器何使用&#xff0c;有哪些不同NI有几款不同的Web服务器&#xff0c;可使用不同的产品并覆盖不同的用例。它们具有非常相似的名称&#xff0c;可以互换使用&#xff0c;但每个都提供不同的功能。应用程序Web服务器描述&#xff1a;NI应用Web服务器加载使用LabV…

企业微信商户号是什么?如何开通?

企业微信作为一款优秀的移动办公工具&#xff0c;与微信全方位打通&#xff0c;既可以与客户沟通交流&#xff0c;也可以在达成交易后直接进行对公收款&#xff0c;但是前提是要开通企业微信商户号。前言企业微信和微信都出自腾讯&#xff0c;而且企业微信全方位连接微信&#…

C#,图像二值化(16)——全局阈值的力矩保持算法(Moment-proserving Thresholding)及其源代码

1、力矩保持法 提出了一种基于矩保持原理的自动阈值选择方法。以这样的方式确定地计算阈值&#xff0c;即在输出画面中保留输入画面的时刻。实验结果表明&#xff0c;该方法可以将给定的图像阈值化为有意义的灰度级。该方法描述了全局阈值&#xff0c;但也适用于局部阈值。 A…

企业微信开发——企业内部自建应用开发(第二篇)---JS_SDK配置

企业微信如果想要使用企业微信的JS_SDK来实现拍照、定位等等功能&#xff0c;就需要预先在使用到的页面进行配置&#xff0c;当然你可以做全局配置。对于JS_SDK的配置设计前端和后端的统一配置。下面我来说明下具体的步骤。特别说明&#xff1a;1、企业微信有的接口需要配置wx.…

shader基础入门(1)

本文基于unity免费公开课“Hi Shader以及网络公开资料等书写”遵循开源协议。 MeshFilter网格过滤器 从海量资源中挑选适合的Mesh将他交给MeshRender MeshRenderer 网格渲染器 负责把MeshFilter丢过来的Mesh&#xff0c;绘制显示到我们的场景中 Material 材质球 Material…

多线程之死锁

目录&#xff1a; 1.什么是死锁&#xff1f; 2.可重入与不可重入 3.发生死锁的三个典型情况 4.发生死锁的四个必要条件 5.如何破除死锁&#xff1f; 1.什么是死锁&#xff1f; 谈到死锁&#xff0c;程序猿们都心存忌惮&#xff0c;因为程序一旦出现死锁&#xff0c;就会导…

深度学习训练营之鸟类识别

深度学习训练营之鸟类识别原文链接环境介绍前置工作设置GPU导入数据并进行查找数据处理可视化数据配置数据集残差网络的介绍构建残差网络模型训练开始编译结果可视化训练样本和测试样本预测原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&am…