предисловие
Недавно в красной книге, см. главу JSON, вдруг думает: "Как реализовать JSON.parse в JS?«С этим вопросом я нашелployfill Дугласа Крокфорда, отца JSON, который предоставляет три метода реализации, давайте проанализируем их один за другим.
Eval
Первый метод самый простой и интуитивно понятный, это прямой вызов eval Код выглядит следующим образом:
var json = '{"a":"1", "b":2}';
var obj = eval("(" + json + ")"); // obj 就是 json 反序列化之后得到的对象
Поскольку JSON родился из JS и также является подмножеством JS, его можно напрямую передать в eval для запуска.
Однако обычно мы все говорим, что Eval — это зло, постараемся не использовать. Почему это здесь?На самом деле eval не зло, но у новичков легко вызвать проблемы, поэтому не рекомендуется его использовать. Если вы достаточно хороши, чтобы правильно использовать Eval, то у него все еще есть много применений, например,статический шаблон.
Хорошо, вернемся к сказанному выше, мы вызываем eval напрямую, как новичок, не будет ли проблем? →Да, здесь есть XSS-уязвимость. Условие срабатывания: параметр json — это не настоящие данные JSON, а исполняемый код JS.
Так как же избежать этой проблемы? → Старик Дуглас КРОКФОРД пожертвовал нам:Проверьте параметр json. Только когда он действительно соответствует формату JSON, можно вызывать eval., в частности следующие регулярные матчи.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we
// replace all simple value tokens with "]" characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or "]" or
// "," or ":" or "{" or "}". If that is so, then the text is safe for eval.
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
rx_one.test(
json
.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {
var obj = eval("(" +json + ")");
}
Смотрите код выше, вы правы?сложное регулярное выражение головокружениеШерстяная ткань? В любом случае, у меня сильно закружилась голова, поэтому я нашел очень простой в использованииШтатный инструмент визуализации RegexperПриходите и помогите мне понять эти закономерности, как показано на рисунке ниже.
Обратите внимание на 2 места:
-
В английской заметке упоминается:
Third, we delete all open brackets that follow a colon or comma or that begin the text.
якобы для удаления открытых скобок
(
, но на самом деле обычное совпадение rx_four удаляет[
,Почему это? потому чтоРазница между китайской и английской семантикой. В китайском языке открытые скобки обычно относятся к(
, а в английском языке открытые скобки обычно относятся к[
, нюансы нужно знать. -
Посмотрите на rx_three, у которого есть
(?:)
Структура, это обычная группа без захвата, подробности см.здесь. Причина не использовать группировку захвата: JSON для анализа может быть большим JSON.Если каждый совпадающий токен кэшируется, потребление памяти огромно, и здесь мы хотим только заменить символы, и вам не нужно знать какие символы совпадают.
Дальнейшее чтение:
- Почему eval устарел в JavaScript?
- JSON: не поймите меня неправильно, на самом деле я не подмножество JavaScript., перевод Магнуса Холма «Блудный сын»
рекурсия
Первый метод eval эквивалентен запихиванию в него строки JSON. На самом деле мы можемВручную сканировать посимвольно, а потом судить, это второй метод: рекурсия.
// 调用核心的 next 函数,逐个读取字符
var next = function (c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== ch) {
error("Expected '" + c + "' instead of '" + ch + "'");
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at += 1;
return ch;
};
Так называемая «рекурсия» заключается в многократном вызове функции значения.
value = function () {
// Parse a JSON value. It could be an object, an array, a string, a number,
// or a word.
white();
// 根据当前字符是什么,我们便能推导出后面应该接的是什么类型
switch (ch) {
case "{":
return object();
case "[":
return array();
case "\"":
return string();
case "-":
return number();
default:
return (ch >= "0" && ch <= "9")
? number()
: word();
}
};
или с'{"a":"1", "b":2}'
Например, общая логика программы такова: старт → первый вызовvalue()
→ Открытие{
→ Это оказался объект, идиobject()
→ пройтиstring()
Получить значение ключа "a" → прочитать двоеточие, о, там могут быть объекты, массивы, логические значения и т.д., что это такое, надо еще раз вызыватьvalue()
только знать → …
Эта схема реализации не использует ни eval, ни регулярность, она просто считывает символы один за другим, поэтому логика кода усложняется и требует дополнительной отладки для прояснения логики. lqt0223 также проанализировал эту реализацию:Самостоятельно реализовать парсинг JSON и XML не так уж и сложно.
Государственный аппарат
Название конечного автомата очень абстрактное, а применение очень широкое, например, обычный движок, лексический анализ и даже алгоритм сопоставления строк KMP можно объяснить. Он представляет собой существенную логику:В состоянии A, если вы войдете в B, он перейдет в состояние C.
Так,Какое отношение конечный автомат имеет к разбору строк JSON?? → Строки JSON форматируются, например, ключ и значение разделяются двоеточиями, например, разные пары ключ-значение разделяются запятыми...Эти спецификации формата могут быть переведены в переходы состояний конечных автоматов., Например, «если он обнаруживает двоеточие, это означает, что на следующем шаге можно ввести значение» и так далее. или с'{"a":"1", "b":2}'
В качестве примера посмотрим, какие состояния проходят через конечный автомат при анализе этой строки JSON.
Кроме того, в этой третьей реализации код выглядит очень аккуратно, поскольку в нем широко используется шаблон посетителя, например:
var string = { // The actions for string tokens
go: function () {
state = "ok";
},
firstokey: function () {
key = value;
state = "colon";
},
okey: function () {
key = value;
state = "colon";
},
ovalue: function () {
state = "ocomma";
},
firstavalue: function () {
state = "acomma";
},
avalue: function () {
state = "acomma";
}
};
позже
Казалось бы, простой JSON.parse имеет к этому большое отношение. Кстати, если вы хотите увидеть реализацию JSON.stringify, вы можете увидетьИздание Дугласа Крокфорда, смотрите такжеВерсия MDN, они похожи. Кроме того, учитывая, что Библиотека JSON, написанная Дугласом Крокфордом, не обрабатывала некоторые особые случаи, и позже была выпущена новая библиотека с именемJSON3, увидеть разницу между нимисвязанные обсуждения.