基础概念
堆内存与栈内存
在操作系统中,内存被分为栈区和堆区。
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。
栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 详细资料参考:什么是堆?什么是栈?他们之间有什么区别和联系?
Undefined
undefined 是一个标识符,所以可以被当作变量来使用和赋值。
(function() {
var undefined = 'not is undefined';
console.log(undefined); //"not is undefined"
console.log(typeof undefined) // "string"
})()
Null
Boolean
Number
在 js 中不同进制数字的表示方式
- 以 0X、0x 开头的表示为十六进制。
- 以 0、0O、0o 开头的表示为八进制。
- 以 0B、0b 开头的表示为二进制格式。
js 中整数的安全范围是多少?
安全整数指的是,在这个范围内的整数转化为二进制存储的时候不会出现精度丢失,能够被“安全”呈现的最大整数是 2^53 - 1,Number.MAX_SAFE_INTEGER
Number.MAX_SAFE_INTEGER===Math.pow(2,53)-1 //true
Number.MIN_SAFE_INTEGER===-Math.pow(2,53)+1 // true
如果某次计算结果得到了一个超过Javascript数值范围的值。那么这个值会被自动转换为特殊的 Infinity ,isFinite可以判断isFinite。
Infinity === Infinity // true
typeof NaN 的结果是什么?
NaN 意指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
typeof NaN; // “number”
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。
可以通过Number.isNaN进行判断。
Object.is(NaN,NaN) // true
数组去重的时候需要注意NaN,某些方法可以去除 比如 includes,有些不可以比如indexOf
isNaN 和 Number.isNaN 函数的区别?
- isNaN 会尝试 将参数转换为数值,任何不可以被转为数值的值都会返回 true。
- Number.isNaN:先判断是不是数字,如果是数字在判断是否为NaN,更准确。
isNaN(“a”) // true
**Number.isNaN(“a”
~操作符
~ 返回 2 的补码,并且 ~ 会将数字转换为 32 位整数,因此我们可以使用 ~ 来进行取整操作。
~x 大致等同于 -(x+1)。
parseInt vs Number
parseInt:解析允许字符串中含有非数字字符,解析按照从左到右的顺序,如果遇到非数字就停止。
Number:如果遇到非数字,就会解析失败返回NaN
生成随机数的各种方法?
《JS - 生成随机数的方法汇总(不同范围、类型的随机数)》
0和-0
- 通过 1/0 和 1/-0 进行判断,Infinity 和 -Infinity。
- Object.is(0,-0) // false
格式化价格
var num = new Number('1.111e+84')
num = num.toLocaleString() // 会以每三位为一组,以逗号间隔
String
Unicode 和 UTF-8 之间的关系
Unicode 是一种字符集合,现在可容纳 100 多万个字符。每个字符对应一个不同的 Unicode 编码,它只规定了符号的二进制代码,却没有规定这个二进制代码在计算机中如何编码传输。
UTF-8 是一种对 Unicode 的编码方式,它是一种变长的编码方式,可以用 1~4 个字节来表示一个字符。Symbol(Es6)
Symbol 代表创建后独一无二且不可变的数据类型,它的出现我认为主要是为了解决可能出现的全局变量冲突的问题。BigInt(Es10)
BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。问题
undefiend与undeclared
undefined:已在作用域中声明但还没有赋值的变量.
undeclared:还没有在作用域中声明过的变量
对于 undeclared 变量的引用,浏览器会报引用错误,如 ReferenceError: b is not defined 。但是我们可以使用 typeof 的安全防范机制来避免报错,因为对于 undeclared(或者 not defined )变量,typeof 会返回 “undefined”。
null 和 undefined 的区别
- undefined和null都是基本数据类型,这两个基本数据类型分别都只有一个值
null
- null代表空对象
- 主要用于赋值给一些可能会返回对象的变量,作为初始化。
- 当又一个数据不在需要时,可以通过为其赋值null解除引用
- typeof null 的值为object。
- 作为对象原型链的终点。
undefined
- 代表的含义是未定义
- 函数相关
- 调用函数,没有被传递的参数的默认值。
- 函数的默认返回值。
- 变量被声明后的默认值。
- 对象没有赋值的属性。
- typeof 未定义的变量 值为undefined
- 函数相关
- undefined不是一个保留字
window.null 的值是 undefined。 typeof window.null 为 “undefined”
window.undefined 的值是 undefined。 typeof window.undefined 为 “undefined”
undefined == null // false
undefined === null // false
ndefined值是派生自null值的。
在ES3之前其实是没有原始值undefined这个值的,第三版引入这个值,其实是为了正式区分空对象指针(后面我们会介绍到这指的就是null)与未经初始化的变量。
类型转换
其他值->字符串
https://262.ecma-international.org/11.0/#sec-tostring
规范的 9.8 节中定义了抽象操作 ToString ,它负责处理非字符串到字符串的强制类型转换。
- Null 和 Undefined 类型 ,null 转换为 “null”,undefined 转换为 “undefined”,
- Boolean 类型,true 转换为 “true”,false 转换为 “false”。
- Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
- Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
- 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString()),来返回内部属性 [[Class]] 的值,如”[object Object]”。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。
其他值->数字值
有时我们需要将非数字值当作数字来使用,比如数学运算。为此 ES5 规范在 9.3 节定义了抽象操作 ToNumber。
(1)Undefined 类型的值转换为 NaN。
(2)Null 类型的值转换为 0。
(3)Boolean 类型的值,true 转换为 1,false 转换为 0。
(4)String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
(5)Symbol 类型的值不能转换为数字,会报错。
(6)对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
Symbol 值的强制类型转换?
- ES6 允许从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误。
- Symbol 值不能够被强制类型转换为数字(显式和隐式都会产生错误)
- 可以被强制类型转换为布尔值(显式和隐式结果都是 true )。 ```javascript let a = Symbol.for(“a”)
a.toString() // “Symbol(a)” String(a) // “Symbol(a)” a + “1” // Cannot convert a Symbol value to a string
Number(a) // Cannot convert a Symbol value to a number a + 1 // Cannot convert a Symbol value to a number
Boolean(a) // true !a // false
<a name="zzb3Z"></a>
## 其他值到布尔类型的值的转换规则?
ES5 规范 9.2 节中定义了抽象操作 ToBoolean,列举了布尔强制类型转换所有可能出现的结果。<br />以下这些是假值:<br />• undefined<br />• null<br />• false<br />• +0、-0 和 NaN<br />• ""<br />假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
<a name="xdjdz"></a>
## 什么情况下会发生布尔值的隐式强制类型转换?
1. **if (..) **语句中的条件判断表达式。
1. **for** ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。
1. **while (..) **和 **do..while(..)** 循环中的条件判断表达式。
1. **? :** 中的条件判断表达式。
1. **逻辑运算符** ||(逻辑或)和 &&(逻辑与)**左边的操作数(**作为条件判断表达式)。
<a name="Yf6Bk"></a>
## {} 和 [] 的 valueOf 和 toString 的结果是什么?
{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"<br />[] 的 valueOf 结果为 [] ,toString 的结果为 ""
<a name="tuudL"></a>
## == 操作符的强制类型转换规则?
1. **字符串**和**数字**之间的相等比较,将字符串转换为**数字**之后再进行比较。
1. **其他类型**和**布尔**类型之间的相等比较,**先**将**布尔值转换为数字**后,再应用其他规则进行比较。
1. **null 和 undefined** 之间的相等比较,**结果为真**。其他值和它们进行比较都**返回假值**。
1. **对象**和**非对象之间**的相等比较,**对象先调用 ToPrimitive 抽象**操作后,再进行比较。
1. 如果一个操作值为** NaN **,则相等比较**返回 false**( NaN 本身也不等于 NaN )。
1. 如果两个操作值**都是对象**,则比较它们是不是指向同一个对象。如果两个操作数都**指向同一个对象**,则相等操作符**返回 true**,否则,返回 false。
```javascript
"[object Object]" == {} //true
[] == {} // false => "" == "[object Object]" false
如何将字符串转化为数字,例如 ‘12.3b’?
- 使用 Number() 方法,前提是所包含的字符串不包含不合法字符。
- 使用 parseInt() 方法,parseInt() 函数可解析一个字符串,并返回一个整数。还可以设置要解析的数字的基数。当基数的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。
- 使用 parseFloat() 方法,该函数解析一个字符串参数并返回一个浮点数。
- 使用 + 操作符的隐式转换。
===、==、Object.is
- 使用双等号进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
- 三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回false)。
- Object.is 在三等号判等的基础上特别处理了 NaN 、-0 和 +0 ,保证 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 会返回 true。
如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为『12,000,000.11』?
// 方法一
function format(number) {
return number && number.replace(/(?!^)(?=(\d{3})+\.)/g, ",");
}
// 方法二
function format1(number) {
return Intl.NumberFormat().format(number)
}
// 方法三
function format2(number) {
return number.toLocaleString('en')
}
思考
({}+{}).length // "[object Object][object Object]".length 30
([]+[]).length // "" 0
操作符
+
操作符什么时候用于字符串的拼接?
根据 ES5 规范 11.6.1 节,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用[[DefaultValue]],以数字作为上下文。如果不能转换为字符串,则会将其转换为数字类型来进行计算。
简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
1 + "1" // 11
"1" + 1 // 11
var c ={valueOf:()=>1}
c + 1 // 2
var d ={valueOf:()=>"1"}
c + 1 // 11
var e ={toString:()=>"1"}
e + 1 // 11
var f ={toString:()=>1}
f + 1 // 2
为什么 0.1 + 0.2 != 0.3?如何解决这个问题
当计算机计算 0.1+0.2 的时候,实际上计算的是这两个数字在计算机里所存储的二进制,0.1 和 0.2 在转换为二进制表示的时候会出现位数无限循环的情况。js 中是以 64 位双精度格式来存储数字的,只有 53 位的有效数字,超过这个长度的位数会被截取掉这样就造成了精度丢失的问题。这是第一个会造成精度丢失的地方。在对两个以 64 位双精度格式的数据进行计算的时候,首先会进行对阶的处理,对阶指的是将阶码对齐,也就是将小数点的位置对齐后,再进行计算,一般是小阶向大阶对齐,因此小阶的数在对齐的过程中,有效数字会向右移动,移动后超过有效位数的位会被截取掉,这是第二个可能会出现精度丢失的地方。当两个数据阶码对齐后,进行相加运算后,得到的结果可能会超过 53 位有效数字,因此超过的位数也会被截取掉,这是可能发生精度丢失的第三个地方。
对于这样的情况,我们可以将其转换为整数后再进行运算,运算后再转换为对应的小数,以这种方式来解决这个问题。
我们还可以将两个数相加的结果和右边相减,如果相减的结果小于一个极小数,那么我们就可以认定结果是相等的,这个极小数可以
使用 es6 的 Number.EPSILON
精度丢失可能出现在进制转换和对阶运算中
详细资料可以参考:
- 0.1 + 0.2不等于0.3?为什么JavaScript有这种“骚”操作?
- 『面试的底气』—— 0.1+0.2等于0.3吗|牛气冲天新年征文
- 《十进制的 0.1 为什么不能用二进制很好的表示?》
- 《十进制浮点数转成二进制》
- 《浮点数的二进制表示》
- 《js 浮点数存储精度丢失原理》
- 《浮点数精度之谜》
- 《JavaScript 浮点数陷阱及解法》
- 《0.1+0.2 !== 0.3?》
- 《JavaScript 中奇特的~运算符》