JS技术

Javascript里有个C:Part 1 – 基础

字号+ 作者:fool 来源: 2014-11-16 22:20 我要评论( )

这是一个系列文章,将从V8引擎的接口出发,从C++的角度解释JavaScript。在最后,我们会学会如何用C++拓展V8引擎,了解node.js的整合机制,最终用C++做出一个node.js的模块。

Javascript里有个C系列文章:

  1. Javascript里有个C:Part 1 – 基础
  2. Javascript里有个C:Part 2 – 对象
  3. Javascript里有个C:Part 3 – 深入对象
  4. Javascript里有个C:Part 4 – 异步
  5. Javascript里有个C:Part 5 – node.js
  6. Javascript里有个C:Part 6 – 实战node.js Module

在此之前,你需要了解如何编译V8引擎,以及如何把自己的代码和V8引擎链接

本文将阐述JavaScript在V8里的基本概念。

Handle

JavaScript里的对象都由垃圾回收器进行管理,对象间的赋值均是传递引用,与C++由用户管理内存和值语义的机制截然不同。那么C++如何表示JavaScript里的对象呢?通过一种GC管理的智能指针。

V8里使用Handle类型来托管 JavaScript对象,与C++的std::shared_pointer类似,Handle类型间的赋值均是直接传递对象引用,但不同的是,V8使用自己的GC来管理对象生命周期,而不是智能指针常用的引用计数。

JavaScript类型在C++中均有对应的自定义类型,如String、Integer、Object、Date、Array等,严格遵守在JavaScript中的继承关系。C++中使用这些类型时,必须使用Handle托管,以使用GC来管理它们的生命周期,而不使用原生栈和堆。

Handle的使用和智能指针相同,以Handle<T>的形式声明,通过operator->调用T的成员函数,通过operator*解引用。建立新的对象时,需要使用T::New()来返回一个Handle<T>。

另外,Handle的生命周期和C++智能指针不同,并不是在C++语义的scope内生存(即{} 包围的部分),而需要通过HandleScope手动指定。 HandleScope只能分配在栈上, HandleScope对象声明后,其后建立的Handle都由HandleScope来管理生命周期,HandleScope对象析构后,其管理的Handle将由GC判断是否回收。

下面是一个例子:

 // We will be creating temporary handles so we use a handle scope.
 HandleScope handle_scope;

 // Create a new empty array.
 Handle<Array> array = Array::New(3);

 // Fill out the values
 array->Set(0, Integer::New(x));
 array->Set(1, Integer::New(y));
 array->Set(2, Integer::New(z));

例子很简单,我们首先声明handle_scope来限定之后Handle的scope,然后通过Array::New返回了一个新的Array对象,最后使用Array::Set来设定Array的数据。

那么,如果我们想通过一个函数来返回一个Handle呢?最直观的代码可能会是这样:

// This function returns a new array with three elements, x, y, and z.
Handle<Array> NewPointArray(int x, int y, int z) {

 // We will be creating temporary handles so we use a handle scope.
 HandleScope handle_scope;

 // Create a new empty array.
 Handle<Array> array = Array::New(3);

 // Return an empty result if there was an error creating the array.
 if (array.IsEmpty())
  return array();

 // Fill out the values
 array->Set(0, Integer::New(x));
 array->Set(1, Integer::New(y));
 array->Set(2, Integer::New(z));

 return array;
}

但问题是,当函数返回时,handle_scope会被析构,其管理的Handle也都将被回收,那么此刻返回的array将不再有意义。V8的解决方案是HandleScope::Close (Handle<T> value),这个成员函数将关闭当前HandleScope并把参数中的Handle的转交给上一个scope,也就是进入这个函数之前所在的scope。下面是正确的代码:

// This function returns a new array with three elements, x, y, and z.
Handle<Array> NewPointArray(int x, int y, int z) {

 // We will be creating temporary handles so we use a handle scope.
 HandleScope handle_scope;

 // Create a new empty array.
 Handle<Array> array = Array::New(3);

 // Return an empty result if there was an error creating the array.
 if (array.IsEmpty())
  return Handle();

 // Fill out the values
 array->Set(0, Integer::New(x));
 array->Set(1, Integer::New(y));
 array->Set(2, Integer::New(z));

 // Return the value through Close.
 return handle_scope.Close(array);
}

此时类似于栈的Handle我们已了解完毕。但还有一种情况。JavaScript里还存在全局对象,我们怎么在C++里表示呢?

Handle有两种类型,Local Handle和PersistentHandle,类型分别是Local<T> : Handle<T>和Persistent<T> : Handle<T>,前者和Handle<T>没有区别,生存周期都在scope内。而后者的生命周期脱离scope,你需要手动调用Persistent::Dispose结束其生命周期。也就是说Local Handle相当于在C++在栈上分配对象,而PersistentHandle相当于C++在堆上分配对象。

Context

V8允许不同的JavaScript代码运行在完全不同的环境下,其运行环境称为Context。不同的Context下拥有自己的全局对象(PersistentHandle),运行代码时必须指定所在的Context。最典型的例子就是Chrome的标签,每个标签都拥有自己的Context。

Context拥有自己的全局代理对象(global proxy object),每个Context下的全局对象都是这个全局代理对象的属性。通过Context::Global ()可以得到这个全局代理对象。新建Context时你可以手动指定它的全局代理对象,这样每个Context都会自动拥有一些全局对象,比如DOM。

Context也是一种scope,通过Context::Enter ()和Context::Exit ()来进入、退出,或者使用类似于HandleScope的Context::Scope来隐式进入,其代码非常简单:

  /**
   * Stack-allocated class which sets the execution context for all
   * operations executed within a local scope.
   */
  class Scope {
   public:
    explicit inline Scope(Handle<Value> context) : context_(context) {
      context_->Enter();
    }
    inline ~Scope() { context_->Exit(); }
   private:
    Handle context_;
  };

进入Context的scope后,接下来新建的Handle都会建立在这个Context下。

下面的图可以表示Context间scope的关系:

当建立scopeA时,程序将自动进入contextA的scope,建立scopeB时,程序又将自动进入contextB的scope,最后scopeB析构时,程序又回到contextA。

Script

JavaScript代码的运行分为两个部分:编译和运行。这两个过程通过Script对象来完成。首先由Script::Compile (Handle<String> source)来由源码字符串进行编译并返回一个编译后的Script对象,然后再由Local<Value> Script::Run ()运行代码并返回结果。

综述

最终一段完整的代码如下所示,它包含了新建Context、运行代码等完整过程:

#include <v8.h>

using namespace v8;

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

 // 新建基于栈分配的handle scope.
 HandleScope handle_scope;

 // 新建context.
 Persistent<Context> context = Context::New();

 // 进入context,我们将在这个context内编译运行代码
 Context::Scope context_scope(context);

 // 新建一个包含源代码的字符串.
 Handle<String> source = String::New("'Hello' + ', World!'");

 // 编译代码.
 Handle<Script> script = Script::Compile(source);

 // 运行代码并获取结果.
 Handle<Value> result = script->Run();

 // 因为context是Persistent Handle,所以要手动释放.
 context.Dispose();

 // 将结果转换成ASCII字符串.
 String::AsciiValue ascii(result);
 printf("%s\n", *ascii);
 return 0;
}

用一张图来表示这段代码Handle的生命周期如下:

至此,我们已经了解了V8的大部分基础概念,下一篇文章里我们将讨论操作JavaScript里的对象。

文中使用的部分代码来自V8文档:http://code.google.com/apis/v8/embed.html

关于作者 赵成, 东南大学, 学生 C++狂热,缺少零花钱 新浪微博 向文章付费 请作者吃饭

Related posts:

  1. Javascript里有个C:Part 3 – 深入对象
  2. Javascript里有个C:Part 2 – 对象
  3. node.js源码研究—模块组织加载
  4. 请求路由模块journey的源码解读和学习心得(一)
  5. Mongoose入门介绍

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 学习JavaScript之this,call,apply

    学习JavaScript之this,call,apply

    2016-01-28 20:45

  • 零基础入门学习Python(10):函数 - qq_33256568的博客 - 博客频道 - CSDN.NET qq_3

    零基础入门学习Python(10):函数 - qq_33256568的博客 - 博客频道

    2015-12-15 09:04

  • Swift 2.0学习笔记(Day48)——类型检查与转换 - 关东升 - 博客频道 - CSDN.NET 关东升 iO

    Swift 2.0学习笔记(Day48)——类型检查与转换 - 关东升 - 博客频道

    2015-12-14 18:16

  • 有趣的Ruby-学习笔记1 - 我可以接受失败,但我不能接受放弃。--迈克尔 乔丹 - 博客频道 - CSDN.NET

    有趣的Ruby-学习笔记1 - 我可以接受失败,但我不能接受放弃。--迈克

    2015-12-14 17:17

网友点评
t