前端需要注意的小细节总结


1、知识点:javaScript的隐式类型转换

// 字符串的拼接
console.log(2 + '2') // 22
// 数字相减
console.log(2 - '2') // 0
// 布尔值转数字 true = 1,false = 0
console.log(true + 1) // 2

2、知识点:NaN

// NaN和所有值都不等,包括自身,判断一个值是否是NaN只能用isNaN()或者Number.isNaN()
console.log(NaN === NaN); // false

3、知识点:运算符,javaScript的隐式类型转换

console.log(5 < 6 < 7); // true
console.log(7 > 6 > 5); // false

x的值使下列等式同时为true

x * x === 0;
x + x === 1;
x - 1 === -1;
x / x === 1;
// x的值为:Number.MIN_VALUE

解析:Number.MIN_VALUEjavaScript能表示的最小的整数,也是最接近0的值,因此前三条成立,但它又不是0,所以可以作为除数,等式4也成立。

4、浮点数

// javaScript中的数字使用的是64位双精度浮点型
console.log(0.1 + 0.2) // 0.30000000000000004

console.log(9999999999999999) // 10000000000000000
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

解析:javaScript中有一个Number_MAX_SAFE_INTEGER,它的值为2^53 - 1,即9007199254740991,这个数的存在换算因为js使用64位双精度浮点型数,它能表示的区间仅仅为-(2^53-1)~(2^53-1),超过这个区间的数就不“安全”了,表现在无法准确表示和比较这些数,比如Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2结果为true,可以通过Number.isSafeInteger()来判断一个数是否“安全”。

当我们需要使用更大的数时可以使用BigInt

5、知识点:javaScript的隐式类型转换

// 两边如果不都为数字则会将他们转化为字符串再拼接
console.log([1, 2, 3] + [4, 5, 6]) // 1,2,34,5,6
// 数组拼接
console.log([1, 2, 3].concat([4, 5, 6])); // [1, 2, 3, 4, 5, 6]
console.log([...[1, 2, 3], ...[4, 5, 6]]); // [1, 2, 3, 4, 5, 6]

6、知识点:作用域

(function() {
  var a = b = 100;
})();
// 方法一
console.log(a); // 报错:Uncaught ReferenceError: a is not defined
console.log(b);

// 方法二
console.log(b); // 100 
console.log(a); // 报错:Uncaught ReferenceError: a is not defined

解析:赋值表达式是从右往左执行,相当于var a = (b = 100);,则应该先执行b = 100,此时b就是一个全局变量,值为100,接着再让var a = b,此时会把100赋值给a,当执行console.log(a)时就会直接报错,因为a是局部变量。而如果先打印b,则会先输出100,然后报错。

使用严格模式(‘use strict’)可以避免b这种意外全局变量的创建

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 0);
}
// 等价于
var i;
for (i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 0);
}
// 打印:5 5 5 5 5

解析:用var定义的变量的作用域是函数作用域,setTimeout函数会等for循环结束之后再执行,此时i变成了5,因此最后打印结果为5个5。

for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 0);
}
// 打印:0 1 2 3 4

7、知识点:变量提升和函数提升(✨)

// 变量提升
function fn() {
  return x;
  function x() {}
}
// 等价于
function fn() {
  function x() {}
  return x;
}

console.log(typeof fn()); // function

解析:函数声明(function fn() {})会存在函数提升的现象,函数表达式不会(var fn = function(){}),因此结果会打印function函数提升的作用是可以在函数定义之前就进行函数调用

// 变量提升和函数提升
var x = 1;
function x() {}
// 等价于
function x() {}
var x;
x = 1;

console.log(typeof x); // number

解析:函数声明和var定义的变量声明都存在被提升现象,但函数提升的优先级要高于变量提升,因此函数被提升到作用域最顶部,接下来是变量定义。

补充:函数与变量的优先级(待完善✨)

函数声明和变量声明都会被提升。但是一个值得注意的是函数会首先被提升,然后才是变量。

foo(); // 1
var foo;
function foo() {
 console.log( 1 );
}
foo = function() {
 console.log( 2 );
};

代码执行结果会输出 1 而不是 2 !这个代码片段会被引擎理解为如下形式:

function foo() {
 console.log( 1 );
}
foo(); // 1
foo = function() {
 console.log( 2 );
};

注意,var foo 尽管出现在 function foo()… 的声明之前,但它是重复的声明,因为函数声明会被提升到普通变量之前。尽管重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。

foo(); // 3
function foo() {
 console.log( 1 );
}
var foo = function() {
 console.log( 2 );
};
function foo() {
 console.log( 3 );
}

虽然我们通常不会在一个作用域内声明相同名称的变量,但是来看一下下面这种代码:

foo(); // "b"
var a = true;
if (a) {
 function foo() { console.log("a"); }
}
else {
 function foo() { console.log("b"); }
}

这段代码执行完成以后会输出b,而且是永远输出b。因为一个普通块内部的函数声明通常会被提升到所在作用域的顶部,这个过程不会像上面的代码暗示的那样可以被条件判断所控制。

参考:
无邪L:https://juejin.im/post/6844903743444500488

8、箭头函数

const fn = () => arguments;
console.log(fn()); // Uncaught ReferenceError: arguments is not defined

解析:箭头函数没有自己的thisarguments,而是引用外层作用域中的,而全局没有定义arguments变量,因此报错。

在箭头函数中如果要访问参数集,建议使用(...args) => {}

9、返回值

const fn = function() {
  return
  {
    message: "hello";
  }
};
// 等价于
const fn = function() {
  return;
  {
    message: "hello";
  }
};
console.log(fn()); // undefined

解析:在javaScript中,如果return和返回值之间存在换行符,则return后面会自动插入;,因此会打印出undefined

参考:

https://segmentfault.com/a/1190000023350519

10、setTimeout0ms、1ms打印(✨)

setTimeout(() => {
  console.log('a');
}, 1);
setTimeout(() => {
  console.log('b');
}, 0);
// 打印:有可能是'a','b',也有可能是'b','a'
  • Node.js中,0ms和1ms是等价的,因为0会被转化为1,所以在node中运行结果是'a','b'Chromenode结果一样
  • Firefox中会打印'b'.'a'

11、event.targetevent.currentTarget的区别

<div id="container">
    <button>click</button>
</div>
<script>
    const container = document.getElementById('container');
     container.addEventListener('click', function(event) {
    console.log('target=', event.target);
    console.log('currentTarget=', event.currentTarget);
  }) 
</script>

输出结果:

结果

解析:event.target 是真正触发event的元素, event.currentTarget是绑定event handler的元素。

12、立即执行函数(IIFE)

function() {
  console.log('hello');
}();
// Uncaught SyntaxError: Function statements require a function name

解析:正确的语法应该是(function() {})();

13、javaScript对象和数组方法

(1)数组indexOf()方法

const arr = [1, 2, 3];
arr[-1] = -1;
console.log(arr[arr.indexOf(30)]); // -1

解析:数组本质是一个javaScript对象,那就可以设置属性,即使数组的索引没有-1,但-1还可以作为对象的key存在,所以可以设置arr[-1] = -1;然后arr.indexOf()方法索要查找的值如果不存在则返回-1,因此相当于打印arr[-1]

(2)数组sort()方法

// 数字转换成字符串比较排序
const arr = [1, 12, 132, 14, 22];
console.log(arr.sort()); // [1, 12, 132, 14, 22]

// 实际数字比较大小排序
console.log(arr.sort((a, b) => a - b)); // 升序  
console.log(arr.sort((a, b) => b - a)); // 降序

解析:sort()方法默认把元素转换成字符串,再比较UTF-16编码的单元值序列进行升序排列。

14、原型链

原型链的顶层是null

console.log(Object.prototype);
console.log(Object.prototype.__proto__); // null

原型链数据

一般认为原型链顶层是Object.protptype,但Object.protptype还是有__proto__内部属性,

Object.protptype.__proto__等于null

15、阻止给一个对象设置属性(✨)
const obj = {};
// 方法:让obj.a = 1无效
1. Object.freeze(obj);
2. Object.seal(obj);
3. Object.preventExtensions(obj);
4. Object.defineProperty(obj, 'a', {writable: false});

obj.a = 1;

解析:

  • Object.freeze() 最严格,会完全禁止对象做任何修改,包括:增加新属性,修改已有属性、修改其原型
  • Object.seal()宽松一点,允许修改writable的属性,但不允许新增和删除属性,且已有属性都会被标记为不可配置的
  • Object.preventExtensions() 更加宽松,可以阻止对象新增属性和修改其__proto__
  • Object.defineProperty() 将属性定义为不可写,因此无法给属性设置新的值(writable默认为false,可以省略)

参考:https://segmentfault.com/a/1190000023350519


评论
评论
  目录