前端面试基础题:JavaScript变量声明、装箱和拆箱...
✨ script标签defer和async

<script>
:如果遇到script标签,会阻塞HTML的解析,等到script下载执行完成之后再继续解析HTML。<script defer>
:script的加载不会阻塞HTML的解析,等到HTML解析完成再按照加载顺序执行脚本。<script async>
:script的加载不会阻塞HTML的解析,脚本加载完成就立即执行,并且是乱序执行的。
async
比defer
具有更高的优先级,如果同时添加两个属性,则会忽略defer
。
✨ 变量声明
关键字 | var | let | const |
---|---|---|---|
作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
变量提升 | 存在 | 不存在 | 不存在 |
能否修改(改变指针指向) | 是 | 是 | 否 |
初始值 | 可以不设置初始值 | 可以不设置初始值 | 必须设置初始值 |
是否添加到全局属性 | 是 | 否 | 否 |
能否重复声明变量 | 是 | 否 | 否 |
- 变量提升:浏览器会默认把带有var声明的变量在内存中进行提前声明或定义
- 暂时性死区:用let或const声明的变量在声明之前都会被放到暂时性死区,在此时访问这些变量就会触发运行时错误
- 块级作用域:就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域
- 全局属性:在全局作用域下,var成为 window 对象的属性, let和const不会
- Array和Object都是引用类型,当用const声明数组和对象时,const声明的常量保存的仅仅是目标的指针,这就意味着只要保证数组和对象的指针不发生改变,修改其中的值是被允许的。
- for循环中var和let的区别
for (var i = 0; i < 5; i++) {
console.log(i);
}
// 输出:0 1 2 3 4
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// 输出:5 5 5 5 5
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// 输出:0 1 2 3 4
由于var的变量提升机制,var命令实际只会执行一次,作用于全局,随着循环不断改变变量的值,且setTimeout是异步任务,等到执行时变量已经改变为循环最后一次的值。而let命令不存在变量提升,所以每次循环都会执行一次,声明一个新变量(但初始化的值不同),for的每次循环都是不同的块级作用域,let声明的变量也是块级作用域,所以也不存在重复声明的问题,let声明变量的for循环里,每个匿名函数实际上引用的都是一个新的变量。
✨ 数据类型

- 基本数据类型的数据直接存储在栈中,而引用类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。
- 栈内存是自动分配的,堆内存是开发者动态分配的,每次使用完对象的时候需要把它设置为null,从而减少无用的内存消耗,或程序结束时由垃圾回收机制回收。
✨ Number类型
- 八进制:前面加0,十六进制:前面加0x
- 范围:Number.MAX_VALUE(1.797631348623157e+308),Number.MIN_VALUE(5e-32)
- 最大安全整数:Number.MAX_SAFE_INTEGER( 253−1 )

位置 | 位数 | 作用 | 表示 |
---|---|---|---|
0-51 | 52 | 尾数位 | 原码表示 |
52-62 | 11 | 指数位 | 移码表示 |
63 | 1 | 符号位 | 0,1 |
“安全”意思是说能够one-by-one表示的整数,也就是说在 (−253,253) 范围内,双精度数表示和整数是一对一的,反过来说,在这个范围以内,所有的整数都有唯一的浮点数表示,这叫做安全整数。在 253−1 之后的数中,只要指数相同,并且尾数前52位相同,则这个两个数数值相同。
4. 三个特殊值:Infinity,-Infinity,NaN(通过isFinite()、isNaN()判断)
5. NaN不等于任何值,包括自身
✨ String类型
方法 | 功能 |
---|---|
str.indexOf(要查找的字符串,开始位置) | 返回指定内容在字符串中的位置,找不到返回-1 |
str.lastIndexOf(要查找的字符串) | 从后往前找,只找第一个匹配的 |
str.charAt(index) | 返回指定位置的字符 |
str.charCodeAt(index) | 获取指定位置处字符的ASCII码 |
str.concat(str1, str2, ...) | 用于连接两个或多个字符串 |
str.substr(start, length) | 从start位置开始,返回length长的子串 |
str.slice(start, end) | 返回从start位置开始到end位置的子串,end取不到 |
str.substring(start, end) | 返回从start位置开始到end位置的子串,end取不到,但不接受负值 |
str.replace(被替换字符串,要替换的字符串) | 用于在字符串中用一些字符替换另一些字符 |
str.toUpperCase() | 将字符串转换为大写 |
str.toLowerCase() | 将字符串转换为小写 |
str.trim() | 去除字符串两端的空白字符 |
模板字符串:反引号定义,${}
插入变量或函数。
手写模板字符串
function render(template, data) {
const reg = /\$\{(\w+)\}/;
if (reg.test(template)) {
const key = reg.exec(template)[1];
template = template.replace(reg, data[key]);
return render(template, data);
}
return template;
}
✨ undefined和null
null == undefined
为true
null === undefined
为false
表达式 | 结果 | 表达式 | 结果 |
---|---|---|---|
Number(null) | 0 | Number(undefined) | NaN |
Boolean(null) | false | Boolean(undefined) | false |
String(null) | null | String(undefined) | undefined |
区别
- 含义不同:
undefined
表示未定义的值,null
表示一个空对象,变量声明了但还没有定义的时候会返回undefined
,null
主要用于赋值给一些可能会返回对象的变量,作为初始化。 - 类型不同:
typeof undefined
为Undefined,typeof null
为Object - 数字转换结果不同:
Number(undefined)
为NaN,Number(null)
为0
如何获取安全的 undefined 值?
因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。
✨ Symbol类型(ES6)
- Symbol类型表示独一无二的值,通过Symbol()函数生成,可以用来防止对象属性名冲突(属性名可以是Symbol类型或String类型)。
- Symbol()函数前不能使用new命令,否则会报错。这是因为Symbol是一个原始类型的值,不是对象,所以不能使用new命令来调用。另外由于Symbol值不是对象,所以也不能添加属性。
- Symbol()函数可以传入一个字符串作为参数,便于区分。如果传入的参数是一个对象,会调用对象的toString方法。
- Symbol值不能与其他类型的值进行运算,会报错。
- Symbol值可以显式转换为字符串或布尔类型,不能转为数值。
let sym = Symbol();
console.log(typeof sym); // symbol
let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');
console.log(genericSymbol == otherGenericSymbol); // false
console.log(fooSymbol == otherFooSymbol); // false
console.log(genericSymbol); // Symbol()
console.log(fooSymbol); // Symbol(foo)
✨ BigInt类型(ES6)
一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
- 要创建BigInt,只需在整数的末尾追加
n
即可。或者,可以调用BigInt()
构造函数。 - 不能使用严格相等运算符将BigInt与常规数字进行比较,因为它们的类型不同。可以使用等号运算符,它在处理操作数之前执行隐式类型转换。
✨ Object类型
创建对象的三种方式
// 方式1:字面量
var star = {
name: 'pink',
age: 18,
sex: '男',
sayHi() { alert("Hi~"); }
}
// 方式2:Object构造函数
var andy = new Object();
andy.name = 'pink';
andy.age = 18;
andy.sex = '男';
andy.sayHi() = function () { alert("Hi~"); }
// 方式3:自定义构造函数
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHi = function () { alert("Hi~"); }
}
var bigBai = Person('大白', 100, '男');
遍历对象属性
for (var k in obj) {
console.log(k);
console.log(obj[k]);
}
对象方法
方法 | 功能 |
---|---|
Object.keys(obj) | 获取对象自身所有属性,类似for...in |
Object.values(obj) | 获取对象自身所有属性值 |
Object.assign(target, source) | 拷贝一个对象的属性值到目标对象中(重复的键会被覆盖,浅拷贝) |
Object.create(obj) | 创建一个新对象,使用现有的对象来提供新创建的对象的__proto__ |
Object.entries(obj) | 返回给定对象自身可枚举属性的键值对数组 |
Object.freeze(obj) | 冻结一个对象,被冻结后的对象不能够修改 |
Object.hasOwnProperty(key) | 用来检测是否为对象的自有属性,对象原型上的属性为false |
Object.defineProperty(obj, prop, descriptor) | 定义对象中新属性或修改原有属性 |
Object.defineProperty方法参数:
obj:目标对象
prop:属性名
descriptor:目标属性所拥有的特性,用对象格式写
- value:设置属性的值,默认undefined
- writable:值是否可以重写,默认false
- enumerable:目标属性是否可以被枚举,默认false
- configurable:目标属性是否可以被删除或再次修改属性,默认false
- get: 属性的getter函数,当访问该属性时会调用此函数
- set: 属性的setter函数,当属性值被修改时,会调用此函数,方法会接收一个默认参数(被赋予的新值)
new创建一个实例对象的过程
- 创建一个空对象
- 继承构造函数的原型
- this指向obj,并调用构造函数
- 返回对象
function myNew(fn, ...args) {
const obj = {};
obj.__proto__ = fn.prototype;
fn.apply(obj, args);
return obj;
}
✨ Array类型
方法 | 功能 |
---|---|
Array.isArray() | 判断一个对象是否为数组 |
arr.push(arg1, arg2, ...) | 数组末尾添加一个或多个元素,并返回新数组长度 |
arr.pop() | 删除数组中最后一个元素,返回删除的元素的值 |
arr.unshift(arg1, arg2, ...) | 数组开头添加一个或多个元素,并返回新数组长度 |
arr.shift() | 删除数组的第一个元素,并返回删除的元素的值 |
arr.reverse() | 颠倒数组中元素的顺序,修改原数组,返回新数组 |
arr.sort((a, b) => { return a - b; }) //升序 | 对数组元素进行排序,修改原数组,返回新数组 |
arr.indexOf(item) | 在数组中查找给定元素的第一个索引,不存在返回-1 |
arr.lastIndexOf(item) | 在数组中查找给定元素的最后一个索引,不存在返回-1 |
arr.join('分隔符') | 将数组中的所有元素转换为一个以分隔符分隔的字符串 |
arr.concat(arr1, arr2, ...) | 连接两个或多个数组,不影响原数组,返回一个新数组 |
arr.slice(start, end) | 返回被截取元素的新数组[start, end) |
arr.splice(start, length) | 返回被删除元素的新数组,原数组中删除这几个元素 |
arr.forEach((item, index, arr) => {}) | 对数组进行遍历循环处理,没有返回值,通过抛出异常跳出循环,通过return跳过当次循环 |
arr.map((item, index, arr) => {}) | 返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,不会改变原数组 |
arr.filter((item, index, arr) => {}) | 数组中的每一项运行给定函数,返回满足过滤条件组成的数组 |
arr.fill(num[, start, end]) | 使用特定值填充数组中的一个或多个元素。当只是用一个参数时,该方法会用该参数的值填充整个数组。3个参数: 填充数值,起始位置参数,结束位置参数(不包括结束位置的那个元素) |
arr.every((item, index, arr) => {}) | 判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true |
arr.some((item, index, arr) => {}) | 判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true |
arr.includes(num) | 判断一个数组是否包含一个指定的值,如果是返回true,否则 false |
arr.reduce((prev, item, index, arr) => {}, initVal) | 实现迭代数组的所有项,然后构建一个最终返回的值 |
arr.find((item, index, arr) => {}) | 返回第一个满足条件的元素值 |
arr.findIndex((item, index, arr) => {}) | 返回第一个满足条件的元素索引 |
Array.from(arrayLike) | 将类数组或可遍历对象转换为真正的数组 |
reduce函数的应用场景:
- 计算数组中所有值的总和/最值
const arr = [1, 3, 5, 6, 4, 2];
// 总和
let tolValue = arr.reduce((prev, item) => {
return prev + item;
}, 0);
// 最值
let maxValue = arr.reduce((prev, item) => {
return Math.max(prev, item);
}, Number.MIN_VALUE);
2. 数组去重
const arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const quchong = (arr) => {
let res = [];
arr.reduce((prev, item) => {
if (!prev.has(item)) {
prev.set(item, 1);
res.push(item);
}
return prev;
}, new Map());
return res;
}
console.log(quchong(arr)); // [1, 2, 3, 4, 5]
3. compose函数的实现
const f1 = x => x + 1;
const f2 = x => x + 2;
const f3 = x => x + 3;
const f4 = x => x + 4;
function compose(...fns) {
if (fns.length === 0) return x => x;
if (fns.length === 1) return fns[0];
return fns.reduce((prev, next) => {
return (num) => prev(next(num));
})
}
console.log(compose(f1, f2, f3, f4)(1));
4. 二维数组转化为一维
const arr = [[1, 2], [3, 4], [5, 6]];
let flattened = arr.reduce((prev, item) => {
return prev.concat(item);
}, []);
5. 计算数组中每个元素出现的次数
const votes = ['vue', 'react', 'angular', 'vue', 'react', 'vue'];
let count = votes.reduce((prev, item) => {
if (!prev[item]) {
prev[item] = 1;
} else {
prev[item]++;
}
return prev;
}, {});
✨ RegExp类型
创建正则表达式
var 变量名 = new RegExp(/表达式/);
var 变量名 = /表达式/;
测试正则表达式
reg.test(str); // 用来测试某个字符串是否与正则匹配,匹配就返回true,否则返回false
reg.exec(str); // 返回的是一个数组,数组中第0个元素是匹配的子字符串,第1个元素是正则中的第一个子分组匹配的结果...
str.match(reg); // 是String对象的方法
三种测试方法的对比:
const template = '我叫{{name}},今年{{age}}岁。'
const reg = /\{\{(\w+)\}\}/;
// test方法
let res_test = reg.test(template);
console.log(res_test); // true
// exec方法
let res_exec = reg.exec(template);
console.log(res_exec);
// [
// '{{name}}',
// 'name',
// index: 2,
// input: '我叫{{name}},今年{{age}}岁。',
// groups: undefined
// ]
// match方法
let res_match = template.match(reg);
console.log(res_match);
// [
// '{{name}}',
// 'name',
// index: 2,
// input: '我叫{{name}},今年{{age}}岁。',
// groups: undefined
// ]
正则表达式语法

其他语法拓展:
-
\b
和\B
\b
匹配单词边界,\B
元字符匹配非单词边界。不同则为界,界左和界右肯定不是相同类型。


-
?=n
和?!n
?=n
匹配任何其后紧接指定字符串 n 的字符串,?!n
匹配任何其后没有紧接指定字符串 n 的字符串。
const str = 'happy happily';
const pattn1 = /happ(?=ily)/;
const pattn2 = /happ(?!ily)/;
console.log(pattn1.exec(str));
// 匹配happily [ 'happ', index: 6, input: 'happy happily', groups: undefined ]
console.log(pattn2.exec(str));
// 匹配happy [ 'happ', index: 0, input: 'happy happily', groups: undefined ]
-
- 回溯引用
所谓回溯引用(backreference)指的是模式的后面部分引用前面已经匹配到的子字符串。你可以把它想象成是变量,回溯引用的语法像\1
,\2
,....,其中\1
表示引用的第一个子表达式,\2
表示引用的第二个子表达式,以此类推。而\0
则表示整个表达式。
- 回溯引用
// 匹配两个连续相同的单词
const str = 'Hello what what is the first thing, and I am am scq000.';
const pattn = /\b(\w+)\s\1/;
console.log(pattn.exec(str));
// [
// 'what what',
// 'what',
// index: 6,
// input: 'Hello what what is the first thing, and I am am scq000.',
// groups: undefined
// ]
✨ Date类型
创建实例
new Date()
new Date(milliseconds)
new Date(dateString)
new Date(year, month, day, hours, minutes, seconds)
获取毫秒形式
date.valueOf()
date.getTime()
Date.now()
+new Date()
方法
函数 | 作用 |
---|---|
getFullYear() | 获取当年 |
getMonth() | 获取当月(0-11) |
getDate() | 获取当天日期 |
getDay() | 获取星期几(周日0—周六6) |
getHours() | 获取当前小时 |
getMinutes() | 获取当前分钟 |
getSeconds() | 获取当前秒钟 |
✨ Math类型
函数 | 作用 |
---|---|
Math.PI | 圆周率 |
Math.floor() / Math.ceil() | 向下/上取整 |
Math.round() | 四舍五入 |
Math.abs() | 绝对值 |
Math.min() / Math.max() | 最小/大值 |
Math.random() | 返回[0,1)范围内随机一个小数 |
Math.sqrt() / Math.pow(base, exp) | 平方根/基数base的exp次幂 |
✨ Function类型
- 声明函数
function funcName (arg1, arg2, …) {}
var fn = function () {}; // 匿名函数
() => {} // 箭头函数
(function () {}) (); // 立即执行函数
(function () {} ()); // 立即执行函数
- JS中没有函数重载,后定义的会覆盖先定义的
return
只能返回一个值,如果用逗号隔开多个值,以最后一个为准arguments
是一个伪数组,存储了函数中传递进来的所有实参。伪数组不能调用数组的方法。- 函数的
length
属性返回的是希望接收的参数个数。 - 箭头函数不会提升,函数表达式归类于变量声明,享受变量提升,函数声明的优先级高于变量声明
- 作用域链:根据在内部函数能够访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问就称为作用域链
- 箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this
✨ Set类型
类似于数组,但成员都是唯一的,没有重复的值
实例方法 | 作用 |
---|---|
add(value) | 添加某个值,返回Set结构本身 |
delete(value) | 删除某个值,返回一个布尔值,表示删除是否成功 |
has(value) | 返回一个布尔值,表示该值是否为Set成员 |
clear() | 清除所有成员,没有返回值 |
✨ Map类型
方法 | 作用 |
---|---|
set(key, value) | 设置Map对象键的值 |
get(key) | 根据键获取Map对象的值 |
delete(key) | 根据键删除Map对象的键值对 |
clear() | 清空Map对象 |
has(key) | 根据键查看Map对象中是否有该键 |
Map类型和Object类型的区别
Map | Object | |
---|---|---|
键类型 | Map的键可以是任意值,包括函数、对象或任意基本类型 | Object 的键必须是 String 或是Symbol |
键的顺序 | Map中的键是有序的(插入的顺序) | Object中的键是无序的 |
创建方法 | 只能通过new创建 | 可以通过字面量,自定义构造函数或new创建 |
访问元素方式 | map.get(key) | obj.key或obj[key] |
新增元素或修改元素值 | map.set(key, val) | obj[key] = val或obj.key = val |
删除元素 | map.delete(key) | delete obj[key]或delete obj.key |
键值对数量 | map.size | Object.keys(obj).length |
迭代 | 支持 | 不支持 |
✨ 深拷贝和浅拷贝
- 深拷贝:每一层都拷贝了,改变数据不会影响原对象
JSON.stringfy
和JSON.parse
- 浅拷贝:只拷贝第一层,深层的依然是引用,改变深层会影响原对象
Object.assign()
手写深拷贝
function isObj(item) {
// typeof 会将 Array 也判断为 Object
// 还要排除空对象的可能
return typeof item === 'object' && item !== null;
}
function deepClone(obj) {
// 判断要拷贝的对象是数组还是对象
const newObj = obj instanceof Array? []: {};
for (const key in obj) {
const item = obj[key];
// 简单数据类型只需要赋值,如果是复杂数据类型需要递归进行深拷贝
newObj[key] = isObj(item)? deepClone(item): item;
}
return newObj;
}
✨ 数据类型检测
- typeof
typeof null
的值为Object
,无法区分是null
还是object
(因为在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object),除function的复杂数据类型均判断为object无法区分。
typeof 1 // number
typeof '1' // string
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof new Date() // 'object'
typeof console.log; // 'function'
2. instanceof
只能判断某对象是否存在于目标对象的原型链上(基本数据类型通过字面量赋值无法用instanceof
检测出正确结果)。
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str1 = 'hello world'
str1 instanceof String // false
var str2 = new String('hello world')
str2 instanceof String // true
手写instanceof
function myInstanceof(L, R) { // L代表instanceof左边,R代表右边
var RP = R.prototype; // 构造函数原型(显式原型)
var LP = L.__proto__; // 对象原型(隐式原型)
while (true) {
if (LP === null) return false;
if (LP === RP) return true;
LP = LP.__proto__;
}
}
3. constructor
constructor并不可靠,容易被修改
var d = new Number(1)
var e = 1
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(e.constructor);//ƒ Number() { [native code] }
console.log(e.constructor.name);//Number
console.log(fn.constructor.name) // Function
console.log(date.constructor.name)// Date
console.log(arr.constructor.name) // Array
console.log(reg.constructor.name) // RegExp
4. Object.prototype.toString.call()
一种最好的基本类型检测方式, Object.prototype.toString()
会返回 [object, [[class]]]
的字符串,其中 [[class]]
会返回es定义的对象类型,包含"Arguments", “Array”, “Boolean”, “Date”, “Error”, “Function”, “JSON”, “Math”, “Number”, “Object”, “RegExp”, 和 “String”;再加上es5新增加的返回 [object Undefined]和[object Null]
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(123)); // "[object Number]"
console.log(Object.prototype.toString.call("abc")); // "[object String]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Object.prototype.toString.call(fn));// "[object Function]"
console.log(Object.prototype.toString.call(date));// "[object Date]"
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
Object对象本身就有一个toString()
方法,返回的是当前对象的字符串形式,原型上的toString()返回的才是我们真正需要的包含对象数据类型的字符串。由于Object.prototype.toString()
本身允许被修改,像Array、Boolean、Number的toString就被重写过,所以需要调用Object.prototype.toString.call(arg)
来判断arg的类型,call将arg的上下文指向Object,所以arg执行了Object的toString方法。
✨ 数据类型转换

null
和undefined
都没有toString()
方法,但String()
可以作用于null
和undefined
返回”null”
和”undefined”
- 只有数字型的
toString()
方法能够接收一个可选参数,用来指定计算时的基数,parseInt()
也可以指定进制数 Boolean([])
和Booean({})
都会转换成true
[] == ![]
:[]转换为数字0,![]首先转换为布尔值,由于[]作为一个引用类型,布尔值为true,因此![]为false
。再转换为数字,变为0。最终的结果为true
[].toString()
:'',{}.toString()
:[object Object]
✨ 运算符优先级
运算符优先级从高到低如下表所示:
优先级 | 运算符 |
---|---|
16 | ()分组 |
15 | x++, x--后置递增、后置递减 |
14 | !逻辑非,~按位非,+x, -x一元加法、一元减法 ++x, --x前置递增、前置递减 |
13 | **幂 |
12 | *乘法,/除法,%取余 |
11 | +加法,-减法 |
10 | <<, >>按位左移、按位右移,>>>无符号右移 |
9 | <, >小于、大于,<=, >=小于等于,大于等于 |
8 | ==, !=相等、不相等,===, !==严格相等,严格不相等 |
7 | &按位与 |
6 | ^按位异或 |
5 | |按位或 |
4 | &&逻辑与 |
3 | ||逻辑或 |
2 | x? a: b条件三元运算符 |
1 | =, +=, -=, **=, *=, /=, %=, <<=, >>=, >>>=, &=, ^=, |=, &&=, ||=赋值运算符号 |
✨ || 和 && 操作符的返回值?
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
- 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
- && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。
|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果
✨ 位运算
位运算符
位运算符 | 运算规则 |
---|---|
& 按位与 | 1&1=1, 1&0=0&1=0, 0&0=0 |
| 按位或 | 1|1=1, 1|0=0|1=1, 0|0=0 |
^ 按位异或 | 1^1=0, 1^0=0^1=1, 0^0=0 |
~ 取反 | ~1=0, ~0=1 |
<< 左移 | 右边补0,左边丢弃 |
>> 右移 | 正数左补0,负数左补1,右边丢弃 |
>>> 无符号右移 | 左边补0,右边丢弃 |
原码、反码和补码
- 原码:原码就是一个数的二进制数。
- 反码:正数的反码与原码相同,负数的反码为除符号位,按位取反。
- 补码:正数的补码与原码相同,负数的补码是原码除符号位外的所有位取反即0变1,1变0,然后加1,也就是反码加1。
✨ isNaN和Number.isNaN的区别
isNaN
:除了判断NaN
为true
外,还会把不能转成数字的判断为true
,比如'abc'
;Number.isNaN
:只会判断NaN
为true
✨ 0.1+0.2>0.3
在JS底层每个变量是以二进制表示的,固定长度为64位,其中第一位为符号位,再往后11位是指数位,最后52表示的是尾数位,而0.1和0.2转为二进制的时候是无限循环小数,所以JS就会进行截取(最大安全数字),截取以后0.1和0.2就不是他们本身了,要比原来大一些,所以0.1+0.2>0.3。 解决方法:设置一个误差范围:Math.abs(arg1 - arg2) < Number.EPSILON
,(Number.EPSILON为)
整数二进制用数值乘以2的幂次依次相加,小数二进制用数值乘以2的负幂次依次相加。
0.1101
→ 2−1+2−2+2−4=0.8125
✨ Object.is和===的区别
Object.is
在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是+0
和-0
,NaN
和NaN
表达式 | 结果 | 表达式 | 结果 |
---|---|---|---|
+0 === -0 | true | Object.is(+0, -0) | false |
NaN === NaN | false | Object.is(NaN, NaN) | true |
function is(x, y) {
if (x === y) {
// 1 / +0 = +Infinity, 1 / -0 = -Infinity是不一样的
return x !== 0 || y !== 0 || 1 / x === 1 / y;
}
else {
return x !== x && y !== y;
}
}
✨ ==和===有什么区别
===
是严格意义上的相等,会比较两边的数据类型和值大小
数据类型不同返回false
数据类型相同,但值大小不同,返回false==
是非严格意义上的相等
两边类型相同,比较大小
两边类型不同,先转换数据类型,再进一步进行比较。
Null == Undefined
→true
✨ 装箱和拆箱
- 装箱:将基本数据类型转换为对应的引用数据类型的操作,装箱又分为显式装箱和隐式装箱
- 显式装箱:通过内置对象或基本包装类型对基本数据类型进行操作
(三个基本包装类型:Boolean、Number、String)
- 显式装箱:通过内置对象或基本包装类型对基本数据类型进行操作
const name = new String('Uni');
console.log(name.length); // 3
-
- 隐式装箱:创建一个对应类型实例 → 在实例中调用需要的方法或属性 → 销毁这个实例
const name = 'Uni';
console.log(name.length); // 3
// 内部执行过程
let newName = new String(name);
console.log(newName.length);
newName = null;
- 拆箱:将引用数据类型转换为对应的基本数据类型的操作(通过
valueOf
方法)
const bool = new Boolean(false);
console.log(bool); // [Boolean: false]
console.log(bool.valueOf()); // false
console.log(!bool); // false
console.log(!bool.valueOf()); // true
✨ ToPrimitive算法
JavaScript 对象转换到基本类型值时,会使用 ToPrimitive
算法,这是一个内部算法,是编程语言在内部执行时遵循的一套规则。
ToPrimitive
算法在执行时,会被传递一个参数 hint
,表示这是一个什么类型的运算(也可以叫运算的期望值),根据这个 hint
参数,ToPrimitive
算法来决定内部的执行逻辑。
hint
参数的取值只能是下列 3 者之一:
string
number
default
hint
值为 "string"
时,先调用 toString
,toString
如果返回一个基本类型值了,则返回、终止运算;否则接着调用 valueOf
方法。
否则,先调用 valueOf
,valueOf
如果返回一个基本类型值了,则返回、终止运算;否则接着调用 toString
方法。