Javascript V8引擎与Blob对象

本文深入探讨了JavaScript的V8引擎,包括内存限制、对象分配和垃圾回收机制,重点阐述了新生代与老生代的垃圾回收策略。此外,还详细介绍了前端的Blob对象,包括创建、属性、方法、使用场景,如文件分片上传和图片粘贴,以及与clipboardData对象的交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaScript V8引擎

V8 是驱动 Google Chrome 的 JavaScript 引擎的名称。

V8 提供了 JavaScript 执行的运行时环境。 DOM 和其他 Web 平台 API 由浏览器提供。

V8的内存限制

在64位系统下约为1.4 GB,32位系统下约为0.7 GB。

V8为什么要限制堆的大小?

  • 表层原因:V8最初是为浏览器设计的,不太可能遇到使用大量内存的场景。
  • 深层原因:V8的垃圾回收机制的限制。

V8的对象分配

在V8中,所有的JavaScript对象都是通过堆来进行分配的。

img

当在代码中声明变量并赋值时,所使用对象的内存就分配在堆中。如果已经申请到的堆空闲内存不够分配新的对象,将继续申请堆内存,直到堆的大小超出V8的限制为止。

V8的垃圾回收机制

V8的垃圾回收策略主要基于分代式垃圾回收机制,把内存分为新生代和老生代。

分代式垃圾回收机制:按照对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同的分代的内存实施更高效的回收算法。

新生代:新生代中的对象为存活时间较短的对象,采用复制算法(内存一分二,分为From和To)。

老生代:老生代中的对象为存活时间较长或常驻内存的对象,使用标记清除和标记整理算法。

img

新生代中的垃圾回收

img

新生代中的对象主要通过Scavenge算法进行垃圾回收,Scavenge算法在具体实现上采用Cheney算法。

大致过程为:将堆内存一分为二,每一部分空间称为semispace。在这两个semispace中,只有一个空间处于使用中,另一个处于空闲状态。处于使用状态的空间称为From空间,空闲状态的空间称为To空间。分配对象时,现在From空间进行分配。当开始进行垃圾回收时会检查From空间中的存活对象,这些存活对象会被分配到To空间中,而非存活对象占用的空间将被释放。完成释放后,From空间和To空间的角色发生对换。简而言之,就是将存活对象在两个空间中来回复制。

对象晋升

当一个对象经过多次复制依然存活时,将会被认为是生命周期较长的对象。这种对象随后会被移动到老生代中(对象晋升),采用新的算法进行管理。

对象晋升的条件:

  • 对象是否经理过Scavenge回收。
  • To空间的内存用比是否超过限制。

在默认情况下,V8的对象分配主要集中在From空间中。对象从From空间中复制到To空间时,会检查它的内存地址来判断这个对象是否已经经历过一次Scavenge回收。如果已经经历过了,会将该对象从From空间复制到老生代空间中,如果没有,则复制到To空间中。

img

另一个判断条件是To空间的内存占用比。当要从From空间复制一个对象到To空间时,如果To空间已经使用了超过25%,则这个对象直接晋升到老生代空间中。之后From和To地位互换。

img

老生代中的垃圾回收

对象晋升后,在老生代中接收新的回收算法处理。

V8在老生代中主要采用Mark-Sweep(标记清除)和Mark-Compact(标记整理)相结合的方式进行垃圾回收。

Mark-Sweep分为两个阶段:标记和清除。

在标记阶段遍历堆中所有对象,并标记活着的对象,在随后的清理阶段中,只清理没有被标记的对象,即只清理死亡的对象。如下图: 黑色部分为标记死亡的对象。

img

Mark-Sweep最大的问题是在进行一次标记清除回收后会造成内存空间的不连续。这种内存碎片会对后续的内存分配造成问题。例如:此时,当需要分配一个比较大的对象时,所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而此时的回收是不必要的。

为解决Mark-Sweep后的内存碎片化问题,Mark-Compact(标记-整理)被提出。Mark-Compact在整理阶段将活的对象向一端移动,移动完成后,直接清理掉边界外的内存。如下图所示,白色格子为存活对象,深色格子为死亡对象,浅色格子为存活对象移动后留下的空洞。

img

在V8的回收策略中,Mark-Sweep和Mark-Compact是结合使用的。主要使用Mark-Sweep,在不足以对从新生代中晋升过来的对象进行分配时才会使用Mark-Compact。

img

Incremental Marking(增量标记)

为了避免出现JavaScript应用逻辑与垃圾回收器看到的不一致的情况,垃圾回收的3种基本算法都需要将应用逻辑暂停下来,待执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为“全停顿”(stop-the-world)。

在V8的分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代默认配置得较小,且其中存活对象通常较少,所以即便它是全停顿的影响也不大。但V8的老生代通常配置得较大,且存活对象较多,全堆垃圾回收(full 垃圾回收)的标记、清理、整理等动作造成的停顿就会比较可怕,需要设法改善。

为了降低全堆垃圾回收带来的停顿时间,V8先从标记阶段入手, 将原本要一口气停顿完成的动作改为增量标记(Incremental Marking),也就是拆分为许多小“步进”,每做完一“步进”就让JavaScript应用逻辑执行一小会儿,垃圾回收与应用逻辑交替执行直到标记阶段完成。

img

V8后续还引入了延迟清理(lazy sweeping)与增量式整理(incremental compaction),让清理与整理动作也变成增量式的。同时还计划引入并行标记与并行清理,进一步利用多核性能降低每次停顿的时间。

前端Blob对象

Blob 对象表示一个不可变、原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript中的原生形式。File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件。

Blob(Binary Large Object)对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的 API(比如 File 对象),都是建立在 Blob 对象基础上的,继承了它的属性和方法。

通过构造函数创建Blob对象

var blob = new Blob(dataArr:Array, opt:{type:string});

  • dataArray:数组,包含了要添加到Blob对象中的数据,数据可以是任意多个ArrayBuffer,ArrayBufferView, Blob,或者 DOMString对象。
  • opt:对象,用于设置Blob对象的属性(如:MIME类型)

1、创建一个装填DOMString对象的Blob对象
img

2、创建一个装填ArrayBuffer对象的Blob对象
img

3、创建一个装填ArrayBufferView对象的Blob对象(ArrayBufferView可基于ArrayBuffer创建,返回值是一个类数组。如下:创建一个8字节的ArrayBuffer,在其上创建一个每个数组元素为2字节的“视图”)
img

Javascript 之 ArrayBuffer: https://www.cnblogs.com/chris-oil/p/8615559.html

slice() 方法原本接受 length 作为第二个参数,以表示复制到新 Blob 对象的字节数。如果设置的参数使 start + length 超出了源 Blob 对象的大小,则返回从开始到结尾的所有数据。

slice() 方法在某些浏览器和版本上带有浏览器引擎前缀:比如 Firefox 12 及更早版本的blob.mozSlice() 和 Safari 中的blob.webkitSlice()。 没有浏览器引擎前缀的老版本 slice() 方法有不同的语义,并且已过时。Firefox 30 取消了对 blob.mozSlice() 的支持。

属性
  • Blob.isClosed (只读)

    布尔值,指示 Blob.close() 是否在该对象上调用过。 关闭的 blob 对象不可读。

  • Blob.size (只读)

    Blob 对象中所包含数据的大小(字节)。

  • Blob.type (只读)

    一个字符串,表明该Blob对象所包含数据的MIME类型。如果类型未知,则该值为空字符串。

方法
  • Blob.close()

    关闭 Blob 对象,以便能释放底层资源。

  • Blob.slice([start[, end[, contentType]]])

    返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。其实就是对这个blob中的数据进行切割,我们在对文件进行分片上传的时候需要使用到这个方法。

看到上面这些方法和属性,使用过HTML5提供的File接口的应该都很熟悉,这些属性和方法在File接口中也都有。 其实File接口就是基于Blob,继承blob功能并将其扩展为支持用户系统上的文件,也就是说:

File接口中的Flie对象就是继承与Blob对象。

使用场景
1、文件分片上传

首先说说分片上传,我们在进行文件上传的时候,因为服务器的限制,会限制每一次上传到服务器的文件大小不会很大,这个时候我们就需要把一个需要上传的文件进行切割,然后分别进行上传到服务器。

假如需要做到这一步,我们需要解决两个问题:

  • 怎么切割?
  • 怎么得知当前传输的进度?

首先怎么切割的问题上面已经有过说明,因为File文件对象是继承与Blob对象的,因此File文件对象也拥有slice这个方法,我们可以使用这个方法将任何一个File文件进行切割。

代码如下:

var BYTES_PER_CHUNK = 1024 * 1024; // 每个文件切片大小定为1MB .
var blob = document.getElementById("file").files[0];
var slices = Math.ceil(blob.size / BYTES_PER_CHUNK);
var blobs = [];
slices.forEach(function(item, index) {
    blobs.push(blob.slice(index,index + 1));
});

通过上面的方法。我们就得到了一个切割之后的File对象组成的数组blobs;

接下来要做的时候就是讲这些文件分别上传到服务器。

在HTTP1.1以上的协议中,有Transfer-Encoding这个编码协议,用以和服务器通信,来得知当前分片传递的文件进程。

这样解决了这两个问题,我们不仅可以对文件进行分片上传,并且能够得到文件上传的进度。

Transfer-Encoding: https://blog.youkuaiyun.com/liuxiao723846/article/details/107432433

2、粘贴图片

blob还有一个应用场景,就是获取剪切板上的数据来进行粘贴的操作。例如通过QQ截图后,需要在网页上进行粘贴操作。

粘贴图片我们需要解决下面几个问题

  1. 监听用户的粘贴操作
  2. 获取到剪切板上的数据
  3. 将获取到的数据渲染到网页中

首先我们可以通过paste事件来监听用户的粘贴操作:

document.addEventListener('paste', function (e) {
    console.info(e);
});

然后通过事件对象中的clipboardData 对象来获取图片的文件数据。

clipboard对象: https://www.ruanyifeng.com/blog/2021/01/clipboard-api.html

clipboardData对象介绍

介绍一下 clipboardData 对象,它实际上是一个 DataTransfer 类型的对象, DataTransfer 是拖动产生的一个对象,但实际上粘贴事件也是它。

clipboardData 的属性介绍
属性类型说明
dropEffectString默认是 none
effectAllowedString默认是 uninitialized
filesFileList粘贴操作为空List
itemsDataTransferItemList剪切板中的各项数据
typesArray剪切板中的数据类型 该属性在Safari下比较混乱
items 介绍

items 是一个 DataTransferItemList 对象,自然里面都是 DataTransferItem 类型的数据了。

属性

items 的 DataTransferItem 有两个属性 kind 和 type

属性说明
kind一般为 string 或者 file
type具体的数据类型,例如具体是哪种类型字符串或者哪种类型的文件,即 MIME-Type
方法
方法参数说明
getAsFile如果 kind 是 file ,可以用该方法获取到文件
getAsStringfunction(str)如果 kind 是 string ,可以用该方法获取到字符串str

在原型上还有一些其他方法,不过在处理剪切板操作的时候一般用不到了。

type 介绍

一般 types 中常见的值有 text/plain 、 text/html 、 Files 。

说明
text/plain普通字符串
text/html带有样式的html
Files文件(例如剪切板中的数据)

有了上面这些方法,我们可以解决第二个问题即获取到剪切板上的数据。

document.addEventListener('paste', function (e) {
    console.info(e);
    const cbd = e.clipboardData;
    for(let i = 0; i < cbd.items.length; i++) {
        const item = cbd.items[i];
        console.info(item);
        if(item.kind == "file"){
            const blob = item.getAsFile();
            if (blob.size === 0) {
                return;
            }
            console.info(blob);
        }
    }
});

最后我们需要将获取到的数据渲染到网页上。

其实这个本质上就是一个类似于上传图片本地浏览的问题。我们可以直接通过HTML5的File接口将获取到的文件上传到服务器然后通过讲服务器返回的url地址来对图片进行渲染。也可以使用fileRender对象来进行图片本地浏览。

fileRender对象简介

从Blob中读取内容的唯一方法是使用 FileReader。

FileReader接口有4个方法,其中3个用来读取文件,另一个用来中断读取。无论读取成功或失败,方法并不会返回读取结果,这一结果存储在result属性中。

方法名参数描述
readAsBinaryStringfile将文件读取为二进制编码
readAsTextfile,[encoding]将文件读取为文本
readAsDataURLfile将文件读取为DataURL
abort(none)终端读取操作

FileReader接口包含了一套完整的事件模型,用于捕获读取文件时的状态。

事件描述
onabort中断
onerror出错
onloadstart开始
onprogress正在读取
onload成功读取
onloadend读取完成,无论成功失败

通过上面的方法以及事件,我们可以发现,通过readAsDataURL方法及onload事件就可以拿到一个可本地浏览图片的DataURL。

最终代码如下:

document.addEventListener('paste', function (e) {
  console.info(e);
  const cbd = e.clipboardData;
  const fr = new FileReader();
  for(let i = 0; i < cbd.items.length; i++) {
    const item = cbd.items[i];
    console.info(item);
    if(item.kind == "file"){
      const blob = item.getAsFile();
      if (blob.size === 0) {
        return;
      }
      console.info(blob);
      fr.readAsDataURL(blob);
      fr.onload = function(e){
        var result=document.getElementById("paste");
        //显示文件
        result.innerHTML='<img src="' + this.result +'" alt="" />';
      }
    }
  }
});

前端用JS实现粘贴图片实现上传图片功能:https://www.freesion.com/article/9231747451/

演示:

1、创建一个文本类型的Blob对象
const str = '123';
const blob = new Blob([str], {
  type: 'text/plain'
})
console.log(blob);
// 读取文本
blob.text().then(res => console.log(res))

image.png

2、文件下载与图片预览
<a id="btn">下载</a>
<input type="file" id="input" />
const str = `<div>hello</div>`
const blob = new Blob([str], {
  type: 'text/html'
})

btn.onclick = function(e){
  this.setAttribute('download', '123.html')
  // 将Blob对象转化为URL地址
  this.href = URL.createObjectURL(blob);
}

input.onchange = function(e){
  const file = e.target.files[0];
  console.log(file);
  
  // 文件下载
  const a = document.createElement('a');
  a.setAttribute('download', 'myDownload.html');
  a.click();
  
  // 图片预览
  const img = new Image();
  img.src = URL.createObjectURL(file);
  document.body.appendChild(img);
  
  // 异步图片预览
  const img = new Image();
  const fileRead = new FileReader();
  
  fileRead.onload = function(){
    img.src = fileRead.result;
  }
  fileRead.readAsDataURL(file);
}
3、文件切片上传与合并
<!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>fileSlice</title>
</head>
<body>

    <input type="file" id="input" >
    <button id="btn">切片上传</button>

    <script>
        let chunkSize = 1024, // 1kb
            index = 0;

        btn.addEventListener('click', upload);

        function upload(){
            console.log(input.files[0]);
            const file = input.files[0];
            const [filename, ext] = file.name.split('.');
            
            let start = index * chunkSize;
            if (start > file.size) {
                const fullName = file.name;
                fetch(`http://127.0.0.1:1000/merge?total=${index}&fullName=${fullName}`).
                    then(() => {
                        console.log('合并完成');
                    })
              return;
            }
            
            const blob = file.slice(start, start + chunkSize);
            // console.log("blob", blob);
            const blobName = `${filename}${index}.${ext}`
            const blobFile = new File([blob], blobName);
            console.log("file", blobFile);
            
            const formData = new FormData();
            formData.append('file', blobFile);
            
            fetch('http://127.0.0.1:1000/upload', {
                method: 'post',
                body: formData
            }).then(() => {
                index ++; 
                upload();
            })
        }
    </script>
</body>
</html>
const express = require('express');
const App = express();
const Path = require('path');
const fs = require('fs');
const multiparty = require('multiparty');
 
App.use(express.static(Path.join(__dirname, 'public')));
App.use(express.static(Path.join(__dirname, 'uploads')));
 
App.all('*',function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
  res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  next();
});
 
App.post('/upload', function(req, res){
  /* 生成multiparty对象,并配置上传目标路径 */
  let form = new multiparty.Form({
    uploadDir: 'uploads'
  });

  form.parse(req, function (err, fields, files) {
    try {
      let inputFile = files.file[0];
      let newPath = form.uploadDir + "/" + inputFile.originalFilename;
      // 同步重命名文件名 会重新写入文件
      fs.renameSync(inputFile.path, newPath);
      res.send({ data: "上传成功!" });
    } catch (err) {
      console.log(err);
      res.send({ err: "上传失败!" });
    };
  })
});

App.get('/merge', function(req, res) {
  console.log(req.query.total);
  const { total, fullName } = req.query;
  console.log("fullname", fullName);
  const [filename, ext] = fullName.split('.');
  for (let i = 0; i < total; i++) {
    // 追加写入到文件中
    fs.appendFileSync(`./uploads/${fullName}`, fs.readFileSync(`./uploads/${filename}${i}.${ext}`));
    // 删除本次使用的chunk    
    fs.unlinkSync(`./uploads/${filename}${i}.${ext}`);
  }
  res.send({data: '合并成功'});
})
 
App.listen(1000, function(){
    console.log('Files Server listening on port 1000');
});
4、图片粘贴
<div id="paste" style="width: 200px; height: 200px; background-color: antiquewhite;"></div>
<script>
  document.addEventListener('paste', function (e) {
    console.info(e);
    const cbd = e.clipboardData;
    const fr = new FileReader();
    for(let i = 0; i < cbd.items.length; i++) {
      const item = cbd.items[i];
      console.info(item);
      if(item.kind == "file"){
        const blob = item.getAsFile();
        if (blob.size === 0) {
          return;
        }
        console.info(blob);
        fr.readAsDataURL(blob);
        fr.onload = function(e){
          var result=document.getElementById("paste");
          //显示文件
          result.innerHTML='<img src="' + this.result +'" alt="" />';
        }
      }
    }
  });
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值