前端面试之 ES6

190 阅读13分钟

ES6 和 ES5 的区别

创建 ECMAScript 的目的是标准化 JavaScript

比较项ES5ES6
定义ES5是ECMAScript(由ECMA International定义的商标脚本语言规范)的第五版。ES6是ECMAScript(ECMA International定义的商标脚本语言规范)的第六版。
发布它于2009年推出它于2015年推出。
数据类型ES5支持原始数据类型,包括字符串,数字,布尔值,空值和未定义(undefined)。引入了一种新的原始数据类型symbol以支持唯一值。
定义变量在ES5中,只能使用var关键字定义变量。在ES6中,有两种定义letconst变量的新方法。
循环在ES5中,使用了for循环来遍历元素。ES6引入了for?of循环的概念,以对可迭代对象的值执行迭代。
箭头函数在ES5中,functionreturn关键字均用于定义函数。箭头功能是ES6中引入的新功能,通过它不需要function关键字来定义函数。

ES6模块和CommonJS模块有什么区别

ES6

  1. 使用 import 和 export 关键字来导入和导出模块.
  2. 支持动态导入、可以异步加载模块

CommonJS

  1. 使用 require 和 module.exports 或 exports 来导入和导出模块。
  2. 在设计时没有考虑异步加载的需求,通常在模块的顶部进行同步加载。
    // ES6 模块
    import { foo } from './module';
    export const bar = 'bar';

    // CommonJS 模块
    const foo = require('./commonjs');
    exports.bar = 'bar';

Number 处理

安全整数: -2^53到2^53之间(不含两个端点)

ES6 引入 Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER 这两个常量,用来表示这个范围的上下限。

Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

取整的方式

  1. Number.parseInt(string, radix)
        第一个参数表示要转换的字符串,如果参数不是一个字符串,则将其转换为字符串。
        第二个参数是基数即进制,默认为10。
        缺点:
            一是parseInt这个函数名,看起来就是将字符串转整数的,用在这里不是很适合。
            另一个是转字符串有点多此一举,而且肯定会带来性能开销。
    
  2. Math.round是四舍五入的,Math.ceil是向上取整,Math.floor是向下取整。
  3. 利用 位或“|” 操作来取整的手段。(缺点:不能处理超过32位的数值取整,而JavaScript有效整数的范围是53位。)

获取小数部分

  1. 直接将原数值减去取整后的数值。
  2. 直接将原数对1取模,即可获得小数部分!

精度缺失问题

为什么会出现

十进制是给人看的,但在进行运算之前,必须先转换为计算机能处理的二进制(0舍1入)。最后,当运算完毕后,再将结果转换回十进制,继续给人看。精度就丢失于这两次转换的过程中。

解决方式

对结果进行处理
    对莫个值乘 100 后除 100
    parseFloat((num.toFixed(小数点后面几个数字)); // toFixed() 精度参数须在 0 与20 之间
    parseFloat((num.toPrecision((有几个有效数字))
对加减乘除的过程进行处理

正确做法是: 把小数转成整数后再运算

其它库
  1. Math.js
  2. big.js

数组的处理

... 运算符

  1. 展开参数。
  2. 复制数组、 合并数组、 都是浅拷贝。
  3. 将字符串转化为真正的数组。

Array.from(): 将类数组对象转换为真正的数组

类数组对象它具有 length 属性、 所以任何有length属性的对象,都可以通过Array.from()方法转为数组。

它的第二个参数可以接收一个函数、 作用类似于数组的 map() 方法。若 map() 函数内部用到了 this 关键字、那么 Array.from() 可传入第三个参数、 用来绑定 this。

Array.of(): 将一组值转化为数组

若没有参数、就返回一个空数组。

fill(填充的成员,起始位置,结束位置)

使用给定的值、填充一个数组

flat(拉平的层数) 将嵌套的数组拉成一维数组 、参数默认为 1、 不改变原数组

特性:

  1. 如果不管有多少层嵌套,都要转成一维数组,可以用 Infinity 关键字作为参数。如 flat(Infinity)
  2. 如果原数组有空位,flat()方法会跳过空位。

与之类似的还有 flatMap((当前数组的成员, 当前数组的成员的位置, 原数组) => {})


查找类

find((当前值, 当前位置, 原数组) => {})

从前往后: 找到第一个符合条件的成员、并返回它。若没有找到就返回 undefined。

filter((当前值, 当前位置, 原数组) => {})

从前往后:找到符合条件的成员、并以数组的形式返回它们。若没有找到就返回空数组 [ ]。

findIndex((当前值, 当前位置, 原数组) => {})

从前往后:找到第一个符合条件的成员、并返回它的下标。 若没有找到、则返回 -1。

它可以借助 Object.is() 找到 NaN、 它内部是用严等于号进行判断、容易误判。

indexOf(莫个元素)

从前往后:查找数组中的莫个元素、如果找到则返回它的下标。 如果没有找到则返回-1。

lastIndexOf(莫个元素)

从后往前:查找数组中的莫个元素、如果找到则返回它的下标。 如果没有找到则返回-1。

findLast() 和 findLastIndex() 和 find()和findIndex() 类似、只是查找的方向不同

includes(查找的成员,开始查找的位置)

数组中是否包含给定的值、存在则、返回 true 、否则、返回 false。

另外:

  • Map 结构的has方法,是用来查找键名的
  • Set 结构的has方法,是用来查找值的

队列操作类

改变原数组

arr.push()

从后面添加元素、 返回值是添加完之后的数组长度

arr.pop()

删除最后一位删除元素、 返回的是被删除的元素

arr.shift()

在数组的第一位删除元素、 返回的是被删除的元素

arr.unshift()

在数组前面添加元素、返回的是添加完之后的数组长度

arr.splice(index, n)

从 index 下标开始、删除 n个、 返回的是被删除的元素

不改变原数组

arr.slice(start, end)

从 start 位置开始、到 end (不包括)截取

arr.concat([])

其它类

arr.split()

将字符串转化为数组

arr.every((当前值, 当前位置, 原数组) => {})

依据判断条件、判断数组的元素是否全部满足、 若满足则返回 true 、否则 flase。

arr.some((当前值, 当前位置, 原数组) => {})

依据判断条件、判断数组的元素是否有一个满足、 若满足则返回 true 、否则 flase。

arr.sort()

    Arr.sort(function(a, b) { 
    
        if (a > b) { 
            return 1 //返回正数 ,b排列在a之前 
        } else { 
            return -1 //返回负数 ,a排列在b之前 
        } 
        
    })
    

callback的返回值是正数时、那么 b 会被排列到 a 之前;

callback的返回值是负数时、那么 a 会被排列到 b 之前;

callback的返回值是为 0 时、那么 a 与 b的位置保持不变;

字符串的处理

模板字符串

  1. 所有空格和换行都会保留。
  2. 可放入任何 JavaScript 表达式。
  3. 可调用函数。
  4. 只要不是字符串、会默认调用对象的 toString方法、将其转化为字符串。
  5. 可以嵌套。

string.includes(字符串, 开始搜索的位置)

返回布尔值,表示是否找到了参数字符串。

string.startsWith(字符串, 开始搜索的位置)

返回布尔值,表示参数字符串是否在原字符串的头部。

string.endsWith(字符串, 从第n个位置直到字符串结束)

返回布尔值,表示参数字符串是否在原字符串的尾部。

string.trim()

删除头尾空格。返回一个新的字符串不改变原字符串。

string.trimStart()

删除头部空格。返回一个新的字符串不改变原字符串。

string.trimEnd()

删除尾部空格。返回一个新的字符串不改变原字符串。

string.replace(字符串)

替换掉第一个匹配的字符。 返回一个新的字符串不改变原字符串。

string.replaceAll(匹配模式, 函数或者字符串)

替换所有匹配的字符。 返回一个新的字符串不改变原字符串。

匹配模式: 可以是一个字符串,也可以是一个全局的正则表达式(带有g修饰符)。

string.at(一个整数)

返回参数指定位置的字符,支持负索引(即倒数的位置)。

此方法数组也可用。

对象的处理

简写方式

const baz = {
    foo,
    fn(x,y) {
        return {x, y}
    },
    // 取值函数
    get foo() {},
    // 存值函数
    set foo(x) {},
 }; 
 baz['f'+ 'oo']
 
 等同于 
 const baz = {
     foo: foo,
     fn: function() {
         return {x: x, y: y}
     }
 };
 baz.foo

解释: [ ] 内部可以写表达式、不止属性名可以写、方法名也可以写。

super 关键字

它指向对象的原型对象。注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。(这里只做了解、应对面试)


Object.is(值1,值2)

它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

== 和 === 的区别

==
  1. 相等运算符
  2. 自动转换数据类型来比较两个操作数是否相等
===
  1. 在比较之前,值不会隐式转换为其他值。
  2. 如果变量值的类型不同,则这些值被视为不相等。
  3. 如果变量具有相同的类型、不是数字并且具有相同的值,则它们被视为相等。
  4. NAN 不等于本身
null、underfined、void 0
  1. undefined值是派生自null值的。
  2. null作为空对象指针。
  3. undefined表示未定义,它的类型只有一个就是 undefined

Object.assign(目标对象,源对象1...)

用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。

注意

  1. 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
  2. 如果只有一个参数,Object.assign()会直接返回该参数。
  3. 如果该参数不是对象,则会先转成对象,然后返回。
  4. 由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

Object.hasOwn(所要判断的对象, 属性名)

可以判断是否为自身的属性。

Object.keys()

返回的是一个数组、参数成员是对象自身(不包含继承的)所有可遍历的键名 key。

### Object.values()

返回的是一个数组、参数成员是对象自身(不包含继承的)所有可遍历的键值 value。

  1. Object.values会过滤属性名为 Symbol 值的属性。
  2. 如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

Object.entries()

返回的是一个数组、参数成员是对象自身(不包含继承的)所有可遍历的键值对。除了返回值不一样,该方法的行为与Object.values基本一致。

用途

  1. 遍历对象的属性。
  2. 将对象转化为真正的 Map 结构。

运算符的扩展

链判断运算符 ?.

如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。

    // 错误的写法
    const  firstName = message.body.user.firstName || 'default';

    // 正确的写法
    const firstName = (message
          && message.body
          && message.body.user
          && message.body.user.firstName) || 'default';
      
    // 由于层层判断过于麻烦、 所以就有了以下写法
    const firstName = message?.body?.user?.firstName || 'default';
    
    上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null``undefined`。如果是的,就不再往下运算,而是返回`undefined`

?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行。

    a?.b
    // 等同于
    a == null ? undefined : a.b

    a?.[x]
    // 等同于
    a == null ? undefined : a[x]

    a?.b()
    // 等同于
    a == null ? undefined : a.b()

    a?.()
    // 等同于
    a == null ? undefined : a()
    

Null 判断运算符 ??

读取对象属性的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。但是属性的值如果为空字符串或false0,默认值也会生效。

所以在ES2020中引入了??、 它只有运算符左侧的值为nullundefined时,才会返回右侧的值。

这个运算符很适合判断函数参数是否赋值。

在 ES2021 引入了新的逻辑赋值运算符

    // 或赋值运算符
    x ||= y
    // 等同于
    x || (x = y)

    // 与赋值运算符
    x &&= y
    // 等同于
    x && (x = y)

    // Null 赋值运算符
    x ??= y
    // 等同于
    x ?? (x = y)
    

它的作用是为变量和属性设置默认值

    // 老的写法
    user.id = user.id || 1;

    // 新的写法
    user.id ||= 1;

Map 和 Set

image.png

代理 Proxy(所要代理的目标对象, 配置对象)

可以理解为在目标对象之前设置一层拦截。当外界对该对象进行访问的时候、都必须先通过这层拦截。所以通过代理可以对外界对访问进行过滤和改写。

实例方法

get(目标对象、属性名、 proxy 实例本身)

get方法用于拦截某个属性的读取操作。

set(目标对象、 属性名、属性值、 proxy 实例本身)

set方法用来拦截某个属性的赋值操作。

apply(目标对象、 上下文对象this、目标对象的参数数组)

apply方法拦截函数的调用、callapply操作。

has(目标对象、 需要查询的属性名)

has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符.

construct(目标对象、 构造函数的参数数组、 创建实例对象时new命令作用的构造函数)

construct()方法用于拦截new命令。

deleteProperty(目标函数、键名key)

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

Promise

是什么

是异步编程的一种解决方案。简单的说就是一个容器、包含了一个未来才可以确定结果的异步操作事件

特点

  1. 对象的状态不受外界影响、只能由异步操作的结果决定当前处于那种状态。当处于 pending 状态时、无法得知目前进行到哪一阶段。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 状态改变只有两种可能
    • 从 pending 变为 fullfilled。
    • 从 pending 变为 rejected。

要点

  1. 构造函数、 在new的时候自动执行。(一旦执行、中途无法取消)
  2. 状态驱动:只能从 pending 变为 fullfilled 或 从 pending 变为 rejected。
  3. then链式调用时、第一步的结果无论是成功或者失败。都将第一步的结果以成功的状态传给第二步。(把它当作成功的回调)

优点

  1. 解决回调地狱问题
  2. 代码可读性高
  3. 更好的捕获错误

缺点

  1. Promise 代表已经在进行中的进程(一旦创建就会立即执行)、 中途无法取消
  2. 当处于 pending 状态时、无法得知目前进行到哪一阶段
  3. 如果不设置回调函数、promise 内部会抛出错误、不会反应到外部

Promise 类提供了四个静态方法来促进异步任务的并发

Promise.all()

在传入的所有 Promise 都被兑现的时候兑现;任意一个 Promise 被拒绝时拒绝。

Promise.any()

在任意 Promise 被兑现时兑现;仅在所有 Promise 被拒绝时拒绝。

Promise.race()

在任意一个 Promise 被兑现时兑现;任意一个 Promise 被拒绝时拒绝。

Promise.allSettled()

在所有 Promise 被敲定(不是待定状态)的时候兑现。