javascript模块化-这次,我想了解一下javascript的模块化

2023-08-21 0 5,246 百度已收录

随着后端js代码复杂度的增加,js模块化是必然趋势。 除了易于维护之外,同时依赖关系非常清晰,不会产生全局污染。 明天我们来整理一下几个模块化规格吧~

首先梳理一下模块化的发展~

无模块化 --> CommonJS 规范 --> AMD 规范 --> CMD 规范 --> ES6 模块化 1. 无模块化

script标签引入js文件,互相列出,把依赖的放在上面,不然使用的时候会报错。 如下:

   <script src="jquery.js">
  <script src="jquery_scroller.js">
  <script src="main.js">
  <script src="other1.js">
  <script src="other2.js">
  <script src="other3.js">

也就是把所有js文件放在一起就可以了。 而且此类文件的顺序不能错。 比如需要先引入jquery,可以引入jquery插件,其他文件中也可以使用jquery。 缺点也很明显:

2.CommonJS规范

该规范最初用于服务器端节点,它有四个重要的环境变量为模块化实现提供支持:module、exports、require 和 global。 实际使用时,使用module.exports定义当前模块的外部输出socket(不建议直接使用exports),使用require加载模块(同步)。

// a-commonJs.js (导出)
var a = 5;var add = function(param){//在这里写上需要向外暴露的函数、变量    return a + param}module.exports.a = a;module.exports.add = add===========================
// b-commonJs.js引用定义模块,参数包含路径,可省略.js(导入)
var addFn = require('./a-commonJs')console.log(addFn.add(3)) //8console.log(addFn.a) //5

一点解释:

exports 是对 module.exports 的引用。比如我们可以认为在一个模块的顶部有这句代码:   exports = module.exports所以,我们不能直接给exports赋值,比如number、function等。 

注意:由于 module.exports 本身是一个对象,所以我们可以在导入时使用它

module.exports={foo:'bar'}//true

module.exports.foo='bar'//true。

不过exports是对module.exports的引用,或者可以理解为exports是一个指针javascript模块化,exports指向module.exports。 这样我们就只能使用exports.foo='bar'的形式来代替

Exports={foo:'bar'}//error 这几个方法都是错误的,相当于重新定义了exports

优势一:解决依赖和全局变量污染问题

缺点之一:CommonJS 同步加载模块。 在服务器端,模块文件都存放在本地c盘,读取速度很快,所以这样做不会有问题。 而在浏览器端,由于网络原因,CommonJS并不适合浏览器端模块加载。 更合理的解决方案是使用异步加载,例如下面的AMD规范。

3.AMD规格

继续上面的内容,AMD规范是一个异步加载模块,它允许指定一个反弹函数。 AMD是RequireJS推广过程中模块定义的标准化输出。 。

AMD标准中定义了以下三个API:

require([模块],回调)define(id,[取决于],回调)require.config()

即通过define定义一个模块,然后使用require加载模块,并使用require.config()指定引用路径。

首先去require.js官网下载最新版本,然后引入到页面中,如下:

data-main 属性不能省略。

以上是定义模块、引用模块以及在浏览器中运行时弹出的提示信息。

引用模块时,我们将模块名称放在[]中作为require()的第一个参数; 如果我们定义的模块本身还依赖于其他模块,那么我们需要将它们放在[]中作为define()第一个参数的第一个参数。

使用require.js时,我们必须提前加载所有依赖才可以使用,而不是需要使用时才加载。

优点一:适合异步加载模块,在浏览器环境下并行加载多个模块

缺点一:无法按需加载javascript模块化,开发成本高

4.命令提示符

AMD主张靠后定位、提前执行,而CMD主张靠就近、延迟执行。 CMD是SeaJS的模块定义在推广过程中的标准化输出。

显然,CMD是按需加载的,基于就近原则。

5.ES6模块化

在ES6中,我们可以使用import关键字导入模块,通过export关键字导入模块。 相比之前的解决方案,功能更加强大,也是我们所欣赏的,而且由于ES6目前还没有在浏览器中实现,因此我们只能通过babel将不支持的import编译成目前广泛支持的require。

es6导入时有一个默认的import,exportdefault,用它导入后,导入时不需要加{},模块名可以任意。 该名称实际上是一个包含导入模块上的函数或变量的对象。

并且一个模块只能有一个exportdefault。

6.CommonJs和ES6的区别

以下引自阮一峰先生:

(1) CommonJS 模块输出值的副本,而 ES6 模块输出对值的引用。 (2) CommonJS模块在运行时加载,ES6模块在编译时是输出套接字。

CommonJS加载的是一个对象(即module.exports属性),该对象只能在脚本运行后生成。 ES6模块不是一个对象,它的外部socket只是一个静态定义,会在代码的静态分析阶段生成。

小记:这篇文章只是我自己的一个小记,方便我记忆和理解。 (^o^)/~

前言

有一些后端的技术点,即使以前用过,但是自己没有总结过,需要很长时间才能回去重新整理。 那么,本文将对Web Worker进行梳理。

为什么我们需要网络工作者

由于 JavaScript 语言使用单线程,因此一次只能完成一件事。 如果有多个同步估算任务执行,则其下面的代码要等到同步估算逻辑执行完毕后才会执行,从而导致阻塞,用户交互也可能变得无响应。

但是,如果这段同步估算逻辑是在Web Worker上执行的,那么在这段逻辑估算执行过程中,它下面的代码仍然可以被执行,并且也可以响应用户的操作。

什么是网络工作者?

HTML5提供并标准化了Web Worker等一组API,它允许JavaScript程序在主线程之外的另一个线程(Worker线程)中运行。

Web Worker的作用是为JavaScript创建一个多线程环境,让主线程创建Worker线程,并分配一些任务给前者运行。 这样做的用处在于,一些计算密集型或高延迟的任务由Worker线程承担,主线程会很流畅,不会被阻塞或变慢。

Web Worker 的分类

根据工作环境的不同,Web Worker可以分为专用线程Dedicated Worker和共享线程Shared Worker。

Dedicated Worker的Worker只能从创建Worker的脚本中访问,而SharedWorker则可以由多个脚本访问。

如果开发中使用Web Worker,目前大部分场景主要使用Dedicated Worker,只能被一个页面使用,而本文也是讲这一类的; 而Shared Worker可以被多个页面共享,为跨浏览器选项卡共享数据提供了解决方案。

Web Worker 使用限制同源限制

javascript模块化-这次,我想了解一下javascript的模块化

分配给Worker线程运行的脚本文件必须与主线程的脚本文件同源。

文件限制

Worker线程很难读取本地文件(file://),并且会拒绝使用文件协议创建Worker实例。 它加载的脚本必须来自网络。

DOM 操作限制

Worker线程所在的全局对象与主线程不同,区别在于:

通讯限制

Worker线程和主线程不在同一个上下文中。 它们不能直接沟通,必须通过消息来完成。 交互方法为postMessage和onMessage,在传输数据时,Worker采用复制的形式。

脚本限制

工作线程无法执行alert()方法和confirm()方法,但可以使用XMLHttpRequest对象发送AJAX请求,或者使用setTimeout/setInterval等API

基本API

const worker = new Worker(aURL, options);

worker.addEventListener('error', function (e) {
    console.log(e.message) // 可读性良好的错误消息
    console.log(e.filename) // 发生错误的脚本文件名
    console.log(e.lineno) // 发生错误时所在脚本文件的行号
})

javascript模块化-这次,我想了解一下javascript的模块化

常用使用方法1.直接指定脚本文件

const myWorker = new Worker(aURL, options);

aURL表示Worker要执行的脚本的URL(脚本文件),即Web Worker要执行的任务。

案例如下:

// 主线程下创建worker线程
const worker = new Worker('./worker.js')
// 监听接收worker线程发的消息
worker.onmessage = function (e) {
  console.log('主线程收到worker线程消息:', e.data)
}
// 向worker线程发送消息
worker.postMessage('主线程发送hello world')

工人.js:

// self 代表子线程自身,即子线程的全局对象
self.addEventListener("message", function (e) {
  // e.data表示主线程发送过来的数据
  self.postMessage("worker线程收到的:" + e.data); // 向主线程发送消息
});

Web Worker的执行上下文名称是self,不能调用主线程的window对象。 上面的表示法等价于下面的表示法:

this.addEventListener("message", function (e) {
  // e.data表示主线程发送过来的数据
  this.postMessage("worker线程收到的:" + e.data); // 向主线程发送消息
});

将JS文件导入HTML并在本地开发环境中运行。 结果如下:

主线程收到worker线程消息: worker线程收到的:主线程发送hello world 

2. 使用 blob URL 创建

javascript模块化-这次,我想了解一下javascript的模块化

除了这些引入js文件的形式之外javascript实战,还可以通过URL.createObjectURL()创建URL对象来创建嵌入式worker

/**
 * const blob = new Blob(array, options);
 * Blob() 构造函数返回一个新的 Blob 对象。blob 的内容由参数数组中给出的值的串联组成。
 * @params array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array
 * @options type,默认值为 "",它代表了将会被放入到 blob 中的数组内容的 MIME 类型。还有两个这里忽略不列举了
 */
 
/**
 * URL.createObjectURL():静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象
 */
const worker = new Worker(URL.createObjectURL(blob));

function func() {
  console.log('hello')
}
function createWorker(fn) {
  // const blob = new Blob([fn.toString() + ' fn()'], { type: 'text/javascript' })
  const blob = new Blob([`(${fn.toString()})()`], { type: 'text/javascript' })
  return URL.createObjectURL(blob)
}
createWorker(func)

将脚本导入工作线程

要在工作线程内加载其他脚本,可以使用 importScripts()

// worker.js
importScripts("constants.js");
// self 代表子线程自身,即子线程的全局对象
self.addEventListener("message", function (e) {
  self.postMessage(foo); // 可拿到 `foo`、`getAge()`、`getName`的结果值 
});
// constants.js
const foo = "变量";
function getAge() {
  return 25;
}
const getName = () => {
  return "jacky";
};

还可以同时加载多个脚本

importScripts('script1.js', 'script2.js');

实际应用场景会处理大量的CPU时长估算操作

大家最关心的就是Web Worker的实战场景。 我们一开始就说过,当有大量复杂的估算场景时,可以使用Web Worker

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>worker计算</title>
  </head>
  <body>
    <div>计算从 1 到给定数值的总和</div>
    <input type="text" placeholder="请输入数字" id="num" />
    <button onclick="calc()">开始计算</button>
    <span>计算结果为:<span id="result">-</span></span>
    <div>在计算期间你可以填XX表单</div>
    <input type="text" placeholder="请输入姓名" />
    <input type="text" placeholder="请输入年龄" />
    <script>
      function calc() {
        const num = parseInt(document.getElementById('num').value)
        let result = 0
        let startTime = performance.now()
        // 计算求和(模拟复杂计算)
        for (let i = 0; i <= num; i++) {
          result += i
        }
        // 由于是同步计算,在没计算完成之前下面的代码都无法执行
        const time = performance.now() - startTime
        console.log('总计算花费时间:', time)
        document.getElementById('result').innerHTML = result
      }
    </script>
  </body>
</html>

如上,第一个输入框和按钮负责模拟复杂的估算,比如输入10000000000,点击开始估算,此时主线程处理的依然是处理同步估算逻辑,在估算完成之前,你会发现页面卡住了,下面的两个输入框也很难点击交互。 在我的笔记本上,估计需要大约 14 秒。 这个冻结时间给用户带来了非常差的体验。

打开控制台调用也可以看到CPU使用率为100%

如果这部分的估算交给Web Worker的话,修改代码:

<script>
const worker = new Worker('./worker.js')
function calc() {
    const num = parseInt(document.getElementById('num').value)
    worker.postMessage(num)
}
worker.onmessage = function (e) {
    document.getElementById('result').innerHTML = e.data
}
</script>

./worker.js

function calc(num) {
  let result = 0
  let startTime = performance.now()
  // 计算求和(模拟复杂计算)
  for (let i = 0; i <= num; i++) {
    result += i
  }
  // 由于是同步计算,在没计算完成之前下面的代码都无法执行
  const time = performance.now() - startTime
  console.log('总计算花费时间:', time)
  self.postMessage(result)
}
self.onmessage = function (e) {
  calc(e.data)
}

然后重复上面同样的操作,输入10000000000进行计算,你会发现下面的两个输入框都可以正常流畅的输入,整个页面不会卡顿。

Worker运行在独立于主线程的后台线程中,共享并执行大量CPU密集型操作(但运行时间不会缩短),解放了主线程,主线程可以在一个线程中响应用户操作。及时处理javascript实战,不会造成死机现象。 使用Web Worker后,控制台工具可以看到CPU使用率处于较低的正常水平,计算过程与估计之前相同。

音视频画布绘图屏幕录制

这是我在工作中遇到的场景,通过绘制WebRTC视频流来录制视频,最后生成视频。

javascript模块化-这次,我想了解一下javascript的模块化

之前写过一篇文章,介绍如何实现前端录屏。 这篇文章基本上没有认真写,纯粹是一个记录,而且现在的代码和本文的示例代码差别很大(也就是说示例代码还有很大的改进空间),就是建议大体看思路,而不是去研究具体的实现细节,毕竟思路是相通的。

以上面文章的代码为例(现在最新的代码与公司代码业务结合在一起,不会发布),有一个CPU密集型的操作

// 16ms一次的定时器
refreshTimer.current = window.setInterval(onRefreshTimer, 16) 
// onRefreshTimer 函数里面做的实际就是高频执行 recorderDrawFrame() 方法
// 录屏绘制操作
const recorderDrawFrame = () => {
    const $recorderCanvas = recorderCanvas.current!
    const $player = videoRef.current!
    const ctx = recorderContext.current!
    const { width, height } = getResolution()
    $recorderCanvas.width = width
    $recorderCanvas.height = height
    
    // 其中这个绘制函数对CPU占用率会比较高(在低配置的电脑浏览器上)
    ctx.drawImage(
      $player,
      0,
      0,
      $player.videoWidth,
      $player.videoHeight,
      0,
      0,
      $recorderCanvas.width,
      $recorderCanvas.height,
    )
    drawWatermark(ctx, width)
}

那么如何优化呢? 就是将整个onRefreshTimer定时器函数交给Web Worker来执行。

上面提到,Web Worker 虽然对 DOM 操作有限制,但是可以使用 setTimeout/setInterval 等 API,所以具体实现就是将 Worker 封装为一个类,处理类内部的逻辑,然后将 setInterval 等方法暴露给调用外部实例。

if (worker) {
    refreshTimer.current = worker.setInterval(onRefreshTimer, 16)
}

其他场景

我看过网上的文章,其他场景包括:

可以参考阅读:我不让大家学worker但是没有应用场景

结语

恐怕Web Worker整体在日常业务开发中使用的不多,但如果遇到上述类似的场景,我们在优化的方向上可以有另一种选择。

参考

本文为参加“金石计划.瓜分6万现金奖”

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 javascript javascript模块化-这次,我想了解一下javascript的模块化 https://www.wkzy.net/game/127475.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务