`
Ydoing
  • 浏览: 106133 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

使用 Google V8 引擎开发可定制的应用程序

 
阅读更多

V8 引擎概览

V8 引擎简介

Google V8 引擎使用 C++ 代码编写,实现了 ECMAScript 规范的第五版,可以运行在所有的主流

操作系统中,甚至可以运行在移动终端 ( 基于 ARM 的处理器,如 HTC G7 等 )。V8 最早被开发用以嵌入到 Google 的开源浏览器 Chrome 中,但是 V8 是一个可以独立的模块,完全可以嵌入您自己的应用,著名的 Node.js( 一个异步的服务器框架,可以在服务端使用 JavaScript 写出高效的网络服务器 ) 就是基于 V8 引擎的。

和其他 JavaScript 引擎一样,V8 会编译 / 执行 JavaScript 代码,管理内存,负责垃圾回收,与宿主语言的交互等。V8 的垃圾回收器采用了众多技术,使得其运行效率大大提高。通过暴露宿主对象 ( 变量,函数等 ) 到 JavaScript,JavaScript 可以访问宿主环境中的对象,并在脚本中完成对宿主对象的操作。

V8 引擎基本概念

图 1. V8 引擎基本概念关系图 ( 根据 Google V8 官方文档 )
图 1. V8 引擎基本概念关系图 ( 根据 Google V8 官方文档 )

handle

handle 是指向对象的指针,在 V8 中,所有的对象都通过 handle 来引用,handle 主要用于 V8 的垃圾回收机制。

在 V8 中,handle 分为两种:持久化 (Persistent)handle 和本地 (Local)handle,持久化 handle 存放在堆上,而本地 handle 存放在栈上。这个与 C/C++ 中的堆和栈的意义相同 ( 简而言之,堆上的空间需要开发人员自己申请,使用完成之后显式的释放;而栈上的为自动变量,在退出函数 / 方法之后自动被释放 )。持久化 handle 与本地 handle 都是 Handle 的子类。在 V8 中,所有数据访问均需要通过 handle。需要注意的是,使用持久化 handle 之后,需要显式的调用 Dispose() 来通知垃圾回收机制。

作用域 (scope)

scope 是 handle 的集合,可以包含若干个 handle,这样就无需将每个 handle 逐次释放,而是直接释放整个 scope。

在使用本地 handle 时,需要声明一个 HandleScope 的实例,scope 是 handle 的容器,使用 scope,则无需依次释放 handle。

 HandleScope handle_scope; 
 Local<ObjectTemplate> temp;

上下文 (context)

context 是一个执行器环境,使用 context 可以将相互分离的 JavaScript 脚本在同一个 V8 实例中运行,而互不干涉。在运行 JavaScript 脚本是,需要显式的指定 context 对象。

数据及模板

由于 C++ 原生数据类型与 JavaScript 中数据类型有很大差异,因此 V8 提供了 Data 类,从 JavaScript 到 C++,从 C++ 到 JavaScrpt 都会用到这个类及其子类,比如:

 Handle<Value> Add(const Arguments& args){ 
	 int a = args[0]->Uint32Value(); 
	 int b = args[1]->Uint32Value(); 

	 return Integer::New(a+b); 
 }

Integer 即为 Data 的一个子类。

V8 中,有两个模板 (Template) 类 ( 并非 C++ 中的模板类 ):对象模板 (ObjectTempalte) 和函数模板 (FunctionTemplate),这两个模板类用以定义 JavaScript 对象和 JavaScript 函数。我们在后续的小节部分将会接触到模板类的实例。通过使用 ObjectTemplate,可以将 C++ 中的对象暴露给脚本环境,类似的,FunctionTemplate 用以将 C++ 函数暴露给脚本环境,以供脚本使用。

初始化 context 是使用 V8 引擎所必需的过程,代码非常简单:

 Persistent<Context> context = Context::New();

V8 引擎使用示例

有了上面所述的基本概念之后,我们来看一下一个使用 V8 引擎的应用程序的基本流程:

  1. 创建 HandleScope 实例
  2. 创建一个持久化的 Context
  3. 进入 Context
  4. 创建脚本字符串
  5. 创建 Script 对象,通过 Script::Compile()
  6. 执行脚本对象的 Run 方法
  7. 获取 / 处理结果
  8. 显式的调用 Context 的 Dispose 方法

基本代码模板

清单 1. 代码模块
 #include <v8.h> 

 using namespace v8; 

 int main(int argc, char *argv[]) { 
	 // 创建一个句柄作用域 ( 在栈上 ) 
	 HandleScope handle_scope; 

	 // 创建一个新的上下文对象
	 Persistent<Context> context = Context::New(); 

	 // 进入上一步创建的上下文,用于编译执行 helloworld 
	 Context::Scope context_scope(context); 

	 // 创建一个字符串对象,值为'Hello, Wrold!', 字符串对象被 JS 引擎
	 // 求值后,结果为'Hello, World!'
	 Handle<String> source = String::New("'Hello' + ', World!'"); 

	 // 编译字符串对象为脚本对象
	 Handle<Script> script = Script::Compile(source); 

	 // 执行脚本,获取结果
	 Handle <Value> result = script->Run(); 

	 // 释放上下文资源
	 context.Dispose(); 

	 // 转换结果为字符串
	 String::AsciiValue ascii(result); 

	 printf("%s\n", *ascii); 

	 return 0; 
 }

以上代码为一个使用 V8 引擎来运行脚本的基本模板,可以看到,开发人员可以很容易的在自己的代码中嵌入 V8 来处理 JavaScript 脚本。我们在下面小节中详细讨论如何在脚本中访问 C++ 资源。

使用 C++ 变量

在 JavaScript 与 V8 间共享变量事实上是非常容易的,基本模板如下:

清单 2. 共享变量
 static type xxx; 

 static Handle<Value> xxxGetter( 
	 Local<String> name, 
	 const AccessorInfo& info){ 

	 //code about get xxx 
 } 

 static void xxxSetter( 
	 Local<String> name, 
	 Local<Value> value, 
	 const AccessorInfo& info){ 

	 //code about set xxx 
 }

首先在 C++ 中定义数据,并以约定的方式定义 getter/setter 函数,然后需要将 getter/setter 通过下列机制公开给脚本:

 global->SetAccessor(String::New("xxx"), xxxGetter, xxxSetter);

其中,global 对象为一个全局对象的模板:

 Handle<ObjectTemplate> global = ObjectTemplate::New();

下面我们来看一个实例:

清单 3. 实例 1
 static char sname[512] = {0}; 

 static Handle<Value> NameGetter(Local<String> name, 
		 const AccessorInfo& info) { 
	 return String::New((char*)&sname,strlen((char*)&sname)); 
 } 

 static void NameSetter(Local<String> name, 
		 Local<Value> value, 
		 const AccessorInfo& info) { 
	 Local<String> str = value->ToString(); 
	 str->WriteAscii((char*)&sname); 
 }

定义了 NameGetter, NameSetter 之后,在 main 函数中,将其注册在 global 上:

 // Create a template for the global object. 
 Handle<ObjectTemplate> global = ObjectTemplate::New(); 

 //public the name variable to script 
 global->SetAccessor(String::New("name"), NameGetter, NameSetter); 

在 C++ 中,将 sname 的值设置为”cpp”:

 //set sname to "cpp" in cpp program 
 strncpy(sname, "cpp", sizeof(sname)); 

然后在 JavaScript 中访问该变量,并修改:

 print(name); 

 //set the variable `name` to "js"
 name='js'; 
 print(name);

运行结果如下:

 cpp 
 js

运行脚本,第一个 print 调用会打印在 C++ 代码中设置的 name 变量的值:cpp,然后我们在脚本中修改 name 值为:js,再次调用 print 函数则打印出设置后的值:js。

调用 C++ 函数

在 JavaScript 中调用 C++ 函数是脚本化最常见的方式,通过使用 C++ 函数,可以极大程度的增强 JavaScript 脚本的能力,如文件读写,网络 / 数据库访问,图形 / 图像处理等等,而在 V8 中,调用 C++ 函数也非常的方便。

在 C++ 代码中,定义以下原型的函数:

 Handle<Value> function(constArguments& args){ 
	 //return something 
 }

然后,再将其公开给脚本:

 global->Set(String::New("function"),FunctionTemplate::New(function));

同样,我们来看两个示例:

清单 4. 实例 2
 Handle<Value> Add(const Arguments& args){ 
	 int a = args[0]->Uint32Value(); 
	 int b = args[1]->Uint32Value(); 

	 return Integer::New(a+b); 
 } 

 Handle<Value> Print(const Arguments& args) { 
	 bool first = true; 
	 for (int i = 0; i < args.Length(); i++) { 
		 HandleScope handle_scope; 
		 if (first) { 
			 first = false; 
		 } else { 
			 printf(" "); 
		 } 
		 String::Utf8Value str(args[i]); 
		 const char* cstr = ToCString(str); 
		 printf("%s", cstr); 
	 } 
	 printf("\n"); 
	 fflush(stdout); 
	 return Undefined(); 
 }

函数 Add 将两个参数相加,并返回和。函数 Print 接受任意多个参数,然后将参数转换为字符串输出,最后输出换行。

 global->Set(String::New("print"), FunctionTemplate::New(Print)); 
 global->Set(String::New("add"), FunctionTemplate::New(Add));

我们定义以下脚本:

 var x = (function(a, b){ 
	 return a + b; 	
 })(12, 7); 

 print(x); 

 //invoke function add defined in cpp 
 var y = add(43, 9); 
 print(y);

运行结果如下:

 19 
 52

使用 C++ 类

如果从面向对象的视角来分析,最合理的方式是将 C++ 类公开给 JavaScript,这样可以将 JavaScript 内置的对象数量大大增加,从而尽可能少的使用宿主语言,而更大的利用动态语言的灵活性和扩展性。事实上,C++ 语言概念众多,内容繁复,学习曲线较 JavaScript 远为陡峭。最好的应用场景是:既有脚本语言的灵活性,又有 C/C++ 等系统语言的效率。使用 V8 引擎,可以很方便的将 C++ 类”包装”成可供 JavaScript 使用的资源。

我们这里举一个较为简单的例子,定义一个 Person 类,然后将这个类包装并暴露给 JavaScript 脚本,在脚本中新建 Person 类的对象,使用 Person 对象的方法。

首先,我们在 C++ 中定义好类 Person:

清单 5. 定义类
 class Person { 
 private: 
	 unsigned int age; 
	 char name[512]; 

 public: 
	 Person(unsigned int age, char *name) { 
		 this->age = age; 
		 strncpy(this->name, name, sizeof(this->name)); 
	 } 

	 unsigned int getAge() { 
		 return this->age; 
	 } 

	 void setAge(unsigned int nage) { 
		 this->age = nage; 
	 } 

	 char *getName() { 
		 return this->name; 
	 } 

	 void setName(char *nname) { 
		 strncpy(this->name, nname, sizeof(this->name)); 
	 } 
 };

Person 类的结构很简单,只包含两个字段 age 和 name,并定义了各自的 getter/setter. 然后我们来定义构造器的包装:

 Handle<Value> PersonConstructor(const Arguments& args){ 
	 Handle<Object> object = args.This(); 
	 HandleScope handle_scope; 
	 int age = args[0]->Uint32Value(); 

	 String::Utf8Value str(args[1]); 
	 char* name = ToCString(str); 

	 Person *person = new Person(age, name); 
	 object->SetInternalField(0, External::New(person)); 
	 return object; 
 }

从函数原型上可以看出,构造器的包装与上一小节中,函数的包装是一致的,因为构造函数在 V8 看来,也是一个函数。需要注意的是,从 args 中获取参数并转换为合适的类型之后,我们根据此参数来调用 Person 类实际的构造函数,并将其设置在 object 的内部字段中。紧接着,我们需要包装 Person 类的 getter/setter:

 Handle<Value> PersonGetAge(const Arguments& args){ 
	 Local<Object> self = args.Holder(); 
	 Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); 

	 void *ptr = wrap->Value(); 

	 return Integer::New(static_cast<Person*>(ptr)->getAge()); 
 } 

 Handle<Value> PersonSetAge(const Arguments& args) 
 { 
	 Local<Object> self = args.Holder(); 
	 Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); 

	 void* ptr = wrap->Value(); 

	 static_cast<Person*>(ptr)->setAge(args[0]->Uint32Value()); 
	 return Undefined(); 
 }

而 getName 和 setName 的与上例类似。在对函数包装完成之后,需要将 Person 类暴露给脚本环境:

首先,创建一个新的函数模板,将其与字符串”Person”绑定,并放入 global:

 Handle<FunctionTemplate> person_template = FunctionTemplate::New(PersonConstructor); 
 person_template->SetClassName(String::New("Person")); 
 global->Set(String::New("Person"), person_template);

然后定义原型模板:

 Handle<ObjectTemplate> person_proto = person_template->PrototypeTemplate(); 

 person_proto->Set("getAge", FunctionTemplate::New(PersonGetAge)); 
 person_proto->Set("setAge", FunctionTemplate::New(PersonSetAge)); 

 person_proto->Set("getName", FunctionTemplate::New(PersonGetName)); 
 person_proto->Set("setName", FunctionTemplate::New(PersonSetName));

最后设置实例模板:

 Handle<ObjectTemplate> person_inst = person_template->InstanceTemplate(); 
 person_inst->SetInternalFieldCount(1);

随后,创建一个用以测试的脚本:

 //global function to print out detail info of person 
 function printPerson(person){ 
    print(person.getAge()+":"+person.getName()); 
 } 

 //new a person object 
 var person = new Person(26, "juntao"); 

 //print it out 
 printPerson(person); 

 //set new value 
 person.setAge(28); 
 person.setName("juntao.qiu"); 

 //print it out 
 printPerson(person);

运行得到以下结果:

 26:juntao 
 28:juntao.qiu

简单示例

在这一小节中,我们将编写一个简单的桌面计算器:表达式求值部分通过 V8 引擎来进行,而流程控制部分则放在 C++ 代码中,这样可以将表达式解析等复杂细节绕开。同时,我们还得到了一个额外的好处,用户在脚本中可以自定义函数,从而可以在计算器中定义自己的运算规则。

桌上计算器

计算器程序首先进入一个 MainLoop,从标准输入读取一行命令,然后调用 V8 引擎去求值,然后将结果打印到控制台,然后再进入循环:

清单 6. 桌面计算器示例
 void MainLoop(Handle<Context> context) { 
	 while(true) { 
		 char buffer[1024] = {0}; 
		 printf("$ "); 
		 char *str = fgets(buffer, sizeof(buffer), stdin); 
		 if(str == NULL) { 
			 break; 
		 } 
		 HandleScope handle_scope; 
		 ExecuteString(String::New(str), String::New("calc"), true); 
	 } 
 }

在 main 函数中设置全局对象,创建上下文对象,并进入 MainLoop:

 int main(int argc, char *argv[]){ 
	 HandleScope handle_scope; 

	 // Create a template for the global object. 
	 Handle<ObjectTemplate> global = ObjectTemplate::New(); 

	 // Expose the local functions to script 
	 global->Set(String::New("load"), FunctionTemplate::New(Load)); 
	 global->Set(String::New("print"), FunctionTemplate::New(Print)); 
	 global->Set(String::New("quit"), FunctionTemplate::New(Quit)); 

	 // Create a new execution environment containing the built-in 
	 // functions 
	 Handle<Context> context = Context::New(NULL, global); 

	 // Enter the newly created execution environment. 
	 Context::Scope context_scope(context); 

	 // Enter main loop 
	 MainLoop(context); 

	 V8::Dispose(); 


	 return 0; 
 }

在 main 函数中,为脚本提供了三个函数,load 函数用以将用户指定的脚本加载进来,并放入全局的上下文中一边引用,print 函数用以打印结果,而 quit 提供用户退出计算器的功能。

测试一下:

 $ 1+2 
 3 

 $ (10+3)/(9.0-5) 
 3.25 

 $ typeof print 
 function 

 $ typeof non 
 undefined 

 // 自定义函数
 $ function add(a, b){return a+b;} 
 $ add(999, 2323) 
 3322 

 // 查看 print 标识符的内容
 $ print 
 function print() { [native code] }

load 函数提供了用户自定义函数的功能,将脚本文件作为一个字符串加载到内存,然后对该字符串编译,求值,并将处理过的脚本对象放入当前 context 中,以便用户使用。

 Handle<Value> Load(const Arguments& args){ 
	 if(args.Length() != 1){ 
		 return Undefined(); 
	 } 

	 HandleScope handle_scope; 
	 String::Utf8Value file(args[0]); 

	 Handle<String> source = ReadFile(*file); 
	 ExecuteString(source, String::New(*file), false); 

	 return Undefined(); 
 } 

而 ExecuteString 函数,负责将字符串编译运行:

 bool ExecuteString(Handle<String> source, 
				   Handle<Value> name, 
				   bool print_result) 
 { 
	 HandleScope handle_scope; 
	 TryCatch try_catch; 
	 Handle<Script> script = Script::Compile(source, name); 
	 if (script.IsEmpty()) { 
		 return false; 
	 } else { 
		 Handle<Value> result = script->Run(); 
		 if (result.IsEmpty()) { 
			 return false; 
		 } else { 
			 if (print_result && !result->IsUndefined()) { 
				 String::Utf8Value str(result); 
				 const char* cstr = ToCString(str); 
				 printf("%s\n", cstr); 
			 } 
			 return true; 
		 } 
	 } 
 }

将下列内容存入一个文本文件,并命令为 calc.js:

 function sum(){ 
	 var s = 0; 
	 for(var i = 0; i < arguments.length; i++){ 
		 s += arguments[i]; 
	 } 
	 return s; 
 } 

 function avg(){ 
	 var args = arguments; 
	 var count = args.length; 
	 var sum = 0; 
	 for(var i = 0; i < count; i++){ 
		 sum += args[i]; 
	 } 
	 return sum/count; 
 }

然后在计算器中测试:

 // 此时 sum 符号位定义
 $ typeof sum 
 undefined 

 // 加载文件,并求值
 $ load("calc.js") 

 // 可以看到,sum 的类型为函数
 $ typeof sum 
 function 

 $ sum(1,2,3,4,5,6,7,8,9) 
 45

结束语

使用 V8 引擎,可以轻松的将脚本的好处带进 C++ 应用,使得 C++ 应用更具灵活性,扩展性。我们在文中讨论了基本的模板,如何使用 C++ 变量,函数,以及类。最后的实例中给出了一个计算器的原型。由于 V8 的设计原则,开发人员可以快速的将其嵌入到自己的应用中,并且无需太过担心脚本语言的执行效率。

分享到:
评论

相关推荐

    goole v8引擎编译集成

    谷歌V8引擎是一款开源的JavaScript运行环境,由Google开发,最初是为Chrome浏览器设计的,但现在已经被广泛应用于其他项目,包括Node.js服务器端环境。V8引擎以其高性能、高效的垃圾回收机制以及对最新JavaScript...

    openrasp-v8 包

    OpenRASP-v8包是为了简化OpenRASP的二次开发流程,特别是针对v8引擎的编译和集成工作。这个包包含了编译OpenRASP-v8所需的软件和预编译的Jar包,为开发者提供便利,避免了手动编译v8的复杂过程。 **1. OpenRASP-v8...

    谷歌内核-调用本地程序 亲测可用

    在IT领域,尤其是在软件开发和系统集成中,有时我们需要让应用程序之间进行交互,例如从一个程序启动另一个程序。本示例以"谷歌内核-调用本地程序 亲测可用"为主题,说明了如何在使用谷歌浏览器(Chrome)内核的环境...

    Chrome V8 Javascript Engine 2011-12-01: Version 3.7.12

    Chrome V8 JavaScript 引擎是谷歌开发的一款高性能的JavaScript引擎,它被广泛应用于Google Chrome...它的源代码文件可以为开发者提供深入学习和定制V8引擎的机会,从而更好地理解和优化JavaScript在浏览器中的运行。

    基于Chrome内核的WPF浏览器开发

    在现代软件开发中,Web浏览器作为一个不可或缺的组件,经常被集成到各种桌面应用程序中,以提供丰富的网络功能。"基于Chrome内核的WPF浏览器开发"是一个项目,它利用了Google Chrome的开源项目Chromium,构建了一个...

    使用Java开发的浏览器,基于Chromium.zip

    Chromium是Google Chrome浏览器的基础,它是一个快速、安全且高度可定制的开源浏览器引擎,由 Blink 渲染引擎和 V8 JavaScript 引擎驱动。 首先,让我们深入了解一下Java。Java的设计理念是“一次编写,到处运行”...

    使用JCEF(chromium内核)开发的抓取数据浏览器,可以用来制作浏览器,也可以用来制作爬虫

    通过JCEF,开发者可以利用Chromium的JavaScript引擎V8、HTML5支持以及CSS样式等功能,创建出与现代Web标准兼容的应用程序。这对于那些希望在Java平台上构建具有丰富Web界面的桌面应用或者需要进行复杂Web交互的...

    wxa_dynamic_pkg_release_arm64-v8a-1.6.8.zip

    5. **libmmskia.so**:Skia是一个2D图形处理库,由Google开发,广泛应用于Android和其他项目。这里可能包含了绘制、渲染和图形操作的函数,对微信小程序的界面渲染非常重要。 6. **libmmphysx.so**:物理引擎库,...

    CEF嵌入google chrome内核的浏览器

    CEF(Chromium Embedded Framework)是一种开源框架,它允许开发者在自己的应用程序中嵌入Google Chrome的Blink渲染引擎和V8 JavaScript引擎,从而实现基于Web技术的用户界面。CEF的使用可以带来高性能、现代的Web...

    Chrome特别定制版本.zip

    Chrome浏览器由Google公司开发,以其高效的JavaScript引擎V8、多进程架构以及强大的安全特性而知名。在这款特别定制版中,这些优点被进一步强化,以适应海康威视设备的特定需求。海康威视是一家知名的安防监控设备...

    简单的谷歌浏览器CEF代码.zip

    1. **CEF(Chromium Embedded Framework)**:CEF是一个开源项目,它将Google Chrome的Blink渲染引擎、V8 JavaScript引擎以及网络堆栈等核心组件封装起来,供其他应用程序使用。它允许开发者在自己的应用中实现Web...

    CEF 开发Js交互

    CEF,全称Chromium Embedded Framework,是一个开源项目,它允许开发者将Google Chrome浏览器的渲染引擎嵌入到他们的应用程序中,从而实现基于Web技术的用户界面。CEF开发涉及到多个层面的知识,包括C++编程、...

    C#封装的谷歌内核浏览器

    在IT行业中,C#是一种广泛使用的面向对象的编程语言,尤其在Windows应用程序和游戏开发领域。而"谷歌内核"通常指的是Chromium,这是一个开源的Web浏览器项目,由Google维护,其核心技术为 Blink 和 V8 JavaScript ...

    Google Chrome 6.0.451.0 Dev 版(一个由Google公司开发的网页浏览器)

    这项服务也可通过使用其他的免费自由应用程序界面(API)“Google Safe Browsing API”来取得。在更新维护这些黑名单的同时,Google也会通知被列入的网站,以避免网站持有者本身不知道网站存有恶意软件。  沙盒...

    v8-ndk:改自Android 6.0源码中V8,能够在NDK中编译通过-android

    NDK允许开发者使用C++编写原生代码,并将其与Java应用程序集成。V8原本是为了桌面环境设计的,因此需要针对Android的交叉编译环境进行调整,包括头文件、链接器选项、以及可能的平台特定优化。 "可生成测试代码d8...

    谷歌浏览器 稳定版_41.0.2272.101

    10. **开发者工具**:对于开发人员,Chrome 提供了一套强大的开发者工具,帮助调试、优化网页和应用程序,包括元素检查、网络请求分析、性能监控等功能。 版本号 41.0.2272.101 是 Chrome 的一个稳定版本,意味着它...

    谷歌Chrome浏览器安装程序

    此时,可以通过桌面快捷方式或者开始菜单(Windows)或应用程序文件夹(Mac)启动Chrome浏览器。 7. **首次启动与设置**:第一次启动Chrome,可能会要求你登录Google账户以同步书签、历史记录和其他浏览数据。如果...

    cefsharp 定制浏览器

    CEFSharp是一款基于Chromium Embedded Framework (CEF) 的开源库,用于在.NET环境中创建自定义的浏览器应用程序。CEFSharp提供了一种简单的方式,让开发者能够利用谷歌Chrome浏览器的Web渲染引擎来构建桌面应用,...

    谷歌浏览器安装包

    10. 性能优化:Chrome 使用多进程架构,使得浏览器在处理多个标签时依然保持流畅,且V8引擎让JavaScript的执行效率更高。 11. 扩展程序:Chrome 应用商店提供大量扩展程序,用户可以按需安装以增强浏览器的功能,如...

    谷歌浏览器在线安装.exe

    在性能方面,谷歌浏览器使用了谷歌自家的V8 JavaScript引擎,这使得它在执行JavaScript代码时非常迅速,从而提高了网页加载和运行的速度。谷歌浏览器还支持多进程架构,这意味着每个标签页、插件和扩展程序都在独立...

Global site tag (gtag.js) - Google Analytics