2024ES6八股文,不定时更新

本文详细讲述了JavaScript中var、let、const的区别,ES6新增的Set、Map数据结构,Promise处理异步操作,Generator的迭代器和控制流程,以及Module和Decorator在代码组织中的应用。
摘要由CSDN通过智能技术生成

八股文合集:
2024Vue八股文Vue2、Vue3

说说Var Let Const的区别

首先, var, let, const 这三个都是JavaScript用来声明变量的关键字,但是它们之间在作用域和可变性方面有所不同。

  1. var: 它是最旧的声明方式,声明的变量具有函数级作用域(function scope),也就是说,在函数内部用var声明的变量在整个函数内部都是可见的。如果在函数外部用var声明变量,那么声明的是一个全局变量。var声明的变量存在变量提升的特性,也就是在声明之前就可以使用,值为undefinedvar声明的变量可以反复赋值。

  2. let: let是在ES6中引入的,用来声明变量,其作用域是块级作用域(block scope),即大括号 {} 包裹的范围内。与var不同的是,let没有变量提升,也就是说你必须在声明变量后才能使用它;否则会引发ReferenceError。同样,let声明的变量可以反复赋值。

  3. const: const也是ES6引入的,用来声明常量。其作用域同样是块级作用域,没有变量提升,必须在声明变量后使用。不同于letvar的是,使用const声明的是一个只读常量,一旦赋值后,就不能重新赋值。需要注意的是,const保证的是变量指向的内存地址不变,并不保证该内存地址的值不变,比如说,我们可以修改一个使用const声明的对象的属性,但是不能将其重新赋值为另一个对象。如:

const obj= {};

// 为 obj 添加一个属性,可以成功
obj.name= 'lh';
console.log(obj.name)// lh

// 将 obj 指向另一个对象,就会报错
obj = {}; // TypeError: "obj" is read-only 或者 Uncaught TypeError: Assignment to constant variable.

总结来说,主要区别在于作用域、变量提升与是否可重新赋值。在现代开发中,更推荐使用letconst来声明变量,因为它们提供了更强的作用域控制,有助于写出更加清晰和可维护的代码。

了解ES6新增的Set、Map两种数据结构吗?

Set 和 Map 是 ES6 中新添加的两种集合数据结构,它们各自有特点和用途。

  • Set:
    Set 是一种集合数据结构,它类似于数组,但是它的一个主要特点是它的成员的值都是唯一的,没有重复的值。
    Set 本身是一个构造函数,可以用来生成 Set 数据结构。
    Set 可以通过 .add(value) 方法添加成员,使用 .delete(value) 方法删除成员,通过 .has(value) 方法检查成员是否存在,以及使用 .clear() 方法清空所有成员。
    Set 还有一个属性 .size,表示 Set 结构的成员总数。
    Set 遍历操作包括 .keys()、.values()、.entries() 方法和 for…of 循环。
    由于 Set 中的元素是唯一的,它经常用于数组去重。
  • Map:
    Map 是一种键值对的集合,类似于对象,但 Map 的“键”的范围不限于字符串,可以含有各种类型的值(包括对象),这对于建立从对象到对象的映射非常有用。
    Map 也是一个构造函数,可以通过 new 关键字创建 Map 数据结构。
    Map 实例有 .set(key, value) 方法添加成员,.get(key) 方法读取成员,.has(key) 方法检查成员是否存在,.delete(key) 方法删除成员,以及 .clear() 方法快速清空所有成员。
    Map 实例也有一个 .size 属性,返回 Map 结构的成员总数。
    Map 结构也可以很容易地遍历,它的遍历顺序是按照成员添加顺序排序的。可以直接使用 .keys()、.values() 和 .entries() 方法以及 for…of 循环进行遍历。

这两种数据结构都弥补了之前 JavaScript 对象和数组的一些限制,提供了更丰富的数据操作功能,尤其在需要快速查找、高效遍历和需要唯一元素集合的场合下很有用。

说说promise和它的使用场景

Promise 是 JavaScript 中用于处理异步操作的对象。它代表了一个值,这个值可能是现有的,也可能是将来才会产生的。Promise 对象主要用于异步计算,可以用来避免更传统的回调地狱(即多层嵌套的回调函数)。
一个 Promise 有以下几种状态:

  • pending:初始状态,既不是成功,也不是失败状态。
  • fulfilled:意味着操作成功完成。
  • rejected:意味着操作失败。

Promise 对象可以用来进行链式调用。每一个 .then 或 .catch 方法都会返回一个新的 Promise,允许我们继续链式调用。
使用场景包括:

  1. Ajax 请求:从服务器获取数据时,可以使用 Promise 来处理异步请求。
fetch('api/data.json')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
  1. 文件操作:在 Node.js 中读写文件,可以返回一个 Promise,从而避免嵌套的回调
const fsPromises = require('fs').promises;
fsPromises.readFile('input.txt', 'utf-8')
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
  1. 数据库操作:数据库查询也是异步的,所以可以使用 Promise 来处理结果。
database.query('SELECT * FROM users')
  .then(results => {
    console.log(results);
  })
  .catch(error => {
    console.error('Error:', error);
  });
  1. 异步循环:当我们想按顺序处理一个包含异步操作的数组时,比如依次保存文件
let files = ['file1.txt', 'file2.txt', 'file3.txt'];
files.reduce((promise, file) => {
  return promise.then(() => fsPromises.writeFile(file, 'Hello, world!'));
}, Promise.resolve());
  1. 并行异步处理:Promise.all 用于处理多个异步操作,等待所有的异步操作完成。
let promises = [fetch('api/data1.json'), fetch('api/data2.json')];
Promise.all(promises)
  .then(results => {
    // 所有请求成功时的处理
  })
  .catch(error => {
    // 任意请求失败时的处理
  });

说说Generator和它的使用场景

Generator 是 ES6 引入的一种新的函数语法,可以通过 function* 的形式来定义。Generator 函数可以通过 yield 关键字暂停执行,并且能够在稍后的任意时间点恢复执行。使用 Generator 函数,可以轻松实现迭代器模式,控制函数的执行流程,以及处理异步操作的同步写法。

Generator 函数返回一个迭代器对象,通过迭代器的 next() 方法可以控制 Generator 函数的执行。调用 next() 方法后,函数会执行到下一个 yield 表达式处或者函数结束,返回一个对象,该对象包含两个属性:value 和 done。value 属性表示暂停处的 yield 表达式的值(如果有的话),done 属性是一个布尔值,表示是否遍历结束。

使用场景:
异步操作的同步写法:Generator 函数可以配合 yield 关键字实现类似同步代码的效果。

function* asyncTask() {
  const data1 = yield fetchData('api/data1.json');
  console.log(data1);
  const data2 = yield fetchData('api/data2.json');
  console.log(data2);
}

function execute(generatorFunc) {
  const generator = generatorFunc();
  
  function step(value) {
    const result = generator.next(value);
    if (result.done) return;
    result.value.then(step);
  }
  
  step();
}

execute(asyncTask);

控制流程管理:可以使用 Generator 来控制复杂的任务执行流程。

function* longRunningTask() {
  yield step1();
  yield step2();
  yield step3();
}
// 使用 next() 来逐步执行任务。

自定义迭代器:为任意对象创建自定义迭代器。

function* makeIterator(array) {
  for (let i = 0; i < array.length; i++) {
    yield array[i];
  }
}

const it = makeIterator(['a', 'b', 'c']);
console.log(it.next().value); // 'a'
console.log(it.next().value); // 'b'
console.log(it.next().value); // 'c'

协程:在多任务的并发和协作方面,可以用 Generator 实现协程。

function* genA() {
  yield* genB();
  // ...
}
function* genB() {
  // ...
}

无限序列:可以使用 Generator 函数生成无限序列,并进行惰性求值。

function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

状态机: Generator 可以保存执行上下文,它完美地适用于创建状态机。

function* toggle() {
  while (true) {
    yield 'on';
    yield 'off';
  }
}

const light = toggle();
console.log(light.next().value); // 'on'
console.log(light.next().value); // 'off'

Generator 函数给 JavaScript 函数提供了更大的灵活性,使得复杂的任务和流程管理更加简单和清晰。在 ES2017 引入了 async/await 之后,异步编程场景中,async/await 常被作为替代方案来使用,它内部是基于 Promise 的,语法也更加直观易懂。但是,在需要精细控制迭代过程的场景中,Generator 依然有其独特的用途和优势。

你是怎么理解Proxy对象的?它有哪些应用场景?

ES6中引入的Proxy是一种特殊的对象,它允许我们定义一个对象的基本操作的自定义行为,比如属性查找、赋值、枚举、函数调用等。我们可以把Proxy看作在对象之前设置的一个层,外界对该对象的所有访问都必须先通过这个层。
使用Proxy的基本语法如下:

let proxy = new Proxy(target, handler);

其中target是要代理的对象,handler是一个包含了捕获器(trap)的对象,捕获器是指定了代理行为的方法。
Proxy的使用场景包括但不限于:

  • 验证:对于某些对象,我们希望能在赋值操作前进行校验。通过Proxy可以在设置属性值时进行检查,确保值符合预期的要求。
  • 观察者模式:通过Proxy可以很容易实现观察者模式,即当对象的某个属性发生变化时,可以自动通知对应的处理程序。
  • 数据绑定:Proxy可以直接监听对象属性的变化,并在变化时同步更新UI,这在前端框架中非常常见。
  • 性能优化:通过懒加载或虚拟化技术,Proxy可以在实际需要时才创建或计算属性的值,从而优化性能。
  • 日志记录和错误报告:可以使用Proxy捕获方法调用等,自动记录日志或者当对象的行为不符合期望时生成错误报告。
  • API封装:可以用Proxy封装外部API,使得API调用更加友好或者符合特定的用例。
    例如,下面是一个简单的Proxy验证赋值操作的例子:
function createValidator(target) {
  return new Proxy(target, {
    set(obj, prop, value) {
      if (prop === 'age') {
        if (!Number.isInteger(value)) {
          throw new TypeError('Age is not an integer');
        }
        if (value > 200) {
          throw new RangeError('Age seems invalid');
        }
      }

      // The default behavior to store the value
      obj[prop] = value;

      // Indicate success
      return true;
    }
  });
}

const person = createValidator({});
person.age = 100; // works fine
person.age = 'young'; // throws a TypeError
person.age = 300; // throws a RangeError

你是怎么理解ES6中Module的?它有哪些使用场景?

在ES6之前,JavaScript没有内置的模块系统,开发者通常通过各种设计模式来组织代码,如立即执行函数(IIFE)来模拟封闭作用域等。ES6引入了原生模块功能,即Module,它是JavaScript官方的模块化标准。

ES6中的Module特点如下:

  1. 可维护性:模块化的代码更易于分离、组合和维护。模块可以分散于不同的文件中,每个文件封装特定的功能或逻辑。
  2. 命名空间:模块帮助避免全局命名空间污染,因为模块内部的变量、函数和类默认都是局部的,不会影响到全局作用域。
  3. 重用性:可以轻松地在不同的项目中重用模块。只需要导入需要的模块即可使用其功能。
  4. 依赖管理:模块系统允许声明依赖,使得代码更易于追踪和管理。

例:
导出模块 math.js

// 定义和导出了两个函数
export function sum(x, y) {
  return x + y;
}

export function multiply(x, y) {
  return x * y;
}

导入模块 app.js

// 导入math模块
import { sum, multiply } from './math.js';

console.log(sum(2, 3)); // 输出: 5
console.log(multiply(2, 3)); // 输出: 6

ES6 Module的使用场景包括:

  • 大型应用程序:可以将应用程序代码分割为多个小模块,每个模块负责特定的功能。
  • 库和框架的开发:在开发复用性强的JavaScript库和框架时,模块是组织和封装各种功能的理想选择。
  • 现代JavaScript前端框架:如React、Vue、Angular等,这些框架都鼓励或强制使用模块化的代码结构。
  • 代码分割(Code Splitting):现代前端构建工具如Webpack等支持模块的代码分割,以实现按需加载,优化应用的加载时间。
    命名空间的概念:在多人协作的项目中,每个人可以在自己的模块中工作,减少命名冲突,方便代码组织和分工。
    通过支持原生模块化,ES6显著提高了JavaScript开发的效率和代码的可维护性,是现代前端开发不可或缺的一个特性。

你是怎么理解ES6中 Decorator 的?它有哪些使用场景?

在ES6中,Decorator(装饰器)是一个非常强大的模式,允许开发者在编写代码时修改和注释类及其成员。装饰器本质上是一种函数,它可以被“贴”在类定义的行为上。它能够在不修改对象自身的基础上,通过包裹的方式添加新的行为。

装饰器可以用于类的声明、方法、访问器或属性。使用装饰器的语法是在声明前以@decoratorName的形式应用。

举个例子,有一个日志装饰器,它的作用是在方法被调用时输出日志:

function log(target, name, descriptor) {
  let originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`Calling "${name}" with`, args);
    return originalMethod.apply(this, args);
  };
  return descriptor;
}

class Person {
  @log
  say(name) {
    console.log(`Hello ${name}`);
  }
}

const person = new Person();
person.say('World'); // 控制台将输出: Calling "say" with ["World"] 和 Hello World

使用场景:

  • 日志记录:如上面所示,记录类中方法的调用和相关参数。
  • 性能监测:可以使用装饰器来监控某个方法的执行效率,例如记录方法的执行时间。
  • 访问控制和鉴权:在执行方法之前进行权限检查。
  • 数据验证:自动对方法的参数进行验证。
  • 依赖注入:在类的构造函数执行参数注入。
  • 事件监听:自动为组件或服务添加事件监听。
  • 领域驱动设计(DDD)中的注解:例如,标记领域事件或实体的边界。
  • 状态管理:在类属性的读取和赋值过程中进行额外的处理,如Vuex中的getter和setter。
  • 缓存:对方法的返回值进行缓存。

装饰器原理上类似于高阶组件或函数,在提供的原始项上增加额外的包裹层,从而提供附加的功能性。装饰器可以大大提高代码的复用性,使得代码结构更加清晰,切面逻辑的分离也更为容易。
值得注意的是,截至目前(2024年)装饰器仍然是ECMAScript的一个实验性特性,在使用之前需要确保编译器或转换器(如Babel)支持该语法。同时,使用装饰器通常也需要在构建系统中额外的配置。

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值