总结JavaScript踩过的坑

数组排序问题

1
[0,1,10,11,8,-2].sort(); //[[-2, 0, 1, 10, 11, 8]

sort()默认情况下会按照Unicode码点排序,而不是数值顺序排序,所以正确的解决方案为:

1
[0,1,10,11,8,-2].sort((a,b)=>a-b); //[[-2, 0, 1, 8, 10, 11]

0.1+0.2 !== 0.3的问题

先看如下代码

1
2
0.1 + 0.2 === 0.30000000000000004 //true
1000000000000000128 === 1000000000000000129 //true

本质上这是二进制浮点数造成的精度丢失问题,这种问题也存在于除JavaScript之外的其他语言中,可以参考0.30000000000000004.com
因为计算机只能读懂二进制数值,我们看下0.1和0.2转换成二进制:

0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)

双精度浮点数的小数部分最多支持 52 位,所以计算后会进行小数位的限制截断,也就造成了舍入误差,再次转换成十进制后就是我们所看到的0.30000000000000004

解决方法

1
2
(0.1+0.2).toFixed(1) //"0.3"
(0.1+0.2).toFixed(2) //"0.30"

toFixed()方法是保留小数后面的位数是几位,注意:得到的结果是字符串

全局变量问题

1
2
3
4
5
6
var b = 0;
function fn() {
var a = b = 3;
}
fn()
console.log(b); //3

一般会有不少人觉得应该打印0,因为b=3是函数内声明,属于局部变量。

这里var a=b=3是个坑,赋值运算是从右往左的,所以这里的b=3是全局变量。

所以声明变量建议单个单个的来。

变量提升问题

1
2
3
4
if(d){
var d = 5;
}
console.log(d) //undefined

因为预解析的原因,所以var d的代码会提升到代码顶部,且值为undefined,然后执行流程语句,因为d为undefined,转为布尔值为false,所以不会进入if判断语句,所以打印结果为undefined

下面的代码也是同理

1
2
3
4
if(!('d' in window)){
var d = 5;
}
console.log(d) //undefined

匿名函数预解析1

1
2
3
4
5
6
7
8
function foo() {
console.log('1')
}
foo() //2
function foo() {
console.log('2')
}
foo() //2

匿名函数的预解析会把函数提升到函数所在作用域的顶部,声明先于调用,而第二个foo函数会覆盖第一个foo函数的声明,所以两次foo()的调用都是在执行第二个声明的函数,所以打印两次2

匿名函数预解析2

1
2
3
4
5
6
7
var getName = function(){
console.log(2);
}
function getName (){
console.log(1);
}
getName(); //2

匿名函数会预解析,但是函数表达式不会预解析。
所以匿名函数getName会提升到所在作用域顶部,然后依次执行代码,执行到函数表达式时,getName函数表达式会覆盖匿名函数getName,所以打印结果是2

匿名函数预解析3

1
2
3
4
5
6
7
8
9
10
function fn2(){
var a = 666;
fn1()
}
fn2() //undefined
function fn1(){
console.log(a);
}
var a = 333;

匿名函数fn1会提前预解析,提升到作用域顶部。 a变量也会提升到代码顶部并初始化值为undefined。
执行fn2。调用fn1是执行fn1函数,而fn1执行时a只和fn1所在作用域有关,调用时a只是初始化,还未赋值为333,所以打印结果为undefined

null和undefined

1
null == undefined //true

请记住这个特殊情况吧!
所以我建议写代码判断相等用===
还有null和undefined的区别

null表示”没有对象”,即该处不应该有值。
undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。

Math.min>Math.max()

1
2
3
console.log(Math.max()) //-Infinity
console.log(Math.min()) //+Infinity
console.log(Math.min()>Math.max()) //true

typeof(null)===’object’

1
typeof(null)==='object' //true

这是错的,虽然他返回的结果是true,但这是JavaScript设计的错误,考虑到后兼容,就保持了这一错误

JavaScript注释

1
2
let x = 5;
let y = 1<!--x;

上面的代码之后x和y的值是多少?
答案是5和1.
因为<!--会把后面的代码注释掉(虽然是HTML注释,但这里浏览器依然会当做注释),所以后面的代码都是不影响y的值的,就像下面代码

1
2
let z = 8<!--你好#¥#%¥……456-->
console.log(z) //8

哪怕是浏览器不能识别,会报错的字符依然不受影响,因为浏览器会跳过注释执行代码

JavaScript函数中参数的传递

1
2
3
4
5
6
7
8
9
var young = {name: 'su'}
var old = {age: 66}
function fn(a,b){
a.name = 'yoowin'
b = {age: 20}
}
fn(young,old)
console.log(young)
console.log(old)

这道代码题最开始我疑惑了挺久,在网上也有搜过,貌似对于值传递一直有争议,我自己刚开始也不是很明白。
先看下答案:

1
2
3
4
5
6
7
8
9
var young = {name: 'su'}
var old = {age: 66}
function fn(a,b){
a.name = 'yoowin'
b = {age: 20}
}
fn(young,old)
console.log(young) //{name: "yoowin"}
console.log(old) //{age: 66}

现在我理解了所谓的函数参数都是值传递的说法。
首先,函数调用的时候,其中的参数是实参把值复制给形参的一个过程。
如果实参是原始类型,参数传递则是值的直接复制;如果实参是引用类型,参数传递则是引用类型的地址的传递(指针的传递),相当于实参和形参都同时指向这个引用类型的内存。

再看题目中,重点就在于fn函数中,a和b都是局部变量,
执行的第一行是把a的属性改变了,其中a指向的和变量young一样的内存:{name: 'su'},改变a的属性就是改变内存中的属性,所以会反映到他的指针上面,也就是变量young也会改变,所以打印的young值也确实改变了;
执行的第二行中,本来形参b的指针指向和old一样的:{age: 66},然后 b = {age: 20}的赋值语句把b的指针改变了,指向了{age: 20},b不在指向{age: 66}了,但是old还依然指向{age: 66},所以实参old对应的内存并没有改变,所以会打印{age: 66}

0%