阅读视图

发现新文章,点击刷新页面。

js中生成器和迭代器

迭代器

可迭代对象的定义

  1. 实现[Symbol.iterator]方法:该方法必须返回一个迭代器对象。

  2. 迭代器对象:必须有一个next()方法,该方法返回一个对象,包含:

    • value:当前迭代的值,可以是任意类型。
    • done:布尔值,表示迭代是否已经结束。

示例

下面是一个简单的可迭代对象示例:

javascript

const myIterable = {
  [Symbol.iterator]: function() {
    let count = 0;
    return {
      next: function() {
        if (count < 3) {
          count++;
          return { value: count, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

// 使用 for...of 循环遍历
for (const value of myIterable) {
  console.log(value); // 输出 1, 2, 3
}

内置可迭代对象

JavaScript中许多内置对象是可迭代的,例如:

  • Array
  • String
  • Map
  • Set
  • arguments 对象
  • NodeList 等DOM集合

可迭代对象的用途

  1. 用于for...of循环:可以直接遍历可迭代对象。
  2. 展开语法(Spread syntax) :例如 [...array]
  3. 解构赋值(Destructuring assignment) :例如 let [a, b] = iterable
  4. Array.from() :可以将可迭代对象转换为数组。
  5. Promise.all() 和 Promise.race()  等接受可迭代对象的方法。
  6. Map 和 Set 的构造函数

和生成器关系

生成器是迭代器语法糖;生成器函数返回的对象即是一个生成器,也是一个迭代器,因为他实现了迭代器协议

生成器

核心定义

生成器(Generator)  是 ES6 引入的一种特殊函数,它可以通过 yield 关键字暂停和恢复函数的执行。生成器函数返回一个生成器对象,这个对象同时符合可迭代协议迭代器协议

语法特征

javascript

// 生成器函数的定义
function* generatorFunction() {
    // 函数体
    yield 'value1';
    yield 'value2';
    return 'final value';
}

// 或者使用函数表达式
const generator = function* () {
    yield 'value';
};

生成器的基本使用

创建和执行生成器

javascript

// 1. 定义生成器函数
function* simpleGenerator() {
    console.log('开始执行');
    yield '第一个值';
    console.log('恢复执行');
    yield '第二个值';
    console.log('结束执行');
    return '最终返回值';
}

// 2. 创建生成器对象
const gen = simpleGenerator();
console.log('生成器创建完成,但尚未执行');

// 3. 执行生成器
console.log(gen.next()); 
// 输出: "开始执行"
// 输出: { value: '第一个值', done: false }

console.log(gen.next());
// 输出: "恢复执行"  
// 输出: { value: '第二个值', done: false }

console.log(gen.next());
// 输出: "结束执行"
// 输出: { value: '最终返回值', done: true }

console.log(gen.next());
// 输出: { value: undefined, done: true }

yield 关键字详解

javascript

function* yieldExamples() {
    // 1. 基本 yield
    yield '普通值';
    
    // 2. yield 表达式
    const result = yield '表达式结果';
    console.log('从外部传入的值:', result);
    
    // 3. yield 复杂表达式
    yield 2 + 3 * 4; // 14
    
    // 4. yield 函数调用
    yield Math.random();
    
    return '完成';
}

const exampleGen = yieldExamples();
console.log(exampleGen.next());    // { value: '普通值', done: false }
console.log(exampleGen.next());    // { value: '表达式结果', done: false }
console.log(exampleGen.next('外部传入的值')); // 从外部传入的值: 外部传入的值
                                              // { value: 14, done: false }
console.log(exampleGen.next());    // { value: 0.123..., done: false }
console.log(exampleGen.next());    // { value: '完成', done: true }

生成器的核心特性

1. 暂停和恢复执行

javascript

function* pauseResumeDemo() {
    console.log('阶段 1');
    yield '暂停点 1';
    
    console.log('阶段 2');
    yield '暂停点 2';
    
    console.log('阶段 3');
    
    // 可以在循环中使用 yield
    for (let i = 0; i < 3; i++) {
        console.log(`循环第 ${i + 1} 次`);
        yield `循环值 ${i}`;
    }
    
    return '全部完成';
}

const demo = pauseResumeDemo();

// 分步执行,完全控制执行流程
let result = demo.next(); // "阶段 1"
console.log('手动处理其他任务...');
setTimeout(() => {
    result = demo.next(); // "阶段 2"
    console.log('继续其他任务...');
    
    setTimeout(() => {
        // 一次性执行剩余部分
        while (!result.done) {
            result = demo.next();
            console.log(result);
        }
    }, 1000);
}, 1000);

2. 双向通信

javascript

function* twoWayCommunication() {
    console.log('生成器启动');
    
    // 第一次暂停,等待外部输入名字
    const name = yield '请问你的名字?';
    console.log(`收到名字: ${name}`);
    
    // 第二次暂停,等待外部输入年龄
    const age = yield `你好 ${name},请问你的年龄?`;
    console.log(`收到年龄: ${age}`);
    
    // 根据年龄判断
    if (age < 18) {
        return `${name},你还未成年`;
    } else {
        return `${name},你已经成年了`;
    }
}

// 使用生成器进行对话
const conversation = twoWayCommunication();

// 启动对话
let response = conversation.next();
console.log('系统:', response.value); // "请问你的名字?"

// 用户输入名字
response = conversation.next('Alice');
console.log('系统:', response.value); // "你好 Alice,请问你的年龄?"

// 用户输入年龄
response = conversation.next(25);
console.log('系统:', response.value); // "Alice,你已经成年了"

3. 错误处理

javascript

function* errorHandlingGenerator() {
    try {
        console.log('开始执行');
        yield '第一步';
        
        // 这里可能会抛出错误
        const riskyValue = yield '进行危险操作';
        
        if (riskyValue === 'error') {
            throw new Error('手动触发的错误');
        }
        
        yield `成功: ${riskyValue}`;
        return '完成';
        
    } catch (error) {
        console.log('捕获到错误:', error.message);
        yield `错误处理: ${error.message}`;
        return '错误恢复完成';
    }
}

const errorGen = errorHandlingGenerator();

// 正常流程
console.log(errorGen.next());    // { value: '第一步', done: false }
console.log(errorGen.next('safe')); // { value: '成功: safe', done: false }
console.log(errorGen.next());    // { value: '完成', done: true }

// 错误流程
const errorGen2 = errorHandlingGenerator();
console.log(errorGen2.next());    // { value: '第一步', done: false }

// 从外部抛出错误到生成器内部
console.log(errorGen2.throw(new Error('外部错误')));
// 输出: "捕获到错误: 外部错误"
// 返回: { value: '错误处理: 外部错误', done: false }

console.log(errorGen2.next());    // { value: '错误恢复完成', done: true }

4、状态机

function* trafficLight() {
    const states = ['红色', '黄色', '绿色'];
    let index = 0;
    
    while (true) {
        const command = yield states[index];
        
        if (command === 'next') {
            index = (index + 1) % states.length;
        } else if (command === 'reset') {
            index = 0;
        }
    }
}

// 使用交通灯状态机
const light = trafficLight();
console.log(light.next().value); // '红色'
console.log(light.next('next').value); // '黄色'
console.log(light.next('next').value); // '绿色'
console.log(light.next('next').value); // '红色'
console.log(light.next('reset').value); // '红色'

vue多页项目如何在每次版本更新时做提示

一、遇到的问题

项目中使用懒加载方式加载组件,在新部署镜像后,由于浏览器缓存又去加载旧的js chunk,但是之时旧的js chunk已经不存在,加载不出来造成bug

image.png

二、解决方式

在每次部署后更改版本号,在页面做提示,当前版本又更新,提示用户刷新页面

(1)可以使用的方案有哪些

  • 使用轮训查询最新的版本号做对比
  • 使用websocket
  • 使用service worker

(2)最终采用了什么方案

最终使用了方案1;原因是配置简单方便;缺点是会加大服务器压力!~ (1)在public中创建一个version.json文件,写清楚各个模块的版本, 我这里项目vue多页的,每个项目都要单独版本管理

{
"A项目": {
  "version": "1.18.0",
  "description": ""
},
"B项目": {
  "version": "1.18.0",
  "description": ""
},
"C项目": {
  "version": "1.18.0",
  "description": ""
},
}

(2)创建一个全局的versionUpdate方法,来检测版本是否更新

import 'element-plus/dist/index.css'
import { ElMessageBox } from 'element-plus'

/**
 * 版本信息接口
 */
type TVersionInfo = {
  [moduleName: string]: TModuleInfo
}

/**
 * 模块版本存储信息
 */
type TModuleInfo = {
  version: string
  description?: string
}

/**
 * 基于version.json的版本检测和更新提示工具
 */
export class VersionUpdateService {
  private versionCheckInterval: number | null = null
  private readonly CHECK_INTERVAL = 5 * 60 * 1000 // 5分钟检查一次
  private moduleName: string
  private storageKey: string

  constructor(moduleName: string = 'home') {
    this.moduleName = moduleName
    this.storageKey = `module-version-${moduleName}`
  }

  /**
   * 获取模块版本信息
   */
  private getModuleVersionInfo(): TModuleInfo | null {
    const stored = localStorage.getItem(this.storageKey)
    return stored ? JSON.parse(stored) : null
  }

  /**
   * 保存模块版本信息
   */
  private saveModuleVersionInfo(info: TModuleInfo): void {
    localStorage.setItem(this.storageKey, JSON.stringify(info))
  }

  /**
   * 从version.json获取版本信息(统一从 public/version.json 中按模块名读取)
   */
  private async fetchVersionInfo(): Promise<TModuleInfo | null> {
    try {
        const fullUrl = `${window.location.origin}/version.json?t=${Date.now()}`
        console.log(`[${this.moduleName}] 正在获取version.json: ${fullUrl}`)
  
        const response = await fetch(fullUrl, {
            method: 'GET',
            cache: 'no-cache',
            headers: { 'Content-Type': 'application/json' }
        })
  
        if (!response.ok) {
            console.warn(`[${this.moduleName}] 无法获取version.json: ${response.status} ${response.statusText}`)
            return null
        }
  
        // 期望 public/version.json 结构为:{ "A项目": { ... }, "B项目": { ... }, "C项目": { ... }, ... }
        const indexData = await response.json() as TVersionInfo

        console.log(`[${this.moduleName}] 获取到版本信息:`, indexData)
        return indexData[this.moduleName]
    } catch (error) {
        console.warn(`[${this.moduleName}] 获取version.json失败:`, error)
        return null
    }
  }

  /**
   * 检查是否有新版本
   */
  private async checkForUpdate(): Promise<boolean> {
    const currentVersionInfo = await this.fetchVersionInfo()
    if (!currentVersionInfo) {
      console.warn(`[${this.moduleName}] 无法获取当前版本信息,跳过检测`)
      return false
    }
    
    const storedInfo = this.getModuleVersionInfo()
    
    if (!storedInfo) {
      // 第一次检查,保存当前版本信息
      this.saveModuleVersionInfo(currentVersionInfo)
      console.log(`[${this.moduleName}] 首次检查,保存版本信息`)
      return false
    }
    
    const versionUpdated = currentVersionInfo.version !== storedInfo.version
    
    if (versionUpdated) {
      console.log(`[${this.moduleName}] 检测到版本更新:`, currentVersionInfo)
      return true
    }
    
    console.log(`[${this.moduleName}] 当前为最新版本:`, currentVersionInfo)
    return false
  }

  /**
   * 显示更新提示
   */
  private showUpdateNotification(currentVersionInfo: TModuleInfo): void {
    const moduleTitle = this.getModuleTitle(this.moduleName)
    const currentModuleInfo = currentVersionInfo
    const message = `有新版本可用:${currentModuleInfo.version}\n${currentModuleInfo.description}`

    ElMessageBox.confirm(
      message,
      `${moduleTitle}版本更新`,
      {
        confirmButtonText: '立即刷新',
        cancelButtonText: '稍后提醒',
        type: 'info',
        center: true
      }
    ).then(() => {
      this.updateVersionInfo(currentVersionInfo)
      this.reloadPage()
    }).catch(() => {
      console.log(`[${this.moduleName}] 用户选择稍后更新`)
    })
  }

  /**
   * 获取模块标题
   */
  private getModuleTitle(moduleName: string): string {
    const titles: Record<string, string> = {
      'A项目': 'A项目名称'
      ...
    }
    return titles[moduleName] || moduleName
  }

  /**
   * 更新版本信息
   */
  private async updateVersionInfo(currentVersionInfo: TModuleInfo): Promise<void> {
    this.saveModuleVersionInfo(currentVersionInfo)
    console.log(`[${this.moduleName}] 版本信息已更新:`, currentVersionInfo.version)
  }

  /**
   * 刷新页面
   */
  private reloadPage(): void {
    if ('caches' in window) {
      caches.keys().then(names => {
        names.forEach(name => {
          caches.delete(name)
        })
      })
    }
    
    setTimeout(() => {
      window.location.reload()
    }, 100)
  }

  /**
   * 开始定期检查
   */
  public startVersionCheck(): void {
    this.performVersionCheck()
    
    this.versionCheckInterval = window.setInterval(() => {
      this.performVersionCheck()
    }, this.CHECK_INTERVAL)
  }

  /**
   * 执行版本检查
   */
  private async performVersionCheck(): Promise<void> {
    const currentVersionInfo = await this.fetchVersionInfo()
    if (!currentVersionInfo) return
    
    const hasUpdate = await this.checkForUpdate()
    if (hasUpdate) {
      this.showUpdateNotification(currentVersionInfo)
    }
  }

  /**
   * 停止版本检查
   */
  public stopVersionCheck(): void {
    if (this.versionCheckInterval) {
      clearInterval(this.versionCheckInterval)
      this.versionCheckInterval = null
    }
  }

  /**
   * 获取所有模块版本信息(调试用)
   */
  public static getAllModuleVersions(): Record<string, TModuleInfo | null> {
    const modules = ['A项目'...]
    const result: Record<string, TModuleInfo | null> = {}
    
    modules.forEach(module => {
      const key = `module-version-${module}`
      const stored = localStorage.getItem(key)
      result[module] = stored ? JSON.parse(stored) : null
    })
    
    return result
  }

  /**
   * 清除指定模块的版本信息
   */
  public static clearModuleVersion(moduleName: string): void {
    const key = `module-version-${moduleName}`
    localStorage.removeItem(key)
    console.log(`已清除模块 [${moduleName}] 的版本信息`)
  }

  /**
   * 初始化版本更新检测
   */
  public static init(moduleName: string = 'home'): VersionUpdateService {
    const service = new VersionUpdateService(moduleName)
    service.startVersionCheck()
    return service
  }
}

/**
 * 初始化版本更新检测
 */
export const initVersionUpdateJson = (moduleName?: string) => {
  return VersionUpdateService.init(moduleName || 'home')
}

/**
 * 兼容旧版本的导出
 */
export const initVersionUpdate = initVersionUpdateJson

3、在每个模块中的main.ts中引入使用这个方法

import { initVersionUpdateJson } from '@/utils/VersionUpdate'
// 初始化版本检测
initVersionUpdateJson('chess') // 这里传入的是项目名称
❌