js onchange函数如何判断是第一次点击_阅读V8(二):V8中的js同值相等

本文深入探讨了JavaScript中的同值相等概念,特别是V8引擎如何实现。通过分析V8的单元测试用例,揭示了Object.is()方法处理+0, -0和NaN的逻辑。同时,文章讨论了Math.min()和Math.max()在无参数时返回Infinity和-Infinity的合理性,关联到函数式编程中的Monoid和单位元概念。" 104432871,8429696,Windows环境下Redis安装教程,"['Redis', 'Windows安装', '数据库', '系统配置']
摘要由CSDN通过智能技术生成

15711533bb04964c664c780b619c635f.png
时间:2020/08/14

背景

同上一篇解读v8对JSArray的实现的文章一样,本篇内容也是基于一个小知识点的困惑而翻阅源码得到的理解。

之前在学习Monad与FP的时候对一些概念不甚了解,后来查阅资料发现FPfunctional programming,函数式编程)中Monad的概念居然可以解释我上面提到的困惑(下文论述),受到启发,写了这篇内容。

本文实质上是由js中的“同值相等”概念,追溯到论证v8底层源码对该理论的逻辑实现。通过对v8单元测试用例断言函数的解析,串联起部分函数式编程中的概念(单位元)对该函数方法的解释与支持。同时也通过这些实际使用场景,将函数式编程甚至是本文中出现的部分抽象概念具象化


js中的相等性判断

前置知识点,回顾js中对于相等性和一些特殊值的判定方法:

  1. js中的三种相等性判定
  • 非严格相等(==)

将会对操作数执行类型转换。

  • 严格相等(===)

不会对操作数执行类型转换,只对值作比较。

  • Object.is()(es6新特性)

同三等运算符,但是对+0和-0、NaN值将会做特殊处理(下文论述)。我们再来回顾一下js中对NaN值的判断是如何做的。

2. js中NaN值判断方法

    • window.isNaN(x: any): Boolean
    • Number.isNaN(x: Number): Boolean
    • 利用NaN是js中唯一不等于自身的值(NaN !== NaN为true)
    • Object.is(),同值相等

MDN对Object.is()的解释是这样的:

6b98eec16e8b8e0cc9fbd0caa9c818fa.png

在es6中,Oject.is()将有以下两种特殊场景:

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

在js中其实存在两种0值,+0和-0,它们将分别在0与正、负数值作乘除运算时产生(这在/object-is.js的测试用例中可以证明),区别在于符号不同,代表的意义(功能)不同。虽然在加减运算中没有区别,但一些特殊运算将会产生不同的结果,比如:

1/0    // Infinity
-1/0   //-Infinity
1/0 === -1/0    // false,即Infinity不等于-Infinity

0在运算中作为除数出现似乎有悖于我们的日常认知,而且在几乎所有语言中(golang/c/java/php/python等)这样处理都会抛出异常,但js不会。

63c374b7c20d8744c82a24486e73567a.png

golang中0作为除数抛出异常

所以同值相等的设计解决了一个问题:确定两个值是否在任何情况下功能上是相同的

这个概念抽象吗?

我们可以借助数学方法中极限的定义来理解这个逻辑。对于反比例函数f(x)= k/x(k为常数,k≠0),当x = 0时无意义,但当k>0,x无限趋近于+0时,f(x)无限趋近于+∞,即Infinity,所以这里1/0输出结果为Infinity,同理可知-1/0为-Infinity。

b5d35f4451acea8e61dd8154e3e3ad9f.png
事实上,Infinity这个值也是无意义的,是空值,这体现在函数式编程的幺元概念中。

因此es6同值相等的判定方法认为+0、-0不相等(-0 < +0),这从v8对Object.is()方法的测试用例中可以论证。

cebaa6e71f903538dcf0c1d31870d609.png

./test/object-is.js中的测试用例

断言函数assertSame对传入的两个参数值(expect, found)是否相等是这样判断的:

c800dddc43afbb64fc06c8b36a386ce0.png

mjsunit.js中的assertSame断言函数

这里js巧妙借助了上面提到的符号互为相反的0值倒数不相等来区分+0与-0,用自身!==自身来区分NaN,解释了MDN中对Object.is()与严格相等运算符定义的区别。

在v8最新的代码中,assertSame()的上述核心判断逻辑已经改为了直接使用Object.is()方法来替代if条件语句,作者在注释中也直接说明了这一原因。这样一来,assertSame函数就直接与Object.is方法划上了等号。

1870bbaa7d776c5235c2a720c685a35e.png

写到这里,我们可能已经忘记开篇提到过的那个令人困惑的知识点了。


由Math静态方法默认值带来的思考

Math.min()
Math.max()

对于math的这两个静态方法,当无参传递时,有Math.min() > Math.max()成立。

如果第一次接触这个结论,你可能一时无法理解,Math.min()返回一个参数集合中的最小值,但事实上,当无参传递时其返回值是Infinity,同理Math.max()返回-Infinity。

这是js语言设计的一个怪异的地方,也可能是一个缺陷。要想论证其正确性首先依然可以从v8源码的单测用例入手。

在上一节中我们已经接触了一部分用测试用例论证的逻辑与结果。同样,这些用例也能够从结果上论证Math.min()返回Infinity的正确性。v8底层60%由C++编写,不过对于常用js-api的单元测试使用的是js,对于阅读来说没有什么障碍。

此处关注两个断言方法(mjsunit.js中为不同的api设计定义了不同的断言函数)

assertSame(expect, found)

41dd065a7370ba10705730ab99c2c0c0.png
assertEquals(expect, found)

d01f2c4c64507296e21a1eefca42541e.png
理解这两个函数的区别可以从理解Same与Identical这两个单词含义的差异入手: 1. same: (adj.)相同的,同一的 2. identical: (adj./n.)同一的,完全相同的;同一性 感兴趣的话可以去找适用这两个断言函数的API,通过看源码去理解其中的差异。

从math-min-max.js的测试用例可以看到,Math.min()无参传递时返回了Infinity,而Math.max()返回了Number的NEGATIVE_INFINITY,也就是-Infinity。

5ffce7a7afbbc9d7f3ae8af831dae376.png

e75a4fbdb7361dcb34289cd682af6c5a.png

assertEquals断言使用deepEquals方法作为其条件判断的核心。总的来说,assertSame与assertEquals底层的判断逻辑大同小异,只不过其适用的对象(js-api)不同。

a393bdcadac1dec0d17236d18196a937.png

但是,测试用例只从结果层面给出了答案,该如何从逻辑层面解释其合理性呢?


Monoid与单位元

上一篇的分享已经提到过函数式编程中的Monoid(幺半群)与单位元,,这里不再作详细展开,推荐一篇github上写的不错的blog《函数式编程(三)》作参考。

集合论与幺元的概念从逻辑上论证了Math.min()返回Infinity的正确性

半群(Semigroup)
定义一:对于非空集合 S,若在 S 上定义了二元运算 ○,使得对于任意的 a, b ∈ S,有 a ○ b ∈ S,则称 {S, ○} 为广群。若该广群的二元运算○满足结合律,则该广群称为半群。 * 幺半群(Monoid) 定义:存在单位元(幺元)的半群称为幺半群。 * 单位元(Identity Element,又叫幺元) 定义:对于半群 ,存在 e ∈ S,使得任意 a ∈ S 有 a ○ e = e ○ a。

用最通俗的表述来理解,就是:对于运算集合S上的二元运算○,如果满足 a○x = x○a,则a就是该二元运算的幺元。比如:

1. 加法运算的幺元是0
add(0+x) = add(x+0) = x

2. 乘法运算的幺元是1
multi(1*x) = multi(x*1) = x

3. &&运算的幺元是true;

4. ||运算的幺元是false;

5. 数组合并操作concat的幺元是空数组
concat([], [1,2,3]) = concat([1,2,3], []) = [1,2,3]
concat('', 'chara') = concat('chara', '') = 'chara'

6. ......

因此,对于min与max运算,有下式成立:

min(x, Infinity) = min(Infinity, x) = x
max(x, -Infinity) = max(-Infinity, x) = x

看到这里,是不是感觉幺元就是运算的一种默认值?答案是肯定的。在不传参时,Infinity是min运算的默认参数,所以min() = min(Infinity) = Infinity,也就是说Infinity是min运算的幺元。同理可知-Infinity是max()的幺元。 这样的话,一切就说得通了。


思考一个问题

那么假设一个函数getMax()可以求一个数字中的最大值,该如何为maxVal赋初值?

function getMax(arr) {  
  let maxVal = ____;    // ???
  arr.forEach(item => {
    if (item > maxVal) {
      maxVal = item;
    }
  })
  return maxVal; 
}
帮我把以下数据变成json格式:{ "switch_Cash": "Y", "pageMark": "add", "tx_date": "20230305", "cur_no": "1", "sfk_type": "03", "lease_type": "经营租赁", "change_rate": "0", "cap_name": "20220731SYZC000271", "lease_no": "20220731SYZC000271", "contract_key": "6980120220731001", "contract_name": "测试合同20220731-01", "bef_arch_area": "300", "bef_cash_rate: 2.38, "bill_type: 3, "bef_tax_rate: 5, "sfk_term: 4, "bef_tot_pay_time: 2, "bef_eve_amt: 22500, "exe_renew: N, "bef_renew_amt: 0, "bef_renew_eve_amt: 0, "bef_tot_pay_amt: 19047.62, "bef_unset_fin_exp: 221.39, "bef_contract_amt: 45000, "sige_date: 20220731, "bef_arch_position: 广州省佛山市宝芝林, "bef_effect_date: 20220801, "bef_mtr_date: 20240730, "bef_first_fk_date: 20220731,, "bef_renew_eft_date: , bef_renew_mtr_date: , aft_arch_area: 300, aft_bill_type: 3, aft_tax_rate: 5, aft_levy_rate: 5, aft_cash_rate: 2.38, aft_sfk_type: 03, aft_exe_renew: N, is_onchange: N, aft_sfk_term: 4, aft_contract_amt: 45000.00, aft_tot_pay_time: 2, aft_eve_amt: 22500, bef_renew_tot_pay_time: 0, max_term_seqn: 0, aft_effect_date: 20220801, aft_mtr_date: 20240730, aft_first_fk_date: 20220731, aft_renew_eft_date: , aft_renew_mtr_date: , bef_levy_rate: 5, cap_code: fjr0901, lease_no1: 20220731SYZC000271, aft_tot_pay_amt: 42857.14, aft_unset_fin_exp: 184.48999999999796, apply_amt: 23809.52, remark: test0609, change_valid_date: 20230609, cap_name_zh_: 测试资产-20230731-01, cur_no_zh_: 人民币, sfk_type_zh_: 周期性, flowable_: 1, flowable_targetNodeData: {"category":"CWSYS_69","isInput":"1","menuName":"使用权资产变更","pageNo":"cwsys_05_07"}, flowable_file_dataInfo: {"flowable_file_storeType":"undefined","flowable_file_split":false,"flowable_file_canDelete":true,"flowable_file_readOnly":false,"flowable_file_id":"FL-495eabb0-069c-11ee-9b92-59e9b66c35e6"}, cashList: [{"contract_no":"20220731HTXX000001","pay_date":"20220731","no":"20230201XJL000031","pay_amt":"25000.00","term_start_date":"20220801","create_trace_no":"16","tx_date":"0","trace_no":"0","term_end_date":"20230731","pay_sts":"未支付","version":"1","tax_rate":"0.00","term_seqn":"1","tax_amt":"1190.48","pay_sts_val":"0","id":"1","create_date":"20230201","levy_rate":"0.00"},{"contract_no":"20220731HTXX000001","pay_date":"20230731","no":"20230201XJL000032","pay_amt":"20000.00","term_start_date":"20230731","create_trace_no":"16","tx_date":"0","trace_no":"0","term_end_date":"20240730","pay_sts":"未支付","version":"1","tax_rate":"0.00","term_seqn":"2","tax_amt":"952.38","pay_sts_val":"0","id":"1","create_date":"20230201","levy_rate":"0.00"}] }
06-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值