2024前端面试 -- ES6

483 阅读16分钟

【前端面试系列】:

ES6 入门教程

1. ES6ES5的联系、区别、转换等关系

1. 简述 ES6 代码转成 ES5 代码的实现思路是什么?

es6es5 主要通过babel 工具实现,BabelJavaScript 编译器,能够将 es6 转换成向后兼容的 es5,从而在现有的浏览器和环境中运行。

主要步骤如下:

  1. 词法分析:Babel 首先会将输入的代码进行词法分析,将代码分割成一个个 词法单元
  2. 语法分析:Babel 会对分割后的词法单元进行 语法分析,生成 抽象语法树(AST)
  3. 转换:通过对 AST 进行遍历和修改,BabelES6 代码 转换ES5 代码。
  4. 代码生成:最后,Babel 会将转换后的 AST 生成 可运行的 ES5 代码。

在转换过程中,Babel 会根据预定义的插件和预设对代码进行转换。插件和预设可以分别处理一些特定的语法和功能,如箭头函数、类和模块等。同时,Babel 还支持开发者自定义插件和预设来处理更加特殊的个性化的需求。

  • 语法转换:本质是将 ES6 语法转成 AST,再将 AST 转为 ES5 语法代码。
    • 例如:将 letconst 转换成 var箭头函数 转换成 Function 函数声明等。
  • API 转换:采用 Babel-polyfill 等工具对 ES5 中不存在的 API(包括 SetES6 中新的数据结构)做修复。
    • 例如:Array.prototype.includesSetMap 等在 ES5 中不存在,需要用响应的 ES5 代码实现这些 API

综上所述,ES6 代码转成 ES5 代码的实现思路涉及词法分析、语法分析、AST 的生成和转换、以及使用插件和预设进行特定语法的处理,同事还需要考虑 API 地兼容性问题。

1.1 解释 babel 是什么?有什么作用?

Babel 是一个 JavaScript 编译器,它可以将 ECMAScript 2015+ 版本的代码转换成向后兼容的 JavaScript 代码,以便在现有的浏览器中运行。Babel 可以帮助开发者使用最新的 JavaScript 语言特性,而不用担心浏览器兼容性问题。

Babel 转换代码。这些特性主要包括箭头函数、解构赋值、模版字符串、letconst 等等。还支持转换 JSX 语法,使得 React 的代码可以在浏览器中运行。

Babel 可以使用插件来扩展其功能。例如,可以将 TypeScript 转换成 JavaScript 代码,或者使用插件来判断新的 ECMAScript 特性。Babel 还可以与许多构建工具(如 webpackRollup等)集成,以便在构建过程中自动转换代码。

2. ES5ES6(ES2015)有什么区别?

  • ES5:指的是 ECMAScriot 的第五个版本,发布于 2009 年,是目前最广泛使用的 JavaScript 版本。
  • ES6:指的是 ECMAScriot 的第六个版本,也称为 ES2015,发布于 2015 年,引入了许多新的语言特性和语法糖。

ES6 相较于 ES5 的主要区别包括:

  • 新的语法特性,如箭头函数、类、模版字符串、解构赋值等;
  • 新的数据类型,如 SetMapSymbol 等;
  • 新的迭代器和生成器,使得处理数据集合更加方便;
  • 新的模块化系统,使得代码的组织和管理更加容易;
  • 新的 Promise 函数,使得异步编程更加简单可读;
  • 新的默认参数和剩余参数语法,使得函数的定义和调用更多灵活。

ES6 新增的特性:

  • letconst 定义块级作用域
  • 箭头函数
  • 解构赋值
  • 扩展运算符
  • 常见的数组的方法、伪数组
  • 模版字符串
  • class
  • 参数设置默认值
  • promise
  • for...in, for...of

3. 详细描述 ES6ECMAScript 2015 的关系?

ES2015ES6 的官方名称,但是由于 ES6 引入了太多的新特性,因此人们通常使用 ES2015 代替 ES6.

4. 简述 ES5/ES6 的继承除了写法以外还有什么区别?

ES5/ES6 除了继承除了写法外,主要还有继承的机制和类的方法属性。

4.1. 继承机制:

  • ES5 的继承主要通过原型链和构造函数来实现。
    • 可以通过 Parent.apply(this, arguments) 将父类的属性和方法添加到子类的实例上,这种方法相对复杂且不够直观。
    • 这种方式下,子类的示例创建后,通过调用父类的构造函数来初始化父类的部分属性。
  • ES6 引入了 class 关键字,使得继承的写法更加简洁和直观。
    • 子类的构造函数必须首先调用 super() 来获取父类的属性和方法,然后再添加或修改子类特有的属性和方法。
    • 在这种方式下,子类的实例创建基于父类实例,只能通过 super() 才能访问父类的属性和方法。
// ES5
// 定义父类
function Parent(value) {
  this.language = ["js", "less", "react"];
  this.value = value;
}
// 定义子类
function child() {
  Parent.apply(this, arguments);
}

const test = new Child(888);
test.language; // ["js", "less", "react"]
test.value; // 888

// ES6
class Child2 extends Parent2 {
  constructor(props) {
    super(props);
  }
}

var test2 = new Child2("super data");
test2.value; // super data

4.2. 类的方法和属性

  • ES5 中,通过原型链添加的方法和属性是可枚举的。例如,通过 Object.keys(Bar)可以获取到 Bar 上的所有可枚举数据类型,包括实例方法和静态方法。
  • ES6 中,类的所有方法(包括静态方法和实例方法)都是不可枚举的。这意味着使用Object.keys(Foo)无法获取到Foo类上的任何方法或属性,包括其原型上的方法。

5. 简述 ES6 之前使用 prototype 实现继承?

在引入 class 之前,JavaScript 使用 prototype 来实现继承。每个函数都有一个 prototype 属性,这个属性指向一个对象,而这个对象被用作通过该构造函数创建的所有对象的原型。通过修改这个原型对象,可以添加属性和方法,这些属性和方法会被所有实例共享。

// 父类构造函数
function Parent(name) {
  this.name = name;
}
// 父类方法
Parent.prototype.greet = function () {
  console.log("Hello, my name is" + this.name);
};

// 子类构造函数
function Child(name, age) {
  Parent.call(this, name); // 调用父类构造函数给子类实例设置name属性
  this.age = age;
}
// 创建一个Parent实例,将其方法赋予Children.prototype
Child.prototype = Object.create(Parent.prototype);
// 修正Child.prototype的构造器指向
Child.prototype.constructor = Child;

// 子类持有方法
Child.prototype.introduce = function () {
  console.log("My name is " + this.name + ", I am " + this.age);
};

// 测试继承
var childInstance = new Child("Alice", 22);
childInstance.greet(); // Hello, my name isAlice
childInstance.introduce(); // My name is Alice, I am 22

6. 简述怎样通过 ES5ES6 声明一个类?

  • ES5 中,类的创建主要通过构造函数来实现,构造函数本身可以看作是一种特殊的函数,用于创建和初始化对象。
// 创建构造函数
let Animal = function (type) {
  this.type = type;
};

// 在构造函数的原型上添加共享的方法,这样所有通过该构造函数创建的对象都会继承这些方法。
Animal.prototype.walk = function () {
  console.log("I am walking");
};

new Animal("dog");
new Animal("monkey");

// 这样,通过 `new Animal("dog")` 创建的对象 `dog`和通过 `new Animal("monkey")`创建的对象 `monkey`都会继承`walk`方法。
  • ES6 中,类的声明变得更加直观和简洁。
  • ES6 引入了 class 关键字,允许开发者以更接近传统面向对象编程的方式来声明类
// 声明类
class Animal {
  // consttuctor:一个特殊的方法,用于创建对象时初始化对象的属性。
  constructor(type) {
    this.type = type;
  }

  // 其他的方法,则是类的成员方法,所有该类的实例都会继承这些方法。
  walk() {
    console.log("I am walking");
  }
}

// 语法简洁,更符合传统的面向对象编程思维,代码更易于理解和维护。

2. ES6 的升级

1. 简述 ES6 let 有什么用?有了 var 为什么还要用 let

ES6 之前,声明变量只能用 varvar 方式声明变量其实是很不合理的,准确的说,是因为 ES5 里面没有块级作用域是很不合理的,甚至可以说是一个语言层名的 bug。没有块级作用域会带来很多难以理解的问题,比如 for循环 var 变量泄露,变量覆盖等问题,let 声明的变量拥有自己的块级作用域,且修复了 var 声明变量带来的变量提升问题。

{
  var i = 10;
}
console.log(i); // 10

{
  let j = 10;
}
console.log(j); // Uncaught ReferenceError: j is not defined
  • letfor循环中使用:每次循环都会创建一个新的且独立的块级作用域,使用let声明变量传入到for循环体的作用域中,不会改变,不会受到外界的影响。
// var:
var a = [];
for (var i = 0; i < 3; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 3
a[1](); // 3
a[2](); // 3
// 结果:不是预期的0,1,2
// 解释:因为使用 var 声明变量,i 是全局变量,每次循环,都会修改 i 的值,相当于给 i 重新赋值,因此最后输出的 i 是 for循环 完成后的值,因此输入结果全是 3

// var换成let后:
var b = [];
for (let j = 0; j < 3; j++) {
  b[j] = function () {
    console.log(j);
  };
}
b[0](); // 0
b[1](); // 1
b[2](); // 2
  • let 没有变量提升
  • 变量提升:用 let 声明不存在的变量提升,必须等 let 执行完毕之后,变量才可以使用,否则会报错 Uncaught ReferenceError
console.log(c); // Uncaught ReferenceError: Cannot access 'c' before initialization
let c = "c";

// var 存在变量提升,无论是开头还是结尾使用var声明都可以使用。
console.log(d); // undefined
var d = "d";
  • let 同一个作用域不可重复声明,var 则不会报错
let a = 1;
let e = 2; // Uncaught SyntaxError: Identifier 'e' has already been declared

var a = 1;
var a = 2;

2. 简述 ES6String 字符串类型做的常用升级优化?

  1. 模版字符串(Template Literals):使用反引号( ` )来定义字符串,可以内嵌变量和表达式,还能包含换行符。
let name = "Name";
let greeting = `Hello, ${name}!`;
console.log(greeting); // Hello, Name!
  1. 字符串扩展:包含了多个新的方法,如 includes(), startsWith(), endsWith(), 用于判断字符串是否包含、是否以特定子串开始或结束。
let test = "hello world!";
console.log(test.startsWith("hello")); // true;
console.log(test.endsWith("world!")); // true;
console.log(test.includes("world")); // true;
  1. 重复字符串:新增了字符串的重复功能,可以用repeat(n)来生成一个新的字符串,其中包含原始字符串 n 次。
let str = "abc";
let repeated = str.repeat(3);
console.log(repeated); // abcabcabc
  1. 字符串迭代器:String 类型现在可以用 for...of 循环来进行迭代,每次迭代的是字符串中的没一个字符。
for (const char of "hello") {
  console.log(char);
}
// h
// e
// l
// l
// o

3. 简述 ES6Array 数组类型做的常用升级优化?

  1. 解构赋值:可以直接从数组中提取值并赋值给变量。
let [a, b, c] = [1, 2, 3];
// a=1,b=2,c=3
  1. 扩展运算符(...):用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let arr1 = [1, 2, 3];
let arr2 = [...arr1]; // [1, 2, 3]
  1. Array.from():可以将类似数组的对象和可遍历对象转换为数组。
let arr3 = { 0: "a", 1: "b", 2: "c", length: 3 };
let arr4 = Array.from(arr3); // ['a', 'b', 'c']
  1. 数组的 map()filter()方法:这两个方法可以用于对数组进行更复杂的操作,map()用于创建新数组,filter()用于过滤新数组。
let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.map((number) => number * 2); // [2, 4, 6, 8, 10]
let even = numbers.filter((num) => num % 2 === 0); // [2, 4]
  1. 简写属性名:在对象字面量中,可以在属性名与变量名相同时使用简写。
let a = 1,
  b = 2,
  c = 3;
let arr = [a, b, c];
console.log(`output->obj`, arr); // [1, 2, 3]
  1. rest 参数(...):用户获取函数的多余参数,这些参数以数组形式表示。
function add(...values) {
  let sum = 0;
  for (let val of values) {
    sum += val;
  }
  return sum;
}
console.log(add(1, 2, 3)); // 6
  1. 迭代器与生成器:ES6 中引入了新的 for...of 循环,用于迭代数据集合,比如 ArraysMapsSets
for (let value of ["a", "b", "c"]) {
  console.log(value);
}
// a
// b
// c
  1. Promise 与异步编程:ES6 引入了 Promise 对象,用于更优雅地处理异步编程。
let promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1秒后弹窗显示 "done!"

4. 简述 ES6Number 类型做的常用升级优化?

  1. 指数运算符: 使用\*\*代替 Math.pow()来进行指数运算。
// 指数运算符
let x = 2;
let y = 3;
let result = x ** y; // 结果为8
  1. Number.isFinite(): 用于检查一个数值是否为有限数。
// Number.isFinite()
console.log(Number.isFinite(0.1)); // 输出:true
console.log(Number.isFinite(Infinity)); // 输出:false
  1. Number.isNaN(): 用于检查一个值是否为 NaN
// Number.isNaN()
console.log(Number.isNaN(NaN)); // 输出:true
  1. Number.parseInt()Number.parseFloat(): 作为静态方法,可以直接调用,而不用将它们绑定到 Number.prototype
// Number.parseInt()
let num = Number.parseInt("123", 10); // 输出:123
  1. Number.isInteger(): 判断一个值是否为整数。
// Number.isInteger()
console.log(Number.isInteger(123)); // 输出:true
  1. 安全整数: Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER 标识 JavaScript 中最小和最大安全整数。
// 安全整数
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

5. 简述 ES6Object 对象类型做的常用升级优化【重要】?

  1. 属性的简写
let name = "Alice";
let age = 29;
const person = { name, age };
  1. 属性名可以使用表达式
let key = "name";
const person = { [key]: "Alick", age: "23" };
  1. 方法的简写
const person = {
  name: "Alice",
  greet() {
    return `Hello, my name is ${this.name}!`;
  },
};
  1. 新的方法
// Object.is()
console.log(Object.is("foo", "foo")); // true
console.log(Object.is({}, {})); // false,因为对象比较是引用比较

// Object.assign()
const object1 = { a: 1 };
const object2 = { b: 2 };
const object3 = { c: 3 };
const mergedObject = Object.assign(object1, object2, object3);
// mergedObject 现在有属性 a, b, 和 c

6. 简述 ES6Function 函数类型做的常用升级优化?

  1. 箭头函数:简化了函数的定义的方式,用关键字 => 代替 function
// ES5
var sum = function (a, b) {
  return a + b;
};

// ES6
const sum = (a, b) => a + b;
  1. 函数参数默认值:允许给函数参数指定默认值,避免了在函数体内部进行判断
// ES5
function multiply(a, b) {
  b = b || 1;
  return a * b;
}

// ES6
function multiply(a, b = 1) {
  return a * b;
}
  1. rest 参数:允许函数接收数组或是可迭代对象中的多个参数
// ES5
function sum(arr) {
  return arr.reduce((a, b) => a + b, 0);
}

// ES6
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}
  1. 扩展运算符:允许一个可迭代的对象展开为函数参数
// ES5
var numbers = [1, 2, 3, 4, 5];
Math.max.apply(null, numbers);

// ES6
const numbers = [1, 2, 3, 4, 5];
Math.max(...numbers);
  1. 对象方法:允许在对象字面量中使用简写方法定义
// ES5
var obj = {
  value: 1,
  double: function () {
    return this.value * 2;
  },
};

// ES6
const obj = {
  value: 1,
  double() {
    return this.value * 2;
  },
};
  1. 函数 name 属性: 函数现在有了一个标准化的名字
function foo() {}
console.log(foo.name); // "foo"
  1. 尾调用优化(Tail Call Optimization): 允许函数的最后一个操作是返回一个函数的递归调用或者一个构造函数的调用
// ES5
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

// ES6
function factorial(n) {
  if (n === 1) return 1;
  // 尾调用
  return n * factorial(n - 1);
}

7. 简述 ES6 Symbol 的作用?

SymbolES6 引入的一种新的原始数据类型。它是唯一且不可变的数据类型。主要用于创建对象的唯一属性名,以解决命名冲突的问题,以及为对象添加独一无二的属性,这些属性不会与其他属性键冲突。

Symbol 的主要作用和特性包括:

  1. 唯一性:每个通过 Symbol() 函数创建的 symbol 值都是唯一的,即使是用相同的参数创建的 symbol 也不相等。这保证了使用 symbol 作为对象属性名时,不会与其他属性名发生冲突。
  2. 不可变性:symbol 一旦被创建,就不能被修改,他们是不可变的确保了属性名的稳定性。
  3. 使用场景:
    • 私有属性:Symbol 常被用来作为对象的私有成员,因为 Symbol 类型的属性不会出现在常规的对象属性枚举中,例如 for...in 循环或 Object.keys()方法中,这使得 symbol 属性可以被视为对象的私有属性。
    • 防止命名冲突:在大型项目或者是多人协作的项目中,使用 symbol 可以防止属性名的冲突,特别是扩展第三方库的对象时尤其重要。
    • 使用 Well-known Symbols 来实现对象接口:ES6 定义了一些内置的 well-known symbols,他们通过 Symbol 构造函数的静态属性访问,如 Symbol.iteratorSymbol.asyncIteratorSymbol.toStringTag 等。这些 Symbols 用于实现对象的标准行为,例如定义迭代器、异步迭代器或改变对象的字符串描述等。

创建 Symbol

let sym1 = Symbol();
let sym2 = Symbol("desc");
let sym3 = Symbol("desc");

console.log(sym2 === sym3); // false

使用 Symbol 作为对象的属性名

let mySymbol = Symbol();
let obj = {
  [mySymbol]: "value",
};

console.log(obj[mySymbol]); // "value"

Symbol 保证属性不会被意外覆盖或枚举

let id = Symbol("id");
let person = {
  name: "John",
  age: 20,
  [id]: 111,
};

for (let key in person) {
  console.log(key); // name,age
}
console.log(Object.keys(person)); // ["name","age"]
console.log(person[id]); // 111

8. 简述 ES6 Set 的作用?

ES6 引入 Set 新的数据结构,其主要作用是提供一种存储唯一值的集合,无论这个值是原始值还是对象引用,Set 对象允许存储任何类型的唯一值,无论是原始值还是对象引用,他们在 Set 种不会重复出现。

Set 的主要特性和作用包括:

  1. 唯一性:Set 内部的值都是唯一的,这意味着 Set 集合中没有重复的值,这对于需要元素唯一性的数据结构非常有用。例如:去重
  2. 值的类型:Set 可以存储任何类型的值,包括原始类型和对象引用。
  3. 数据操作:Set 提供了简单的操作方法,包括 add(value) 添加新元素,delete(value)删除元素,has(value)检查元素是否存在,以及 clear() 清空所有元素。这些方法提高了数据操作的便利性。
  4. 迭代方法:Set 是可迭代的,它提供了 forEach 方法及 keys,values,entries 迭代器方法,使得遍历集合变得非常简单。由于 Set 的值是唯一的,所以 keys()values()方法的行为是相同的。
  5. 集合大小:通过 size 属性,可以很方便的获取集合中元素的数量。

创建 Set 并添加元素

let mySet = new Set();

mySet.add(1);
mySet.add("some text");
mySet.add({ a: 1, b: 2 });

console.log(mySet.size); // 3

检查值是否在 Set

console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false

遍历 Set

mySet.forEach((value) => {
  console.log(value);
});
// 1
// some text
// {a: 1, b: 2}

使用 Set 去重

const numbers = [2, 3, 4, 5, 2, 3, 8];
const uniqueNumbers = new Set(numbers);
console.log(uniqueNumbers); // Set(5) {2, 3, 4, 5, 8}

9. 简述 ES6 Map 的作用?

ES6 引入 Map 对象作为一种新的键值对集合结构,它提供了比传统对象字面量更灵活和强大的方式来存储数据。Map 对象可以使用任何类型的值(包括对象)作为键,这里它与传统对象最大的不同之处,后者仅支持字符串和 Symbol 作为键名。

Map 的主要特性和作用:

  1. 键的多样性:在 Map 中,键可以是任意类型的值,包括函数、对象或任何原始类型。
  2. 元素顺序:Map 对象维护键值对的插入顺序,当进行迭代时,会按照元素的插入顺序返回键值对。
  3. 大小可测:通过 Mapsize 属性可以直接获取集合的大小,这比传统对象需要手动计数更为方便。
  4. 性能优化:对于频繁增删键值对的场景,Map 的性能通常优于传统的对象,因为 Map 是专门为了大量数据的存储而设计的。
  5. 更好的迭代器支持:Map 对象是可迭代的,它提供了 forEach 方法以及 keys(),values(),entries()这些迭代器方法,使得遍历数据变得非常简单。
  6. 直接数据操作方法:Map 提供了 set(key, value), get(key), has(key), delete(key)clear 等方法,用于更加直接和便捷的操作数据。

创建 Map 并添加元素:

let myMap = new Map();
myMap.set("key1", "value1");
myMap.set("1", "value2");
myMap.set({}, "value3");

console.log(myMap); // Map(3) {'key1' => 'value1', '1' => 'value2', {…} => 'value3'}
console.log(myMap.size); // 3

获取和设置值:

console.log(myMap.get("key1")); // value1
console.log(myMap.get(1)); // value2
console.log(myMap.get({})); // undefined,因为{}是一个新的对象引用

遍历 Map

myMap.forEach((value, key) => {
  console.log(key, value);
});
// key1 value1
// 1 'value2'
// Object {} 'value3'

使用 Map 进行数据结构的优化:

Map 的引入使得 JavaScript 在处理复杂的数据结构时更加灵活和强大,尤其是在需要键值对存储且键为非字符串时。此外,Map 的性能优化和迭代器支持使得数据操作和遍历更为高效和方便。

简述 ES6 Proxy 的作用?

简述 ES6 Reflect 的作用?

简述 ES6 Promise 的作用?

简述 ES6 Iterator 的作用【重要】?

简述 ES6 Generator 的作用?

简述 ES6 Classextends 是什么?有什么作用?

简述 ES6 moduleexportimport 的作用?

简述 ES6 规定 for...infor...of 有什么区别?

简述 ES6 async 函数?

简述开发过程中有哪些值得用 ES6 去改进的编程优化或者规范?

详细简述 ES6 箭头函数?

解释ES6 includes()startsWith()endsWidth()

简述ES中什么是padStart()padEnd()

简述 ES varletconst、之间的区别?

简述汇总 ES6 中数组新增了那些扩展?

简述汇总 ES7 对象新增了那些扩展?

简述你对 ES6 中新增的 SetMap 两种数据结构的理解?

如何理解 ES6 中的 Promise?

如何理解 ES6 中的 Generator 的?使用场景?

如何理解 ES6 中的 Proxy 的?使用场景?

如何理解 ES6 中的 Module 的?使用场景?

如何理解 ES6 中的 Decorator 的?使用场景?

简述 ECMAStriptJavaScript 的关系?

详细简述 ES6 的数值扩展?

简述 ES6 的对象方法扩展?

简述 ECMAScript 7 新特性?

简述 ECMAScript 8 新特性?

简述 ECMAScript 9 新特性?

简述 ECMAScript 10 新特性?

简述 ECMAScript 11 新特性?

简述 ECMAScript 12 新特性?