本文目录
[[toc]]
类型转换
运算过程自动类型转换
转换规则
| 操作类型 | 转换规则 | 示例 |
|---|---|---|
string + any | 非字符串转为字符串 | console.log('3' + true + 1) // "3true1" |
- / * / / / % | 所有操作数转为数字 | console.log(' 012 ' / 3) // 4 |
boolean 参与运算时 | true 转为数字是 1, false 转为数字是 0 | console.log(true + false) // 1console.log(1 + true + false) // 2 |
null 参与运算时 | null 转为数字是 0 ,转为字符串是 null | console.log(null + 1) // 1console.log(null + "1") // "null1" |
undefined 参与运算时 | undefined 转为数字是 NaN, 转为字符串是 "undefined" | console.log(undefined + 1) // NaNconsole.log(undefined + "1") // "undefined1" |
object / array | Symbol.toPrimitive > 内置的 ToPrimitive内置 toPrimitive 优先调用 valueOf 尝试获取基本类型,如果返回非基本类型则调用 toString, toString 也不返回基本类型,抛出错误 | 参见下方示例 |
Date | 内置的 ToPrimitive 行为中,优先调用的是 toString 而不是 valueOf | console.log(new Date() + 1) // Thu Feb 27 2025 11:05:01 GMT+0800 (中国标准时间)1 |
if / while / ! / && / || | 将值转为 boolean 进行比较 | 转换规则见 真值表 |
in / parseInt | 将值转为 string 再解析 | 见 特殊场景 |
const obj = {
/** 自定义隐式转换,会覆盖内置的,也就是后续不会继续调用 valueOf 与 toString ,但是还是会抛出错误 */
[Symbol.toPrimitive]() {
console.log('自定义转换')
return {}
},
/** 覆盖内置的 valueOf 转换函数 */
valueOf() {
console.log('调用 valueOf')
return {}
},
/** 覆盖内置的 toString 转换函数 */
toString() {
console.log('调用 toString')
return {}
}
}
console.log(1 + obj) // Uncaught TypeError: Cannot convert object to primitive value常见的 toPrimitive
| 类型 | valueOf | toString |
|---|---|---|
| 原始类型 | 返回自身 | 转为字符串 |
Number / String / Boolean | 返回对应的原始值 | 原始值对应的字符串 |
Symbol | 返回自身 | 按照 Symbol(description) 返回 |
| 数组 | 返回数组对象本身 | 与调用 join 相同 |
Date | 时间戳 (1970 至今的毫秒数) | 时间字符串 |
RegExp | 返回对象本身 | 返回正则的原始字符串版本(会进行转义,比如 \\ 会转为 \\\\) |
Math | 返回对象本身 | [object Math] |
| 对象 | 返回对象本身 | [object Object] |
Function | 返回函数本身,即函数的引用 | 函数源码 |
真值表
true:0、NaN、''、null、undefinedfalse: 非零数字、非空字符串、Symbol、 所有对象类型
特殊场景
// 数组转换
[] + {} // "[object Object]" ([] → "",{} → "[object Object]")
{} + [] // 0 ({} 被解析为空代码块,+[] → 0)
console.log({} + []) // "[object Object]"
// Chrome : `[object Object][object Object]`
// Firefox: 第一个 {} 是空作用域,也就是说这里等价于 +{} ,也就是转数字,也就是 NaN
// 如果不是直接在控制台输出,而是放到 console 中,应该是没有异议的,就是 Chrome 的输出结果
{} + {}
+[] // 等价于 (+"") = 0
// 对象转换
const obj = {
a: 123,
valueOf: () => 2,
toString: () => "3"
};
obj + 1 → 3(优先调用 valueOf())
console.log(parseInt(obj)) // 3 , 会直接调用 toString 转为 '3' ,再 parseInt
console.log(['a'] in obj) // true , ['a'] 会被调用 toString , 转为 `'a' in obj` 也就是 true
// 日期对象
new Date() + 1 → "Mon Jul 18 2022 12:00:00 GMT+08001"(优先调用 toString())相等性比较
===
- 不进行类型转换,同时比较类型与值
- 对象比较引用地址,非相同地址为
false NaN与任何值(包括NaN)相比都是false+0与-0视为相等
Object.is
比 === 更加严格的比较,更加精准的比较
0 === -0; // trueNaN === NaN; // falseObject.is(0, -0); // falseObject.is(NaN, NaN); // true
== 相等性比较
原则: 优先转为 number 比较,其次是 string 最后是 boolean ,有非基本类型参与比较,非基本类型先通过 toPrimitive 再比较
| 比较类型 | 转换规则 |
|---|---|
boolean == 其他 | 布尔值转数字(true → 1,false → 0) |
string == number | 字符串转数字("5" == 5 → true) |
object / array == 基本类型 | 转基本类型(通过 ToPrimitive )再比较 |
null == undefined | 标准中的特例,二者相等(null == undefined → true) |
null == 0 | 标准中的特例, null 不会转为 0 , 所以不相等 |
常见比较时转换示例
[] == ![] → true
转换过程:
- 计算
![]: 所有对象类型都是 true ,所以![]就是 false - 计算
[] == false:[]进行toPrimitive,也就是'' - 计算
'' == false: 两边转数字,即0 == 0
[0] == false
转换过程:
- 计算
[0]:[0]进行toPrimitive为'0' - 计算
'0' == false: 两边转数字,即0 == 0
> / < / >= / <= 类型比较
- 若其中之一是数字,两边转数字比较
'21' < 4 → false - 若两边都是字符串,按照字典序比较
'21' < '4' → true
数字计算
console.log(1 / 0) // Infinity
console.log(-1 / 0) // -Infinity
console.log(1 / -0) // -Infinity
console.log(1 / 0) // Infinity
console.log(1n / 0n) // throw Error运算符
隐式转换 + 运算符缺省值
let a = 1
console.log(a++ + a) // 等价于 (a++) + a 等价于 a + (a + 1) = 3
console.log(1 + 2) // 3
console.log(1 + +2) // 等价于 (1 + 0) + 2 = 3
console.log(1 + + +2) // 等价于 ((1 + 0) + 0) + 2 = 3
console.log(`${1}2`) // "12"
console.log(1 + +'2') // 等价于 1 + (+"2") 等价于 1 + 2 = 3
console.log(`1${2}`) // "12"
console.log(`1${+2}`) // 等价于 "1" + (+2) 等价于 "1" + 2 = "12"
console.log(1 + true) // 等价于 1 + 1 = 2
console.log(1 + +true) // 等价于 1 + (+true) 等价于 1 + 1 = 2
console.log(`1${true}`) // "1true"
console.log(`1${+true}`) // 等价于 "1" + (+true) 等价于 "1" + 1 = "11"
console.log(1 + null) // 等价于 1 + 0 = 1
console.log(1 + +null) // 等价于 1 + (+null) 等价于 1 + 0 = 1
console.log(`1${null}`) // "1null"
console.log(`1${+null}`) // 等价于 "1" + (+null) 等价于 "1" + 0 = "10"
console.log(1 + undefined) // 等价于 1 + NaN = NaN
console.log(1 + +undefined) // 等价于 1 + (+undefined) 等价于 1 + NaN = NaN
console.log(`1${undefined}`) // "1undefined"
console.log(`1${+undefined}`) // 等价于 "1" + (+undefined) 等价于 "1" + NaN = "1NaN"
console.log(`1${+ +undefined}`) // 等价于 "1" + (+(+undefined)) 等价于 "1" + (+NaN) 等价于 "1" + NaN = "1NaN"比较运算符连用
console.log(0 == 1 == 2) // 等价于 `console.log(false == 2)` 所以是 false
console.log(2 == 1 == 0) // 等价于 `console.log(false == 0)` 所以是 truefor in 与 for of
如果循环期间动态添加元素
for in: 新元素不会被加入循环中for of: 会将新元素也加入循环中执行
const list = Array.from({ length: 3 }).fill(1).map((num, idx) => num + idx)
for (const key in list) {
if (list[key] === 2) {
list.push(4)
}
console.log(list[key])
}
for (const item of list) {
if (item === 2) {
list.push(5)
}
console.log(item)
}
console.log(list)遍历原型字段
for in会遍历出原型上的字段for of只能遍历当前对象的字段,不会查找原型
const list = Array.from({ length: 3 }).fill(1).map((num, idx) => num + idx)
list.__proto__.val = 123
for (const key in list) {
console.log(`for in ${list[key]}`)
}
for (const item of list) {
console.log(`for of ${item}`)
}
console.log(list)Function
arguments
修改 arguments 会影响参数值
function log(a, b, c, d) {
console.log(a, b, c, d)
arguments[0] = 'bfe'
arguments[3] = 'dev'
// 只有 a 会被修改, d 不会被修改,因为 d 本身就是 undefined ,没有被记录引用
console.log(a, b, c, d)
}
log(1, 2, 3)具名函数可访问性
func1() // 报错,变量提升的时候, func1 是 undefined ,并不能把函数也提升
func2() // 首先如果要执行必须注释掉上一行,否则报错了不会执行到这里,如果执行到了,这里也是报错
// 具名函数被复制后,函数外部无法通过函数名访问此函数,但是函数内部可以,并且这个命名不可被改变
var func1 = function func2() {
console.log(2)
}var a = 1
function a() { // 提升到 var 变量定义之前
}
console.log(typeof a) // 具名函数在外部是可以被覆盖的,这里返回 "number"具名函数内部重定义
在 JavaScript 中,使用 function 关键字进行函数声明时,函数会存在变量提升的情况。
所以在代码开头执行 console.log(typeof a) 时,它能够识别到 a 是一个函数,所以返回 "function" 。
当进入 a 函数内部,先是打印 typeof a ,此时 a 仍然是函数本身,所以返回 "function" 。接着执行 a = 'a' ,这里相当于把原本的函数 a 重新赋值为一个字符串,所以后续再执行 console.log(typeof a) 就返回 "string" 了。
在全局作用域下,最开始 a 是被当作函数声明处理的,在函数内部重新赋值改变了 a 原本指向的函数对象,使其变成了一个字符串,这一系列操作改变了全局变量 a 的类型。
function a() {
console.log(typeof a) // "function"
a = 'a'
console.log(typeof a) // "string"
}
console.log(typeof a) // "function"
a()function a(){
}
const b = function() {
}
const c = function d() {
console.log(typeof d) // "function"
d = 'e'
console.log(typeof d) // "function"
}
console.log(typeof a) // "function"
console.log(typeof b) // "function"
console.log(typeof c) // "function"
// d 是一个 命名函数表达式。这个名称 d 仅在函数体(作用域)内是局部的,因此在函数体外部,typeof d 返回 undefined。
console.log(typeof d) // "undefined"
c()函数定义作为 if 条件
如下示例, foo 会被视为 IIFE 一样的函数块,不会提升到全局,所以内外部访问 foo 都是访问不到的
if (function foo() {
console.log('BFE')
}) {
console.log('dev') // 会输出
foo() // Error , foo 未定义
}
foo() // Error , foo 未定义bind
多次 bind 时,只有第一次 bind 会生效,后续会像箭头函数一样无法修改 this
深拷贝
structuredClone 无法拷贝的数据类型
参见 MDN 算法介绍
FunctionsymbolDOM节点- 原型链
- 部分
Error对象(除了Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError之外的都不能被拷贝) - 属性描述,如只读、不可枚举等描述信息
getter/setter
变量提升
Function 提升
function 关键字定义的函数,如果在条件块中定义,除非条件永假,否则就会被提升
(() => {
if (!fn) {
function fn() {
console.log('2')
}
}
fn() // 输出 "2"
})()
function fn() {
console.log('1')
}
// another one
function fn1() {
console.log('3')
}
(() => {
if (!fn1) {
function fn1() {
console.log('4')
}
}
fn1() // 输出 "4"
})()
(() => {
if (false) {
function fn3() {
console.log('5')
}
}
fn3() // 未定义,抛 Error
})()This
未指定作用域
根据 ECMAScript262 规范规定:如果第一个参数传入的对象调用者是null或者 undefined , call 方法将把全局对象(浏览器上是 window 对象)作为 this 的值。所以,不管传入 null 还是 undefined ,其 this 都是全局对象 window 。
function a() {
console.log(this)
}
a.call(null) // window
a.call(undefined) // window但是在严格模式中, null 就是 null , undefined 就是 undefined
'use strict'
function a() {
console.log(this)
}
a.call(null) // null
a.call(undefined) // undefined对象字面量 {}
{} 内的函数,在定义时没有单独的 this ,会指向更上一层的 this ,但是在调用时有 this ,会指向 {} 对象本身
const obj = {
func1() {
console.log(this)
},
func2: () => {
console.log(this)
}
}
obj.func1() // obj
obj.func2() // windowFunction
在 Javascript 中,this 指向函数执行时的调用者。
function foo() {
console.log(this.a)
}
function doFoo() {
console.log(this.a)
foo()
}
const obj = {
a: 1,
doFoo
}
const a = 2
obj.doFoo() // 1 2const a = 10
const obj = {
a: 20,
say() {
console.log(this.a)
}
}
obj.say() // 20
const anotherObj = { a: 30 }
obj.say.apply(anotherObj) // 30箭头函数
箭头函数的 this 在函数定义时就已经确定了,并且不能通过 call / bind / apply 等方式修改(不会报错,只是没效果)。
const a = 10
const obj = {
a: 20,
say: () => {
console.log(this.a)
}
}
obj.say() // 10
const anotherObj = { a: 30 }
obj.say.apply(anotherObj) // 10const obj = {
dev: 'bfe',
/**
* 定义函数类型的属性
*/
a() {
return this.dev
},
/**
* 与 a 函数等价,是语法糖形式
*/
b() {
return this.dev
},
/**
* 对象中出现箭头函数,会创建一个新的作用域,该作用域与对象定义时的 this 相同,在本例子中为 window
*/
c: () => {
return this.dev
},
d() {
return (() => {
// 作用域与 d 函数执行时的作用域相同
return this.dev
})()
},
e() {
return this.b()
},
f() {
return this.b
},
g() {
return this.c()
},
h() {
return this.c
},
i() {
return () => {
return this.dev
}
}
}
console.log(obj.a()) // "bfe"
console.log(obj.b()) // "bfe"
console.log(obj.c()) // undefined
console.log(obj.d()) // "bfe"
console.log(obj.e()) // "bfe"
console.log(obj.f()()) // undefined
console.log(obj.g()) // undefined
console.log(obj.h()()) // undefined
console.log(obj.i()()) // "bfe"const obj = {
a: 1,
b() {
console.log(this.a)
},
c() {
console.log(this.a)
},
d: () => {
console.log(this.a)
},
e: (function () {
return () => {
console.log(this.a)
}
})(),
f() {
return () => {
console.log(this.a)
}
}
}
console.log(obj.a) // 没有疑问就是 1
obj.b() // 1
;(obj.b)() // 1 ,括号不会改变作用域
const b = obj.b
b() // 赋值会改变 this ,所以这里是 undefined
obj.b.apply({ a: 2 }) // apply 会改变 this ,所以这里是 2
obj.c() // 1
obj.d() // undefined
;(obj.d)() // undefined
obj.d.apply({ a: 2 }) // undefined , 箭头函数也有 call/apply/bind , 调用后不会报错,只是没有效果
obj.e() // undefined , IIFE + 箭头函数,在 IIFE 执行时, this 还是 window 而不是 obj
;(obj.e)() // undefined , 括号不改变作用域,所以没区别
obj.e.call({ a: 2 }) // undefined , 作用域已经被箭头函数锁死,通过 call 也无法修改 this
obj.f()() // 1 , this 只有 f 被调用时才会被箭头函数绑定
;(obj.f())() // 1 , 括号不改变作用域, this 不变
obj.f().call({ a: 2 }) // 1 , f 被执行后, this 就被箭头函数锁死,所以 call 也无法修改 thisPromise
Promise.resolve
参数
Promise.resolve 方法的参数如果是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved , Promise.resolve 方法的参数,会同时传给回调函数。
then方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为then(null), 这就会导致前一个Promise的结果会传递下面。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)返回值
Promise.resolve 执行时,如果参数本身为 Promise,则返回其本身
const p = new Promise((res, rej) => {
res(1)
})
async function asyncReturn() {
return p
}
function basicReturn() {
return Promise.resolve(p)
}
console.log(p === basicReturn()) // true
console.log(p === asyncReturn()) // falsePromise.reject
返回值
与 Promise.resolve 不同, Promise.reject 方法不会重用已存在的 Promise 实例。它始终返回一个新的 Promise 实例,该实例封装了拒绝的原因(reason)。
const p = Promise.resolve(1)
const rejected = Promise.reject(p)
console.log(rejected === p) // false
rejected.catch((v) => {
console.log(v === p) // true
}).then()
返回值
如果在 then 中 return Promise,则 return 会创建一个新的 Promise(PromiseResolveThenableJob) 如果将 then 返回的 Promise 称为 promise1 , return 接受的 Promise 称为 promise2 ,则 PromiseResolveThenableJob 的任务就是将 promise1 的 resolve 、 reject 与 promise2 的 resolve 、 reject 关联起来,此时会多一个 micro task 周期,也就是说,这个后面的 then 需要在两个 micro task 周期后才会触发。
Promise.resolve()
.then(() => {
console.log(0)
return Promise.resolve(4)
})
.then((res) => {
console.log(res)
})
Promise.resolve()
.then(() => {
console.log(1)
})
.then(() => {
console.log(2)
})
.then(() => {
console.log(3)
})
.then(() => {
console.log(5)
})
.then(() => {
console.log(6)
}).finally()
见 mdn 文档
参数
回调的参数不会接受任何参数
Promise.resolve(1)
.finally(res => console.log(res))返回值
finally 不会改变原 Promise 的结果。所以返回的默认是一个上一层 Promise 值(不分 resolve 或者 reject )。
如果抛出的是一个异常,或者返回 Promise.reject 则会返回异常的 Promise 对象。
除此之外, return 非 Promise.reject 类型的值都不会改变 .then() 的参数(指向上一层 Promise 的值)。
Promise.resolve('1')
.then((res) => {
console.log(res)
})
.finally(() => {
console.log('finally')
})
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then((res) => {
console.log('finally2后面的then函数', res)
})
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中抛出的异常')
})
.then((res) => {
console.log('finally后面的then函数', res)
})
.catch((err) => {
console.log('捕获错误', err)
})Promise.all 与 Promise.race
错误处理流程
all 和 race 传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。
function runAsync(x) {
const p = new Promise(r =>
setTimeout(() => r(x, console.log(x)), 1000)
)
return p
}
function runReject(x) {
const p = new Promise((res, rej) =>
setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
)
return p
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('result: ', res))
.catch(err => console.log(err))总结
只有 Promise.resolve 才可能会复用 Promise ,其他的都会生成新的 Promise 。
Array
array hole
hole 指的是洞,即数组中未被初始化的部分,一般表示为 empty ,在打印时会输出 undefined ,在 JSON 序列化的时候会转为 null 。
使用 new Array() 创建的数组默认都是 hole
hole 具有以下特性:
map/forEach/Object.keys/for ... in等方式,不会对其施加影响,也就是通过map无法改变hole值,forEach也无法读取hole值for ... of可以读到hole值,输出位undefined- 打印完整数组的时候,
hole值会被填充为undefined
const a = [0]
console.log(a.length) // 1
a[3] = 3
console.log(a.length) // 4 ,数组实际会扩展为 4 个成员,表现为 [0, empty, empty, 3]
for (const item of a) {
console.log(item) // 4 个成员都会被 of 遍历到, empty 会被视为意料之外的空值,也就是 undefined ,最终打印四次,分别为 0, undefined, undefined, 3
}
a.map((item) => {
console.log(item)
}) // map 内部会过滤掉 empty ,这里只会打印 0、3
console.log(a.map((item) => {
console.log(item)
})) // [0,empty,empty,3] 虽然 hole 不会应用到回调里面,但是整体打印的时候还在
a.forEach((item) => {
console.log(item)
}) // forEach 内部会过滤掉 empty ,这里只会打印 0、3
console.log([...a]) // [0,undefined,undefined,3], 浅拷贝的时候, empty 使用 undefined 填充
console.log(Object.keys(a)) // 这里会过滤掉值为 empty 的 key ,数字的 key 会被转为字符串,也就数打印 ["0", "3"]
delete a[3]
console.log(a.length) // 虽然值被删除了,但是删除成员不会影响数组容量,这里保存了 [0, empty, empty, empty]
a[2] = 2
a.length = 1
console.log(a[0], a[1], a[2]) // 手动修正数组容量,超出的部分访问时会是空值,也就是打印 0, undefined, undefinedlength
length 是数组的特殊属性,具有以下特性:
- 改变
length会影响数组行为,如追加hole或者删除已有的部分元素,直到数组长度与length匹配为止 - 无法通过继承覆盖
length的值,使其保持不变,length是不可配置的,只能赋值与取值
数组最大下标为 2 ** 32 - 2 ,超出的部分值赋值无效,如果 new Array(2 ** 32) 会报错
class MyArray extends Array {
get length() {
return 3 // this will not override parent's length
}
}
const arr1 = new MyArray(10)
console.log(arr1.length) // 10
const arr2 = Array.from({ length: 10 })
console.log(arr2.length) // 10比较
indexOf: 使用===比较,[NaN].indexOf(NaN) // -1includes: 使用Same-value-zero比较,能检查NaN与-0等值,[NaN].includes(NaN) // true
function sameValueZero(x, y) {
if (typeof x === 'number' && typeof y === 'number') {
// x and y are equal (may be -0 and 0) or they are both NaN
return x === y || (x !== x && y !== y)
}
return x === y
}Math
异常计算
console.log(1 / 0) // Infinity PS: 正常来说 0 不能是除数,但是 JS 内置了当 0 为除数,并且被除数不是 0 的时候,就是无穷大,符号还是照样算
console.log(-1 / 0) // -Infinity
console.log(0 / 0) // NaN PS: 无意义
console.log(0 === -0) // true 。 在 JS 中通过全等无法判断 0 的符号,通过 Math.sign 也无法判断
console.log(Object.is(0, -0)) // true 。 打了补丁,这个函数可以严格区分 +0 与 -0
console.log(Object.is(0, Math.round(-0.5))) // 等价于 Object.is(0, -0) = false
console.log(Object.is(0, Math.round(0.5))) // 等价于 Object.is(0, 1) = false
console.log(0 * Infinity) // 无意义,为 NaN
console.log(Infinity / Infinity) // 无意义, 为 NaN
console.log(Object.is(0, Math.sign(0))) // 等价于 Object.is(0, 0) = true
console.log(Object.is(0, Math.sign(-0))) // 等价于 Object.is(0, -0) = false
console.log(1 / -0) // -Infinity
console.log(1 / 0) // Infinity
console.log(1n / 0n) // 抛异常, BigInt 已经修复了上述的不合理行为缺省值
console.log(Math.min()) // 默认会与 Infinity 比较,所以这里打印 Infinity
console.log(Math.max()) // 默认会与 -Infinity 比较,所以这里打印 -Infinity
console.log(Math.min(1)) // 1
console.log(Math.max(1, 2)) // 2
console.log(Math.min([1, 2, 3])) // 数组无法正常转为数字,这里是 NaN特殊值
console.log(Math.sign(-10)) // -1
console.log(Math.sign(0.1)) // 1
console.log(Math.sign(0)) // 0
console.log(Math.sign(-0)) // -0RegExp
const arr = ['a', 'b', 'c', '1']
const regExp = /^[a-z]$/gi
const chars = arr.filter(elem => regExp.test(elem))
console.log(chars) // ["a","c"]正则表达式 /^[a-z]$/gi 启用了全局匹配标志 g, 这导致 regExp.test() 方法在每次匹配后会更新正则对象的 lastIndex 属性(记录上次匹配的结束位置)。
- 第一次迭代:
elem = 'a', test() 返回 true, lastIndex 变为 1 (因为匹配到了第 1 个字符)。 - 第二次迭代:
elem = 'b', 此时 test() 从 lastIndex = 1 开始匹配,但 b 是单字符字符串,无法从位置 1 开始匹配,因此返回 false, 随后 lastIndex 重置为 0。 - 第三次迭代:
elem = 'c', test() 从 lastIndex = 0 开始匹配,返回 true, lastIndex 变为 1。 - 第四次迭代:
elem = '1', 匹配失败,返回false。 - 最终只有 ‘a’ 和 ‘c’ 通过了过滤条件。
可以通过每次 filter 手动重置 lastIndex 解决
const chars = arr.filter((elem) => {
regExp.lastIndex = 0 // 强制重置匹配位置
return regExp.test(elem)
})string
JS 字符串不可改变
const a = 'bfe.dev'
a[0] = 'c' // 赋值无效,不能改变字符串值
console.log(a)
const b = new String('bfe.dev')
b[0] = 'c' // new String 也不支持赋值改变字符串值
console.log(b)预留字段
window.name是一个特殊的字段,不管赋值为什么,都会被转为stringfunction.name是一个预留字段,默认指向函数名functin.length是一个预留字段,默认为函数定义时,声明的参数个数
浮点数
浮点数取自科学计数法,将数字使用 尾数 * 指数 的形式表示,根据 IEEE 规范,尾数的形式转为二进制后会是 1.xxx 的形式。
小数部分转二进制,采用 乘二取整法 ,将小数 * 2,取出每次计算出来的整数位,顺序排列,即为对应的二进制
比如十进制数 0.15625 ,转为二进制是 0.00101 。为了让第 1 位为 1 ,执行逻辑右移 3 位,尾数部分成为 1.01,因为右移了 3 位,所以指数部分是 -3 。
按照 IEEE 标准来看:
float: 1 符号位 + 8 指数位 + 23 尾数位- 范围: -3.40×10³⁸ 至 3.40×10³⁸
- 精度: 十进制来说是小数点后 6-7 位
double: 1 符号位 + 11 指数位 + 52 尾数位- 范围: -1.79×10³⁰⁸ 至 +1.79×10³⁰⁸
- 精度: 十进制来说是小数点后 15-16 位
底层实现来看,浮点数占用的数位并没有因为多存储了 尾数 而有所增加,依旧是 32 位 与 64 位,但是数字所能表示的上下限以及精度会被尾数挤占,导致上下限降低,以及精度丢失。