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_VALUE
是javaScript
能表示的最小的整数,也是最接近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。因为一个普通块内部的函数声明通常会被提升到所在作用域的顶部,这个过程不会像上面的代码暗示的那样可以被条件判断所控制。
8、箭头函数
const fn = () => arguments;
console.log(fn()); // Uncaught ReferenceError: arguments is not defined
解析:箭头函数没有自己的this
和arguments
,而是引用外层作用域中的,而全局没有定义arguments
变量,因此报错。
在箭头函数中如果要访问参数集,建议使用
(...args) => {}
9、返回值
const fn = function() {
return
{
message: "hello";
}
};
// 等价于
const fn = function() {
return;
{
message: "hello";
}
};
console.log(fn()); // undefined
解析:在javaScript
中,如果return
和返回值之间存在换行符,则return
后面会自动插入;
,因此会打印出undefined
。
参考:
10、setTimeout
0ms、1ms打印(✨)
setTimeout(() => {
console.log('a');
}, 1);
setTimeout(() => {
console.log('b');
}, 0);
// 打印:有可能是'a','b',也有可能是'b','a'
- 在
Node.js
中,0ms和1ms是等价的,因为0会被转化为1,所以在node中运行结果是'a','b'
;Chrome
和node
结果一样 Firefox
中会打印'b'.'a'
11、event.target
和event.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
,可以省略)