前端面试基础题:JavaScript变量声明、装箱和拆箱...

✨ script标签defer和async

  • <script>:如果遇到script标签,会阻塞HTML的解析,等到script下载执行完成之后再继续解析HTML。
  • <script defer>:script的加载不会阻塞HTML的解析,等到HTML解析完成再按照加载顺序执行脚本。
  • <script async>:script的加载不会阻塞HTML的解析,脚本加载完成就立即执行,并且是乱序执行的。

asyncdefer具有更高的优先级,如果同时添加两个属性,则会忽略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类型

  1. 八进制:前面加0,十六进制:前面加0x
  2. 范围:Number.MAX_VALUE(1.797631348623157e+308),Number.MIN_VALUE(5e-32)
  3. 最大安全整数: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

区别

  1. 含义不同:undefined表示未定义的值,null表示一个空对象,变量声明了但还没有定义的时候会返回 undefinednull主要用于赋值给一些可能会返回对象的变量,作为初始化。
  2. 类型不同:typeof undefined为Undefined,typeof null为Object
  3. 数字转换结果不同: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创建一个实例对象的过程

  1. 创建一个空对象
  2. 继承构造函数的原型
  3. this指向obj,并调用构造函数
  4. 返回对象
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函数的应用场景:

  1. 计算数组中所有值的总和/最值
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.stringfyJSON.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;
}

✨ 数据类型检测

  1. 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方法。

✨ 数据类型转换

  • nullundefined都没有toString()方法,但String()可以作用于nullundefined返回”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,右边丢弃

原码、反码和补码

  1. 原码:原码就是一个数的二进制数。
  2. 反码:正数的反码与原码相同,负数的反码为除符号位,按位取反。
  3. 补码:正数的补码与原码相同,负数的补码是原码除符号位外的所有位取反即0变1,1变0,然后加1,也就是反码加1。

✨ isNaN和Number.isNaN的区别

  • isNaN:除了判断NaNtrue外,还会把不能转成数字的判断为true,比如'abc'
  • Number.isNaN:只会判断NaNtrue

✨ 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-0NaNNaN

表达式 结果 表达式 结果
+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;
    }
}

✨ ==和===有什么区别

  1. ===是严格意义上的相等,会比较两边的数据类型和值大小
    数据类型不同返回false
    数据类型相同,但值大小不同,返回false
  2. ==是非严格意义上的相等
    两边类型相同,比较大小
    两边类型不同,先转换数据类型,再进一步进行比较。
  • 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" 时,先调用 toStringtoString 如果返回一个基本类型值了,则返回、终止运算;否则接着调用 valueOf 方法。

否则,先调用 valueOfvalueOf 如果返回一个基本类型值了,则返回、终止运算;否则接着调用 toString 方法。

THE END