持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情
6. intanceof 操作符的实现原理及实现
instanceof 运算符⽤于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
class Person { }
class Teacher extends Person { }
const p = new Teacher()
console.log(p instanceof Person, p instanceof Teacher, p instanceof Object)
// 手写 instanceof实现
function myInstanceof(obj, Prototype) {
const prototype = Prototype.prototype
// 拿到对象的原型
let proto = Object.getPrototypeOf(obj)
while (proto) {
if (proto === prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}
console.log(myInstanceof(p, Person), myInstanceof(p, Teacher), myInstanceof(p, Object))
为什么0.1+0.2 ! == 0.3,如何让其相等
在开发过程中遇到类似这样的问题:
const n1 = 0.1 , n2 = 0.2;
console.log(n1+n2) // 0.3000000000.....
这⾥得到的不是想要的结果,要想等于0.3,就要把它进⾏转化
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五⼊
toFixed(num) 方法可把 Number 四舍五⼊为指定⼩数位数的数字。那为什么会出现这样的结果呢? 计算机是通过二进制的⽅式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进 制的和。0.1的二进制是0.0001100110011001100...(1100循环),0.2的二进制 是:0.00110011001100...(1100循环),这两个数的二进制都是⽆限循环的数。那JavaScript是 如何处理⽆限循环的二进制⼩数呢?
⼀般我们认为数字包括整数和⼩数,但是在 JavaScript 中只有⼀种数字类型:Number,它的实现遵 循IEEE 754标准,使⽤64位固定⻓度来表示,也就是标准的double双精度浮点数。在⼆进制科学表示 法中,双精度浮点数的⼩数部分最多只能保留52位,再加上前⾯的1,其实就是保留53位有效数字,剩余 的需要舍去,遵从“0舍1⼊”的原则。
根据这个原则,0.1和0.2的⼆进制数相加,再转化为⼗进制数就是:0.30000000000000004。 说了这么多,是时候该最开始的问题了,如何实现0.1+0.2=0.3呢?
对于这个问题,⼀个直接的解决⽅法就是设置⼀个误差范围,通常称为“机器精度”。对JavaScript来 说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,⽽它的值就是2-52,只要判断 0.1+0.2-0.3是否⼩于Number.EPSILON,如果⼩于,就可以判断为0.1+0.2 ===0.3
function numberepsilon(arg1, arg2) {
return Math.abs(arg1 - arg2) < Number.EPSILON;
}
console.log(numberepsilon(0.1 + 0.2, 0.3)) // true
8. 如何获取安全的 undefined 值?
老实说,在JavaScript中,没有把undefined作为关键字,是一个败笔。因为不是关键字,导致我们可以把undefined作为标识符,所以可能导致我们给某个变量赋值为undefinded的时候,可能取到的值是修改后的 undefined变量的值。
function test(){
const undefined = "123"
const a = undefined
console.log(a) // 123
}
test()
因此我们会使用一个void xx 表达式, 该void表达式的返回值是真正的undefined,而一般来说void 0占用的空间最小,且也是最美观的,我们会使用这个表达式来拿到真正的undefined
9. typeof NaN 的结果是什么?
NaN 指“不是⼀个数字”(not a number),NaN 是⼀个“警戒值”(sentinel value,有特殊⽤途 的常规值),⽤于指出数字类型中的错误情况,即“执⾏数学运算没有成功,这是失败后返回的结果”。
typeof NaN; // number
NaN 是⼀个特殊值,它和⾃身不相等,是唯⼀⼀个非自反(自反,reflexive,即 x === x 不成立) 的值。⽽ NaN !== NaN 为 true。
// 使用 isNaN 可以判断一个变量是否是NaN
console.log(Number.isNaN(NaN))
// 使用 is方法可以判断两个NaN为相等
console.log(Object.is(NaN, NaN))
10. isNaN 和 Number.isNaN 函数的区别?
- 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传⼊也会返回 true ,会影响 NaN 的判断。
- 函数 Number.isNaN 会首先判断传⼊参数是否为数字,如果是数字再继续判断是否为 NaN ,不会 进行数据类型的转换,这种⽅法对于 NaN 的判断更为准确。
11. == 操作符的强制类型转换规则?
对于 == 来说,如果对比双方的类型不⼀样,就会进行类型转换。假如对⽐ x 和 y 是否相同,就会 进行如下判断流程:
- 首先会判断两者类型是否相同,相同的话就⽐较两者的大小
- 类型不相同的话,就会进行类型转换;
- 会先判断是否在对⽐ null 和 undefined,是的话就会返回 true
- 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
- 判断其中⼀方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
- 判断其中⼀方是否为 object 且另⼀⽅为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进⾏判断
12. 其他值到字符串的转换规则?
- Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined"
- Boolean 类型,true 转换为 "true",false 转换为 "false"。
- Number 类型的值直接转换,不过那些极⼩和极⼤的数字会使⽤指数形式。
- Symbol 类型的值直接转换,但是只允许显式强制类型转换,使⽤隐式强制类型转换会产⽣错误。
- 对普通对象来说,除非自行定义 toString() ⽅法,否则会调⽤ toString() (Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有⾃⼰的 toString() ⽅法,字符串化时就会调⽤该⽅法并使⽤其返回值。
其他值到数字值的转换规则?
- Undefined 类型的值转换为 NaN。
- Null 类型的值转换为 0。
- Boolean 类型的值,true 转换为 1,false 转换为 0。
- String 类型的值转换如同使⽤ Number() 函数进行转换,如果包含非数字值则转换为 NaN,空 字符串为 0。
- Symbol 类型的值不能转换为数字,会报错。
- 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循 以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf()⽅法。如果有并且返回基本类型值,就使⽤该值进⾏强制 类型转换。如果没有就使⽤ toString() 的返回值(如果存在)来进⾏强制类型转换。 如果 valueOf() 和 toString() 均不返回基本类型值,会产⽣ TypeError 错误。
其他值到布尔类型的值的转换规则?
以下这些是假值: • undefined • null • false • +0、-0 和 NaN • "" 假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
Object.is() 与⽐较操作符 “===”、“==” 的区别?
- 使⽤双等号(==)进⾏相等判断时,如果两边的类型不⼀致,则会进行强制类型转化后再进行⽐较。
- 使⽤三等号(===)进⾏相等判断时,如果两边的类型不⼀致时,不会做强制类型准换,直接返回 false。
- 使⽤ Object.is 来进⾏相等判断时,⼀般情况下和三等号的判断相同,它处理了⼀些特殊的情 况,⽐如
-0 和+0不再相等,两个 NaN 是相等的。
JavaScript 中如何进⾏隐式类型转换?
首先要介绍ToPrimitive⽅法,这是 JavaScript 中每个值隐含的⾃带的⽅法,⽤来将值 (⽆论是 基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象:
/**
obj 期望转换的对象
type 期望转换为的类型
*/
ToPrimitive(obj, type)
type的值为number或者string或者是默认值default
- type为number时,规则如下:
- 调⽤obj的valueOf⽅法,如果为原始值,则返回,否则下⼀步;
- 调⽤obj的toString⽅法,如果为原始值,则返回,否则下⼀步;
- 抛出TypeError 异常。
- 当type为string时规则如下:
- 调⽤obj的toString⽅法,如果为原始值,则返回,否则下⼀步
- 调⽤obj的valueOf⽅法,后续同上;
- 抛出TypeError 异常。
可以看出两者的主要区别在于调⽤toString和valueOf的先后顺序。默认情况下:
- 如果对象为 Date 对象,则type默认为string;
- 其他情况下,type默认为number。
总结上⾯的规则,对于 Date 以外的对象,转换为基本类型的大概规则可以概括为⼀个函数:
const objToNumber = value=>Number(value.valueOf().toString())
⽽ JavaScript 中的隐式类型转换主要发⽣在+、-、*、/以及==、>、<这些运算符之间。⽽这些运 算符只能操作基本类型值,所以在进⾏这些运算前的第⼀步就是将两边的值⽤ToPrimitive转换成基本 类型,再进行操作。 以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive转换成 基本类型,所以最终还是要应⽤基本类型转换规则):
-
+操作符
- +操作符的两边有⾄少⼀个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边 的变量都会被转换为数字。
-
-、*、\操作符
-
对于==操作符,操作符两边的值都尽量转成number:
-
对于<和>⽐较符
- 如果两边都是字符串,则⽐较字⺟表顺序:
- 其他情况下,转换为数字再⽐较: 以上说的是基本类型的隐式转换,而对象会被ToPrimitive转换为基本类型再进⾏转换:
+ 操作符什么时候⽤于字符串的拼接?
根据 ES5 规范,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进⾏拼接操 作。如果其中⼀个操作数是对象(包括数组),则⾸先对其调⽤ ToPrimitive 抽象操作,该抽象操作 再调⽤ [[DefaultValue]],以数字作为上下⽂。如果不能转换为字符串,则会将其转换为数字类型来 进行计算。
简单来说就是,如果 + 的其中⼀个操作数是字符串(或者通过以上步骤最终得到字符串),则执⾏字符串拼接,否则执行数字加法。 那么对于除了加法的运算符来说,只要其中⼀⽅是数字,那么另⼀⽅就会被转为数字。