代码分析
满足条件 a == 1 && a == 2 && a == 3
下面代码中 a 在什么情况下会打印 1
// var a = ?;
if (a == 1 && a == 2 && a == 3) {
console.log(1)
}
- 考察类型的隐式转换
- 引用类型在比较运算 (==) 时,隐式转换会调用本类型的 toString/valueOf 方法
// code1
var a = [1, 2, 3]
a.toString = a.shift
if (a == 1 && a == 2 && a == 3) {
console.log(1)
}
// code2
var a = { num: 0 }
a.valueOf = function () {
return ++a.num
}
// code3
var a = {
i: 1,
toString() {
return a.i++
}
}
// code4
// prettier-ignore
var a = {
[Symbol.toPrimitive]: ((i) => () => ++i)(0)
}
// code5
var a = {
gn: (function* () {
yield 1
yield 2
yield 3
})(),
valueOf() {
return this.gn.next().value
}
}
// code6
Object.defineProperty(window, 'a', {
get: function () {
if (this.value) {
return (this.value += 1)
} else {
return (this.value = 1)
}
}
})
规范
3.5.7 相等操作符
确定两个变量是否相等是编程中的一个非常重要的操作。在比较字符串、数值和布尔值的相等性时,问题还比较简单。但在涉及到对象的比较时,问题就变得复杂了。最早的 ECMAScript 中的相等和不等模作符会在执行比较之前,先将对象转换成相似的类型。后来,有人提出了这种转换到底是否合理的质疑。最后,ECMAScript 的解决方案就是提供两组操作符:
相等 和不相等——先转换再比,
全等 和不全等——比较而不转换。
- 相等和不相等
ECMASctipt 中的相等模作符由两个等于号 (==) 表示,如果两个作数相等,则返回 tue。而不相等模作符由号后跟等于号 (与) 表如果两人提作数不相等,则返回 u。这两人模作符都会先转换模作数 (通常称为强制转型),然后再比软它们的相等件
在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:
- 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值 false 转换为 0,而 true 转换为 1;
- 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
- 如果一个操作数是对象,另一个操作数不是,则调用对象的
valueOf()
方法,用得到的基本类型值按照前面的规则进行比较;
实现 (5).add(3).minus(2)
功能
Number.prototype.add = function (i = 0) {
return this.valueOf() + i
}
Number.prototype.minus = function (i = 0) {
return this.valueOf() - i
}
这里未考虑 JS 经典的浮点数陷阱
这里 github 上有一个阿里大佬的解法-JavaScript 浮点数陷阱及解法
注意
大数加减:直接通过 Number 原生的安全极值来进行判断,超出则直接取安全极值
超级多位数的小数加减:取 JS 安全极值位数 -2 作为最高兼容小数位数。
实现 LazyMan('Tony').eat('lunch').sleepFirst(5)
要求设计 LazyMan 类,实现以下功能
LazyMan('Tony')
// Hi I am Tony
LazyMan('Tony').sleep(10).eat('lunch')
// Hi I am Tony
// 等待了 10 秒...
// I am eating lunch
LazyMan('Tony').eat('lunch').sleep(10).eat('dinner')
// Hi I am Tony
// I am eating lunch
// 等待了 10 秒...
// I am eating dinner
LazyMan('Tony')
.eat('lunch')
.eat('dinner')
.sleepFirst(5)
.sleep(10)
.eat('junk food')
// Hi I am Tony
// 等待了 5 秒...
// I am eating lunch
// I am eating dinner
// 等待了 10 秒...
// I am eating junk food
实现
点我查看详细
class LazyManClass {
constructor(name) {
this.name = name
this._queue = []
console.log(`Hi I am ${name}`)
// 把 this.next() 放到调用栈清空之后执行
setTimeout(() => {
this.next()
}, 0)
}
sleepFirst(time) {
this._sleepWrapper(time, true)
return this // 链式调用
}
sleep(time) {
this._sleepWrapper(time, false)
return this
}
eat(food) {
const fn = () => {
setTimeout(() => {
console.log(`I am eating ${food}`)
this.next()
}, 0)
}
this._queue.push(fn)
return this
}
_sleepWrapper(time, first) {
const task = () => {
setTimeout(() => {
console.log(`等待了 ${time} 秒...`)
this.next()
}, time * 1000)
}
if (first) {
this._queue.unshift(task)
} else {
this._queue.push(task)
}
return this;
}
next() {
const fn = this._queue.shift()
fn && fn()
}
}
function LazyMan(name) {
return new LazyManClass(name)
}
['1', '2', '3'].map(parseInt) 返回值
先思考下结果,答案在最后
解析
- map 是传入的函数是有 3 个参数的:
value, index, arr
- 而 parseInt 有两个参数
parseInt(string, radix)
string
要被解析的值。如果参数不是一个字符串,则将其转换为字符串 (使用 toString 抽象操作)。字符串开头的空白符将会被忽略。
radix 可选
从 2 到 36,表示字符串的基数。例如指定 16 表示被解析值是十六进制数。请注意,10 不是默认值!
所以 ['1', '2', '3'].map(parseInt)
的过程是这样子的:
parseInt('1', 0) // radix 是 0 的情况见如下解释
parseInt('2', 1) // radix 基数只能取到 2 - 36 之间,所以 NaN
parseInt('3', 2) // radix=2 表示是二进制数,只能有 0 和 1, 解析的字符串是'3', 所以是 NaN
解释
如果 radix 是 undefined、0 或未指定的,JavaScript 会假定以下情况:
1. 如果输入的 string 以 "0x"或 "0x"(一个 0,后面是小写或大写的 X)开头,那么 radix 被假定为 16,字符串的其余部分被当做十六进制数去解析。
2. 如果输入的 string 以 "0"(0)开头,radix 被假定为 8(八进制)或 10(十进制)。具体选择哪一个 radix 取决于实现。ECMAScript 5 澄清了应该使用 10 (十进制),但不是所有的浏览器都支持。因此,在使用 parseInt 时,一定要指定一个 radix。
3. 如果输入的 string 以任何其他值开头,radix 是 10 (十进制)。
答案:返回值为 [1, NaN, NaN]
关于执行顺序
神说要有光,发了一篇公众号,提到一道赋值面试题,如下
let a = { n: 1 }
a.x = a = { n: 2 }
console.log(a.x)
// 输出什么
有点和以前理解的不一致,如下
// prettier-ignore
let a = b = 10
console.log(a) // 10
// 实际开启格式化,会变成下面这样
// 因运算符结合性,运算机制加上括号更直观
let a = (b = 10)
// 继续看,如下仍然有结合性的机制在,不同于最上面的例子
let b = { n: 1 }
let a = (b = { n: 2 })
console.log(a)
继续查了下,这是个老题了,参见JS 中一个赋值的问题
继续看,这里有个解释 由 ES 规范学 JavaScript(二):深入理解“连等赋值”问题
最后,我们再来看神光的解释——手写 JS 引擎来解释一道赋值面试题
下面的代码输出什么
var foo = function () {
console.log('foo1')
}
foo()
var foo = function () {
console.log('foo2')
}
foo()
function foo() {
console.log('foo3')
}
foo()
function foo() {
console.log('foo4')
}
foo()
另一个
var box = 1
;(function box() {
box = 2
console.log(box)
})()