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方法。

相關文章
相關標籤/搜索