Dachang Technology Nœud Frontal Avancé Avancé
Cliquez sur le guide de croissance du meilleur programmeur, faites attention au numéro public
Répondre 1, rejoignez le groupe d'échange avancé Node
Lien d'origine : https://juejin.im/post/5cf61ed3e51d4555fd20a2f3
Auteur : AlienZHOU
1. FamilierJSON.stringify
Côté navigateur ou côté serveur, JSON.stringify()
c'est notre méthode très courante :
Stockez l'objet JSON dans localStorage ;
Corps JSON dans la requête POST ;
Traiter les données sous forme de JSON dans le corps de la réponse ;
Même sous certaines conditions, nous l'utiliserons pour implémenter une simple copie profonde ;
……
Dans certaines situations sensibles aux performances (par exemple, le serveur gère beaucoup de simultanéité), ou face à un grand nombre d'opérations de stringification, nous aimerions qu'il fonctionne mieux et plus rapidement. Cela a également engendré des solutions/bibliothèques stringify optimisées, et la figure suivante montre leurs performances par rapport aux méthodes natives :
![8a9c98d91dc15e4f3bbe39d6532e6c7c.png](https://img-blog.csdnimg.cn/img_convert/8a9c98d91dc15e4f3bbe39d6532e6c7c.png)
La partie verte est native JSON.stringify()
et les performances visibles sont bien inférieures à ces bibliothèques. Alors, quelle est la justification technique derrière l'amélioration spectaculaire des performances ?
2. stringify
plus rapidestringify
Comme JavaScript est un langage très dynamique, pour une variable de type Object, le nom de clé, la valeur de clé et le type de valeur de clé qu'elle contient ne peuvent être déterminés qu'au moment de l'exécution. Il JSON.stringify()
y a donc beaucoup de travail à faire lors de l'exécution. Il n'y a rien que nous puissions faire pour optimiser radicalement sans le savoir.
Donc, si nous connaissons le nom de la clé et les informations sur la valeur de la clé dans cet objet - c'est-à-dire, connaissons ses informations de structure, cela nous aidera-t-il ?
Voir un exemple :
L'Objet suivant,
const obj = {
name: 'alienzhou',
status: 6,
working: true
};
Nous l'appliquons pour JSON.stringify()
obtenir le résultat tel que
JSON.stringify(obj);
// {"name":"alienzhou","status":6,"working":true}
Maintenant, si nous savons que obj
la structure de ceci est fixe :
nom de clé inchangé
Le type de la valeur de la clé doit être
En fait, je peux créer une méthode stringify "personnalisée"
function myStringify(o) {
return (
'{"name":"'
+ o.name
+ '","status":'
+ o.status
+ ',"isWorking":'
+ o.working
+ '}'
);
}
Jetez un oeil à myStringify
la sortie de notre méthode:
myStringify({
name: 'alienzhou',
status: 6,
working: true
});
// {"name":"alienzhou","status":6,"isWorking":true}
myStringify({
name: 'mengshou',
status: 3,
working: false
});
// {"name":"mengshou","status":3,"isWorking":false}
Le résultat correct est obtenu, mais seules la conversion de type et la concaténation de chaînes sont utilisées, de sorte que la méthode "personnalisée" peut rendre "stringify" plus rapide.
Pour résumer, comment obtenir stringify
une stringify
méthode plus rapide que ?
Les informations de structure de l'objet doivent être déterminées en premier ;
Selon ses informations de structure, une méthode "personnalisée"
stringify
est créée pour l'objet de cette structure, et le résultat interne est en fait généré par concaténation de chaînes ;Enfin, utilisez la méthode "custom" pour stringifier l'objet.
C'est également la routine de la plupart des bibliothèques d'accélération stringify, qui se traduit par un code comme celui-ci :
import faster from 'some_library_faster_stringify';
// 1. 通过相应规则,定义你的对象结构
const theObjectScheme = {
// ……
};
// 2. 根据结构,得到一个定制化的方法
const stringify = faster(theObjectScheme);
// 3. 调用方法,快速 stringify
const target = {
// ……
};
stringify(target);
3. Comment générer des méthodes "personnalisées"
Selon l'analyse ci-dessus, la fonction principale est de créer une méthode stringify "personnalisée" pour cette classe d'objets en fonction de ses informations de structure, qui est en fait un simple accès aux attributs et un épissage de chaîne.
Afin de comprendre la méthode d'implémentation spécifique, permettez-moi de présenter brièvement deux bibliothèques open source avec des implémentations légèrement différentes à titre d'exemples.
3.1. rapide-json-stringifier
![128d38b4d650b8b8e17ce72caf78d5de.png](https://img-blog.csdnimg.cn/img_convert/128d38b4d650b8b8e17ce72caf78d5de.png)
La figure suivante est une comparaison des performances basée sur les résultats de référence fournis par fast-json-stringify [1] .
![566a1fc331852318a98f0d80f6612a70.png](https://img-blog.csdnimg.cn/img_convert/566a1fc331852318a98f0d80f6612a70.png)
On peut voir qu'il y a une amélioration des performances de 2 à 5 fois dans la plupart des scénarios.
3.1.1. Comment le schéma est défini
fast-json-stringify utilise JSON Schema Validation [2] pour définir le format de données des objets (JSON). La structure définie par son schéma lui-même est également au format JSON, comme object
{
name: 'alienzhou',
status: 6,
working: true
}
Le schéma correspondant est :
{
title: 'Example Schema',
type: 'object',
properties: {
name: {
type: 'string'
},
status: {
type: 'integer'
},
working: {
type: 'boolean'
}
}
}
Ses règles de définition de schéma sont riches, et l'utilisation spécifique peut se référer à la bibliothèque de vérification JSON Ajv [3] .
3.1.2 Génération de la méthode stringify
fast-json-stringify générera la chaîne de code de fonction réelle en épissant selon le schéma qui vient d'être défini, puis utilisera le constructeur Function [4] pour générer dynamiquement la fonction stringify correspondante lors de l'exécution.
En génération de code, tout d'abord, il va injecter différentes méthodes outils prédéfinies, les différents schémas de cette partie sont les mêmes :
var code = `'use strict'`
code += `
${$asString.toString()}
${$asStringNullable.toString()}
${$asStringSmall.toString()}
${$asNumber.toString()}
${$asNumberNullable.toString()}
${$asIntegerNullable.toString()}
${$asNull.toString()}
${$asBoolean.toString()}
${$asBooleanNullable.toString()}
`
Dans un second temps, le code spécifique de la fonction stringify sera généré en fonction du contenu spécifique défini par le schéma. La manière de générer est également relativement simple : en parcourant le schéma.
Lors de la traversée du schéma, selon le type défini, insérez la fonction d'outil correspondante au niveau du code correspondant pour la conversion clé-valeur. name
Par exemple, cette propriété dans l' exemple ci -dessus :
var accessor = key.indexOf('[') === 0 ? sanitizeKey(key) : `['${sanitizeKey(key)}']`
switch (type) {
case 'null':
code += `
json += $asNull()
`
break
case 'string':
code += nullable ? `json += obj${accessor} === null ? null : $asString(obj${accessor})` : `json += $asString(obj${accessor})`
break
case 'integer':
code += nullable ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})` : `json += $asInteger(obj${accessor})`
break
……
Les variables dans le code ci-dessus code
contiennent la chaîne de code du corps de fonction généré final. Puisque dans la définition du schéma, il name
s'agit d'un string
type et non vide, la code
chaîne de code suivante lui sera ajoutée :
"json += $asString(obj['name'])"
Le code réel est souvent omis en raison de la complexité du traitement des tableaux, des objets concaténés, etc.
Ensuite, la code
chaîne complète résultante est à peu près la suivante :
function $asString(str) {
// ……
}
function $asStringNullable(str) {
// ……
}
function $asStringSmall(str) {
// ……
}
function $asNumber(i) {
// ……
}
function $asNumberNullable(i) {
// ……
}
/* 以上是一系列通用的键值转换方法 */
/* $main 就是 stringify 的主体函数 */
function $main(input) {
var obj = typeof input.toJSON === 'function'
? input.toJSON()
: input
var json = '{'
var addComma = false
if (obj['name'] !== undefined) {
if (addComma) {
json += ','
}
addComma = true
json += '"name":'
json += $asString(obj['name'])
}
// …… 其他属性(status、working)的拼接
json += '}'
return json
}
return $main
Enfin, passez la code
chaîne au constructeur Function pour créer la fonction stringify correspondante.
// dependencies 主要用于处理包含 anyOf 与 if 语法的情况
dependenciesName.push(code)
return (Function.apply(null, dependenciesName).apply(null, dependencies))
3.2. slow-json-stringify
![a9f71867201074eb57568d94ac25a4e6.png](https://img-blog.csdnimg.cn/img_convert/a9f71867201074eb57568d94ac25a4e6.png)
slow-json-stringify [5] , malgré le nom "slow", est en fait une bibliothèque stringify "rapide" (avec un nom coquin).
Le stringificateur le plus lent de l'univers connu. Je plaisante, c'est le plus rapide (:
Son implémentation est plus légère que le fast-json-stringify susmentionné, et l'idée est très intelligente. En même temps, il sera plus rapide que fast-json-stringify dans de nombreux scénarios [6] .
![1f248b87fced8126618ec3578426aadd.png](https://img-blog.csdnimg.cn/img_convert/1f248b87fced8126618ec3578426aadd.png)
![36ab80e437942ee4ef5d4bd95ffefc39.png](https://img-blog.csdnimg.cn/img_convert/36ab80e437942ee4ef5d4bd95ffefc39.png)
3.2.1. Comment le schéma est défini
La définition du schéma de slow-json-stringify est plus naturelle et simple, remplaçant principalement les valeurs clés par des descriptions de type. Ou l'exemple de l'objet ci-dessus, le schéma deviendra
{
name: 'string',
status: 'number',
working: 'boolean'
}
Très intuitif en effet.
3.2.2 Génération de la méthode stringify
je ne sais pas si tu as remarqué
// scheme
{
name: 'string',
status: 'number',
working: 'boolean'
}
// 目标对象
{
name: 'alienzhou',
status: 6,
working: true
}
La structure du schéma et l'objet d'origine sont-ils très similaires ?
La chose intelligente à propos de ce schéma est qu'après cette définition, nous pouvons d'abord mettre le schéma JSON.stringify
, puis "déduire" toutes les valeurs de type, et enfin ce qui nous attend est de remplir directement la valeur réelle dans la déclaration de type correspondant au schéma .
Comment ça marche?
Tout d'abord, vous pouvez appeler directement le schéma JSON.stringify()
pour générer le modèle de base et emprunter JSON.stringify()
le deuxième paramètre comme chemin d'accès de la méthode de traversée pour collecter les propriétés :
let map = {};
const str = JSON.stringify(schema, (prop, value) => {
const isArray = Array.isArray(value);
if (typeof value !== 'object' || isArray) {
if (isArray) {
const current = value[0];
arrais.set(prop, current);
}
_validator(value);
map[prop] = _deepPath(schema, prop);
props += `"${prop}"|`;
}
return value;
});
À ce stade, map
les chemins d'accès de toutes les propriétés sont collectés dans le fichier. Dans le même temps, les expressions régulières générées props
peuvent être fusionnées en chaînes correspondantes du type correspondant, comme l'expression régulière dans notre exemple /"name"|"status"|"working"|"(string|number|boolean|undef)"|\\[(.*?)\\]/
.
Faites ensuite correspondre séquentiellement ces attributs en fonction de l'expression régulière, remplacez la chaîne de type d'attribut par une chaîne d'espace réservé uniforme "__par__"
et "__par__"
divisez la chaîne en fonction de :
const queue = [];
const chunks = str
.replace(regex, (type) => {
switch (type) {
case '"string"':
case '"undefined"':
return '"__par__"';
case '"number"':
case '"boolean"':
case '["array-simple"]':
case '[null]':
return '__par__';
default:
const prop = type.match(/(?<=\").+?(?=\")/)[0];
queue.push(prop);
return type;
}
})
.split('__par__');
De cette façon, vous obtiendrez une chunks
somme et props
deux tableaux. chunks
contient la chaîne JSON fractionnée. Par exemple, les deux tableaux sont les suivants
// chunks
[
'{"name":"',
'","status":"',
'","working":"',
'"}'
]
// props
[
'name',
'status',
'working'
]
Enfin, étant donné que le mappage entre les noms de propriété et les chemins d'accès est stocké dans la carte, la valeur d'une propriété dans l'objet peut être consultée en fonction de la prop, bouclée dans le tableau et épissée avec les morceaux correspondants.
Du point de vue de la taille et de l'implémentation du code, cette solution sera plus légère et plus ingénieuse, et elle n'a pas besoin de générer ou d'exécuter dynamiquement des fonctions via Function, eval, etc.
4. Résumé
Bien que l'implémentation de différentes bibliothèques soit différente de l'idée générale, la façon d'obtenir un stringify hautes performances est la même :
Le développeur définit le schéma JSON d'Object ;
La bibliothèque stringify génère la méthode de modèle correspondante selon le schéma, et la méthode de modèle effectuera l'épissage de chaîne des attributs et des valeurs (évidemment, l'efficacité de l'accès aux attributs et de l'épissage de chaîne est beaucoup plus élevée) ;
Enfin, le développeur peut appeler la méthode renvoyée pour stringifier Object.
En dernière analyse, il ajoute essentiellement à l'optimisation et à l'analyse des informations structurelles statiques.
Conseils
Enfin, je voudrais mentionner
Tous les benchmarks ne peuvent être utilisés que comme référence, s'il y a une amélioration des performances et dans quelle mesure, ou s'il est recommandé de tester dans une entreprise réelle ;
Le constructeur Function est utilisé dans fast-json-stringify, il est donc recommandé de ne pas utiliser l'entrée utilisateur directement comme schéma pour éviter certains problèmes de sécurité.
Node 社群
我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
如果你觉得这篇内容对你有帮助,我想请你帮我2个小忙:
1. 点个「在看」,让更多人也能看到这篇文章2. 订阅官方博客 www.inode.club 让我们一起成长
点赞和在看就是最大的支持❤️
Les références
[1]
https://github.com/fastify/fast-json-stringify#benchmarks : https://github.com/fastify/fast-json-stringify#benchmarks
[2]http://json-schema.org/latest/json-schema-validation.html : http://json-schema.org/latest/json-schema-validation.html
[3]https://ajv.js.org/ : https://ajv.js.org/
[4]https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ Une fonction
[5]https://github.com/lucagez/slow-json-stringify : https://github.com/lucagez/slow-json-stringify
[6]https://github.com/lucagez/slow-json-stringify/blob/master/benchmark.md : https://github.com/lucagez/slow-json-stringify/blob/master/benchmark.md