JS的一些奇奇怪怪的坑
本文最后更新于 410 天前,其中的信息可能已经有所发展或是发生改变。

JS 的一些奇奇怪怪的坑

JS 是一门弱语言, 并且从一开始设计也没想到这玩意儿现在能这么火, 但是历史遗留问题还在哪儿, 只能去默默承受了

我也是一个搬运工, 都是从 stackoverflow 搬运过来, 然后自己做一些个人的理解, 比较菜, 勿喷. 如果有错误, 欢迎留言指出问题 Ray

Let’s right

.= 操作符的优先级

let a = {
  n: 1,
}
let b = a

a.x = a = { n: 2 }

console.log(a.x)
console.log(b.x)

输出

1. undefined
2. { n: 2 }

执行 => a.x = a = { n: 2 }

结果 => a = { n: 2 }, b = { n: 1, x: { n: 2 } }

作用域

var a = 0
var b = 0

function A(a) {
  A = function (b) {
    console.log(a + b++)
  }

  console.log(a++)
}

A(1)
A(2)

输出

1. 1
2. 4

执行 => A(1)

A(a) => a = 1

# 重写 A() 方法, 但不会执行

console.log(a++) => a => 1

执行 => A(2)

# 由于第一次执行函数后, A 已经被重写, 所以会直接执行 A = function ...

console.log(a + b++) => a + b => 2 + 2 => 4

伪数组 length 属性

var obj = {
  2: 3,
  3: 4,
  length: 2,
  splice: Array.prototype.splice,
  push: Array.prototype.push,
}

obj.push(1)
obj.push(2)

console.log(obj)

输出

Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]

因为 obj 属性中含有 length、splice 属性, 所以在调用输出函数输出时, 会被认为是一个伪数组.
因为没有设置: 0、1 属性, 故而在转换为数组时会被认为是空, 使用 empty 补位.
如果使用下标获取: console.log(obj[0]), 会输出 undefined

# 伪数组, 可以直接使用 Array.from() 方法直接转换为真实数组
Array.from(obj) => [undefined, undefined, 1, 2]

非严格模式下, 非匿名自执行函数, 函数名只读

var b = 10

;(function b() {
  // 'use strict'
  b = 20
  console.log(b)
})()

输出

Function b

初始化 b => undefined

赋值 b => 10

自执行函数, 赋值声明函数 b => function

赋值 b => 20 失败, 因为 IIFE 的函数内部无法进行赋值

# 如果打开严格模式会抛出错误
TypeError: Assignment to constant variable.

非匿名自执行函数 II

var b = 10

;(function b() {
  var b = 20
  console.log(window.b)
  console.log(b)
})()

输出

1. 10
2. 20

# 1
第一个输出函数, 调用 window.b, 输出 10

# 2
第二个输出函数, 函数内部声明变量 b, 然后赋值 20, 所以输出 20

非匿名自执行函数 III

var b = 10

;(function b() {
  console.log(b)
  b = 5
  console.log(window.b)
  var b = 20
  console.log(b)
})()

输出

1. undefined
2. 10
3. 20

# 1
第一个输出函数, 声明变量 b, 函数执行时, b 的值为 10, 执行匿名函数 b, 函数内部存在 var b 声明变量, 所以提升变量, 访问内部变量 b, 为 undefined, 所以输出 undefined

# 2
第二个输出函数, 赋值操作 b(undefined) = 5, 实质上是执行 b = 5, 所以输出 5

# 3
第三个输出函数, b(5) = 20, 实质上是执行 b = 20, 所以输出 20

变量提升

var name = 'World!'
;(function () {
  if (typeof name === 'undefined') {
    var name = 'Jack'
    console.log('Goodbye ' + name)
  } else {
    console.log('Hello ' + name)
  }
})()

javascriptfunctionvar 都存在变量提升

# js 引擎实际解析顺序

var name = 'World!'
;(function () {
  var name
  if (typeof name === 'undefined') {
    name = 'Jack'
    console.log('Goodbye ' + name)
  } else {
    console.log('Hello ' + name)
  }
})()

变量提升 II

var str = 'World!'
;(function (name) {
  if (typeof name === 'undefined') {
    var name = 'Jack'
    console.log('Goodbye ' + name)
  } else {
    console.log('Hello ' + name)
  }
})(str)

输出

1. Hello World!

# 1
由于函数内部同样声明变量 name, 所以此时 name 成为函数内部变量, 并且是先调用, 后声明, 所以 if 为 false, 输出 Hello World!

Array.prototype.filter

var ary = [0, 1, 2]
ary[10] = 10
ary.filter(function (x) {
  return x === undefined
})

输出

1. []

# 1
filter 会判断此数组当前索引值是否为数组中的一个属性, 所以会自动忽略 undefined、null, 所以输出 []

浮点数精度问题

var num1 = 0.1
var num2 = 0.2
var num3 = 0.8
var num4 = 0.6
console.log(num2 - num1 == num1, num3 - num4 == num2)

输出

1. true, false

# 1
由于浮点数存储问题, 一些特殊的浮点数进行运算时, 会出现精度问题(具体可以去 MDN 官网查看, 此处不展开讨论), 所以 0.8 - 0.6 实际等于 0.20000000000000007

switch 使用

function output(value) {
  switch (value) {
    case 'A':
      console.log('Case A')
      break

    case 'B':
      console.log('Case B')
      break

    case undefined:
      console.log('undefined')
      break

    default:
      console.log('Do not know!')
  }
}
output(new String('A'))

输出

1. Do not know

# 1
此处还有另外一个知识点, 就是在 js 中, 声明变量的形式(字面量、构造函数)的区别, 虽然构造函数方式声明, 也是一个 string, 但是它仅仅是表现行为上与字面量表现一致, 并且 switch 内部采用严格比较方式, 所以代码走到 default 中然后输出 Do not know!

% 运算符

function useValidOdd(num) {
  return num % 2 == 1
}
function useValidEven(num) {
  return num % 2 == 0
}
function useValidSane(num) {
  return useValidEven(num) || useValidOdd(num)
}
var values = [7, 4, '13', -9, Infinity]
values.map(useValidSane)

输出

1. [true, true, true, false, false]

# 1
使用 % 运算符时, 如果值不为 number, 则会调用 Number() 转换该值后继续运算

==! 操作符号优先级

console.log([] == ![])

输出

1. true

# 1
a. ! 的优先级大于 ==, 所以根据运算规则, 先 Boolean([]), 返回 true, 然后取反返回 false
b. 根据转换规则, 从 [] == false, 变成 [] == 0
c. 然后再进行隐式转换规则, Number([].toString()), 变成 0
d. 0 == 0, 所以输出 true

一元运算符的骚操作

console.log(1 + -+(+(+-+1)))

function demo() {}

console.log(+demo)
console.log(-demo)
console.log(~demo)
console.log(void demo)

输出

1. 2
2. NaN NaN -1 undefined

# 1
这里被格式化后, 就能明显看出 JS 引擎的解析顺序, 原式其实为 1 + - + + + - + 1. 并且 +- 在一元操作符中代表加减操作符号

# 2
a. 执行 +、- 操作的时候其实是直接返回 Number(demo) 的结果 NaN
b. ~ 取余也同理, ~(Number(demo)), ~NaN 返回 -1
c. void 运算符, 与之运算的都直接返回 undefined. 小技巧, 在开发过程中, 有时候需要给一个初始化值赋值为 undefined 的时候, 不建议直接使用 undefined 直接赋值, 而是使用大多数人约定的一个写法 void 0

神操作之数组比较大小

const a = [1, 2, 3]
const b = [1, 2, 3]
const c = [1, 2, 4]

console.log(a == b)
console.log(a === b)
console.log(a > c)
console.log(a < c)

输出

1. false false false true

# 1
a. ==、=== 符号会直接比较引用地址
b. 引用类型之间比较大小, 会按照字典顺序比较, 依次按照二者的字典顺序比较大小, 如果一样就往后挪位, 直到比较出结果为止

Array.prototype.sort

const num = [1, 2, 5, 10, 3]

console.log(num.sort())

输出

1. [1, 10, 2, 3, 5]

# 1
JS 引擎默认是按照字典顺序(alphanumeric)排序

+ 运算符的坑

const str = 'b' + 'a' + +'a' + 'a'

console.log(str)

输出

1. baNaNa

# 1
上述表达式实际上等价于 'b' + 'a' + (+'a') + 'a', 因为 +'a' 等于 NaN, 所以输出 baNaNa

隐式转换

var a = [0]
if (a) {
  console.log(a == true)
} else {
  console.log('ray')
}

输出

1. false

# 1
转换顺序
a. if a => Boolean([0]) true
b. 左侧优先转换为, Number(true) => 1
c. 引用类型 a 转换为, Number(a.toString()) => 0
d. 0 == 1, 输出 false

后续持续更新中 …

【版权声明】
本文首发于云博客,欢迎转载,但是必须保留本文的署名云博客(包含链接)。
如您想成为本站的作者或者编辑,请给我留言:yun@yka.moe
本文永久链接:JS的一些奇奇怪怪的坑
本文作者:ray
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇