This commit is contained in:
Ilya Kantor 2016-07-28 19:41:51 +03:00
parent 0831ea228f
commit 168a0d5d0f
9 changed files with 246 additions and 20 deletions

View file

@ -0,0 +1,12 @@
```js
var leader = {
name: "Василий Иванович",
age: 35
};
var leaderStr = JSON.stringify(leader);
leader = JSON.parse(leaderStr);
```

View file

@ -0,0 +1,16 @@
importance: 3
---
# Превратите объект в JSON
Превратите объект `leader` из примера ниже в JSON:
```js
var leader = {
name: "Василий Иванович",
age: 35
};
```
После этого прочитайте получившуюся строку обратно в объект.

View file

@ -0,0 +1,59 @@
# Ответ на первый вопрос
Обычный вызов `JSON.stringify(team)` выдаст ошибку, так как объекты `leader` и `soldier` внутри структуры ссылаются друг на друга.
Формат JSON не предусматривает средств для хранения ссылок.
# Варианты решения
Чтобы превращать такие структуры в JSON, обычно используются два подхода:
1. Добавить в `team` свой код `toJSON`:
```js
team.toJSON = function() {
/* свой код, который может создавать копию объекта без круговых ссылок и передавать управление JSON.stringify */
}
```
При этом, конечно, понадобится и своя функция чтения из JSON, которая будет восстанавливать объект, а затем дополнять его круговыми ссылками.
2. Можно учесть возможную проблему в самой структуре, используя вместо ссылок `id`. Как правило, это несложно, ведь на сервере у данных тоже есть идентификаторы.
Изменённая структура может выглядеть так:
```js
var leader = {
id: 12,
name: "Василий Иванович"
};
var soldier = {
id: 51,
name: "Петька"
};
*!*
// поменяли прямую ссылку на ID
leader.soldierId = 51;
soldier.leaderId = 12;
*/!*
var team = {
12: leader,
51: soldier
};
```
..Но действительно ли это решение будет оптимальным? Использовать структуру стало сложнее, и вряд ли это изменение стоит делать лишь из-за JSON. Вот если есть другие преимущества, тогда можно подумать.
Универсальный вариант подхода, описанного выше -- это использование особой реализации JSON, которая не входит в стандарт и поддерживает расширенный формат для поддержки ссылок.
Она, к примеру, есть во фреймворке Dojo.
При вызове `dojox.json.ref.toJson(team)` будет создано следующее строковое представление:
```js no-beautify
[{"name":"Василий Иванович","soldier":{"name":"Петька","leader":{"$ref":"#0"}}},{"$ref":"#0.soldier"}]
```
Метод разбора такой строки -- также свой: `dojox.json.ref.fromJson`.

View file

@ -0,0 +1,26 @@
importance: 3
---
# Превратите объекты со ссылками в JSON
Превратите объект `team` из примера ниже в JSON:
```js
var leader = {
name: "Василий Иванович"
};
var soldier = {
name: "Петька"
};
// эти объекты ссылаются друг на друга!
leader.soldier = soldier;
soldier.leader = leader;
var team = [leader, soldier];
```
1. Может ли это сделать прямой вызов `JSON.stringify(team)`? Если нет, то почему?
2. Какой подход вы бы предложили для чтения и восстановления таких объектов?

View file

@ -0,0 +1,443 @@
# JSON methods, toJSON [todo: after Date]
The [JSON](http://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) format is used to represent an object as a string.
When we need to send an object over a network -- from the client to server or in the reverse direction, this format is the most widespread.
Javascript provides built-in methods to convert objects into JSON and back, which also allow a few tricks that make them even more useful.
[cut]
## JSON.stringify
JSON, despite its name (JavaScript Object Notation) is an independent standard. It is described as [RFC 4627](http://tools.ietf.org/html/rfc4627). Most programming languages have libraries to encode objects in it. So it's easy to use JSON for data exchange when the client uses Javascript and the server is written on Ruby/PHP/Java/Whatever.
In Javascript, the native method [JSON.stringify(value)](mdn:js/JSON/stringify) accepts a value and returns it's representation as a string in JSON format.
For instance:
```js run
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json = JSON.stringify(student);
alert(typeof json); // string
alert(json);
/*
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"wife": null
}
*/
```
The JSON-encoded object has several important differences from the original variant:
- Strings use double quotes. No single quotes or backticks in JSON. So `'John'` became `"John"`.
- Object property names are double-quoted also. Also obligatory. So `name` became `"name"`.
`JSON.stringify` can be applied not only to objects, but to other values:
```js run
// a number in JSON is just a number
alert( JSON.stringify(1) ) // 1
// a string in JSON is still a string, but double-quoted
alert( JSON.stringify('test') ) // "test"
// double quotes are escaped
alert( JSON.stringify('"quoted"') ) // "\"quoted\"" (inner quotes are escaped with \")
// array of objects
alert( JSON.stringify([{name: "John"},{name: "Ann"}]) ); // [{"name":"John"},{"name":"Ann"}]
```
The supported JSON types are:
- Objects `{ ... }`
- Arrays `[ ... ]`
- Primitives:
- strings,
- numbers,
- boolean values `true/false`,
- `null`.
In the examples above we can see them all.
JSON format does not support any other values.
For instance, functions and `undefined` values, symbolic properties are skipped by `JSON.stringify`:
```js run
let user = {
sayHi() { // ignored
alert("Hello");
},
[Symbol("id")]: 123, // ignored
something: undefined // ignored
};
alert( JSON.stringify(user) ); // {} (empty object)
```
### Custom "toJSON"
Normally, an object is represented with a list of its properties.
But it may provide its own JSON-transformation by implementing method `toJSON`.
For instance:
```js run
let room = {
number: 23
};
event = {
title: "Conference",
date: new Date(Date.UTC(2017, 0, 1)),
room
};
alert( JSON.stringify(event) );
/*
{
"title":"Conference",
*!*
"date":"2017-01-01T00:00:00.000Z", // (1)
*/!*
"room": {"number":23} // (2)
}
*/
```
Please note that `date` `(1)` became a string. That's because all dates have a built-in `toJSON` method.
Обратим внимание на два момента:
1. Дата превратилась в строку. Это не случайно: у всех дат есть встроенный метод `toJSON`. Его результат в данном случае -- строка в таймзоне UTC.
2. У объекта `room` нет метода `toJSON`. Поэтому он сериализуется перечислением свойств.
Мы, конечно, могли бы добавить такой метод, тогда в итог попал бы его результат:
```js run
var room = {
number: 23,
*!*
toJSON: function() {
return this.number;
}
*/!*
};
alert( JSON.stringify(room) ); // 23
```
If our object wants to provide its own logic for JSON conversion TOJSON
The result of `JSON.stringify` is a string. We can send it over the wire or put in the plain data storage.
To turn a JSON-string back into the object, we need another method named [JSON.parse](mdn:js/JSON/parse).
## JSON.parse
Вызов `JSON.parse(str)` превратит строку с данными в формате JSON в JavaScript-объект/массив/значение.
Например:
```js run
var numbers = "[0, 1, 2, 3]";
numbers = JSON.parse(numbers);
alert( numbers[1] ); // 1
```
Или так:
```js run
var user = '{ "name": "Вася", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';
user = JSON.parse(user);
alert( user.friends[1] ); // 1
```
Данные могут быть сколь угодно сложными, объекты и массивы могут включать в себя другие объекты и массивы. Главное чтобы они соответствовали формату.
````warn header="JSON-объекты ≠ JavaScript-объекты"
Объекты в формате JSON похожи на обычные JavaScript-объекты, но отличаются от них более строгими требованиями к строкам -- они должны быть именно в двойных кавычках.
В частности, первые два свойства объекта ниже -- некорректны:
```js
{
*!*name*/!*: "Вася", // ошибка: ключ name без кавычек!
"surname": *!*'Петров'*/!*,// ошибка: одинарные кавычки у значения 'Петров'!
"age": 35, // .. а тут всё в порядке.
"isAdmin": false // и тут тоже всё ок
}
```
Кроме того, в формате JSON не поддерживаются комментарии. Он предназначен только для передачи данных.
Есть нестандартное расширение формата JSON, которое называется [JSON5](http://json5.org/) и как раз разрешает ключи без кавычек, комментарии и т.п, как в обычном JavaScript. На данном этапе, это отдельная библиотека.
````
## Умный разбор: JSON.parse(str, reviver)
Метод `JSON.parse` поддерживает и более сложные алгоритмы разбора.
Например, мы получили с сервера объект с данными события `event`.
Он выглядит так:
```js
// title: название события, date: дата события
var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}';
```
...И теперь нужно *восстановить* его, то есть превратить в JavaScript-объект.
Попробуем вызвать для этого `JSON.parse`:
```js run
var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}';
var event = JSON.parse(str);
*!*
alert( event.date.getDate() ); // ошибка!
*/!*
```
...Увы, ошибка!
Дело в том, что значением `event.date` является строка, а отнюдь не объект `Date`. Откуда методу `JSON.parse` знать, что нужно превратить строку именно в дату?
**Для интеллектуального восстановления из строки у `JSON.parse(str, reviver)` есть второй параметр `reviver`, который является функцией `function(key, value)`.**
Если она указана, то в процессе чтения объекта из строки `JSON.parse` передаёт ей по очереди все создаваемые пары ключ-значение и может возвратить либо преобразованное значение, либо `undefined`, если его нужно пропустить.
В данном случае мы можем создать правило, что ключ `date` всегда означает дату:
```js run
// дата в строке - в формате UTC
var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}';
*!*
var event = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
*/!*
alert( event.date.getDate() ); // теперь сработает!
```
Кстати, эта возможность работает и для вложенных объектов тоже:
```js run
var schedule = '{ \
"events": [ \
{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}, \
{"title":"День рождения","date":"2015-04-18T12:00:00.000Z"} \
]\
}';
schedule = JSON.parse(schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
*!*
alert( schedule.events[1].date.getDate() ); // сработает!
*/!*
```
## Сериализация, метод JSON.stringify
Метод `JSON.stringify(value, replacer, space)` преобразует ("сериализует") значение в JSON-строку.
Пример использования:
```js run
var event = {
title: "Конференция",
date: "сегодня"
};
var str = JSON.stringify(event);
alert( str ); // {"title":"Конференция","date":"сегодня"}
// Обратное преобразование.
event = JSON.parse(str);
```
**При сериализации объекта вызывается его метод `toJSON`.**
Если такого метода нет -- перечисляются его свойства, кроме функций.
Посмотрим это в примере посложнее:
```js run
var room = {
number: 23,
occupy: function() {
alert( this.number );
}
};
event = {
title: "Конференция",
date: new Date(Date.UTC(2014, 0, 1)),
room: room
};
alert( JSON.stringify(event) );
/*
{
"title":"Конференция",
"date":"2014-01-01T00:00:00.000Z", // (1)
"room": {"number":23} // (2)
}
*/
```
Обратим внимание на два момента:
1. Дата превратилась в строку. Это не случайно: у всех дат есть встроенный метод `toJSON`. Его результат в данном случае -- строка в таймзоне UTC.
2. У объекта `room` нет метода `toJSON`. Поэтому он сериализуется перечислением свойств.
Мы, конечно, могли бы добавить такой метод, тогда в итог попал бы его результат:
```js run
var room = {
number: 23,
*!*
toJSON: function() {
return this.number;
}
*/!*
};
alert( JSON.stringify(room) ); // 23
```
### Исключение свойств
Попытаемся преобразовать в JSON объект, содержащий ссылку на DOM.
Например:
```js run
var user = {
name: "Вася",
age: 25,
window: window
};
*!*
alert( JSON.stringify(user) ); // ошибка!
// TypeError: Converting circular structure to JSON (текст из Chrome)
*/!*
```
Произошла ошибка! В чём же дело, неужели некоторые объекты запрещены? Как видно из текста ошибки -- дело совсем в другом. Глобальный объект `window` -- сложная структура с кучей встроенных свойств и круговыми ссылками, поэтому его преобразовать невозможно. Да и нужно ли?
**Во втором параметре `JSON.stringify(value, replacer)` можно указать массив свойств, которые подлежат сериализации.**
Например:
```js run
var user = {
name: "Вася",
age: 25,
window: window
};
*!*
alert( JSON.stringify(user, ["name", "age"]) );
// {"name":"Вася","age":25}
*/!*
```
Для более сложных ситуаций вторым параметром можно передать функцию `function(key, value)`, которая возвращает сериализованное `value` либо `undefined`, если его не нужно включать в результат:
```js run
var user = {
name: "Вася",
age: 25,
window: window
};
*!*
var str = JSON.stringify(user, function(key, value) {
if (key == 'window') return undefined;
return value;
});
*/!*
alert( str ); // {"name":"Вася","age":25}
```
В примере выше функция пропустит свойство с названием `window`. Для остальных она просто возвращает значение, передавая его стандартному алгоритму. А могла бы и как-то обработать.
```smart header="Функция `replacer` работает рекурсивно"
То есть, если объект содержит вложенные объекты, массивы и т.п., то все они пройдут через `replacer`.
```
### Красивое форматирование
В методе `JSON.stringify(value, replacer, space)` есть ещё третий параметр `space`.
Если он является числом -- то уровни вложенности в JSON оформляются указанным количеством пробелов, если строкой -- вставляется эта строка.
Например:
```js run
var user = {
name: "Вася",
age: 25,
roles: {
isAdmin: false,
isEditor: true
}
};
*!*
var str = JSON.stringify(user, "", 4);
*/!*
alert( str );
/* Результат -- красиво сериализованный объект:
{
"name": "Вася",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
```
## Итого
- JSON -- формат для представления объектов (и не только) в виде строки.
- Методы [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) и [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) позволяют интеллектуально преобразовать объект в строку и обратно.

View file

@ -136,7 +136,9 @@ alert(`Length: ${arr.length}`); // 5, remember, length is the last index + 1
````
## Summary [todo]
Futher in the chapter <info:map-set-weakmap-weakset> we will meet other data structures `Map` and `Set` that also implement methods `keys()`, `values()` and `entries()`.

View file

@ -1,4 +1,5 @@
# TODO: Code quality
# Code quality
This chapter goes early to explain coding practices that we'll use further in the development.
In this chapter we cover common, but very important things. Probably you'll want to reread some chapters later when you learn more about the language and write more code.

View file

@ -42,7 +42,8 @@ data-types
number (rounding, precision, isFinite, isNaN, parse*, Math.*)
string (quotes, search, substring, tagged template notice)
<<< object-descriptors
<<< property-descriptors
array-methods (TODO: translate tasks)
map-set-weakmap-weakset

96
1-js/plan3.txt Normal file
View file

@ -0,0 +1,96 @@
getting-started
introduction
editor
devtools
first-steps
hello-world (intro, ext scripts)
structure
use strict
variables (const, name things right)
types (no objects, but typeof)
type-conversions (conversions string/number/boolean, no objects)
operators
comparison (no objects)
uibasic
logical-ops
while-for (labels, no objects)
switch
function-basics (decl, shadowing, naming, default params)
function-expressions-arrows (function expr, arrow, todo: move new Function out of this?)
javascript-specials (TODO, remove it? migrate all function* to separate chapter?)
object-basics
object(props, for-in, refs, assign)
garbage-collection
object-methods (this, method syntax, no call/apply)
primitives-methods (on-the-fly objects)
symbol
object-toprimitive
array
iterable
<<< json
more-syntax
function-arguments-rest-spread
destructuring-assignment (also func params destructuring)
advanced-loops (iterators over objects, arrays)
code-quality
debugging-chrome (TODO)
coding-style (TODO)
write-unmaintainable-code (TODO)
test-driven-development (TODO)
polyfills (TODO)
data-types
number (rounding, precision, isFinite, isNaN, parse*, Math.*)
string (quotes, search, substring, tagged template notice)
<<< property-descriptors
array-methods (TODO: translate tasks)
map-set-weakmap-weakset
date (TODO: tasks)
deeper
recursion
running execution context = where + lexical environment = envrec + outer
context stack
pow task
traverse list task
task: traverse list back
closures
LE outer
returning a function
garbage collection
var
window
function-object
name property (obj props, methods, assignments - set it)
length property
custom properties
new function
scheduling: settimeout, setinterval
recursive settimeout
call-apply-decorators
bind
-------
<<< descriptors (TODO: LATER, need JSON to output, better after inheritance to explain getOwnProps)
<<< getter setter
constructors
classes
instanceof
после 4-object сделать
descriptors
more-features
try..catch
setTimeout
JSON
======
class A extends Object != class A