推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

【第1726期】JavaScript中的“黑话”

   2023-07-22 网络整理佚名1440
核心提示:或者有时候我们希望利用强转特性比较字符串与数字:开头的数字,常见于数值计算、css属性中,比如0.从十进制转其他进制请查阅方法,从其他进制转十进制请查阅方法,从其他进制转其他进制请先转为十进制再转为其他方法。在进行位运算时,采用32位有符号整型,即数字5有以下表示方式:

前言

另一篇基础文章。 今天晨读文章由@vv13授权分享。

正文从这里开始~~

因为球是圆的,所以无论发生什么都有可能,我坚信这一点,但最近我一直怀疑它是否也是圆的!

什么是“黑话”

黑话,原指旧时黑帮的暗语、暗号,多见于小说中,后来指在特定行业流行、外人无法理解的语言。 本文涉及的“黑话”,其实是利用语言特性的一些不常见、淫秽的手法。 语法非常简单和灵活。 在项目中,建议大家遵循规范,编写可维护的代码。 诸神亦应克己。 毕竟,“黑话”并不都是好东西。 如果很多事情都可以直接说出来,何必拐弯抹角呢?

“算术”

算术中的位运算已被作者列为禁止,所以当你在项目中使用位运算时,请确保你有足够的理由使用它们,并在必要时写下Hack注释。

!和!!

! 是一个逻辑 NOT 运算符,可以应用于 中的任何值,无论该值是什么类型,它都会被强制转换为布尔变量,然后其值将被取反。

!!它只是对操作数执行两次逻辑非,它可以将任何类型的值转换为相应的布尔值,其包含的步骤是:

假设需要用一个布尔变量来表示是否有id值,下面的写法建议使用最后一种方法进行转换:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const enable1 = !!id

  2. const enable2 = id ? true : false;

  3. const enable3 = Boolean(id);

〜与〜

~表示按位取反,~5的运算步骤为:

原码、反码、补码的原理请参见原理章节。

~~ 它代表双非按位否定运算符,如果你想使用比 Math.floor() 更快的方法,就是它。 需要注意的是,对于正数,向下舍入; 对于负数,向上舍入; 非数字取值为0,其具体表达为:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. ~~null; // => 0

  2. ~~undefined; // => 0

  3. ~~Infinity; // => 0

  4. --NaN; // => 0

  5. ~~0; // => 0

  6. ~~{}; // => 0

  7. ~~[]; // => 0

  8. ~~(1/0); // => 0

  9. ~~false; // => 0

  10. ~~true; // => 1

  11. ~~1.9; // => 1

  12. ~~-1.9; // => -1

在变量值之前使用 + 的目的是将变量转换为数字,当函数接受数字类型参数时,这特别有用:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. +'1' // 1

  2. +'-1' // '-1

  3. +[] // 0

  4. +{} // NaN

据观察,+a与a*1的结果类似。 另外,使用+还可以作为立即执行函数:+(){}(),相当于((){})()。

当向数字添加字符串时,该值将默认转换为字符串,因此有一个将数字转换为字符串的快捷方式:'' + 1。

& 和&&

如果您来自类C语言,请放弃以前的刻板印象:&可以充当逻辑运算符符号。 中,&只能进行位运算。

&,表示按位与,该运算符接受两个数字并返回一个数字。 如果它们不是数字,则将它们转换为数字。 如果执行7&3,将会执行以下步骤:

也可用于基数偶数判断:const isOdd = num => !!(num & 1);

&&,意思是逻辑与,通常用来判断if条件,但和你想象的不一样。 && 不是简单地返回 true 或 false,而是基于:

这里有一些例子:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. 0 && false 0 (both are false-y, but 0 is the first)

  2. true && false false (second one is false-y)

  3. true && true true (both are true-y)

  4. true && 20 20 (both are true-y)

&&可以连接多个运算符,如:a && b && c && d,返回值的规则同上。 另外,它经常被用作短路逻辑:如果前面的表达式不存在,后面的表达式将不会继续执行。 例如,在获取对象的属性时,我们需要在获取值之前判断是否为空,否则会抛出异常。 这种情况下,我们一般会通过逻辑或的方式给表达式一个默认值:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const value = obj && obj.value || false

当压缩工具遇到if判断时,也会使用&&短路逻辑来节省内存空间:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. // before

  2. if (test) { alert('hello') }

  3. // after

  4. test && alert('hello')

| 与||

它们与 & 和 && 非常相似,不同之处在于它们表示逻辑或,因此使用 | 将执行按位或运算,并且 || 将返回第一个值。

使用|| for 默认值赋值在 中很常见,可以省略很多不必要的 if 语句,例如:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. // before

  2. let res;

  3. if (a) {

  4. res = a;

  5. } else if (b) {

  6. res = b;

  7. } else if (c) {

  8. res = c;

  9. } else {

  10. res = 1;

  11. }


  12. // after

  13. const res = a || b || c || 1;

== 和 ===

== 是一个相等运算符。 该运算符首先将左操作数和右操作数转换为相同的操作数,然后执行相等比较。

===是同余运算符,只不过比较时不强制操作数变换,其余相等判断与==一致。

简单来说,==是用来判断值是否相等的,而===则是判断值和类型是否相等的,所以使用全等运算符来判断操作数是比较准确的。 新手也在学习。 收到的前几个提示是避免使用相等运算符。 这是真的? 是的,这可以确保你在不完全熟悉语言的情况下尽可能避免犯错误,但是我们也应该清楚在什么情况下应该使用相等运算符。 规则往往只适合新手,而对于聪明的你来说,最重要的是知道自己在做什么。

相等运算符比较不同类型的值,如下图所示:

对于null:等于null,不等于任何其他对象,因此在某些lib中,您可能会看到以下内容:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. if (VAR == undefined) {}

  2. if (VAR == null) {}

它相当于:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. if (VAR === undefined || VAR === null) {}

对于''、false、0来说,它们都属于Falsy类型,它们会通过对象转换为假值,并且通过==判断三者之间的关系时它们总是相等的,因为在比较值时会因为类型不同而转换为假值:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. console.log((false == 0) && (0 == '') && ('' == false)) // true

或者有时我们想使用强制转换功能来比较字符串和数字:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. console.log(11 == '11') // true

  2. console.log(11 === '11') // false

按位异或运算符比较每个位,如果位不同则返回 1,否则返回 0。 很少有人在Web开发中使用这个运算符,除了一个传奇的场景:交换值。

要交换a和b的值,建议您使用:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. [a, b] = [b, a];

或者新建一个c来存放临时变量,如果遇到有人这样写:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. // 异或运算,相同位取0,不同位取1,a ^ b ^ b = a, a ^ a ^ b = b

  2. a = a ^ b

  3. b = a ^ b

  4. a = a ^ b

通过异或运算交换两个数值变量,请原谅他并忽略它,他可能只是一个着迷于魔法的初学者,祝他早日发现简洁易读的函数才是最佳实践。

数字表示 3e9

科学记数法是一个数学术语,将数字表示为10的n次方,例如光速为每秒30万公里。 在计算中,通常使用米作为单位,记为:/s,3e9中我们可以使用科学计数法。

以下是科学计数法的一些示例:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. 1e5; // 100000

  2. 2e-4; // 0.0002

  3. -3e3; // -3000

该对象有一个 () 方法,该方法返回以科学计数法表示的值的字符串表示形式。 该参数是可选的,用于指定小数点后有多少位,例如:().(); //“1.79e+5”。

以下情况会自动将数值转换为科学计数法:

.5像素

通常,有些人习惯省略以0.开头的数字,这在数值计算和css属性中很常见。 例如0.5px可以直接写为.5px,0.2*0.3可以写为:.2*.3

0x、0o 和 0b

在十进制世界待久了,请不要忘记还有其他的基数,它们在计算机中的地位是一样的。 提供了以下表示方法:

默认情况下,在执行运算之前,它会自动将八进制、十六进制和二进制转换为十进制。 十进制转换为其他进制,请参考方法,其他进制转换为十进制,请参考方法,其他进制转换为其他进制,请先转换为十进制,然后再转换为其他方法。

“说话技巧”数组..排序

Array..sort() 默认根据字符串的编码进行排序。 具体算法取决于实现的浏览器。 在v8引擎中,如果长度小于10,则使用插入排序,如果长度大于10,则使用快速排序。

并且sort支持传入参数为(a,b),其中a和b是数组中用于比较的两个非空对象(所有空对象都会排在数组末尾),具体比较规则为:

于是用sort写了一个乱序的方法:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. [1,2,3,4].sort(() => .5 - Math.random())

然而,上述实现并不是完全随机的。 原因是由于排序算法的不稳定,有些元素没有机会进行比较。 详情请参阅问题。 要在彩票程序中实现完全随机性,请使用 –Yates 算法。 下面是一个简单的实现:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. function shuffle(arrs) {

  2. for (let i = arrs.length - 1; i > 0; i -= 1) {

  3. const random = Math.floor(Math.random() * (i + 1));

  4. [arrs[random], arrs[i]] = [arrs[i], arrs[random]];

  5. }

  6. }

数组...应用

apply接受数组类型的参数来调用函数,并且接受多个字符串或数组的参数,因此可以使用该技术直接将二维数组展平:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. Array.prototype.concat.apply([], [1, [2,3], [4]])

并且通过这个方法,还可以写一个深度遍历的方法:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. function flattenDeep(arrs) {

  2. let result = Array.prototype.concat.apply([], arrs);

  3. while (result.some(item => item instanceof Array)) {

  4. result = Array.prototype.concat.apply([], result);

  5. }

  6. return result;

  7. }

经过测试,效率及对比如下:

上述方法中的Array...apply([], )也可以写成:[].(...)。

数组..push.apply

在es5中,如果我们想要拼接数组,我们习惯使用数组中的方法:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. let arrs = [1, 2, 3];

  2. arrs = arrs.concat([4,5,6]);

不过还有一个很酷的方法,利用apply方法的数组参数传递特性,可以更简洁地进行拼接操作:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const arrs = [1, 2, 3];

  2. arrs.push.apply(arrs, [4, 5, 6]);

大批..

它通常用于返回数组的长度,但它也是一个具有复杂行为的属性。 首先,它不是用来统计数组中元素的个数,而是用来表示数组中最高的索引值:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const arrs = [];

  2. arrs[5] = 1;

  3. console.log(arrs.length); // 6

另外,长度会随着数组的变化而变化,但这种变化仅限于:子元素最高索引值的变化,如果使用该方法删除最高元素,则不会变化,因为最高索引值没有变化:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const arrs = [1, 2, 3];

  2. delete arrs[2]; // 长度依然为3

另一个重要的功能是它允许您修改其值。 如果修改的值小于数组本身的最大索引,则数组将被部分截断:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const arrs = [1, 2, 3, 4];

  2. arrs.length = 2; // arrs = [1, 2]

  3. arrs.length = 0; // arrs = []

如果赋值的值大于当前最大索引,将得到一个稀疏数组:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const arrs = [1, 2];

  2. arrs.length = 5; // arrs = [1, 2,,,,]

如果该值赋值为0,则执行清空数组的操作:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const arrs = [1, 2, 3, 4];

  2. arrs.length = 0; // arrs = []

使用该方法会删除数组中的所有索引,因此也会影响引用该数组的其他值,这与使用 arrs = [] 有很大不同:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. let a = [1,2,3];

  2. let b = [1,2,3];

  3. let a1 = a;

  4. let b1 = b;

  5. a = [];

  6. b.length = 0;

  7. console.log(a, b, a1, b1); // [], [], [1, 2, 3], []

修改时还需要注意:

... 称呼

每个对象都有一个(),用于当对象被作为字符串引用时自动调用。 如果这个方法没有被重写,就会返回[type],所以...call只是调用原对象上没有被重写的方法。

在ES3中,获取到的类型是内部属性[[Class]]属性,可以用来判断一个原生属性属于哪个内置值; ES5中新增了两条规则:如果this的值为null,则分别返回:[ Null]、[ ]; 在ES6中,[[Class]]不存在,取而代之的是一个内部属性:[[]],它是一个标签值,用于区分原生对象的属性。 具体判断规则为:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. 19.1.3.6Object.prototype.toString ( )

  2. When the toString method is called, the following steps are taken:


  3. If the this value is undefined, return "[object Undefined]".

  4. If the this value is null, return "[object Null]".

  5. Let O be ! ToObject(this value).

  6. Let isArray be ? IsArray(O).

  7. If isArray is true, let builtinTag be "Array".

  8. Else if O is a String exotic object, let builtinTag be "String".

  9. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".

  10. Else if O has a [[Call]] internal method, let builtinTag be "Function".

  11. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".

  12. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".

  13. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".

  14. Else if O has a [[Datevalue]] internal slot, let builtinTag be "Date".

  15. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".

  16. Else, let builtinTag be "Object".

  17. Let tag be ? Get(O, @@toStringTag).

  18. If Type(tag) is not String, set tag to builtinTag.

  19. Return the string-concatenation of "[object ", tag, and "]".

  20. This function is the %ObjProto_toString% intrinsic object.


  21. NOTE

  22. Historically, this function was occasionally used to access the String value of the [[Class]] internal slot that was used in previous editions of this specification as a nominal type tag for various built-in objects. The above definition of toString preserves compatibility for legacy code that uses toString as a test for those specific kinds of built-in objects. It does not provide a reliable type testing mechanism for other kinds of built-in or program defined objects. In addition, programs can use @@toStringTag in ways that will invalidate the reliability of such legacy type tests.

。(无效的)

用于创建一个没有“副作用”的对象,即创建一个不包含原型链和其他属性的空对象。 如果使用const map = {}创建的对象相当于.(.),则继承该对象的原型链。

JSON。 解析(JSON。(对象))

深度复制对象的一种非常常见的方法是将对象格式化为 JSON 字符串,然后解析它以获得新的对象。 需要注意的是,它的性能并不是特别好,无法处理闭环引用,例如:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. const obj = {a: 1};

  2. obj.b = obj;

  3. JSON.parse(JSON.stringify(obj)) // Uncaught TypeError: Converting circular structure to JSON

事实上,通过JSON解析的性能并不高。 如果对象可以通过浅拷贝的方式复制,请使用浅拷贝的方式,无论你使用{...obj}还是.({}, obj),如果你有性能需求,请不要重新发明轮子,直接使用npm:clone包或者其他的。

与 Falsy 的“理论”

对于每种类型的值,其每个对象都有一个布尔值。 Falsy 表示对象中的值为 false。 在条件判断和循环中,任何类型都会被强制转换为对象。 以下对象在遇到 if 语句时都会表现出 Falsy 行为:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. if (false)

  2. if (null)

  3. if (undefined)

  4. if (0)

  5. if (NaN)

  6. if ('')

  7. if ("")

  8. if (document.all)

.all属于历史遗留下来的原因,所以它是假的,它违反了规范,你可以忽略它,而且NaN是一个变量,不要用同余或相等来判断它,因为它会发疯甚至撞到自己:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. console.log(NaN === 0) // false

  2. console.log(NaN === NaN) // false

  3. console.log(NaN == NaN) // false

但我们可以使用.is方法来判断该值是否为NaN。 它是ES6添加的新语法,用于比较两个值是否相同。 可以认为是比同余判断更严格的判断方法,但也不能混淆:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. Object.is(NaN, NaN) // true

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

而所有值都是除 Falsy 值之外的值,Falsy 值在上下文中表现为 true。

原码、反码、补码

进行位运算时,使用 32 位有符号整数,即数字 5 具有以下表示形式:

而数字-5则表示为:

概括来说,有以下规则:

那么它们有什么用呢? 事实上,位计算是利用计算机底层电路进行所有计算的基础。 为了使计算机的计算更容易,无需区分符号位,所有值都相加。 因此,人们设计了最初的代码,通过符号位来识别数字的正负:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. 1 = 0000 0001

  2. -1 = 1000 0001

如果计算机要两个数相加:1+(-1),原码相加的结果是:,显然-2不是我们想要的结果,所以有一个反码,如果用反码来运算会得到什么结果,我们看看:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. 1[反码] + (-1)[反码] = 0000 0001 + 1111 1110 = 11111111[反码] = 10000000[原码]

此时计算结果是正确的,但是仍然存在问题。 可以表示0的值有两个:1000 0000、0000 0000。对于计算机来说,带符号的0是没有意义的。 为了优化0的存在性,人们设计了补码代码:

portant;border-width: 1px !important;border-style: solid !important;border-color: rgb(226, 226, 226) !important;">
  1. 1[补码] + (-1)[补码] = 0000 0001 + 1111 1111 = 00000000[原码]

这样就可以解决-0的问题了。

参考

/////这是什么——和——

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON