Protobuf是Google开源的一种可用于结构化数据串行化(序列化)的数据缓存格式,适用于数据存储以及RPC数据交换格式.
在前端使用时,会通过自动转换器将proto文件转换为对应的js文件,进行下一步使用。 转换方式有两种:
- 使用bazel, bazel也是Google出品,内部的一些项目会使用bazel进行构建,其中bazel也会使用Protobuf的相关转换器进行build, 其实跟第二步道理是一样的
- 单独使用转换器 参考protobuf
proto文件经过转换后,会生成一个protoFileName.js文件,文件中会包含一系列读取、设置字段的方法,相当于第一个字段成为私有字段,同时提供Public的get和set方法,对字段进行处理。这样的好处是,字段被保护起来,减少出错,相对安全。
如下一个proto结构定义,表现的信息
message Person {
uint32 id = 1;
string first_name = 2;
string last_name = 3;
Email email = 4;
BirthdayMonth birthday_month = 5;
bool active = 6;
}
经过转换后,(以转化为js为例),成为一个类的实例,
相当于
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
其中:
- 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(就是置空)方法。
代码示例如下:
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写的转换器,
第三个问题,jspb 从何而来
protobuf/js下binary文件夹及message.js中定义了jspb相关的方法,在转换过程中,会自动集成在一起
一些总结:
好处的话不多说,自动转化,封装性
还可优化的地方,方法定义太多,导致相似代码太多冗余,还可以再精简一下,比如get, set方法再进行抽象下,将字段当成参数,不用一一定义,类似php中的__call方法。