使用v8与js做交互,理解node基本原理

April 28, 2015

前阶段刚说到全栈的问题,到底要多深,多后期才能算是全栈每个人心中都有自己的看法,我自己感觉多学一些总不是坏事。

最近用 node 遇到了一些障碍,在看 node 的源码了解一下,然后想到网上现在还没有相关的分析,大多数都是开发 addons,而且中途 v8 进行了一次大升级,很多能查到的资料都是错得,所以就在这里简单地写一些自己的经验吧。

众所周知,node 是基于 v8 做的,v8 是什么?简单来说就是一个 javascript 的解析器,他会读取 js 程序,使用 jit 技术实时编译我们写的程序,最后运行的是机器码,所以 v8 运行的 js 特别快。

安装依赖工具

首先我们需要进行环境的配置,首先去 v8 主页https://code.google.com/p/v8/,在wiki中找到BuildingWithGYP这一项,然后发现被移动到另一个页面,我们点击过去

我们发现这个页面提到了一个新的工具 gclient,v8 项目就是用这个管理的,所以我们需要下载它,现在去他的主页https://code.google.com/p/gclient/,我们发现他已经成为 depot_tools 的一个子项目[如下图],然后我们点击链接跳转过去

由于我是用的 mac,所以直接使用 mac 的安装方式

Installing on Linux and Mac

  1. Confirm git is installed. git 2.2.1+ recommended.
  2. Fetch depot_tools: $ git clone chromium/tools/depot_tools.git - Git at Google
  3. Add depot_tools to your PATH:$ export PATH=`pwd`/depot_tools:"$PATH"
    • Yes, you want to put depot_tools ahead of everything else, otherwise gcl will refer to the GNU Common Lisp compiler.
    • You may want to add this to your .bashrc file or your shell’s equivalent so that you don’t need to reset your $PATH manually each time you open a new shell.

这里建议提前安装 xcode,这样的话相关的工具都会安装好,不需要自己在配置。

安装 v8

安装使用两个命令

gclient config https://chromium.googlesource.com/v8/v8.git
gclient sync

剩下 gclient 就会全自动的帮我们完成之后的操作,看网速,我大概等了将近 2 个小时

生成 xcode 工程

进入到我们刚才下号的 v8 目录,输入下面的命令

build/gyp_v8 -Dtarget_arch=x64

然后就可以使用 xcode 打开/build 目录下面的 all.xcodeproj

制作简单地 log 函数

我在这里就举一个简单地例子,让大家熟悉 node 的原理,在 xcode 中找到 sample 项目,右键 process 复制一个 target,我们重命名这个复制好的 target 叫 helloV8,然后再 source 这个 group 里面创建一个 cpp 文件,随意起名字,我这里叫做 helloV8.cpp

将下面的代码复制一份到新创建好的 helloV8.cpp 中

#include "include/v8.h"
#include "include/libplatform/libplatform.h"
#include <iostream>
#include <fstream>
#include <sstream>

using namespace v8;

void printjs(const FunctionCallbackInfo<Value>& args) {
  v8::String::Utf8Value v8Str(args[0]);
  Isolate* isolate = args.GetIsolate();
  HandleScope scope(isolate);
  std::cout << *v8Str << std::endl;
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "from yeanzhi"));
}

void strLength(const FunctionCallbackInfo<Value>& args) {
  v8::String::Utf8Value v8Str(args[0]);
  Isolate* isolate = args.GetIsolate();
  HandleScope scope(isolate);
  int length = strlen(*v8Str);
  args.GetReturnValue().Set(Integer::New(isolate, length));
}

void loadjs(const FunctionCallbackInfo<Value>& args) {
  v8::String::Utf8Value v8Str(args[0]);
  std::ifstream f(*v8Str);
  std::stringbuf buf;
  f >> buf;
  Local<Value> result = Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), buf.str().c_str()))->Run();
  // Convert the result to an UTF8 string and print it.
  String::Utf8Value utf8(result);
  printf("\n%s\n", *utf8);
}

int main(int argc, char* argv[]) {
  // Initialize V8.
  V8::InitializeICU();
  Platform* platform = platform::CreateDefaultPlatform();
  V8::InitializePlatform(platform);
  V8::Initialize();

  // Create a new Isolate and make it the current one.
  Isolate* isolate = Isolate::New();
  {
    Isolate::Scope isolate_scope(isolate);
    // Create a stack-allocated handle scope.
    HandleScope handle_scope(isolate);
    auto global = v8::ObjectTemplate::New(isolate);
    global->Set(v8::String::NewFromUtf8(isolate, "printjs"), FunctionTemplate::New(isolate, &printjs));
    global->Set(v8::String::NewFromUtf8(isolate, "loadjs"), FunctionTemplate::New(isolate, &loadjs));
    global->Set(v8::String::NewFromUtf8(isolate, "strLength"), FunctionTemplate::New(isolate, &strLength));

    // Create a new context.
    Local<Context> context = Context::New(isolate, NULL, global);
    // Enter the context for compiling and running the hello world script.
    Context::Scope context_scope(context);
    // Create a string containing the JavaScript source code.
    Local<String> source = String::NewFromUtf8(isolate, "loadjs('app.js')");
    // Compile the source code.
    Local<Script> script = Script::Compile(source);
    // Run the script to get the result.
    Local<Value> result = script->Run();
    // Convert the result to an UTF8 string and print it.
    String::Utf8Value utf8(result);
    printf("\n%s\n", *utf8);
  }

  // Dispose the isolate and tear down V8.
  isolate->Dispose();
  V8::Dispose();
  V8::ShutdownPlatform();
  delete platform;

  return 0;
}

这端代码最核心的功能就是下面这段

auto global = v8::ObjectTemplate::New(isolate);
global->Set(v8::String::NewFromUtf8(isolate, "printjs"), v8::FunctionTemplate::New(isolate, &printjs));
global->Set(v8::String::NewFromUtf8(isolate, "loadjs"), v8::FunctionTemplate::New(isolate, &loadjs));
global->Set(v8::String::NewFromUtf8(isolate, "strLength"), v8::FunctionTemplate::New(isolate, &strLength));
// Create a new context.
Local<Context> context = Context::New(isolate, nullptr, global);

众所周知,js 有一个全局空间的概念,这里我们创建了三个函数 printjs,loadjs,strLength,并将他们绑定到全局空间中,这样我在创建好的 app.js 中就可以直接使用这三个函数

然后看这段代码

// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
Local<String> source = String::NewFromUtf8(isolate, "loadjs('app.js')");
// Compile the source code.
Local<Script> script = Script::Compile(source);
// Run the script to get the result.
Local<Value> result = script->Run();

这里我们做了两件事,一个是通过 loadjs 加载了 app.js 这个文件,然后编译这段代码,去执行他,下面是 app.js 的源码

printjs("hello world")
function hello() {
  printjs("from yeanzhi")
}
hello()
var patt1 = new RegExp("e")
var myDate = new Date()
var arr = []
arr.push("2fjdsaf")
var i = 5,
  j = 1
var res = i + j
printjs(res)
if (i == 5) {
  printjs(true)
} else {
  printjs(false)
}
while (i < 10) {
  printjs("yeanzhi is supermen")
  i++
  printjs(i)
}
printjs("==============>>>>>>>>>>>>>")
printjs(strLength("yeanzhi is supermen"))
printjs(arr[0])
printjs(myDate.getTime())

loadJS

loadjs 中做了几件事情,首先拿到需要执行文件的内容,然后编译,执行,输出结果,下面是源码

void loadjs(const FunctionCallbackInfo<Value>&args){
    v8::String::Utf8Value v8Str(args[0]);
    std::ifstream f(*v8Str);
    std::stringbuf buf;
    f>>&buf;
    Local<Value> result =           Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),     buf.str().c_str()))->Run();
// Convert the result to an UTF8 string and print it.
    String::Utf8Value utf8(result);
    printf("\n%s\n", *utf8);
}

运行

运行 helloV8 这个 target,会发现在 js 文件已经成功的执行

总结,v8 就是一个解析器,通过加载 js 文件的内容,在 v8 中编译后就可以执行 js 文件。node 就是基于这个做的,我们会在 node 的 Projects 里面发现 v8 的项目,和我们刚才下载的是完全一致的。

v8 在 node 0.10~0.11 这段时间中进行了一次惨无人道的升级,波及相当多的 api,导致网上能搜索到得大部分内容都不再适用了,如果想了解更多 v8 地内容,可以去这里获得https://developers.google.com/v8,如果以后有时间,我会在写一些 nodejs 的源码分析。