protobuf转换js的原理研究与分析

Protobuf是Google开源的一种可用于结构化数据串行化(序列化)的数据缓存格式,适用于数据存储以及RPC数据交换格式.php

在前端使用时,会经过自动转换器将proto文件转换为对应的js文件,进行下一步使用。 转换方式有两种:前端

  • 使用bazel, bazel也是Google出品,内部的一些项目会使用bazel进行构建,其中bazel也会使用Protobuf的相关转换器进行build, 其实跟第二步道理是同样的
  • 单独使用转换器 参考protobuf

proto文件通过转换后,会生成一个protoFileName.js文件,文件中会包含一系列读取、设置字段的方法,至关于第一个字段成为私有字段,同时提供Public的get和set方法,对字段进行处理。这样的好处是,字段被保护起来,减小出错,相对安全。java

以下一个proto结构定义,表现的信息git

message Person { 
  uint32 id = 1; 
  string first_name = 2; 
  string last_name = 3; 
  Email email = 4; 
  BirthdayMonth birthday_month = 5; 
  bool active = 6; 
}
复制代码

通过转换后,(以转化为js为例),成为一个类的实例,github

至关于缓存

info = new Person() 
info.setFisrtName('YOUR FIRST NAME') 
info.setLastName('YOUR LAST NAME')
复制代码

此实例的属性信息以下, 能够看到它的原型是jspb.Message, (一个抽象公共类)安全

array: (5) [100, "YOUR FIRST NAME""YOUR LAST NAME", Array(2), 1] 
arrayIndexOffset_: -1 
convertedFloatingPointFields_: {} 
messageId_: undefined 
pivot_:1.7976931348623157e+308 
wrappers_: {4: proto.Email} 
__proto__: jspb.Message
复制代码

其中:bash

  • array,实际存储数据信息
  • warppers: Message中包含的下一级Message字段,记录其字段ID,及类型

它的原型方法有哪些呢?数据结构

Object.keys(info.__proto) 
// 输出 ["constructor", "toObject", "serializeBinary", "getId", "setId", "getFirstName", "setFirstName", "getLastName", "setLastName", "getEmail", "setEmail", "clearEmail", "hasEmail", "getBirthdayMonth", "setBirthdayMonth", "getActive", "setActive"]
复制代码

能够看到, 除去前两个方法是Object类型共有,后面的方法都是根据字段自动生成的,其中,若是字段是复合字段,(即message类型),这个字段还会额外增长clearEmail(就是置空)方法。app

代码示例以下:

proto.Person.prototype.getId = function() {
  return jspb.Message.getFieldWithDefault(this, 1, 0);
};
proto.Person.prototype.setId = function(value) {
  jspb.Message.setProto3IntField(this, 1, value);
};
proto.Person.prototype.getFirstName = function() {
  return jspb.Message.getFieldWithDefault(this, 2, "");
};
proto.Person.prototype.setFirstName = function(value) {
  jspb.Message.setProto3StringField(this, 2, value);
};
proto.Person.prototype.getLastName = function() {
  return jspb.Message.getFieldWithDefault(this, 3, "");
};
proto.Person.prototype.setLastName = function(value) {
  jspb.Message.setProto3StringField(this, 3, value);
};
proto.Person.prototype.getEmail = function() {
  return jspb.Message.getWrapperField(this, proto.Email, 4);
};
proto.Person.prototype.setEmail = function(value) {
  jspb.Message.setWrapperField(this, 4, value);
};
proto.Person.prototype.clearEmail = function() {
  this.setEmail(undefined);
};
proto.Person.prototype.hasEmail = function() {
  return jspb.Message.getField(this, 4) != null;
};
proto.Person.prototype.getBirthdayMonth = function() {
  return jspb.Message.getFieldWithDefault(this, 5, 0);
};
proto.Person.prototype.setBirthdayMonth = function(value) {
  jspb.Message.setProto3EnumField(this, 5, value);
};
proto.Person.prototype.getActive = function() {
  return jspb.Message.getFieldWithDefault(this, 6, false);
};
proto.Person.prototype.setActive = function(value) {
  jspb.Message.setProto3BooleanField(this, 6, value);
};
复制代码

每一种数据类型都有对应的赋值方法,最终都会调用同一个方法setFieldIgnoringDefault_

jspb.Message.setProto3IntField = function(msg, fieldNumber, value) {
  jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, 0);
};
jspb.Message.setProto3FloatField = function(msg, fieldNumber, value) {
  jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, 0.0);
};
jspb.Message.setProto3BooleanField = function(msg, fieldNumber, value) {
  jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, false);
};
jspb.Message.setProto3StringField = function(msg, fieldNumber, value) {
  jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, "");
};
jspb.Message.setProto3BytesField = function(msg, fieldNumber, value) {
  jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, "");
};
jspb.Message.setProto3EnumField = function(msg, fieldNumber, value) {
  jspb.Message.setFieldIgnoringDefault_(msg, fieldNumber, value, 0);
};
复制代码

粗略看过去,方法不少,关键参数是其中的1-6的数字,这就是proto定义中的数字,在js实例中,array字段存储了全部属性信息,若索引和上述数字是一一对应的,固然,若是字段为空,在array中会skip.

好比, proto中定义了10个字段,其中只有3,6字段不为空,则最终数据结构为

array(empty, emtpy, 3, empty, empty, 6)
复制代码

这个转换过程当中,有些细节须要关注。

  • 如何将proto转为js
  • 实际的转化代码
  • jspb 从何而来

第一个问题, 如何将proto转为js

protobuf 提供的compile, 有相应的php 、js 、Python, java版本, 见github.com/protocolbuf…

第二个问题, 实际的转化代码

以js为例, 用c写的转换器,

github.com/protocolbuf…

第三个问题,jspb 从何而来

protobuf/js下binary文件夹及message.js中定义了jspb相关的方法,在转换过程当中,会自动集成在一块儿

一些总结:

好处的话很少说,自动转化,封装性

还可优化的地方,方法定义太多,致使类似代码太多冗余,还能够再精简一下,好比get, set方法再进行抽象下,将字段当成参数,不用一必定义,相似php中的__call方法。

相关文章
相关标签/搜索