찾다
웹 프론트엔드JS 튜토리얼매립 포인트 시스템에 대한 사전 탐색

매립 포인트 시스템에 대한 사전 탐색

관련 학습 권장사항: javascript 비디오 튜토리얼

머리말

최근에는 이전 시리즈를 보충할 시간을 찾기가 어렵습니다. 이제 도약을 시작합니다

영화 속 시스템

에 집중해야 하는 이유

프론트엔드 개발 시즈라이온 즐겁게 코딩하고 있으며, 비즈니스와 UI의 별도 개발, 다양한 디자인 패턴, 알고리즘 최적화, 완벽함을 매우 자랑스럽게 생각합니다. 코드작성(노동법세계최초), BUG가 없고, 프로그램이 완벽하고, 호환성 1위, 코드 입력 및 저항이 가능하고, 품질이 높습니다. 퇴근 후 쉽게 출근하여 집에 가서 아기를 돌볼 수 있습니다.

실제로는

사실 개발 환경과 프로덕션 환경이 같지 않고, 아무리 테스트 과정이 완벽해도 테스트를 놓치는 경우가 있기 마련입니다. 사용자의 클라이언트 환경, 네트워크 환경 등 일련의 불확실한 요소의 존재를 고려합니다. 그래서 개발 과정에서 세 가지 주요 원칙을 꼭 기억해야 합니다. (

말도 안 되는 소리만 하고 있어요

)

    완벽한 코드는 없고, 발견되지 않은 버그만 있을 뿐입니다
  1. 테스트 환경을 절대 믿지 마세요
  2. , 있습니다. 어떤 종류의 테스트 환경도 없습니다. 모든 온라인 상황이 커버됩니다온라인에서 피드백이 없다면 의심하지 마세요. 문제는 아주 깊게 숨겨져 있어야 합니다
  3. 매장 포인트 시스템이 무엇인가요?

매장 포인트는 카메라와 같습니다. 도시에서 제품 관점에서 제품에 대한 사용자의 행동 궤적을 모니터링하여 제품 반복 및 프로젝트 안정성의 기반을 제공할 수 있다고 생각하세요.

데이터 수집의 기본 차원은 누구, 언제, 어디서, 어떻게, 무엇입니까

. 프런트 엔드 개발을 위해 페이지 리소스 로딩 성능, 예외 등을 모니터링하고, 페이지 경험 및 상태 지수를 제공하고, 후속 성능 최적화를 위한 기반을 제공하고, 예외 및 발생 시나리오를 적시에 보고할 수 있습니다. 이를 통해 적시에 문제를 수정하고 프로젝트 품질을 향상시킬 수 있습니다.

매장 포인트는 크게 3가지로 나눌 수 있습니다.

    추적 없는 매매 포인트
  1. - 페이지 진입 및 퇴장, 이벤트 클릭 등 페이지의 모든 정보가 무차별적으로 수집됩니다. 유용한 정보를 얻으려면 데이터 플러시가 필요합니다
  2. 시각적 매립 포인트
  3. - 생성된 페이지 구조를 바탕으로 특정 포인트를 획득하고, 별도로 포인트를 매립하여 분석합니다.
  4. 비즈니스 코드에 대한 수동 매립 포인트
  5. - 특정하고 복잡한 업종에 따라 커버할 수 없는 부분을 제거합니다.
일반적인 시나리오장점불충분

대부분의 경우 추적 없는 매설점을 통해 모든 정보 데이터를 수집한 후 시각적 매설점과 협력하여 특정 지점을 구체적으로 찾아내면 대부분의 매설점 정보가 이에 따라 분석될 수 있습니다.

특별한 상황에서는 비즈니스 코드를 더 추가하여 포인트를 수동으로 묻어 특별한 시나리오를 처리할 수 있습니다(대부분의 경우 강력한 비즈니스 및 정상적인 클릭 및 새로 고침 이벤트는 보고해야 하는 정보와 관련이 없습니다)

Bury SDK 개발

Bury Point 데이터 수집 및 분석

  • 이벤트 기본 데이터
    • 이벤트 시간
    • 발생 시 페이지 정보 스냅샷
  • Page
    • Page PV, UV
    • 사용자 페이지 길이 stay
    • 페이지 점프 이벤트
    • 페이지가 배경으로 들어갑니다
    • 사용자가 페이지를 떠납니다
  • 사용자 정보
    • 사용자 uid
    • 사용자 기기 지문
    • 기기 정보
    • ip
    • 포지셔닝
  • 사용자 작업 동작
    • 사용자 클릭
      • 대상 클릭
  • 페이지 AJAX 요청
    • 요청 성공
    • 요청 실패
    • 요청 시간 초과
  • 페이지 오류 보고
    • 리소스 로드 오류 보고
    • JS 실행 오류 보고
  • 새로운 리소스 로딩 성능
  • Pictures
  • Script
  • 페이지 로딩 성능

위 데이터는 3차원

  • ·LEVEL을 통해 숨겨진 이벤트를 정의합니다. 묻힌 데이터LEVEL: 描述埋点数据的日志级别
    • INFO:一些用户操作,请求成功,资源加载等等正常的数据记录
    • ERROR: JS报错,接口报错等等错误类型的数据记录
    • DEBUG: 预留开发人员通过手动调用的方式回传排除bug的数据记录
    • WARN: 预留开发人员通过手动调用的方式回传非正常用户行为的的数据记录
  • CATEGORY:描述埋点数据的分类
    • TRACK: 埋点SDK对象的生命周期管理整个埋点数据。
      • WILL_MOUNT:sdk对象即将初始化加载,生成一个默认ID,跟踪全部相关事件
      • DID_MOUNTED:sdk对象初始化完成,主要获取设备指纹等等的异步操作完成
    • AJAX: AJAX相关数据
    • ERROR:页面中的异常相关数据
    • PERFORMANCE: 关于性能相关数据
    • OPERATION: 用户操作相关数据
  • EVENT_NAME:具体的事件名称

根据上述的维度,我们可以简单设计如下的架构

매립 포인트 시스템에 대한 사전 탐색

根据上图的架构,再进行下面的具体代码开发

代理请求

在浏览器中现在主要有 2 种请求方式,一个是 XMLHttpRequest, 一个是 Fetch

代理 XMLHttpRequest

function NewXHR() {  var realXHR: any = new OldXHR(); // 代理模式里面有提到过
  realXHR.id = guid()  const oldSend = realXHR.send;

  realXHR.send = function (body) {
    oldSend.call(this, body)    //记录埋点
  }
  realXHR.addEventListener('load', function () {    //记录埋点
  }, false);
  realXHR.addEventListener('abort', function () {    //记录埋点
  }, false);

  realXHR.addEventListener('error', function () {    //记录埋点
  }, false);
  realXHR.addEventListener('timeout', function () {    //记录埋点
  }, false);  return realXHR;
}复制代码

代理 Fetch

 const oldFetch = window.fetch;  function newFetch(url, init) {    const fetchObj = {      url: url,      method: method,      body: body,
    }
    ajaxEventTrigger.call(fetchObj, AJAX_START);    return oldFetch.apply(this, arguments).then(function (response) {      if (response.ok) {       //记录埋点
      } else {       //上报错误
      }      return response
    }).catch(function (error) {
      fetchObj.error = error        //记录埋点      
        throw error
    })
  }复制代码

监听页面的 PVUV

在进入页面时,我们通过算法生成一个唯一 session id,作为这次埋点行为的全局 id,上报用户 id,设备指纹,设备信息。在用户未登录的情况下,通过设备指纹来计算 UV,通过 session id计算 PV

异常捕获

异常就是干扰程序的正常流程的不寻常事故

RUNTIME ERROR

JS中可以通过 window.onerrorwindow.addEventListener('error', callback) 捕捉运行时异常,一般使用window.onerror,它兼容性更好。

window.onerror = function(message, url, lineno, columnNo, error) {    const lowCashMessage = message.toLowerCase()    if(lowCashMessage.indexOf('script error') > -1) {      return
    }    const detail = {      url: url    
      filename: filename,      columnNo: columnNo,      lineno: lineno,      stack: error.stack,      message: message
    }    //记录埋点}复制代码

Script Error

在这里我们过滤了 Script Error, 它产生的原因主要是页面中加载的第三方跨域脚本报错,比如托管在第三方 CDN 中的 js 脚本。这类问题比较难以排查。解决的方法有:

  • 打开 CORS(Cross Origin Resource Sharing,跨域资源共享),如下步骤
    • <srcipt src="another%20domain/main.js" cossorigin="anonymous"></srcipt>
    • 修改Access-Control-Allow-Origin: * | 指定域名
  • 使用 try catch
      <script scr="crgt.js"></script> //加载crgt脚本,window.crgt = {getUser: () => string}
      try{      window.crgt.getUser();
      }catch(error) {      throw error // 输出正确的错误堆栈
      }复制代码

Promise reject

js 在异步异常时无法通过 onerrorunhandledrejectionINFO: 일부 사용자 작업, 요청 성공, 리소스 로딩 등 일반 데이터 기록

🎜ERROR: JS 오류 보고, 인터페이스 오류 보고 등 데이터 오류 유형 기록🎜🎜DEBUG: 예약된 개발자는 수동 호출을 사용하여 버그를 제거하기 위해 데이터 레코드를 반환할 수 있습니다🎜🎜WARN: 개발자가 수동 호출을 사용하여 반환하도록 예약되어 있습니다. 비정상적인 사용자 행위에 대한 데이터 기록🎜🎜🎜🎜CATEGORY: 매장지 데이터의 분류를 설명합니다.🎜🎜TRACK: 매장지 SDK 객체의 라이프사이클이 전체 매장지 데이터를 관리합니다. 포인트 데이터. 🎜🎜WILL_MOUNT: SDK 객체가 초기화 및 로드되려고 하며 기본 ID가 생성되고 모든 관련 이벤트가 추적됩니다. 🎜🎜DID_MOUNTED: sdk 객체가 완료되었습니다. 주로 비동기적으로 기기 지문을 획득하는 등입니다. 작업 완료🎜🎜🎜🎜AJAX: AJAX 관련 데이터🎜🎜ERROR: 페이지의 예외 관련 데이터🎜🎜 PERFORMANCE: 성능 관련 데이터에 대한 정보 🎜🎜OPERATION: 사용자 작업 관련 데이터🎜🎜🎜🎜EVENT_NAME: 특정 이벤트 이름🎜🎜🎜🎜에 따르면 위의 차원을 사용하면 다음 아키텍처를 간단하게 설계할 수 있습니다🎜🎜 매립 포인트 시스템에 대한 사전 탐색
🎜위 그림의 구조에 따라 다음과 같은 구체적인 코드 개발을 진행합니다🎜

현재 브라우저에는 프록시 요청에 대한 두 가지 주요 요청 방법이 있습니다. 하나는 XMLHttpRequest이고 다른 하나는 Fetch입니다. 코드>. 🎜

프록시 XMLHttpRequest

window.addEventListener("unhandledrejection", event => {  throw event.reason
});复制代码

프록시 가져오기

window.addEventListener(&#39;error&#39;, (event) => {  if (event.target instanceof HTMLElement) {    const target = parseDom(event.target, [&#39;src&#39;]);    const detail = {      target: target,      path: parseXPath(target),
    }    //  记录埋点
  }
}, true)复制代码

페이지의 PV, UV를 모니터링합니다🎜🎜페이지에 들어갈 때 알고리즘을 통해 고유한 세션 ID를 이 매립 행위 Global ID로 생성합니다. , 사용자 ID, 장치 지문, 장치 정보를 보고합니다. 사용자가 로그인하지 않은 경우 UV는 기기 지문을 통해 계산되고 PVsession id를 통해 계산됩니다. 🎜

예외 포착🎜🎜예외는 프로그램의 정상적인 흐름을 방해하는 비정상적인 사고입니다🎜

런타임 오류

🎜 JS에서는 window.onerrorwindow.addEventListener('error', callback)를 통해 런타임 예외를 캡처할 수 있습니다. >window가 사용됩니다. onerror는 호환성이 더 좋습니다. 🎜
window.addEventListener(&#39;click&#39;, (event) => {    //记录埋点}, true)复制代码

스크립트 오류

🎜여기에서는 주로 스크립트 페이지에 로드된 타사 교차 도메인으로 인해 발생하는 스크립트 오류를 필터링했습니다. 타사 CDN에서 호스팅되는 js 스크립트와 같은 오류를 보고합니다. 이러한 유형의 문제는 해결하기가 더 어렵습니다. 해결 방법은 다음과 같습니다. 🎜🎜🎜다음과 같이 CORS(교차 원본 리소스 공유, 도메인 간 리소스 공유)를 엽니다. 🎜🎜<srcipt src="another%20domain/main.js" cossorigin="anonymous"></srcipt>🎜🎜Access-Control-Allow-Origin 수정: * | 도메인 이름 지정🎜🎜🎜🎜 try catch 사용 code >
window.addEventListener(&#39;hashchange&#39;, event => {  const { oldURL, newURL } = event;  const oldURLObj = url.parseUrl(oldURL);  const newURLObj = url.parseUrl(newURL);  const from = oldURLObj.hash && url.parseHash(oldURLObj.hash);  const to = newURLObj.hash && url.parseHash(newURLObj.hash);  if(!from && !to ) return;  // 记录埋点})复制代码
🎜🎜

Promise Reject

🎜js는 비동기 예외가 발생하는 경우 onerror 메서드로 캡처할 수 없습니다. 발생, Promise에서 객체가 거부되고 동시에 처리되지 않는 경우 위의 방법으로는 🎜 에러가 발생하고 catch되지 않으므로 별도의 처리 이벤트를 추가해야 합니다. 🎜
window.addEventListener("unhandledrejection", event => {  throw event.reason
});复制代码

资源加载异常

在浏览器中,可以通过 window.addEventListener('error', callback) 的方式监听资源加载异常,比如 js 或者 css 脚本文件丢失。

window.addEventListener(&#39;error&#39;, (event) => {  if (event.target instanceof HTMLElement) {    const target = parseDom(event.target, [&#39;src&#39;]);    const detail = {      target: target,      path: parseXPath(target),
    }    //  记录埋点
  }
}, true)复制代码

监听用户行为

通过 addEventListener click 监听 click 事件

window.addEventListener(&#39;click&#39;, (event) => {    //记录埋点}, true)复制代码

在这里通过组件的 displaName 来定位元素的位置,displaName 表示组件的文件目录,比如 src/components/Form.js 文件导出的组件 FormItem 通过 babel plugin 自动添加属性 @components/Form.FormItem,或者使用者主动给组件添加 static 属性 displayName

页面路由变化

  • hashRouter 监听页面hash变化,对hash进行解析
window.addEventListener(&#39;hashchange&#39;, event => {  const { oldURL, newURL } = event;  const oldURLObj = url.parseUrl(oldURL);  const newURLObj = url.parseUrl(newURL);  const from = oldURLObj.hash && url.parseHash(oldURLObj.hash);  const to = newURLObj.hash && url.parseHash(newURLObj.hash);  if(!from && !to ) return;  // 记录埋点})复制代码

监听页面离开

通过 addEventListener beforeunload 监听离开页面事件

window.addEventListener(&#39;beforeunload&#39;, (event) => {    //记录埋点})复制代码

SDK 架构

class Observable {    constructor(observer) {
        observer(this.emit)
    }
    emit = (data) => {        this.listeners.forEach(listener => {
            listener(data)
        })
    }
    listeners = [];
    
    subscribe = (listener) => {        this.listeners.push(listeners);        return () => {            const index = this.listeners.indexOf(listener);            if(index === -1) {                return false
            }            
            this.listeners.splice(index, 1);            return true;
        }
     }
}复制代码
const clickObservable = new Observable((emit) => {    window.addEventListener(&#39;click&#39;, emit)
})复制代码

然而在处理 ajax,需要将多种数据组合在一起,需要进行 merg 操作,则显得没有那么优雅,也很难适应后续复杂的数据流的操作。

const ajaxErrorObservable = new Observable((emit) => {    window.addEventListener(AJAX_ERROR, emit)
})const ajaxSuccessObservable = new Observable((emit) => {    window.addEventListener(AJAX_SUCCESS, emit)
})const ajaxTimeoutObservable = new Observable((emit) => {    window.addEventListener(AJAX_TIMEOUT, emit)
})复制代码

可以选择 RxJS 来优化代码

export const ajaxError$ = fromEvent(window, &#39;AJAX_ERROR&#39;, true)export const ajaxSuccess$ = fromEvent(window, &#39;AJAX_SUCCESS&#39;, true)export const ajaxTimeout$ = fromEvent(window, &#39;AJAX_TIMEOUT&#39;, true)复制代码
ajaxError$.pipe(
    merge(ajaxSuccess$, ajaxTimeout$), 
    map(data=> (data) => ({category: &#39;ajax&#39;, data; data}))
    subscribe(data => console.log(data))复制代码

通过 merge, map 两个操作符完成对数据的合并和处理。

数据流

매립 포인트 시스템에 대한 사전 탐색

项目结构

  • core
    • event$ 数据流合并
    • snapshot 获取当前设备快照,例如urluserIDrouter
    • track 埋点类,组合数据流和日志。
  • logger
    • logger 日志类
      • info
      • warn
      • debug
      • error
  • observable
    • ajax
    • beforeUpload
    • opeartion
    • routerChange
    • logger
    • track

参考

  • www.alibabacloud.com/help/zh/doc…

结尾

自建埋点系统是一个需要前后端一起合作的事情,如果人力不足的情况下,建议使用第三方分析插件,例如 Sentry 就能足够满足大部分日常使用

但还是建议多了解,在第三方插件出现不能满足业务需求的时候,可以顶上。

想了解更多编程学习,敬请关注php培训栏目!


코드 매장지
Visual 매장지점 보이지 않는 매장지점
보이지 않는 매장지점은 커버할 수 없습니다. 그 비즈니스 데이터가 필요한 간단하고 표준화된 페이지 시나리오 간단하고 표준화된 페이지 시나리오,
비즈니스 데이터 명확성 낮은 개발 비용, 운영자가 관련 매장 지점을 직접 구성 가능 구성이 필요 없으며 데이터가 추적할 수 있음
데이터 추적 불가, 높은 개발 비용 비즈니스 데이터 연결 불가, 데이터 추적 불가 데이터 양이 많아 비즈니스 데이터 연결 불가

위 내용은 매립 포인트 시스템에 대한 사전 탐색의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명
이 기사는 juejin에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제
JavaScript 데이터 유형 : 브라우저와 Nodejs 사이에 차이가 있습니까?JavaScript 데이터 유형 : 브라우저와 Nodejs 사이에 차이가 있습니까?May 14, 2025 am 12:15 AM

JavaScript 코어 데이터 유형은 브라우저 및 Node.js에서 일관되지만 추가 유형과 다르게 처리됩니다. 1) 글로벌 객체는 브라우저의 창이고 node.js의 글로벌입니다. 2) 이진 데이터를 처리하는 데 사용되는 Node.js의 고유 버퍼 객체. 3) 성능 및 시간 처리에는 차이가 있으며 환경에 따라 코드를 조정해야합니다.

JavaScript 댓글 : / / * * /사용 안내서JavaScript 댓글 : / / * * /사용 안내서May 13, 2025 pm 03:49 PM

javaScriptUSTWOTYPESOFSOFCOMMENTS : 단일 라인 (//) 및 multi-line (//)

Python vs. JavaScript : 개발자를위한 비교 분석Python vs. JavaScript : 개발자를위한 비교 분석May 09, 2025 am 12:22 AM

Python과 JavaScript의 주요 차이점은 유형 시스템 및 응용 프로그램 시나리오입니다. 1. Python은 과학 컴퓨팅 및 데이터 분석에 적합한 동적 유형을 사용합니다. 2. JavaScript는 약한 유형을 채택하며 프론트 엔드 및 풀 스택 개발에 널리 사용됩니다. 두 사람은 비동기 프로그래밍 및 성능 최적화에서 고유 한 장점을 가지고 있으며 선택할 때 프로젝트 요구 사항에 따라 결정해야합니다.

Python vs. JavaScript : 작업에 적합한 도구 선택Python vs. JavaScript : 작업에 적합한 도구 선택May 08, 2025 am 12:10 AM

Python 또는 JavaScript를 선택할지 여부는 프로젝트 유형에 따라 다릅니다. 1) 데이터 과학 및 자동화 작업을 위해 Python을 선택하십시오. 2) 프론트 엔드 및 풀 스택 개발을 위해 JavaScript를 선택하십시오. Python은 데이터 처리 및 자동화 분야에서 강력한 라이브러리에 선호되는 반면 JavaScript는 웹 상호 작용 및 전체 스택 개발의 장점에 없어서는 안될 필수입니다.

파이썬 및 자바 스크립트 : 각각의 강점을 이해합니다파이썬 및 자바 스크립트 : 각각의 강점을 이해합니다May 06, 2025 am 12:15 AM

파이썬과 자바 스크립트는 각각 고유 한 장점이 있으며 선택은 프로젝트 요구와 개인 선호도에 따라 다릅니다. 1. Python은 간결한 구문으로 데이터 과학 및 백엔드 개발에 적합하지만 실행 속도가 느립니다. 2. JavaScript는 프론트 엔드 개발의 모든 곳에 있으며 강력한 비동기 프로그래밍 기능을 가지고 있습니다. node.js는 풀 스택 개발에 적합하지만 구문은 복잡하고 오류가 발생할 수 있습니다.

JavaScript의 핵심 : C 또는 C에 구축 되었습니까?JavaScript의 핵심 : C 또는 C에 구축 되었습니까?May 05, 2025 am 12:07 AM

javaScriptisNotBuiltoncorc; it'SangretedLanguageThatrunsonOngineStenWrittenInc .1) javaScriptWasDesignEdasAlightweight, 해석 hanguageforwebbrowsers.2) Endinesevolvedfromsimpleplemporectreterstoccilpilers, 전기적으로 개선된다.

JavaScript 응용 프로그램 : 프론트 엔드에서 백엔드까지JavaScript 응용 프로그램 : 프론트 엔드에서 백엔드까지May 04, 2025 am 12:12 AM

JavaScript는 프론트 엔드 및 백엔드 개발에 사용할 수 있습니다. 프론트 엔드는 DOM 작업을 통해 사용자 경험을 향상시키고 백엔드는 Node.js를 통해 서버 작업을 처리합니다. 1. 프론트 엔드 예 : 웹 페이지 텍스트의 내용을 변경하십시오. 2. 백엔드 예제 : node.js 서버를 만듭니다.

Python vs. JavaScript : 어떤 언어를 배워야합니까?Python vs. JavaScript : 어떤 언어를 배워야합니까?May 03, 2025 am 12:10 AM

Python 또는 JavaScript는 경력 개발, 학습 곡선 및 생태계를 기반으로해야합니다. 1) 경력 개발 : Python은 데이터 과학 및 백엔드 개발에 적합한 반면 JavaScript는 프론트 엔드 및 풀 스택 개발에 적합합니다. 2) 학습 곡선 : Python 구문은 간결하며 초보자에게 적합합니다. JavaScript Syntax는 유연합니다. 3) 생태계 : Python에는 풍부한 과학 컴퓨팅 라이브러리가 있으며 JavaScript는 강력한 프론트 엔드 프레임 워크를 가지고 있습니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

SublimeText3 영어 버전

SublimeText3 영어 버전

권장 사항: Win 버전, 코드 프롬프트 지원!

PhpStorm 맥 버전

PhpStorm 맥 버전

최신(2018.2.1) 전문 PHP 통합 개발 도구

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

안전한 시험 브라우저

안전한 시험 브라우저

안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.

WebStorm Mac 버전

WebStorm Mac 버전

유용한 JavaScript 개발 도구