js 里那些常用但又特殊的场景应用

for 循环里面的 异步 处理

  1. for循环

    • 基本原理
      • for循环是最基础的循环结构,它有一个初始化表达式、一个条件表达式和一个更新表达式。语法为for (初始化; 条件; 更新) {循环体}。例如:
        for (var i = 0; i < 5; i++) {
          console.log(i);
        }
        
      • 它会按照顺序依次执行循环体,在每次循环开始时检查条件表达式是否为真,执行完循环体后,执行更新表达式。
    • 异步操作问题
      • for循环中直接处理异步操作会出现问题。因为for循环不会等待异步操作完成就会继续下一次循环。例如:
        for (let i = 0; i < 3; i++) {
          setTimeout(() => {
            console.log(i);
          }, 1000);
        }
        
      • 上述代码会在1秒后同时输出3个数字(在JavaScript中,很可能是333),因为setTimeout是异步操作,for循环不会等待setTimeout中的回调函数执行就会继续下一次循环,当所有setTimeout的回调函数执行时,i的值已经变成了3
    • 解决方法(使用async/await结合Promise
      • 如果要在for循环中等待异步操作完成,可以将异步操作封装在Promise中,然后在async函数中使用await。例如,假设有一个返回Promise的异步函数fetchData
        async function loopWithAsync() {
          for (let i = 0; i < 3; i++) {
            await new Promise((resolve) => {
              setTimeout(() => {
                console.log(i);
                resolve();
              }, 1000);
            });
          }
        }
        loopWithAsync();
        
      • 这样就可以实现每次等待setTimeout中的异步操作完成后,再进行下一次循环。
  2. for...in循环

    • 基本原理
      • for...in循环主要用于遍历对象的可枚举属性(包括继承的可枚举属性)。语法为for (变量 in 对象) {循环体}。例如:
        const obj = {a: 1, b: 2, c: 3};
        for (let key in obj) {
          console.log(key + ': ' + obj[key]);
        }
        
      • 它会将对象的属性名(键)赋值给变量,然后在循环体中可以通过对象[变量]的方式访问属性值。
    • 异步操作问题和解决方法
      • for循环类似,for...in循环本身不会等待异步操作。如果要在for...in循环中等待异步操作,也可以使用async/await结合Promise的方式。例如,假设对象的属性值是通过异步操作获取的:
        const asyncObj = {
          a: () => new Promise((resolve) => setTimeout(
          () => resolve(1), 1000)
          ),
          b: () => new Promise((resolve) => setTimeout(
          () => resolve(2), 2000)
          ),
          c: () => new Promise((resolve) => setTimeout(
          () => resolve(3), 3000)
          )
        };
        async function loopForInWithAsync() {
          for (let key in asyncObj) {
            let value = await asyncObj[key]();
            console.log(key + ': ' + value);
          }
        }
        loopForInWithAsync();
        
      • 这样就可以在每次获取对象属性对应的异步值后,再进行下一个属性的处理。
  3. for...of循环

    • 基本原理
      • for...of循环用于遍历可迭代对象(如数组、字符串、SetMap等)的值。语法为for (变量 of 可迭代对象) {循环体}。例如:
        const arr = [1, 2, 3];
        for (let value of arr) {
          console.log(value);
        }
        
      • 它会将可迭代对象中的每个元素依次赋值给变量,然后执行循环体。
    • 异步操作问题和解决方法
      • 同样,for...of循环本身不等待异步操作。如果在遍历的可迭代对象中涉及异步操作获取元素的值,也可以使用async/await结合Promise来处理。例如,假设有一个数组,其中的元素需要通过异步操作获取:
        const asyncArray = [
          () => new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
          () => new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
          () => new Promise((resolve) => setTimeout(() => resolve(3), 3000))
        ];
        async function loopForOfWithAsync() {
          for (let func of asyncArray) {
            let value = await func();
            console.log(value);
          }
        }
        loopForOfWithAsync();
        
      • 这样就可以逐个等待异步操作获取元素的值,再进行下一个元素的处理。

区别总结

  • for循环:主要用于固定次数的循环,通过初始化、条件判断和更新操作来控制循环流程,适用于遍历数组索引等情况。在处理异步操作时需要额外注意等待异步完成的机制。
  • for...in循环:侧重于遍历对象的属性(键),包括继承的可枚举属性。在处理对象属性相关的异步操作时可以使用async/await来等待异步操作完成。
  • for...of循环:用于遍历可迭代对象的值,如数组、字符串等。当可迭代对象中的元素获取涉及异步操作时,也可以通过async/await来正确处理异步等待。

symbol

  1. 基本概念
    • Symbol是ES6引入的一种原始数据类型,表示独一无二的值。它的主要特点是其值具有唯一性,即使创建了相同描述的Symbol,它们的值也不相同。例如:
      let sym1 = Symbol('description');
      let sym2 = Symbol('description');
      console.log(sym1 === sym2); // false
      
  2. 应用实例
    • 作为对象属性名
      • 由于Symbol值的唯一性,它可以用来作为对象的属性名,防止属性名冲突。例如,假设有一个对象,其中有一些内部使用的属性,不想被外部代码意外访问或覆盖:
        let myObject = {};
        let internalProp = Symbol('internal property');
        myObject[internalProp] = 'This is an internal value';
        console.log(myObject[internalProp]); // This is an internal value
        for (let key in myObject) {
          // 使用for...in循环不会遍历到Symbol属性
          console.log(key); 
        }
        
      • 在这个例子中,internalProp作为一个Symbol类型的属性名,不会被常规的for...in循环遍历到,这样就可以有效地隐藏对象的内部属性。
    • 实现私有属性(某种程度上)
      • 在JavaScript类中,可以利用Symbol来模拟私有属性。例如:
        class MyClass {
          constructor() {
            this[Symbol('privateProp')] = 'private value';
          }
          getPrivateProp() {
            return this[Symbol('privateProp')];
          }
        }
        let myInstance = new MyClass();
        console.log(myInstance.getPrivateProp());
        // 外部无法直接访问Symbol('privateProp')属性
        
      • 这里通过Symbol定义的属性在类的外部不能直接访问,只能通过类内部定义的方法来访问,实现了一定程度的属性私有性。
    • 作为常量枚举
      • Symbol可以用于定义一组唯一的常量,类似于枚举类型。例如,定义一个表示不同操作类型的符号集合:
        const OPERATION_ADD = Symbol('add');
        const OPERATION_SUBTRACT = Symbol('subtract');
        const OPERATION_MULTIPLY = Symbol('multiply');
        function calculate(num1, num2, operation) {
          if (operation === OPERATION_ADD) {
            return num1 + num2;
          } else if (operation === OPERATION_SUBTRACT) {
            return num1 - num2;
          } else if (operation === OPERATION_MULTIPLY) {
            return num1 * num2;
          }
        }
        console.log(calculate(2, 3, OPERATION_ADD)); // 5
        

bigInt

  1. 基本概念

    • BigInt是JavaScript中的一种数据类型,用于表示任意精度的整数。在JavaScript中,Number类型有安全范围限制(-90071992547409919007199254740991,即Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER),超出这个范围的整数计算可能会出现精度丢失的情况。而BigInt可以处理任意大小的整数。
  2. 应用实例

    • 大整数运算
      • 进行非常大的整数加法运算。例如,计算两个超过Number类型安全范围的整数相加:
        const num1 = BigInt("9007199254740992");
        const num2 = BigInt("9007199254740992");
        const result = num1 + num2;
        console.log(result); 
        
      • 在这里,num1num2BigInt类型的整数,通过+运算符进行加法运算,得到正确的结果,不会出现Number类型可能出现的精度丢失问题。
    • 处理数据库中的大整数ID
      • 假设在一个数据库应用中,有一些表使用非常大的整数作为主键,如雪花算法(Snowflake)生成的唯一ID。雪花算法生成的ID是一个64位的整数,在JavaScript中可能会超出Number类型的安全范围。
        // 假设从数据库中获取的雪花ID(这只是示例,实际应用中会从数据库查询等方式获取)
        const snowflakeId1 = BigInt("1234567890123456789");
        const snowflakeId2 = BigInt("1234567890123456790");
        function compareIds(id1, id2) {
          if (id1 > id2) {
            return 1;
          } else if (id1 < id2) {
            return -1;
          } else {
            return 0;
          }
        }
        console.log(compareIds(snowflakeId1, snowflakeId2)); 
        
      • 这里使用BigInt类型来正确地比较两个可能超出Number安全范围的数据库ID的大小。
    • 密码学应用(简单示例)
      • 在一些简单的密码学概念验证示例中,可能会涉及到非常大的整数计算。例如,简单的RSA加密算法(这只是一个非常简化的示例,实际的RSA加密要复杂得多)的密钥生成可能会涉及到很大的质数相乘。
        // 简单的两个大质数相乘(假设这是密钥生成的一部分)
        const prime1 = BigInt("123456789012345678901234567890123456789");
        const prime2 = BigInt("987654321098765432109876543210987654321");
        const publicKeyPart = prime1 * prime2;
        console.log(publicKeyPart);
        
      • 这个例子展示了如何使用BigInt来处理密码学应用中可能出现的大整数乘法运算,以确保计算的准确性。

isNaN

  1. 基本介绍

    • 在JavaScript中,Number.isNaN()是一个用于判断一个值是否为NaN(Not - a - Number)的方法。它是Number对象的一个静态方法。
  2. 语法和参数

    • 语法:Number.isNaN(value)
    • 参数value:要被检测的值,可以是任何类型的值。
  3. 应用实例

    • 简单数据类型判断
      • 例如,判断一个变量是否为NaN
        let num1 = NaN;
        let num2 = 5;
        console.log(Number.isNaN(num1)); // true
        console.log(Number.isNaN(num2)); // false
        
      • 在这个例子中,Number.isNaN(num1)返回true,因为num1的值是NaN,而Number.isNaN(num2)返回false,因为num2是一个正常的数字5
    • 复杂运算结果判断
      • 在一些数学运算中,可能会出现NaN的结果,如0除以0或者对负数开平方根(在JavaScript的默认数学运算中)等情况。可以使用Number.isNaN来检查这些运算的结果:
        let result1 = 0 / 0;
        console.log(Number.isNaN(result1)); // true
        let result2 = Math.sqrt(-1);
        console.log(Number.isNaN(result2)); // true
        
      • 这里0/0的结果是NaNMath.sqrt(-1)(在实数范围内)的结果也是NaNNumber.isNaN能够准确地检测出这些NaN值。
    • 函数参数验证
      • 在一个函数中,如果期望接收一个有效的数字参数,而不是NaN,可以使用Number.isNaN来进行验证。例如:
        function processNumber(num) {
          if (Number.isNaN(num)) {
            console.log("参数不能是NaN");
            return;
          }
          console.log("接收到的有效数字是: " + num);
        }
        processNumber(3.14);
        processNumber(NaN);
        
      • 这个函数processNumber首先检查传入的参数是否为NaN,如果是,则输出提示信息并返回;如果不是,则输出接收到的有效数字。
  4. 与全局isNaN函数的区别

    • 全局的isNaN()函数会先尝试将传入的值转换为数字,然后再判断是否为NaN。而Number.isNaN()不会进行这种类型转换。例如:
      console.log(isNaN("abc")); // true
      console.log(Number.isNaN("abc")); // false
      
      • 对于isNaN("abc"),它首先会尝试将"abc"转换为数字,由于转换失败得到NaN,所以返回true。而Number.isNaN("abc")直接判断"abc"不是NaN类型,所以返回false

特殊符号的特殊意义

  1. ?.(可选链操作符)
    • 基本概念
      • 可选链操作符允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。在以前,如果要访问一个嵌套多层的对象属性,需要进行多次条件判断来防止出现undefinednull引用错误。
    • 语法和应用实例
      • 语法:obj?.propobj?.[expr]func?.(...args)
      • 例如,有一个可能为nullundefined的对象,想访问它内部的属性:
        const user = {
          address: {
            street: '123 Main St'
          }
        };
        const street = user?.address?.street;
        console.log(street); // 123 Main St
        const user2 = null;
        const street2 = user2?.address?.street;
        console.log(street2); // undefined
        
      • 在这个例子中,第一个user对象正常访问了street属性,而对于user2null的情况,使用可选链操作符不会抛出错误,而是返回undefined
  2. ??(空值合并操作符)
    • 基本概念
      • 空值合并操作符用于在nullundefined时提供一个默认值。它是一个逻辑操作符,当左侧的值为nullundefined时,返回右侧的值。
    • 语法和应用实例
      • 语法:leftValue?? rightValue
      • 例如,设置一个默认值:
        const value1 = null;
        const defaultValue1 = value1?? 'default';
        console.log(defaultValue1); // default
        const value2 = 0;
        const defaultValue2 = value2?? 'default';
        console.log(defaultValue2); // 0
        
      • 这里value1null,所以defaultValue1返回default,而value20,不是nullundefined,所以defaultValue2返回0
  3. ||(逻辑或)和??的区别
    • ||操作符在左侧值为假值(如0false""nullundefined)时会返回右侧的值。而??操作符只有在左侧值为nullundefined时才返回右侧的值。例如:
      const falseValue = 0;
      const defaultValueWithOr = falseValue || 'default';
      const defaultValueWithNullishCoalescing = falseValue?? 'default';
      console.log(defaultValueWithOr); // default
      console.log(defaultValueWithNullishCoalescing); // 0
      
  4. #(私有字段)
    • 基本概念
      • 在类中可以使用#来定义私有字段。私有字段只能在类的内部访问,外部无法访问这些字段,这有助于更好地封装和保护类的内部状态。
    • 语法和应用实例
      • 语法:class MyClass { #privateField; constructor() { this.#privateField = 'private value'; } getPrivateValue() { return this.#privateField; } }
      • 例如:
        class Counter {
          #count = 0;
          increment() {
            this.#count++;
          }
          get count() {
            return this.#count;
          }
        }
        const myCounter = new Counter();
        myCounter.increment();
        console.log(myCounter.count); // 1
        // 以下代码会报错,因为无法从外部访问#count
        // console.log(myCounter.#count);
        
  5. async/await的进一步应用和优化
    • Promise.allSettledasync/await结合
      • Promise.allSettled返回一个Promise,该Promise在所有给定的Promise都已经完成(无论成功或失败)后才会完成。可以和async/await结合来处理多个异步操作。例如:
        async function handleMultiplePromises() {
          const promises = [
            Promise.resolve('success1'),
            Promise.reject('error2'),
            Promise.resolve('success3')
          ];
          const results = await Promise.allSettled(promises);
          for (const result of results) {
            if (result.status === 'fulfilled') {
              console.log('Fulfilled:', result.value);
            } else {
              console.log('Rejected:', result.reason);
            }
          }
        }
        handleMultiplePromises();
        
      • 这个例子中,Promise.allSettled会等待所有Promise完成,然后通过async/await可以方便地处理每个Promise的结果,无论是成功还是失败。

Promise.allSettled 场景应用

  1. 批量处理异步操作并获取所有结果

    • 场景描述
      • 当需要同时发起多个异步操作(如多个网络请求、多个文件读取等),并且希望获取每个异步操作的最终状态(无论成功还是失败)时,Promise.allSettled非常有用。
    • 示例代码
      • 假设需要同时获取多个用户的信息,这些用户信息可能来自不同的API端点。
        const userIds = [1, 2, 3];
        const userInfoPromises = userIds.map((id) => {
          return fetch(`https://api.example.com/users/${id}`)
           .then((response) => response.json())
           .catch((error) => ({error}));
        });
        Promise.allSettled(userInfoPromises).then((results) => {
          results.forEach((result) => {
            if (result.status === 'fulfilled') {
              console.log('用户信息获取成功:', result.value);
            } else {
              console.log('用户信息获取失败:', result.reason);
            }
          });
        });
        
      • 在这个例子中,通过map函数为每个用户ID创建一个获取用户信息的Promise,这些Promise被收集到userInfoPromises数组中。然后使用Promise.allSettled来等待所有Promise完成,之后遍历结果数组,分别处理成功和失败的情况。
  2. 事务处理或多步骤操作中的错误处理

    • 场景描述
      • 在涉及多个步骤的事务性操作中(例如,在数据库事务中同时插入多条记录、更新多个数据等操作),如果希望执行所有操作,然后统一处理所有可能出现的错误,Promise.allSettled可以提供帮助。
    • 示例代码
      • 假设要同时更新一个用户的多个信息(如姓名、年龄和地址),这些更新操作可能是独立的数据库更新操作。
        const updateNamePromise = updateUserInfo('name', 'John Doe');
        const updateAgePromise = updateUserInfo('age', 30);
        const updateAddressPromise = updateUserInfo('address', '123 Main St');
        const updatePromises = [updateNamePromise, updateAgePromise, 
        updateAddressPromise];
        Promise.allSettled(updatePromises).then((results) => {
          let hasError = false;
          results.forEach((result) => {
            if (result.status === 'rejected') {
              console.log('更新用户信息出错:', result.reason);
              hasError = true;
            }
          });
          if (!hasError) {
            console.log('用户信息更新成功');
          }
        });
        function updateUserInfo(field, value) {
          return new Promise((resolve, reject) => {
            // 假设这是一个模拟的数据库更新操作,可能会出错
            const success = Math.random() > 0.2; 
            if (success) {
              resolve({field, value});
            } else {
              reject(new Error(`更新${field}出错`));
            }
          });
        }
        
      • 这里创建了三个更新用户信息的Promise,将它们放入数组后使用Promise.allSettled等待所有操作完成。然后检查每个操作的结果,如果有任何一个操作失败,就记录错误信息,最后根据是否有错误来判断整个更新过程是否成功。
  3. 并行加载资源(如模块、图片等)

    • 场景描述
      • 在前端开发中,可能需要同时加载多个资源(如多个JavaScript模块、多张图片等),并且要对加载的结果进行统一处理,确保所有资源都尝试加载后再进行下一步操作。
    • 示例代码
      • 假设要同时加载多个图片并在页面上显示加载状态。
        const imageUrls = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
        const imagePromises = imageUrls.map((url) => {
          return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(img);
            img.onerror = () => reject(new Error(`图片${url}加载失败`));
            img.src = url;
          });
        });
        Promise.allSettled(imagePromises).then((results) => {
          const loadedImages = [];
          results.forEach((result) => {
            if (result.status === 'fulfilled') {
              loadedImages.push(result.value);
            } else {
              console.log('图片加载失败:', result.reason);
            }
          });
          // 将加载成功的图片添加到页面的某个元素中
          const imageContainer = document.getElementById('image - container');
          loadedImages.forEach((img) => {
            imageContainer.appendChild(img);
          });
        });
        
      • 首先为每个图片URL创建一个加载图片的Promise,使用Promise.allSettled等待所有图片加载完成,然后将加载成功的图片添加到页面的指定容器中,同时记录加载失败的图片信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值