全面探索Chrome浏览器的源代码

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Chrome浏览器是Google开发的开源项目,源代码包含了现代网络技术、浏览器架构和软件工程实践的知识。本文将从C++编程、多线程处理、渲染引擎、网络协议、JavaScript引擎、性能优化、安全机制等多个方面深入分析Chrome源代码的技术细节。开发者可以通过Git仓库获取和研究源代码,学习如何构建高效的浏览器架构和提升软件工程能力。
chrome 源代码

1. Chrome浏览器开源项目概述

Chrome浏览器作为互联网上最广泛使用的浏览器之一,其开源项目的背后涵盖了复杂的技术架构和高效的开发流程。本章将带您快速浏览Chrome项目的核心组成,并且简要介绍其开源社区的运作方式,为接下来更深入的技术探索奠定基础。

1.1 Chrome的起源与发展

Google Chrome浏览器在2008年首次发布,其设计理念简洁、快速,并且提供了丰富的扩展性。随着时间的发展,Chrome逐渐成长为了一个功能丰富、性能优越的现代浏览器,支持包括PC、移动设备以及平板电脑在内的多种平台。

1.2 开源项目与贡献方式

作为开源项目,Chromium(Chrome的基础开源版本)允许全球开发者访问、研究和贡献代码。开发者可以通过GitHub等平台提交代码变更,参与问题跟踪和讨论,共同推动项目进步。

1.3 Chrome技术栈概览

Chrome采用了多层架构设计,其技术栈包括但不限于渲染引擎Blink、JavaScript引擎V8、网络协议栈、安全机制等。这些组件相互协作,形成了一个高效的浏览器运行环境。接下来的章节,我们将深入探讨这些关键技术组件。

2. 深度探索C++编程与内存管理

2.1 C++在Chrome中的应用

2.1.1 C++在浏览器开发中的优势

C++语言因其执行效率高、系统资源占用小而被广泛应用于高性能软件的开发。在浏览器这样的需要处理大量数据和进行复杂计算的场景中,C++的优势尤为明显。它能够让开发者对内存管理、资源释放以及执行流程有更加细致的控制,从而编写出执行效率极高的代码。此外,C++支持面向对象、泛型编程等编程范式,为大型软件项目的模块化开发提供了便利。在Chrome这样的大型项目中,C++的这些特性有助于提升浏览器的性能和稳定性。

2.1.2 C++特性在Chrome中的具体实例

Chrome浏览器中的许多核心组件都是使用C++编写的。例如,V8 JavaScript引擎,它是Chrome中处理JavaScript代码的核心部件,其底层即采用了C++进行构建。V8引擎的性能优化技术,如即时编译(JIT)和隐藏类,都是依赖于C++来实现的。同时,Blink渲染引擎的多个组件,比如DOM树的构建与操作,也是用C++实现,以确保在解析和渲染网页时能够达到最优的性能表现。

2.2 Chrome的内存管理机制

2.2.1 内存分配与释放策略

在C++项目中,内存管理是一个至关重要的方面。由于C++提供了直接操作内存的能力,如果内存管理不当,很容易导致内存泄漏、访问违规等问题。因此,Chrome采用了精细的内存分配和释放策略。浏览器中,内存分配通常由对象池(Object Pool)、内存池(Memory Pool)或更高级的内存分配器(如tcmalloc)来管理。这些方法旨在减少内存碎片化、降低内存分配和回收的开销。此外,通过智能指针(如std::shared_ptr)来管理资源的生命周期,从而减少开发者忘记手动释放内存的风险。

2.2.2 内存泄漏的检测与预防

Chrome利用多种工具来检测和预防内存泄漏。在编译时,可以开启AddressSanitizer等内存错误检测工具来识别潜在的内存泄漏。运行时,Chrome使用自己的内存分析工具(比如分配大小记录器)来监控内存使用情况,并通过定期的垃圾回收来释放无用对象所占用的内存。此外,Chrome的内存泄漏检测机制还包括内存分配跟踪和实时内存使用报告,这有助于开发团队及时发现并修复内存泄漏问题。

2.2.3 Chrome的垃圾回收过程

Chrome中,垃圾回收机制是内存管理的关键部分。V8引擎作为Chrome的JavaScript执行环境,其垃圾回收过程对浏览器性能有直接影响。V8使用了几种不同的垃圾回收算法来平衡性能和内存利用率,如标记-清除算法、标记-整理算法以及分代收集算法等。在Chrome中,垃圾回收过程主要由V8控制,但浏览器也会进行一定的优化,如减少DOM对象的垃圾回收频率,以避免对用户界面的性能影响。

// 示例代码:使用智能指针来管理资源
#include <memory>

class Resource {
public:
    void UseResource() { /* 使用资源的代码 */ }
};

void FunctionUsingResource() {
    std::shared_ptr<Resource> resourcePtr = std::make_shared<Resource>();
    // 使用资源
    resourcePtr->UseResource();
    // 当shared_ptr离开作用域时,资源自动释放
}

上例中,通过 std::shared_ptr 来管理 Resource 类的实例,该智能指针负责在适当的时候释放资源,从而避免内存泄漏。这是现代C++中推荐的内存管理方式。

在讲述完C++在Chrome中的应用和内存管理机制之后,我们可以看到C++编程语言在构建高性能、可维护的复杂系统中发挥着关键作用。下一章节我们将深入探讨Chrome的Blink引擎是如何解析和渲染网页内容的。

3. Blink引擎与网页渲染解析

3.1 Blink渲染引擎解析HTML/CSS

3.1.1 Blink如何解析HTML文档结构

Blink引擎是Google开发的开源网页浏览器引擎,它负责在Chrome浏览器中解析HTML文档。作为渲染引擎,Blink的工作原理是将HTML文档转换为可视化的网页。解析过程从文档对象模型(DOM)树的构建开始。

当Blink开始解析HTML文档时,它首先遇到的是HTML标记。引擎会按照这些标记构建一个节点树,每个节点代表文档中的一个元素。解析过程中,Blink会使用一系列的解析规则来确定标签的嵌套结构,属性值以及文本内容。

在构建DOM树的过程中,Blink还会遇到一些特殊的指令,比如 <!DOCTYPE> 。这些指令会指导解析器按照特定的标准模式进行解析,比如HTML5标准。

<!DOCTYPE html>
<html>
<head>
    <title>Document Title</title>
</head>
<body>
    <h1>Welcome to Blink World</h1>
</body>
</html>

上述代码段是一个简单的HTML文档。Blink渲染引擎会首先读取 <!DOCTYPE html> 来设置标准模式,并开始构建文档的DOM结构。每个开始标签 <xxx> 和结束标签 </xxx> 都会创建一个元素节点,而文本内容则创建一个文本节点。

解析的过程涉及到了状态机和一系列的词法分析规则。例如,当解析器遇到 < 时,它会期待一个标签的开始,然后会解析标签名,属性,直到遇到 > 标记标签的结束。标签内部的内容随后会被处理,继续构建DOM树。

3.1.2 CSS属性的解析与样式计算

Blink引擎不仅解析HTML,还负责将CSS样式应用于DOM树。这一过程涉及到CSS选择器的解析和匹配,以及最终计算每个元素的最终样式。

当Blink接收到一个或多个CSS文件时,它将这些规则解析为样式表,再将样式表应用于相应的DOM节点。这个过程主要分为三个步骤:

  1. 样式表的解析 :CSS规则被分解成CSS声明,每个声明包含了属性名和属性值。声明被组织在选择器中,选择器决定了哪些HTML元素会被该声明影响。

  2. 选择器匹配 :Blink引擎会计算每个选择器与DOM树中哪些节点匹配。这个过程非常复杂,因为CSS选择器的组合是相当灵活的。

  3. 样式计算 :一旦选择器和DOM节点匹配完成,Blink就计算每个元素的最终样式。这个过程会涉及到层叠(优先级确定)和继承(比如字体家族)。

h1 {
    color: blue;
    font-size: 20px;
}

在上述CSS例子中, h1 选择器会匹配所有的 <h1> 标签,然后这些标签的文本颜色会被设置为蓝色,字体大小会被设置为20像素。

样式计算后,Blink会创建一个包含所有计算样式的结构,称为渲染树(Render Tree)。渲染树与DOM树类似,但不包括那些不需要显示的节点,比如 <head> 标签或被CSS隐藏的元素。渲染树中每一个节点都包含了与元素最终样式相关的信息。

3.2 Blink的性能优化策略

3.2.1 加速DOM操作的技巧

DOM操作在浏览器中是非常常见的,但也是非常消耗性能的操作之一。随着页面的复杂性增加,DOM操作的性能开销会显著增加,尤其在JavaScript中进行大量的DOM操作时。因此,Blink引擎对性能优化尤为重视,尤其是在DOM操作方面。

  1. 使用DocumentFragment :如果需要进行一系列的DOM操作,可以将这些操作放到一个DocumentFragment中。DocumentFragment是一个轻量级的文档对象,不与实际的DOM结构关联,因此可以在不触发布局计算的情况下创建和修改。
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = 'Item ' + i;
    fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);

在上述代码中,创建了一个DocumentFragment,将100个列表项添加到其中,然后一次性将这个DocumentFragment附加到DOM上,大大减少了重绘和重排的次数。

  1. 最小化DOM重排和重绘 :DOM重排是浏览器重新计算元素位置和几何结构的过程,而重绘则是重新绘制元素的过程。Blink通过优化DOM操作的顺序和使用硬件加速等技术来减少这些操作的开销。

  2. 使用 requestAnimationFrame :对于需要动画效果的DOM操作,使用 requestAnimationFrame 可以让浏览器在适当的时机进行重绘,优化动画性能。

3.2.2 图层合并与合成技术

由于现代网页包含大量的动画和复杂的布局,仅仅通过传统的方式来渲染网页已经无法满足性能上的要求。Blink引擎在渲染过程中应用了图层合并和合成技术,大大提高了渲染性能。

  1. 图层的使用 :通过将页面拆分成多个图层,Blink可以独立地更新和渲染这些图层,而不必每次都重新渲染整个页面。图层的创建基于某些DOM元素的属性,比如元素具有 transform opacity 属性时,通常会被创建为独立图层。

  2. 合成器线程 :图层数据传递给合成器线程,该线程负责图层的合并和最终的渲染。合成器线程通过GPU(图形处理单元)加速来处理这些任务,将图层数据转换成屏幕上的像素。

  3. 隐式合成 :即使没有显式地使用CSS3变换,Blink也会隐式地为一些需要频繁更新的DOM部分创建独立图层,以优化性能。例如,滚动页面时,滚动区域的内容会被单独渲染在图层上,这样滚动操作就无需对整个页面进行渲染。

const el = document.getElementById('myElement');
el.style.transform = 'translateY(10px)';
el.style.willChange = 'transform';

在这个例子中,通过添加 transform 属性和 willChange 属性,Blink可以知道这个元素将会进行动画操作,从而可能提前创建一个独立图层。

通过这些优化技术,Blink能够提供更加流畅的用户体验,特别是在动画和交互比较密集的应用场景中。

3.2.3 CSS动画与硬件加速

Blink引擎还利用CSS动画和硬件加速的结合,进一步提升了渲染效率。硬件加速意味着使用计算机的图形处理硬件来执行某些计算任务,通常比CPU执行速度快得多。

当使用CSS3动画对DOM元素进行动画效果时,如果元素使用了GPU硬件加速的属性,如 transform opacity ,Blink引擎会将相关的渲染任务交给GPU处理。由于GPU擅长处理并行任务,它能够快速地处理大量的绘图操作,大大提升动画性能。

然而,硬件加速并非没有成本。当元素离开GPU加速状态时,可能会触发一个叫做“合成器劫持”的问题,这时浏览器需要重新计算元素的样式和布局,从而导致性能下降。为了避免这种情况,开发者可以通过适当的技术手段,比如使用 will-change 属性,提前通知浏览器对特定元素启用硬件加速。

.element {
    will-change: transform;
}

上述CSS规则告知浏览器该元素将会发生变换,浏览器可能会为此元素创建独立的图层,并使用硬件加速来处理变换。

3.2.4 Web Workers与后台渲染

为了进一步提高性能,Blink还支持使用Web Workers来处理JavaScript代码。Web Workers允许在浏览器后台线程中运行JavaScript,这不会影响主线程的渲染和用户交互。

Web Workers是独立于主线程的线程,因此在Web Workers中执行的JavaScript代码不会阻塞UI。这对于处理复杂的后台计算非常有用,例如,一个网络请求或者图像处理任务可以在Web Workers中进行,而主线程可以继续响应用户的输入和更新UI。

// main.js
const worker = new Worker('worker.js');
worker.postMessage('start');

// worker.js
self.onmessage = function(event) {
    if (event.data === 'start') {
        // 执行后台任务
    }
    self.postMessage('完成');
}

在这个简单的例子中, main.js 脚本创建了一个Web Worker并发送了一个开始任务的消息。 worker.js 脚本接收到这个消息后执行后台任务,并在完成后向主线程发送一个完成消息。

Blink的这些性能优化技术使得复杂网页的性能可以和桌面应用程序相媲美,从而为开发者提供了强大的工具来构建高性能的Web应用。

graph TB
    A[开始解析HTML] -->|构建DOM树| B[识别标签和属性]
    B --> C[处理特殊指令]
    C --> D[DOM树构建完成]
    D --> E[开始解析CSS]
    E -->|匹配选择器| F[计算元素样式]
    F --> G[构建渲染树]
    G --> H[渲染树完成]
    H -->|图层处理| I[图层合并与合成]
    I --> J[硬件加速]
    J --> K[渲染到屏幕]

通过这个流程图,我们可以清晰地看到从HTML和CSS的解析到最终渲染的整个过程,以及Blink如何通过各种技术手段优化渲染性能。

4. Chrome的多进程架构与进程间通信

4.1 Chrome的多进程架构

4.1.1 浏览器进程与渲染进程的分工

为了提高性能和安全性,现代浏览器如Chrome采用了多进程架构。这种架构将浏览器的不同功能划分为多个独立的进程,每个进程负责特定的任务。在Chrome中,主要的进程类型包括浏览器进程和渲染进程。

浏览器进程主要负责浏览器的界面部分,包括地址栏、书签栏、前进后退按钮以及访问的网页之外的其他所有部分。此外,浏览器进程还包括网络请求的处理、文件访问和权限管理等功能。

渲染进程则专注于网页内容的渲染。当用户打开一个新标签页时,Chrome会为每个标签页启动一个新的渲染进程。每个渲染进程都包括了渲染引擎的核心部分,用于解析HTML、CSS,以及执行JavaScript来生成页面的布局、样式和动态内容。这个架构允许每个网页都在隔离的环境中运行,极大提高了系统的稳定性和安全性。

4.1.2 插件进程与GPU进程的作用

Chrome的多进程架构还包括专门的插件进程和GPU进程。插件进程运行浏览器插件,如Flash、PDF阅读器等,它们可以独立运行而不影响到浏览器的其他部分,从而避免了插件崩溃导致整个浏览器崩溃的问题。每个插件都是在自己的进程中运行,这提供了更好的隔离和安全性。

GPU进程则负责处理与图形相关的任务。随着网页开始使用更多的硬件加速特性,如Canvas和WebGL,大量的图形处理任务被委托给GPU来执行。通过将这些图形任务分配给专门的进程,Chrome可以更高效地利用硬件资源,并提高渲染性能。

4.2 进程间通信IPC机制

4.2.1 Chrome中IPC的实现原理

进程间通信(IPC)是多进程架构中不可或缺的一部分,允许不同的进程交换信息和请求服务。在Chrome中,IPC是基于消息传递的机制。每个进程都有自己的主线程,主线程可以发送和接收消息来与其他进程通信。消息可以包括命令、请求或数据,它们通过一个通道传递,并被相应进程中的监听器处理。

Chrome的IPC系统被设计得非常高效,主要采用同步和异步消息。同步消息会阻塞发送进程直到消息被处理,适用于需要立即反馈的操作。异步消息则不会阻塞发送进程,发送者可以在不等待消息处理的情况下继续执行其他任务。这种方式使得Chrome的多进程架构既保持了高性能,又避免了进程间的死锁。

4.2.2 IPC在安全性和性能上的考量

在安全性方面,IPC机制确保了不同进程之间的隔离。由于每个进程都运行在独立的内存空间中,一个进程中的安全漏洞不会直接影响到其他进程。即使一个渲染进程被恶意代码攻击,其他进程如浏览器进程和GPU进程仍然可以继续运行,降低了单点故障的风险。

从性能角度来看,IPC的设计直接影响到了浏览器的响应速度和资源利用率。为了优化性能,Chrome不断对IPC机制进行改进,包括减少消息传递的开销和优化消息序列化和反序列化的过程。Chrome还实现了跨进程对象共享,比如利用Mojo系统提供的能力,有效地减少了资源的重复复制,提高了IPC的效率。

Chrome的多进程架构和IPC机制为浏览器的高效、安全运行提供了坚实的基础。在下一章节中,我们将深入探讨Chrome的V8引擎和其在网络安全方面的表现。

5. 深入研究Chrome的V8引擎与网络安全

5.1 JavaScript引擎V8实现细节

5.1.1 V8的编译与执行流程

V8引擎是Chrome浏览器的核心组件之一,其主要负责JavaScript代码的编译和执行。V8使用了即时编译技术(JIT),这意味着JavaScript代码在执行前不需要编译成机器码,而是在运行时直接编译。V8引擎首先会将JavaScript源代码解析成语法树(AST),然后将AST转换为字节码(bytecode),这一步是V8执行过程中的优化阶段。

当代码执行到一个函数时,V8会将该函数的字节码编译成更高效的机器码,这个过程称为全代码生成(Full-codegen)。V8的另一个编译器称为Ignition,它负责将字节码解释执行并收集类型反馈。基于这些反馈,TurboFan编译器可以生成优化后的机器码。整个编译和执行流程如下:

  1. 解析JavaScript代码并创建语法树(AST)。
  2. 生成字节码。
  3. 字节码解释执行。
  4. 收集类型反馈。
  5. 根据类型反馈生成优化后的机器码。
// 示例JavaScript代码
function sum(a, b) {
  return a + b;
}
console.log(sum(1, 2));

以上代码在V8引擎中的编译和执行过程中,会经历字节码到优化后机器码的转换。一旦函数 sum 被调用多次并收集到足够的类型信息,TurboFan就能够生成更有效的机器码,加快函数的执行速度。

5.1.2 V8的优化技术如JIT和隐藏类

V8引擎引入了多种优化技术来提升JavaScript代码的执行性能。即时编译(JIT)技术已经在上文中介绍,而隐藏类(Hidden classes)是V8优化对象属性访问的一种机制。在JavaScript中,对象的属性可以动态添加和删除,这使得传统的静态类型语言中的优化手段不适用于JavaScript。隐藏类机制通过记录对象属性的添加顺序和类型,来优化属性访问。

当创建多个具有相同结构的对象时,V8可以使用相同的隐藏类来优化这些对象的操作。如果后续有对象的属性添加或修改,V8会创建一个新的隐藏类,从而保持属性访问的优化。

// 创建具有相同结构的对象
let obj1 = { x: 1, y: 2 };
let obj2 = { x: 1, y: 2 };

// obj1和obj2可能共享相同的隐藏类

在实际代码执行中,开发者可以通过优化对象创建和属性访问的模式来利用V8的隐藏类优化。

5.2 Chrome的安全机制

5.2.1 HTTPS加密通信过程

在互联网安全中,HTTPS是保障数据传输安全的重要技术。Chrome浏览器采用了一系列措施来加强其网络安全,包括支持和推广HTTPS通信。HTTPS是在HTTP基础上加入了SSL/TLS协议,用于加密和验证数据包,确保通信安全。Chrome浏览器在进行HTTPS通信时,会执行以下步骤:

  1. 客户端向服务器发起HTTPS连接请求。
  2. 服务器响应客户端请求,并发送服务器的SSL证书。
  3. 客户端验证服务器证书的有效性和真实性。
  4. 客户端与服务器协商加密算法和密钥。
  5. 双方使用协商好的加密算法和密钥进行加密通信。

通过使用HTTPS,即使是敏感数据如登录凭证、支付信息等在传输过程中也不会轻易被截获和篡改。

5.2.2 Chrome沙箱技术的实现与应用

沙箱技术是Chrome实现用户环境隔离的一种机制,用以限制网页代码能够执行的操作,防止恶意代码对用户系统造成损害。在Chrome中,每个标签页都是在一个独立的进程中运行,这些进程受到沙箱技术的限制。

沙箱环境通常会限制对文件系统的访问权限,只允许读取某些特定目录;同时,沙箱进程也无法直接与硬件进行交互。这种隔离机制大大降低了因恶意代码执行而导致的系统损害风险。

// 示例代码,展示在C++中使用Chrome沙箱
// 这是沙箱初始化的伪代码
Sandbox sandbox(sandbox_params);
if (!sandbox.Initialize()) {
  LOG(ERROR) << "Failed to initialize sandbox";
}

在上面的伪代码中,Chrome使用 Sandbox 类初始化一个沙箱环境,并确保这个环境被正确初始化。开发者在编写浏览器插件或者扩展时,也需要遵循沙箱的安全约束,保证代码的安全性。

5.3 Chrome性能优化技巧

5.3.1 异步编程模型的实践

异步编程模型是提高Web应用性能的关键技术之一。在Chrome中,通过Promise、async/await等异步编程结构,开发者可以编写出无需阻塞主事件循环的代码。这在处理耗时任务,如文件读写、网络请求时尤为重要。

// 示例使用async/await进行异步操作
async function fetchData() {
  try {
    let response = await fetch('https://api.example.com/data');
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data: ', error);
  }
}

fetchData();

在Chrome中,异步操作通过事件循环机制来处理,不会阻塞用户界面的更新,这使得页面能够保持响应状态,即使在处理耗时的I/O操作时也不例外。

5.3.2 事件驱动架构的优势与应用

Chrome浏览器采用事件驱动架构来处理用户交互、网络通信和其他输入。这种架构允许浏览器在接收到事件时触发相应的处理程序,而无需阻塞等待某个操作完成。例如,当用户点击一个链接时,浏览器会触发一个事件,然后通过事件监听器处理点击事件,进行导航。

事件驱动架构的优势在于它的高效率和轻量级,使得浏览器能够以较小的内存占用支持大量的并发操作。Chrome内部使用了高效的事件队列和事件分发机制,确保事件能够快速、准确地被处理。

5.4 使用Git进行源代码版本控制

5.4.1 Git在Chrome项目中的应用

Chrome项目采用Git作为版本控制系统,这使得成千上万的开发者能够协作开发。Chrome的源代码通过Git的分支和提交管理来组织,开发者可以在本地进行更改,然后通过Pull Request的方式提交到远程仓库。

在Chrome项目中,有一个标准的提交信息格式,它包括了提交的类型、影响的组件和一个简短的描述,例如:

Component: Change description

Bug: IssueNumber

使用这种结构化的提交信息,可以让项目维护者更快地了解提交的性质和影响范围。

5.4.2 大型开源项目中的Git工作流

对于像Chrome这样的大型开源项目,有效的Git工作流至关重要。一个常见的工作流是使用特性分支(feature branch)来进行开发。开发者首先从 main 分支创建一个新的分支,在这个分支上进行更改和提交。

# 创建并切换到新分支
git checkout -b new-feature

# 添加和提交更改
git add .
git commit -m "Add new feature"

完成开发后,开发者会将这个分支推送至远程仓库,并创建一个Pull Request。项目维护者会审查这些更改,进行必要的讨论,然后合并到 main 分支中。

# 推送本地分支到远程仓库
git push -u origin new-feature

# 创建Pull Request
# 这一步通常在Git客户端或浏览器中的GitHub/GitLab界面操作完成

此外,还会使用一些Git钩子(hook)和自动化工具来执行代码质量检查、自动化测试以及代码合并前的CI/CD流程。

通过上述章节的讨论,我们对Chrome浏览器的V8引擎、网络安全机制、性能优化技巧和版本控制有了深入的理解。这些知识不仅有助于我们更好地使用Chrome浏览器,也能够指导我们在相关领域的技术实践和项目开发。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Chrome浏览器是Google开发的开源项目,源代码包含了现代网络技术、浏览器架构和软件工程实践的知识。本文将从C++编程、多线程处理、渲染引擎、网络协议、JavaScript引擎、性能优化、安全机制等多个方面深入分析Chrome源代码的技术细节。开发者可以通过Git仓库获取和研究源代码,学习如何构建高效的浏览器架构和提升软件工程能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值