亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

首頁 web前端 js教程 探索Canvas系列:結合Transformers.js實現(xiàn)智慧型影像處理

探索Canvas系列:結合Transformers.js實現(xiàn)智慧型影像處理

Nov 26, 2024 pm 09:26 PM

介紹

我目前正在維護一個強大的開源創(chuàng)意畫板。這款畫板整合了許多有趣的畫筆和輔助繪圖功能,可以讓使用者體驗到全新的繪圖效果。無論是在行動端還是PC端,都可以享受到更好的互動體驗和效果展示。

在這篇文章中,我將詳細講解如何結合 Transformers.js 實現(xiàn)背景移除和影像標記分割。結果如下

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

連結:https://songlh.top/paint-board/

Github:https://github.com/LHRUN/paint-board 歡迎Star ??

Transformers.js

Transformers.js 是一個基於 Hugging Face 的 Transformers 的強大 JavaScript 庫,可以直接在瀏覽器中運行,無需依賴伺服器端計算。這意味著您可以在本地運行模型,從而提高效率並降低部署和維護成本。

目前Transformers.js 在Hugging Face 上提供了1000 個模型,涵蓋各個領域,可以滿足你的大部分需求,例如影像處理、文字產生、翻譯、情緒分析等任務處理,你都可以透過Transformers 輕鬆實作.js。依下列方式搜尋型號。

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

目前 Transformers.js 的主要版本已更新為 V3,增加了很多很棒的功能,詳細資訊:Transformers.js v3:WebGPU 支援、新模型和任務以及更多......

我在這篇文章中添加的兩個功能都使用了 WebGpu 支持,該支持僅在 V3 中可用,並且大大提高了處理速度,現(xiàn)在解析速度為毫秒級。不過要注意的是,支援WebGPU的瀏覽器並不多,建議使用最新版本的Google進行存取。

功能一:去除背景

為了刪除背景,我使用 Xenova/modnet 模型,如下圖

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

處理邏輯可以分為三步驟

  1. 初始化狀態(tài),並載入模型和處理器。
  2. 介面的顯示,這是你自己設計的,不是我的。
  3. 展示效果,這是你自己設計的,不是我的?,F(xiàn)在比較流行的是用邊框線來動態(tài)展示去除背景前後的對比效果。

程式碼邏輯如下,React TS ,具體參見我的專案原始碼,原始碼位於 src/components/boardOperation/uploadImage/index.tsx

import { useState, FC, useRef, useEffect, useMemo } from 'react'
import {
  env,
  AutoModel,
  AutoProcessor,
  RawImage,
  PreTrainedModel,
  Processor
} from '@huggingface/transformers'

const REMOVE_BACKGROUND_STATUS = {
  LOADING: 0,
  NO_SUPPORT_WEBGPU: 1,
  LOAD_ERROR: 2,
  LOAD_SUCCESS: 3,
  PROCESSING: 4,
  PROCESSING_SUCCESS: 5
}

type RemoveBackgroundStatusType =
  (typeof REMOVE_BACKGROUND_STATUS)[keyof typeof REMOVE_BACKGROUND_STATUS]

const UploadImage: FC<{ url: string }> = ({ url }) => {
  const [removeBackgroundStatus, setRemoveBackgroundStatus] =
    useState<RemoveBackgroundStatusType>()
  const [processedImage, setProcessedImage] = useState('')

  const modelRef = useRef<PreTrainedModel>()
  const processorRef = useRef<Processor>()

  const removeBackgroundBtnTip = useMemo(() => {
    switch (removeBackgroundStatus) {
      case REMOVE_BACKGROUND_STATUS.LOADING:
        return 'Remove background function loading'
      case REMOVE_BACKGROUND_STATUS.NO_SUPPORT_WEBGPU:
        return 'WebGPU is not supported in this browser, to use the remove background function, please use the latest version of Google Chrome'
      case REMOVE_BACKGROUND_STATUS.LOAD_ERROR:
        return 'Remove background function failed to load'
      case REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS:
        return 'Remove background function loaded successfully'
      case REMOVE_BACKGROUND_STATUS.PROCESSING:
        return 'Remove Background Processing'
      case REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS:
        return 'Remove Background Processing Success'
      default:
        return ''
    }
  }, [removeBackgroundStatus])

  useEffect(() => {
    ;(async () => {
      try {
        if (removeBackgroundStatus === REMOVE_BACKGROUND_STATUS.LOADING) {
          return
        }
        setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOADING)

        // Checking WebGPU Support
        if (!navigator?.gpu) {
          setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.NO_SUPPORT_WEBGPU)
          return
        }
        const model_id = 'Xenova/modnet'
        if (env.backends.onnx.wasm) {
          env.backends.onnx.wasm.proxy = false
        }

        // Load model and processor
        modelRef.current ??= await AutoModel.from_pretrained(model_id, {
          device: 'webgpu'
        })
        processorRef.current ??= await AutoProcessor.from_pretrained(model_id)
        setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS)
      } catch (err) {
        console.log('err', err)
        setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOAD_ERROR)
      }
    })()
  }, [])

  const processImages = async () => {
    const model = modelRef.current
    const processor = processorRef.current

    if (!model || !processor) {
      return
    }

    setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.PROCESSING)

    // load image
    const img = await RawImage.fromURL(url)

    // Pre-processed image
    const { pixel_values } = await processor(img)

    // Generate image mask
    const { output } = await model({ input: pixel_values })
    const maskData = (
      await RawImage.fromTensor(output[0].mul(255).to('uint8')).resize(
        img.width,
        img.height
      )
    ).data

    // Create a new canvas
    const canvas = document.createElement('canvas')
    canvas.width = img.width
    canvas.height = img.height
    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D

    // Draw the original image
    ctx.drawImage(img.toCanvas(), 0, 0)

    // Updating the mask area
    const pixelData = ctx.getImageData(0, 0, img.width, img.height)
    for (let i = 0; i < maskData.length; ++i) {
      pixelData.data[4 * i + 3] = maskData[i]
    }
    ctx.putImageData(pixelData, 0, 0)

    // Save new image
    setProcessedImage(canvas.toDataURL('image/png'))
    setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS)
  }

  return (
    <div className="card shadow-xl">
      <button
        className={`btn btn-primary btn-sm ${
          ![
            REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS,
            REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS,
            undefined
          ].includes(removeBackgroundStatus)
            ? 'btn-disabled'
            : ''
        }`}
        onClick={processImages}
      >
        Remove background
      </button>
      <div className="text-xs text-base-content mt-2 flex">
        {removeBackgroundBtnTip}
      </div>
      <div className="relative mt-4 border border-base-content border-dashed rounded-lg overflow-hidden">
        <img
          className={`w-[50vw] max-w-[400px] h-[50vh] max-h-[400px] object-contain`}
          src={url}
        />
        {processedImage && (
          <img
            className={`w-full h-full absolute top-0 left-0 z-[2] object-contain`}
            src={processedImage}
          />
        )}
      </div>
    </div>
  )
}

export default UploadImage

功能2:影像標記分割

影像標記分割是使用 Xenova/slimsam-77-uniform 模型實現(xiàn)的。效果如下,圖片載入完成後點擊即可,根據(jù)你點擊的座標產生分割

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

處理邏輯可以分為五個步驟

  1. 初始化狀態(tài),並載入模型和處理器
  2. 取得圖像並加載,然後儲存圖像加載資料和嵌入資料。
  3. 監(jiān)聽影像點擊事件,記錄點擊數(shù)據(jù),分為正標記和負標記,每次點擊後根據(jù)點擊數(shù)據(jù)解碼產生mask數(shù)據(jù),然後根據(jù)mask數(shù)據(jù)繪製分割效果.
  4. 介面展示,這個要自己設計任意發(fā)揮,不是我的為準
  5. 點擊儲存影像,根據(jù)mask像素數(shù)據(jù),匹配原始影像數(shù)據(jù),然後透過canvas繪圖匯出

程式碼邏輯如下,React TS ,具體參見我的專案原始碼,原始碼位於 src/components/boardOperation/uploadImage/imageSegmentation.tsx

從 'react' 導入 { useState, useRef, useEffect, useMemo, MouseEvent, FC }
進口 {
  薩姆模型,
  自動處理器,
  原始影像,
  預訓練模型,
  處理器,
  張量,
  SamImageProcessor結果
} 來自 '@huggingface/transformers'

從 '@/components/icons/loading.svg?react' 導入 LoadingIcon
從 '@/components/icons/boardOperation/image-segmentation-positive.svg?react' 導入 PositiveIcon
從 '@/components/icons/boardOperation/image-segmentation-negative.svg?react' 導入 NegativeIcon

介面標記點{
  位置:數(shù)字[]
  標籤: 數(shù)字
}

常數(shù) SEGMENTATION_STATUS = {
  正在加載:0,
  NO_SUPPORT_WEBGPU:1,
  載入錯誤:2,
  載入成功:3,
  加工:4、
  處理成功:5
}

類型分段狀態(tài)類型 =
  (SEGMENTATION_STATUS 類型)[SEGMENTATION_STATUS 類型鍵]

const ImageSegmentation: FC; = ({ url }) =>; {
  const [markPoints, setMarkPoints] = useState<markpoint>([])
  const [segmentationStatus, setSegmentationStatus] =
    useState<segmentationstatustype>()
  const [pointStatus, setPointStatus] = useState<boolean>(true)

  const maskCanvasRef = useRef<htmlcanvaselement>(null) // 分段遮罩
  const modelRef = useRef<pretrainedmodel>() // 模型
  const handlerRef = useRef<processor>() // 處理器
  const imageInputRef = useRef<rawimage>() // 原始影像
  const imageProcessed = useRef<samimageprocessorresult>() // 處理後的映像
  const imageEmbeddings = useRef<tensor>() // 嵌入數(shù)據(jù)

  const分段提示 = useMemo(() => {
    開關(分段狀態(tài)){
      案例 SEGMENTATION_STATUS.LOADING:
        return '在圖像分割函數(shù)載入中'
      案例 SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU:
        return '此瀏覽器不支援WebGPU,若要使用影像分割功能,請使用最新版本的Google Chrome。 '
      案例 SEGMENTATION_STATUS.LOAD_ERROR:
        return '圖像分割函數(shù)載入失敗'
      案例SEGMENTATION_STATUS.LOAD_SUCCESS:
        return '圖像分割函數(shù)載入成功'
      案例 SEGMENTATION_STATUS.PROCESSING:
        返回“圖像處理...”
      案例SEGMENTATION_STATUS.PROCESSING_SUCCESS:
        return '圖片處理成功,可以點擊圖片標記,綠色遮罩區(qū)域為分割區(qū)域。 '
      預設:
        返回 '??'
    }
  }, [分段狀態(tài)])

  // 1. 載入模型和處理器
  useEffect(() => {
    ;(非同步() => {
      嘗試 {
        if (segmentationStatus === SEGMENTATION_STATUS.LOADING) {
          返回
        }

        setSegmentationStatus(SEGMENTATION_STATUS.LOADING)
        if (!navigator?.gpu) {
          setSegmentationStatus(SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU)
          返回
        }const model_id = 'Xenova/slimsam-77-uniform'
        modelRef.current ??= 等待 SamModel.from_pretrained(model_id, {
          dtype: 'fp16', // 或 "fp32"
          設備:'webgpu'
        })
        handlerRef.current ??= 等待 AutoProcessor.from_pretrained(model_id)

        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_SUCCESS)
      } 捕獲(錯誤){
        console.log('錯誤', 錯誤)
        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_ERROR)
      }
    })()
  }, [])

  // 2. 處理影像
  useEffect(() => {
    ;(非同步() => {
      嘗試 {
        如果 (
          !modelRef.current ||
          !processorRef.current ||
          !url ||
          分段狀態(tài) === SEGMENTATION_STATUS.PROCESSING
        ){
          返回
        }
        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING)
        清除點()

        imageInputRef.current = 等待 RawImage.fromURL(url)
        imageProcessed.current = 等待處理器Ref.current(
          imageInputRef.current
        )
        imageEmbeddings.current = 等待 (
          modelRef.current 為任意
        ).get_image_embeddings(imageProcessed.current)

        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING_SUCCESS)
      } 捕獲(錯誤){
        console.log('錯誤', 錯誤)
      }
    })()
  },[url,modelRef.current,processorRef.current])

  // 更新遮罩效果
  函數(shù) updateMaskOverlay(掩碼:RawImage,分數(shù):Float32Array) {
    const maskCanvas = maskCanvasRef.current
    如果(!maskCanvas){
      返回
    }
    const maskContext = maskCanvas.getContext('2d') as CanvasRenderingContext2D

    // 更新畫布尺寸(如果不同)
    if (maskCanvas.width !== mask.width || maskCanvas.height !== mask.height) {
      maskCanvas.width = mask.width
      maskCanvas.height = mask.高度
    }

    // 為像素資料分配緩衝區(qū)
    const imageData = maskContext.createImageData(
      maskCanvas.寬度,
      maskCanvas.height
    )

    // 選擇最佳遮罩
    const numMasks = Scores.length // 3
    讓最佳索引 = 0
    for (令 i = 1; i scores[bestIndex]) {
        最佳索引 = i
      }
    }

    // 用顏色填滿蒙版
    const PixelData = imageData.data
    for (令 i = 0; i  {
    如果 (
      !modelRef.current ||
      !imageEmbeddings.current ||
      !processorRef.current ||
      !imageProcessed.current
    ){
      返回
    }// 沒有點擊資料直接清除分割效果
    if (!markPoints.length && maskCanvasRef.current) {
      const maskContext = maskCanvasRef.current.getContext(
        '2d'
      ) 作為 CanvasRenderingContext2D
      maskContext.clearRect(
        0,
        0,
        maskCanvasRef.current.width,
        maskCanvasRef.current.height
      )
      返回
    }

    // 準備解碼輸入
    const reshape = imageProcessed.current.reshape_input_sizes[0]
    常數(shù)點 = 標記點
      .map((x) => [x.position[0] * 重塑[1], x.position[1] * 重塑[0]])
      .flat(無窮大)
    const labels = markPoints.map((x) => BigInt(x.label)).flat(Infinity)

    const num_points = markPoints.length
    const input_points = new Tensor('float32', 點, [1, 1, num_points, 2])
    const input_labels = new Tensor('int64', labels, [1, 1, num_points])

    // 產生掩碼
    const { pred_masks, iou_scores } = 等待 modelRef.current({
      ...imageEmbeddings.current,
      輸入點,
      輸入標籤
    })

    // 對遮罩進行後處理
    const mask = wait (processorRef.current as any).post_process_masks(
      pred_masks,
      imageProcessed.current.original_sizes,
      imageProcessed.current.reshape_input_sizes
    )

    updateMaskOverlay(RawImage.fromTensor(masks[0][0]), iou_scores.data)
  }

  const 箝位 = (x: 數(shù)字, 最小值 = 0, 最大值 = 1) => {
    返回 Math.max(Math.min(x, max), min)
  }

  const clickImage = (e: MouseEvent) =>; {
    if (segmentationStatus !== SEGMENTATION_STATUS.PROCESSING_SUCCESS) {
      返回
    }

    const { clientX, clientY, currentTarget } = e
    const { 左,上 } = currentTarget.getBoundingClientRect()

    常數(shù) x = 箝位(
      (clientX - 左 currentTarget.scrollLeft) / currentTarget.scrollWidth
    )
    常數(shù) y = 箝位(
      (clientY - 頂部 currentTarget.scrollTop) / currentTarget.scrollHeight
    )

    const現(xiàn)有PointIndex = markPoints.findIndex(
      (點)=>
        Math.abs(point.position[0] - x) ; {
    設定標記點([])
    解碼([])
  }

  返回 (
    <div classname="cardshadow-xloverflow-auto">
      <div classname="flex items-center gap-x-3">
        
          清除積分
        按鈕>

        ; setPointStatus(true)}
        >
          {點狀態(tài)? 「正」:「負」}
        按鈕>
      </div>
      <div classname="text-xs text-base-content mt-2">{segmentationTip}</div>;
      <div>



<h2>
  
  
  結論
</h2>

<p>感謝您的閱讀。這就是本文的全部內容,希望本文對您有所幫助,歡迎點讚收藏。如有任何疑問,歡迎在留言區(qū)討論! </p>


          </div>

            
        </div></tensor></samimageprocessorresult></rawimage></processor></pretrainedmodel></htmlcanvaselement></boolean></segmentationstatustype></markpoint>

以上是探索Canvas系列:結合Transformers.js實現(xiàn)智慧型影像處理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發(fā)現(xiàn)涉嫌抄襲或侵權的內容,請聯(lián)絡admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72
如何在node.js中提出HTTP請求? 如何在node.js中提出HTTP請求? Jul 13, 2025 am 02:18 AM

在Node.js中發(fā)起HTTP請求有三種常用方式:使用內置模塊、axios和node-fetch。 1.使用內置的http/https模塊無需依賴,適合基礎場景,但需手動處理數(shù)據(jù)拼接和錯誤監(jiān)聽,例如用https.get()獲取數(shù)據(jù)或通過.write()發(fā)送POST請求;2.axios是基於Promise的第三方庫,語法簡潔且功能強大,支持async/await、自動JSON轉換、攔截器等,推薦用於簡化異步請求操作;3.node-fetch提供類似瀏覽器fetch的風格,基於Promise且語法簡單

JavaScript數(shù)據(jù)類型:原始與參考 JavaScript數(shù)據(jù)類型:原始與參考 Jul 13, 2025 am 02:43 AM

JavaScript的數(shù)據(jù)類型分為原始類型和引用類型。原始類型包括string、number、boolean、null、undefined和symbol,其值不可變且賦值時復制副本,因此互不影響;引用類型如對象、數(shù)組和函數(shù)存儲的是內存地址,指向同一對象的變量會相互影響。判斷類型可用typeof和instanceof,但需注意typeofnull的歷史問題。理解這兩類差異有助於編寫更穩(wěn)定可靠的代碼。

React與Angular vs Vue:哪個JS框架最好? React與Angular vs Vue:哪個JS框架最好? Jul 05, 2025 am 02:24 AM

選哪個JavaScript框架最好?答案是根據(jù)需求選擇最適合的。 1.React靈活自由,適合需要高度定制、團隊有架構能力的中大型項目;2.Angular提供完整解決方案,適合企業(yè)級應用和長期維護的大項目;3.Vue上手簡單,適合中小型項目或快速開發(fā)。此外,是否已有技術棧、團隊規(guī)模、項目生命週期及是否需要SSR也都是選擇框架的重要因素??傊?,沒有絕對最好的框架,適合自己需求的就是最佳選擇。

JavaScript時間對象,某人構建了一個eactexe,在Google Chrome上更快的網站等等 JavaScript時間對象,某人構建了一個eactexe,在Google Chrome上更快的網站等等 Jul 08, 2025 pm 02:27 PM

JavaScript開發(fā)者們,大家好!歡迎閱讀本週的JavaScript新聞!本週我們將重點關注:Oracle與Deno的商標糾紛、新的JavaScript時間對象獲得瀏覽器支持、GoogleChrome的更新以及一些強大的開發(fā)者工具。讓我們開始吧! Oracle與Deno的商標之爭Oracle試圖註冊“JavaScript”商標的舉動引發(fā)爭議。 Node.js和Deno的創(chuàng)建者RyanDahl已提交請願書,要求取消該商標,他認為JavaScript是一個開放標準,不應由Oracle

什麼是緩存API?如何與服務人員使用? 什麼是緩存API?如何與服務人員使用? Jul 08, 2025 am 02:43 AM

CacheAPI是瀏覽器提供的一種緩存網絡請求的工具,常與ServiceWorker配合使用,以提升網站性能和離線體驗。 1.它允許開發(fā)者手動存儲如腳本、樣式表、圖片等資源;2.可根據(jù)請求匹配緩存響應;3.支持刪除特定緩存或清空整個緩存;4.通過ServiceWorker監(jiān)聽fetch事件實現(xiàn)緩存優(yōu)先或網絡優(yōu)先等策略;5.常用於離線支持、加快重複訪問速度、預加載關鍵資源及後臺更新內容;6.使用時需注意緩存版本控制、存儲限制及與HTTP緩存機制的區(qū)別。

處理諾言:鏈接,錯誤處理和承諾在JavaScript中 處理諾言:鏈接,錯誤處理和承諾在JavaScript中 Jul 08, 2025 am 02:40 AM

Promise是JavaScript中處理異步操作的核心機制,理解鍊式調用、錯誤處理和組合器是掌握其應用的關鍵。 1.鍊式調用通過.then()返回新Promise實現(xiàn)異步流程串聯(lián),每個.then()接收上一步結果並可返回值或Promise;2.錯誤處理應統(tǒng)一使用.catch()捕獲異常,避免靜默失敗,並可在catch中返回默認值繼續(xù)流程;3.組合器如Promise.all()(全成功才成功)、Promise.race()(首個完成即返回)和Promise.allSettled()(等待所有完成)

利用Array.Prototype方法用於JavaScript中的數(shù)據(jù)操作 利用Array.Prototype方法用於JavaScript中的數(shù)據(jù)操作 Jul 06, 2025 am 02:36 AM

JavaScript數(shù)組內置方法如.map()、.filter()和.reduce()可簡化數(shù)據(jù)處理;1).map()用於一對一轉換元素生成新數(shù)組;2).filter()按條件篩選元素;3).reduce()用於聚合數(shù)據(jù)為單一值;使用時應避免誤用導致副作用或性能問題。

JS綜述:深入研究JavaScript事件循環(huán) JS綜述:深入研究JavaScript事件循環(huán) Jul 08, 2025 am 02:24 AM

JavaScript的事件循環(huán)通過協(xié)調調用棧、WebAPI和任務隊列來管理異步操作。 1.調用棧執(zhí)行同步代碼,遇到異步任務時交由WebAPI處理;2.WebAPI在後臺完成任務後將回調放入相應的隊列(宏任務或微任務);3.事件循環(huán)檢查調用棧是否為空,若為空則從隊列中取出回調推入調用棧執(zhí)行;4.微任務(如Promise.then)優(yōu)先於宏任務(如setTimeout)執(zhí)行;5.理解事件循環(huán)有助於避免阻塞主線程並優(yōu)化代碼執(zhí)行順序。

See all articles