up
This commit is contained in:
parent
2a0516d174
commit
40ca6daa87
6 changed files with 709 additions and 55 deletions
|
@ -43,14 +43,16 @@ let user = new User("John");
|
||||||
user.sayHi();
|
user.sayHi();
|
||||||
```
|
```
|
||||||
|
|
||||||
It's easy to see that the two examples are alike. So, what exactly does `class` do? We may think that it defines a new language-level entity, but that would be wrong.
|
It's easy to see that the two examples are alike. Just please note that methods in a class do not have a comma between them. Notice developers sometimes forget it and put a comma between class methods, and things don't work. That's not a literal object, but a class syntax.
|
||||||
|
|
||||||
|
So, what exactly does `class` do? We may think that it defines a new language-level entity, but that would be wrong.
|
||||||
|
|
||||||
The `class User {...}` here actually does two things:
|
The `class User {...}` here actually does two things:
|
||||||
|
|
||||||
1. Declares a variable `User` that references the function named `"constructor"`.
|
1. Declares a variable `User` that references the function named `"constructor"`.
|
||||||
2. Puts into `User.prototype` methods listed in the definition. Here it includes `sayHi` and the `constructor`.
|
2. Puts into `User.prototype` methods listed in the definition. Here it includes `sayHi` and the `constructor`.
|
||||||
|
|
||||||
Here's some code to demonstrate that:
|
Here's the code to dig into the class and see that:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class User {
|
class User {
|
||||||
|
@ -69,15 +71,19 @@ alert(User == User.prototype.constructor); // true
|
||||||
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
|
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's the illustration of `class User`:
|
Here's the illustration of what `class User` creates:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
So `class` is a special syntax to define the constructor with prototype methods.
|
|
||||||
|
|
||||||
...But not only that. There are minor tweaks here and there to ensure the right usage.
|
|
||||||
|
|
||||||
For instance, the `constructor` function can't be called without `new`:
|
So `class` is a special syntax to define a constructor together with its prototype methods.
|
||||||
|
|
||||||
|
...But not only that. There are minor tweaks here and there:
|
||||||
|
|
||||||
|
Constructors require `new`
|
||||||
|
: Unlike a regular function, a class `constructor` can't be called without `new`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class User {
|
class User {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@ -87,23 +93,19 @@ alert(typeof User); // function
|
||||||
User(); // Error: Class constructor User cannot be invoked without 'new'
|
User(); // Error: Class constructor User cannot be invoked without 'new'
|
||||||
```
|
```
|
||||||
|
|
||||||
```smart header="Outputting a class"
|
Different string output
|
||||||
If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.
|
: If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.
|
||||||
|
|
||||||
Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in JavaScript language.
|
Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in JavaScript language.
|
||||||
```
|
|
||||||
|
|
||||||
```smart header="Class methods are non-enumerable"
|
Class methods are non-enumerable
|
||||||
Class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods.
|
: A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods.
|
||||||
```
|
|
||||||
|
|
||||||
```smart header="What if there's no constructor?"
|
Classes have a default `constructor() {}`
|
||||||
If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`.
|
: If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`.
|
||||||
```
|
|
||||||
|
|
||||||
```smart header="Classes always `use strict`"
|
Classes always `use strict`
|
||||||
All code inside the class construct is automatically in strict mode.
|
: All code inside the class construct is automatically in strict mode.
|
||||||
```
|
|
||||||
|
|
||||||
### Getters/setters
|
### Getters/setters
|
||||||
|
|
||||||
|
@ -141,13 +143,26 @@ alert(user.name); // John
|
||||||
user = new User(""); // Name too short.
|
user = new User(""); // Name too short.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Internally, getters and setters are also created on the `User` prototype, like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
Object.defineProperty(User.prototype, {
|
||||||
|
name: {
|
||||||
|
get() {
|
||||||
|
return this._name
|
||||||
|
},
|
||||||
|
set(name) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Only methods
|
### Only methods
|
||||||
|
|
||||||
Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods (without a comma between them) and getters/setters.
|
Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods and getters/setters. There is some work going on in the specification to lift that limitation, but it's not yet there.
|
||||||
|
|
||||||
The idea is that everything inside `class` goes to the prototype. And the prototype should store methods only, which are shared between objects. The data describing a concrete object state should reside in individual objects.
|
If we really need to put a non-function value into the prototype, then we can alter `prototype` manually, like this:
|
||||||
|
|
||||||
If we really insist on putting a non-function value into the prototype, then `class` can't help here. We can alter `prototype` manually though, like this:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class User { }
|
class User { }
|
||||||
|
@ -157,9 +172,9 @@ User.prototype.test = 5;
|
||||||
alert( new User().test ); // 5
|
alert( new User().test ); // 5
|
||||||
```
|
```
|
||||||
|
|
||||||
So, technically that's possible, but we should know why we're doing it.
|
So, technically that's possible, but we should know why we're doing it. Such properties will be shared among all objects of the class.
|
||||||
|
|
||||||
An alternative here would be to use a getter:
|
An "in-class" alternative is to use a getter:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class User {
|
class User {
|
||||||
|
@ -171,7 +186,7 @@ class User {
|
||||||
alert( new User().test ); // 5
|
alert( new User().test ); // 5
|
||||||
```
|
```
|
||||||
|
|
||||||
From the external code, the usage is the same. But the getter variant is probably a bit slower.
|
From the external code, the usage is the same. But the getter variant is a bit slower.
|
||||||
|
|
||||||
## Class Expression
|
## Class Expression
|
||||||
|
|
||||||
|
@ -180,8 +195,9 @@ Just like functions, classes can be defined inside another expression, passed ar
|
||||||
Here's a class-returning function ("class factory"):
|
Here's a class-returning function ("class factory"):
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
function getClass(phrase) {
|
function makeClass(phrase) {
|
||||||
*!*
|
*!*
|
||||||
|
// declare a class and return it
|
||||||
return class {
|
return class {
|
||||||
sayHi() {
|
sayHi() {
|
||||||
alert(phrase);
|
alert(phrase);
|
||||||
|
@ -190,30 +206,31 @@ function getClass(phrase) {
|
||||||
*/!*
|
*/!*
|
||||||
}
|
}
|
||||||
|
|
||||||
let User = getClass("Hello");
|
let User = makeClass("Hello");
|
||||||
|
|
||||||
new User().sayHi(); // Hello
|
new User().sayHi(); // Hello
|
||||||
```
|
```
|
||||||
|
|
||||||
That's quite normal if we recall that `class` is just a special form of function-with-prototype definition.
|
That's quite normal if we recall that `class` is just a special form of a function-with-prototype definition.
|
||||||
|
|
||||||
And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
|
And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
// "Named Class Expression" (alas, no such term, but that's what's going on)
|
||||||
let User = class *!*MyClass*/!* {
|
let User = class *!*MyClass*/!* {
|
||||||
sayHi() {
|
sayHi() {
|
||||||
alert(MyClass);
|
alert(MyClass); // MyClass is visible only inside the class
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
new User().sayHi(); // works, shows MyClass definition
|
new User().sayHi(); // works, shows MyClass definition
|
||||||
|
|
||||||
alert(MyClass); // error, MyClass is only visible in methods of the class
|
alert(MyClass); // error, MyClass not visible outside of the class
|
||||||
```
|
```
|
||||||
|
|
||||||
## Static methods
|
## Static methods
|
||||||
|
|
||||||
Static methods are bound to the class function, not to its `"prototype"`.
|
We can also assign methods to the class function, not to its `"prototype"`. Such methods are called *static*.
|
||||||
|
|
||||||
An example:
|
An example:
|
||||||
|
|
||||||
|
@ -241,7 +258,7 @@ User.staticMethod = function() {
|
||||||
|
|
||||||
The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule).
|
The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule).
|
||||||
|
|
||||||
Usually, static methods are used when the code is related to the class, but not to a particular object of it.
|
Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
|
||||||
|
|
||||||
For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this:
|
For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this:
|
||||||
|
|
||||||
|
@ -273,9 +290,15 @@ articles.sort(Article.compare);
|
||||||
alert( articles[0].title ); // Body
|
alert( articles[0].title ); // Body
|
||||||
```
|
```
|
||||||
|
|
||||||
Here `Article.compare` stands "over" the articles, as a means to compare them.
|
Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
|
||||||
|
|
||||||
Another example would be a so-called "factory" method, that creates an object with specific parameters.
|
Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
|
||||||
|
|
||||||
|
1. Create by given parameters (`title`, `date` etc).
|
||||||
|
2. Create an empty article with today's date.
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
|
||||||
|
|
||||||
Like `Article.createTodays()` here:
|
Like `Article.createTodays()` here:
|
||||||
|
|
||||||
|
@ -299,12 +322,36 @@ let article = Article.createTodays();
|
||||||
alert( articles.title ); // Todays digest
|
alert( articles.title ); // Todays digest
|
||||||
```
|
```
|
||||||
|
|
||||||
Now every time we need to create a todays digest, we can call `Article.createTodays()`.
|
Now every time we need to create a todays digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class.
|
||||||
|
|
||||||
Static methods are often used in database-related classes to search/save/remove entries from the database, like this:
|
Static methods are also used in database-related classes to search/save/remove entries from the database, like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// assuming Article is a special class for managing articles
|
// assuming Article is a special class for managing articles
|
||||||
// static method to remove the article:
|
// static method to remove the article:
|
||||||
Article.remove({id: 12345});
|
Article.remove({id: 12345});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The basic class syntax looks like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class MyClass {
|
||||||
|
constructor(...) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
method1(...) {}
|
||||||
|
method2(...) {}
|
||||||
|
get something(...) {}
|
||||||
|
set something(...) {}
|
||||||
|
static staticMethod(..) {}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The value of `MyClass` is a function provided as `constructor`. If there's no `constructor`, then an empty function.
|
||||||
|
|
||||||
|
In any case, methods listed in the class declaration become members of its `prototype`, with the exception of static methods that are written into the function itself and callable as `MyClass.staticMethod()`. Static methods are used when we need a function bound to a class, but not to any object of that class.
|
||||||
|
|
||||||
|
In the next chapter we'll learn more about classes, including inheritance.
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
# Mixins
|
# Mixins
|
||||||
|
|
||||||
In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]`.
|
In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class.
|
||||||
|
|
||||||
But sometimes we need such kind of thing. For instance, we have a code that implements events exchange or templating, and we'd like to be able to add these capabilities to any class easily.
|
But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bycicle`, and want to make a `StreetSweepingBycicle`.
|
||||||
|
|
||||||
What can help here is *mixins*.
|
Or, talking about programming, we have a class `Renderer` that implements templating and a class `EventEmitter` that implements event handling, and want to merge these functionalities together with a class `Page`, to make a page that can use templates and emit events.
|
||||||
|
|
||||||
As [defined in Wikipedia](https://en.wikipedia.org/wiki/Mixin), a *mixin* is a class that contains methods for use by other classes without having to be the parent class of those other classes.
|
There's a concept that can help here, called "mixins".
|
||||||
|
|
||||||
In other words, a *mixin* is a class that implements a certain behavior. But we do not use it alone, we use it to add the behavior to other classes.
|
As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class that contains methods for use by other classes without having to be the parent class of those other classes.
|
||||||
|
|
||||||
|
In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.
|
||||||
|
|
||||||
## A mixin example
|
## A mixin example
|
||||||
|
|
||||||
The simplest way to make a mixin in JavaScript -- is to make an object with useful methods, that we can just copy into the prototype.
|
The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.
|
||||||
|
|
||||||
For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`:
|
For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`:
|
||||||
|
|
||||||
|
@ -45,9 +47,17 @@ Object.assign(User.prototype, sayHiMixin);
|
||||||
new User("Dude").sayHi(); // Hi Dude!
|
new User("Dude").sayHi(); // Hi Dude!
|
||||||
```
|
```
|
||||||
|
|
||||||
There's no inheritance, there's a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods.
|
There's no inheritance, but a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods, like this:
|
||||||
|
|
||||||
Mixins also can make use of inheritance.
|
```js
|
||||||
|
class User extends Person {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(User.prototype, sayHiMixin);
|
||||||
|
```
|
||||||
|
|
||||||
|
Mixins can make use of inheritance inside themselves.
|
||||||
|
|
||||||
For instance, here `sayHiMixin` inherits from `sayMixin`:
|
For instance, here `sayHiMixin` inherits from `sayMixin`:
|
||||||
|
|
||||||
|
@ -59,8 +69,7 @@ let sayMixin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let sayHiMixin = {
|
let sayHiMixin = {
|
||||||
// can use any way of prototype setting here
|
__proto__: sayMixin, // (or we could use Object.create to set the prototype here)
|
||||||
__proto__: sayMixin,
|
|
||||||
|
|
||||||
sayHi() {
|
sayHi() {
|
||||||
*!*
|
*!*
|
||||||
|
@ -94,25 +103,22 @@ That's because methods from `sayHiMixin` have `[[HomeObject]]` set to it. So `su
|
||||||
|
|
||||||
## EventMixin
|
## EventMixin
|
||||||
|
|
||||||
Now a mixin for the real life.
|
Now let's make a mixin for real life.
|
||||||
|
|
||||||
The important feature of many objects is working with events.
|
The important feature of many objects is working with events.
|
||||||
|
|
||||||
That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "subscribe" to receive such notifications.
|
That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "listen" to such events.
|
||||||
|
|
||||||
An event must have a name and, if necessary, the attached data.
|
An event must have a name and, optionally, bundle some additional data.
|
||||||
|
|
||||||
For instance, an object `user` can generate an event `"login"` when the visitor logs in. And an object `calendar` may want to receive such notifications and load the information about that visitor.
|
For instance, an object `user` can generate an event `"login"` when the visitor logs in. And another object `calendar` may want to receive such events to load the calendar for the logged-in person.
|
||||||
|
|
||||||
Or, the object `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event.
|
Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event.
|
||||||
|
|
||||||
Events is a way to "share information" with anyone who wants it.
|
Events is a way to "share information" with anyone who wants it. They can be useful in any class, so let's make a mixin for them:
|
||||||
|
|
||||||
Here is `eventMixin` that implements the corresponding methods:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let eventMixin = {
|
let eventMixin = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to event, usage:
|
* Subscribe to event, usage:
|
||||||
* menu.on('select', function(item) { ... }
|
* menu.on('select', function(item) { ... }
|
||||||
|
|
440
7-network/1-xmlhttprequest/article.md
Normal file
440
7-network/1-xmlhttprequest/article.md
Normal file
|
@ -0,0 +1,440 @@
|
||||||
|
# XMLHttpRequest and AJAX
|
||||||
|
|
||||||
|
`XMLHttpRequest` is a built-in browser object that allows to make HTTP requests in JavaScript.
|
||||||
|
|
||||||
|
Despite of having the word "XML" in its name, it can operate on any data, not only in XML format.
|
||||||
|
|
||||||
|
## Asynchronous XMLHttpRequest
|
||||||
|
|
||||||
|
XMLHttpRequest has two modes of operation: synchronous and asynchronous.
|
||||||
|
|
||||||
|
First let's see the asynchronous variant as it's used in the majority of cases.
|
||||||
|
|
||||||
|
The code below loads the URL at `/article/xmlhttprequest/hello.txt` from the server and shows its content on-screen:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
*!*
|
||||||
|
// 1. Create a new XMLHttpRequest object
|
||||||
|
*/!*
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// 2. Configure it: GET-request for the URL /article/.../hello.txt
|
||||||
|
xhr.open('GET', '/article/xmlhttprequest/hello.txt');
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// 3. Send the request over the network
|
||||||
|
*/!*
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// 4. This will be called after the response is received
|
||||||
|
*/!*
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status != 200) { // analyze HTTP status of the response
|
||||||
|
// if it's not 200, consider it an error
|
||||||
|
alert(xhr.status + ': ' + xhr.statusText); // e.g. 404: Not Found
|
||||||
|
} else {
|
||||||
|
// show the result
|
||||||
|
alert(xhr.responseText); // responseText is the server response
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
As we can see, there are several methods of `XMLHttpRequest` here. Let's cover them.
|
||||||
|
|
||||||
|
## Setup: "open"
|
||||||
|
|
||||||
|
The syntax:
|
||||||
|
```js
|
||||||
|
xhr.open(method, URL, async, user, password)
|
||||||
|
```
|
||||||
|
|
||||||
|
This method is usually called first after `new XMLHttpRequest`. It specifies the main parameters of the request:
|
||||||
|
|
||||||
|
- `method` -- HTTP-method. Usually `"GET"` or `"POST"`, but we can also use TRACE/DELETE/PUT and so on.
|
||||||
|
- `URL` -- the URL to request. Can use any path and protocol, but there are cross-domain limitations called "Same Origin Policy". We can make any requests to the same `protocol://domain:port` that the current page comes from, but other locations are "forbidden" by default (unless they implement special HTTP-headers, we'll cover them in chapter [todo]).
|
||||||
|
- `async` -- if the third parameter is explicitly set to `false`, then the request is synchronous, otherwise it's asynchronous. We'll talk more about that in this chapter soon.
|
||||||
|
- `user`, `password` -- login and password for basic HTTP auth (if required).
|
||||||
|
|
||||||
|
Please note that `open` call, contrary to its name, does not open the connection. It only configures the request, but the network activity only starts with the call of `send`.
|
||||||
|
|
||||||
|
## Send it out: "send"
|
||||||
|
|
||||||
|
The syntax:
|
||||||
|
```js
|
||||||
|
xhr.send([body])
|
||||||
|
```
|
||||||
|
|
||||||
|
This method opens the connection and sends the request to server. The optional `body` parameter contains the request body. Some request methods like `GET` do not have a body. And some of them like `POST` use `body` to send the data. We'll see examples with a body in the next chapter.
|
||||||
|
|
||||||
|
|
||||||
|
## Cancel: abort and timeout
|
||||||
|
|
||||||
|
If we changed our mind, we can terminate the request at any time. The call to `xhr.abort()` does that:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.abort(); // terminate the request
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also specify a timeout using the corresponding property:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
```
|
||||||
|
|
||||||
|
The timeout is expressed in ms. If the request does not succeed within the given time, it gets canceled automatically.
|
||||||
|
|
||||||
|
## Events: onload, onerror etc
|
||||||
|
|
||||||
|
A request is asynchronous by default. In other words, the browser sends it out and allows other JavaScript code to execute.
|
||||||
|
|
||||||
|
After the request is sent, `xhr` starts to generate events. We can use `addEventListener` or `on<event>` properties to handle them, just like with DOM objects.
|
||||||
|
|
||||||
|
The modern [specification](https://xhr.spec.whatwg.org/#events) lists following events:
|
||||||
|
|
||||||
|
- `loadstart` -- the request has started.
|
||||||
|
- `progress` -- the browser received a data packet (can happen multiple times).
|
||||||
|
- `abort` -- the request was aborted by `xhr.abort()`.
|
||||||
|
- `error` -- an network error has occured, the request failed.
|
||||||
|
- `load` -- the request is successful, no errors.
|
||||||
|
- `timeout` -- the request was canceled due to timeout (if the timeout is set).
|
||||||
|
- `loadend` -- the request is done (with an error or without it)
|
||||||
|
- `readystatechange` -- the request state is changed (will cover later).
|
||||||
|
|
||||||
|
Using these events we can track successful loading (`onload`), errors (`onerror`) and the amount of the data loaded (`onprogress`).
|
||||||
|
|
||||||
|
Please note that errors here are "communication errors". In other words, if the connection is lost or the remote server does not respond at all -- then it's the error in the terms of XMLHttpRequest. Bad HTTP status like 500 or 404 are not considered errors.
|
||||||
|
|
||||||
|
Here's a more feature-full example, with errors and a timeout:
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<script>
|
||||||
|
function load(url) {
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.timeout = 1000;
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
alert(`Loaded: ${this.status} ${this.responseText}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = () => alert('Error');
|
||||||
|
|
||||||
|
xhr.ontimeout = () => alert('Timeout!');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick="load('/article/xmlhttprequest/hello.txt')">Load</button>
|
||||||
|
<button onclick="load('/article/xmlhttprequest/hello.txt?speed=0')">Load with timeout</button>
|
||||||
|
<button onclick="load('no-such-page')">Load 404</button>
|
||||||
|
<button onclick="load('http://example.com')">Load another domain</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The first button triggers only `onload` as it loads the file `hello.txt` normally.
|
||||||
|
2. The second button loads a very slow URL, so it calls only `ontimeout` (because `xhr.timeout` is set).
|
||||||
|
3. The third button loads a non-existant URL, but it also calls `onload` (with "Loaded: 404"), because there's no network error.
|
||||||
|
4. The last button tries to load a page from another domain. That's prohibited unless the remote server explicitly agrees by sending certain headers (to be covered later), so we have `onerror` here. The `onerror` handler would also trigger in other cases if we start a request, and then sever the network connection of our device.
|
||||||
|
|
||||||
|
## Response: status, responseText and others
|
||||||
|
|
||||||
|
Once the server has responded, we can receive the result in the following properties of the request object:
|
||||||
|
|
||||||
|
`status`
|
||||||
|
: HTTP status code: `200`, `404`, `403` and so on. Also can be `0` if an error occured.
|
||||||
|
|
||||||
|
`statusText`
|
||||||
|
: HTTP status message: usually `OK` for `200`, `Not Found` for `404`, `Forbidden` for `403` and so on.
|
||||||
|
|
||||||
|
`responseText`
|
||||||
|
: The text of the server response,
|
||||||
|
|
||||||
|
Есть и ещё одно свойство, которое используется гораздо реже:
|
||||||
|
|
||||||
|
`responseXML`
|
||||||
|
: Если сервер вернул XML, снабдив его правильным заголовком `Content-type: text/xml`, то браузер создаст из него XML-документ. По нему можно будет делать запросы `xhr.responseXml.querySelector("...")` и другие.
|
||||||
|
|
||||||
|
Оно используется редко, так как обычно используют не XML, а JSON. То есть, сервер возвращает JSON в виде текста, который браузер превращает в объект вызовом `JSON.parse(xhr.responseText)`.
|
||||||
|
|
||||||
|
## Синхронные и асинхронные запросы
|
||||||
|
|
||||||
|
Если в методе `open` установить параметр `async` равным `false`, то запрос будет синхронным.
|
||||||
|
|
||||||
|
Синхронные вызовы используются чрезвычайно редко, так как блокируют взаимодействие со страницей до окончания загрузки. Посетитель не может даже прокручивать её. Никакой JavaScript не может быть выполнен, пока синхронный вызов не завершён -- в общем, в точности те же ограничения как `alert`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Синхронный запрос
|
||||||
|
xhr.open('GET', 'phones.json', *!*false*/!*);
|
||||||
|
|
||||||
|
// Отсылаем его
|
||||||
|
xhr.send();
|
||||||
|
*!*
|
||||||
|
// ...весь JavaScript "подвиснет", пока запрос не завершится
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Если синхронный вызов занял слишком много времени, то браузер предложит закрыть "зависшую" страницу.
|
||||||
|
|
||||||
|
Из-за такой блокировки получается, что нельзя отослать два запроса одновременно. Кроме того, забегая вперёд, заметим, что ряд продвинутых возможностей, таких как возможность делать запросы на другой домен и указывать таймаут, в синхронном режиме не работают.
|
||||||
|
|
||||||
|
Из всего вышесказанного уже должно быть понятно, что синхронные запросы используются чрезвычайно редко, а асинхронные -- почти всегда.
|
||||||
|
|
||||||
|
Для того, чтобы запрос стал асинхронным, укажем параметр `async` равным `true`.
|
||||||
|
|
||||||
|
Изменённый JS-код:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.open('GET', 'phones.json', *!*true*/!*);
|
||||||
|
|
||||||
|
xhr.send(); // (1)
|
||||||
|
|
||||||
|
*!*
|
||||||
|
xhr.onreadystatechange = function() { // (3)
|
||||||
|
if (xhr.readyState != 4) return;
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
button.innerHTML = 'Готово!';
|
||||||
|
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
alert(xhr.status + ': ' + xhr.statusText);
|
||||||
|
} else {
|
||||||
|
alert(xhr.responseText);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
button.innerHTML = 'Загружаю...'; // (2)
|
||||||
|
button.disabled = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
Если в `open` указан третий аргумент `true` (или если третьего аргумента нет), то запрос выполняется асинхронно. Это означает, что после вызова `xhr.send()` в строке `(1)` код не "зависает", а преспокойно продолжает выполняться, выполняется строка `(2)`, а результат приходит через событие `(3)`, мы изучим его чуть позже.
|
||||||
|
|
||||||
|
Полный пример в действии:
|
||||||
|
|
||||||
|
[codetabs src="phones-async"]
|
||||||
|
|
||||||
|
# Событие readystatechange
|
||||||
|
|
||||||
|
Событие `readystatechange` происходит несколько раз в процессе отсылки и получения ответа. При этом можно посмотреть "текущее состояние запроса" в свойстве `xhr.readyState`.
|
||||||
|
|
||||||
|
В примере выше мы использовали только состояние `4` (запрос завершён), но есть и другие.
|
||||||
|
|
||||||
|
Все состояния, по [спецификации](http://www.w3.org/TR/XMLHttpRequest/#states):
|
||||||
|
|
||||||
|
```js
|
||||||
|
const unsigned short UNSENT = 0; // начальное состояние
|
||||||
|
const unsigned short OPENED = 1; // вызван open
|
||||||
|
const unsigned short HEADERS_RECEIVED = 2; // получены заголовки
|
||||||
|
const unsigned short LOADING = 3; // загружается тело (получен очередной пакет данных)
|
||||||
|
const unsigned short DONE = 4; // запрос завершён
|
||||||
|
```
|
||||||
|
|
||||||
|
Запрос проходит их в порядке `0` -> `1` -> `2` -> `3` -> ... -> `3` -> `4`, состояние `3` повторяется при каждом получении очередного пакета данных по сети.
|
||||||
|
|
||||||
|
Пример ниже демонстрирует переключение между состояниями. В нём сервер отвечает на запрос `digits`, пересылая по строке из 1000 цифр раз в секунду.
|
||||||
|
|
||||||
|
[codetabs src="readystate"]
|
||||||
|
|
||||||
|
```warn header="Точка разрыва пакетов не гарантирована"
|
||||||
|
При состоянии `readyState=3` (получен очередной пакет) мы можем посмотреть текущие данные в `responseText` и, казалось бы, могли бы работать с этими данными как с "ответом на текущий момент".
|
||||||
|
|
||||||
|
Однако, технически мы не управляем разрывами между сетевыми пакетами. Если протестировать пример выше в локальной сети, то в большинстве браузеров разрывы будут каждые 1000 символов, но в реальности пакет может прерваться на любом байте.
|
||||||
|
|
||||||
|
Чем это опасно? Хотя бы тем, что символы русского языка в кодировке UTF-8 кодируются двумя байтами каждый -- и разрыв может возникнуть *между ними*.
|
||||||
|
|
||||||
|
Получится, что при очередном `readyState` в конце `responseText` будет байт-полсимвола, то есть он не будет корректной строкой -- частью ответа! Если в скрипте как-то по-особому это не обработать, то неизбежны проблемы.
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTTP-заголовки
|
||||||
|
|
||||||
|
`XMLHttpRequest` умеет как указывать свои заголовки в запросе, так и читать присланные в ответ.
|
||||||
|
|
||||||
|
Для работы с HTTP-заголовками есть 3 метода:
|
||||||
|
|
||||||
|
`setRequestHeader(name, value)`
|
||||||
|
: Устанавливает заголовок `name` запроса со значением `value`.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
```
|
||||||
|
|
||||||
|
```warn header="Ограничения на заголовки"
|
||||||
|
Нельзя установить заголовки, которые контролирует браузер, например `Referer` или `Host` и ряд других (полный список [тут](http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader-method)).
|
||||||
|
|
||||||
|
Это ограничение существует в целях безопасности и для контроля корректности запроса.
|
||||||
|
```
|
||||||
|
|
||||||
|
````warn header="Поставленный заголовок нельзя снять"
|
||||||
|
Особенностью `XMLHttpRequest` является то, что отменить `setRequestHeader` невозможно.
|
||||||
|
|
||||||
|
Повторные вызовы лишь добавляют информацию к заголовку, например:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.setRequestHeader('X-Auth', '123');
|
||||||
|
xhr.setRequestHeader('X-Auth', '456');
|
||||||
|
|
||||||
|
// в результате будет заголовок:
|
||||||
|
// X-Auth: 123, 456
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
`getResponseHeader(name)`
|
||||||
|
: Возвращает значение заголовка ответа `name`, кроме `Set-Cookie` и `Set-Cookie2`.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.getResponseHeader('Content-Type')
|
||||||
|
```
|
||||||
|
|
||||||
|
`getAllResponseHeaders()`
|
||||||
|
: Возвращает все заголовки ответа, кроме `Set-Cookie` и `Set-Cookie2`.
|
||||||
|
|
||||||
|
Заголовки возвращаются в виде единой строки, например:
|
||||||
|
|
||||||
|
```
|
||||||
|
Cache-Control: max-age=31536000
|
||||||
|
Content-Length: 4260
|
||||||
|
Content-Type: image/png
|
||||||
|
Date: Sat, 08 Sep 2012 16:53:16 GMT
|
||||||
|
```
|
||||||
|
|
||||||
|
Между заголовками стоит перевод строки в два символа `"\r\n"` (не зависит от ОС), значение заголовка отделено двоеточием с пробелом `": "`. Этот формат задан стандартом.
|
||||||
|
|
||||||
|
Таким образом, если хочется получить объект с парами заголовок-значение, то эту строку необходимо разбить и обработать.
|
||||||
|
|
||||||
|
## Таймаут
|
||||||
|
|
||||||
|
Максимальную продолжительность асинхронного запроса можно задать свойством `timeout`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.timeout = 30000; // 30 секунд (в миллисекундах)
|
||||||
|
```
|
||||||
|
|
||||||
|
При превышении этого времени запрос будет оборван и сгенерировано событие `ontimeout`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
alert( 'Извините, запрос превысил максимальное время' );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Полный список событий
|
||||||
|
|
||||||
|
Современная [спецификация](http://www.w3.org/TR/XMLHttpRequest/#events) предусматривает следующие события по ходу обработки запроса:
|
||||||
|
|
||||||
|
- `loadstart` -- запрос начат.
|
||||||
|
- `progress` -- браузер получил очередной пакет данных, можно прочитать текущие полученные данные в `responseText`.
|
||||||
|
- `abort` -- запрос был отменён вызовом `xhr.abort()`.
|
||||||
|
- `error` -- произошла ошибка.
|
||||||
|
- `load` -- запрос был успешно (без ошибок) завершён.
|
||||||
|
- `timeout` -- запрос был прекращён по таймауту.
|
||||||
|
- `loadend` -- запрос был завершён (успешно или неуспешно)
|
||||||
|
|
||||||
|
Используя эти события можно более удобно отслеживать загрузку (`onload`) и ошибку (`onerror`), а также количество загруженных данных (`onprogress`).
|
||||||
|
|
||||||
|
Ранее мы видели ещё одно событие -- `readystatechange`. Оно появилось гораздо раньше, ещё до появления текущего стандарта.
|
||||||
|
|
||||||
|
В современных браузерах от него можно отказаться в пользу других, необходимо лишь, как мы увидим далее, учесть особенности IE8-9.
|
||||||
|
|
||||||
|
## IE8,9: XDomainRequest
|
||||||
|
|
||||||
|
В IE8 и IE9 поддержка `XMLHttpRequest` ограничена:
|
||||||
|
|
||||||
|
- Не поддерживаются события, кроме `onreadystatechange`.
|
||||||
|
- Некорректно поддерживается состояние `readyState = 3`: браузер может сгенерировать его только один раз во время запроса, а не при каждом пакете данных. Кроме того, он не даёт доступ к ответу `responseText` до того, как он будет до конца получен.
|
||||||
|
|
||||||
|
Дело в том, что, когда создавались эти браузеры, спецификации были не до конца проработаны. Поэтому разработчики браузера решили добавить свой объект `XDomainRequest`, который реализовывал часть возможностей современного стандарта.
|
||||||
|
|
||||||
|
А обычный `XMLHttpRequest` решили не трогать, чтобы ненароком не сломать существующий код.
|
||||||
|
|
||||||
|
Мы подробнее поговорим про `XDomainRequest` в главе <info:xhr-crossdomain>. Пока лишь заметим, что для того, чтобы получить некоторые из современных возможностей в IE8,9 -- вместо `new XMLHttpRequest()` нужно использовать `new XDomainRequest`.
|
||||||
|
|
||||||
|
Кросс-браузерно:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
|
||||||
|
var xhr = new XHR();
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь в IE8,9 поддерживаются события `onload`, `onerror` и `onprogress`. Это именно для IE8,9. Для IE10 обычный `XMLHttpRequest` уже является полноценным.
|
||||||
|
|
||||||
|
### IE9- и кеширование
|
||||||
|
|
||||||
|
Обычно ответы на запросы `XMLHttpRequest` кешируются, как и обычные страницы.
|
||||||
|
|
||||||
|
Но IE9- по умолчанию кеширует все ответы, не снабжённые антикеш-заголовком. Другие браузеры этого не делают. Чтобы этого избежать, сервер должен добавить в ответ соответствующие антикеш-заголовки, например `Cache-Control: no-cache`.
|
||||||
|
|
||||||
|
Впрочем, использовать заголовки типа `Expires`, `Last-Modified` и `Cache-Control` рекомендуется в любом случае, чтобы дать понять браузеру (не обязательно IE), что ему следует делать.
|
||||||
|
|
||||||
|
Альтернативный вариант -- добавить в URL запроса случайный параметр, предотвращающий кеширование.
|
||||||
|
|
||||||
|
Например, вместо `xhr.open('GET', 'service', false)` написать:
|
||||||
|
|
||||||
|
```js
|
||||||
|
xhr.open('GET', *!*'service?r=' + Math.random()*/!*, false);
|
||||||
|
```
|
||||||
|
|
||||||
|
По историческим причинам такой способ предотвращения кеширования можно увидеть много где, так как старые браузеры плохо обрабатывали кеширующие заголовки. Сейчас серверные заголовки поддерживаются хорошо.
|
||||||
|
|
||||||
|
## Итого
|
||||||
|
|
||||||
|
Типовой код для GET-запроса при помощи `XMLHttpRequest`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.open('GET', '/my/url', true);
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (this.readyState != 4) return;
|
||||||
|
|
||||||
|
// по окончании запроса доступны:
|
||||||
|
// status, statusText
|
||||||
|
// responseText, responseXML (при content-type: text/xml)
|
||||||
|
|
||||||
|
if (this.status != 200) {
|
||||||
|
// обработать ошибку
|
||||||
|
alert( 'ошибка: ' + (this.status ? this.statusText : 'запрос не удался') );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// получить результат из this.responseText или this.responseXML
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Мы разобрали следующие методы `XMLHttpRequest`:
|
||||||
|
|
||||||
|
- `open(method, url, async, user, password)`
|
||||||
|
- `send(body)`
|
||||||
|
- `abort()`
|
||||||
|
- `setRequestHeader(name, value)`
|
||||||
|
- `getResponseHeader(name)`
|
||||||
|
- `getAllResponseHeaders()`
|
||||||
|
|
||||||
|
Свойства `XMLHttpRequest`:
|
||||||
|
|
||||||
|
- `timeout`
|
||||||
|
- `responseText`
|
||||||
|
- `responseXML`
|
||||||
|
- `status`
|
||||||
|
- `statusText`
|
||||||
|
|
||||||
|
События:
|
||||||
|
|
||||||
|
- `onreadystatechange`
|
||||||
|
- `ontimeout`
|
||||||
|
- `onerror`
|
||||||
|
- `onload`
|
||||||
|
- `onprogress`
|
||||||
|
- `onabort`
|
||||||
|
- `onloadstart`
|
||||||
|
- `onloadend`
|
1
7-network/1-xmlhttprequest/hello.txt
Normal file
1
7-network/1-xmlhttprequest/hello.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello from the server!
|
155
7-network/1-xmlhttprequest/phones.json
Executable file
155
7-network/1-xmlhttprequest/phones.json
Executable file
|
@ -0,0 +1,155 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"age": 0,
|
||||||
|
"id": "motorola-xoom-with-wi-fi",
|
||||||
|
"imageUrl": "img/phones/motorola-xoom-with-wi-fi.0.jpg",
|
||||||
|
"name": "Motorola XOOM\u2122 with Wi-Fi",
|
||||||
|
"snippet": "The Next, Next Generation\r\n\r\nExperience the future with Motorola XOOM with Wi-Fi, the world's first tablet powered by Android 3.0 (Honeycomb)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 1,
|
||||||
|
"id": "motorola-xoom",
|
||||||
|
"imageUrl": "img/phones/motorola-xoom.0.jpg",
|
||||||
|
"name": "MOTOROLA XOOM\u2122",
|
||||||
|
"snippet": "The Next, Next Generation\n\nExperience the future with MOTOROLA XOOM, the world's first tablet powered by Android 3.0 (Honeycomb)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 2,
|
||||||
|
"carrier": "AT&T",
|
||||||
|
"id": "motorola-atrix-4g",
|
||||||
|
"imageUrl": "img/phones/motorola-atrix-4g.0.jpg",
|
||||||
|
"name": "MOTOROLA ATRIX\u2122 4G",
|
||||||
|
"snippet": "MOTOROLA ATRIX 4G the world's most powerful smartphone."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 3,
|
||||||
|
"id": "dell-streak-7",
|
||||||
|
"imageUrl": "img/phones/dell-streak-7.0.jpg",
|
||||||
|
"name": "Dell Streak 7",
|
||||||
|
"snippet": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 4,
|
||||||
|
"carrier": "Cellular South",
|
||||||
|
"id": "samsung-gem",
|
||||||
|
"imageUrl": "img/phones/samsung-gem.0.jpg",
|
||||||
|
"name": "Samsung Gem\u2122",
|
||||||
|
"snippet": "The Samsung Gem\u2122 brings you everything that you would expect and more from a touch display smart phone \u2013 more apps, more features and a more affordable price."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 5,
|
||||||
|
"carrier": "Dell",
|
||||||
|
"id": "dell-venue",
|
||||||
|
"imageUrl": "img/phones/dell-venue.0.jpg",
|
||||||
|
"name": "Dell Venue",
|
||||||
|
"snippet": "The Dell Venue; Your Personal Express Lane to Everything"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 6,
|
||||||
|
"carrier": "Best Buy",
|
||||||
|
"id": "nexus-s",
|
||||||
|
"imageUrl": "img/phones/nexus-s.0.jpg",
|
||||||
|
"name": "Nexus S",
|
||||||
|
"snippet": "Fast just got faster with Nexus S. A pure Google experience, Nexus S is the first phone to run Gingerbread (Android 2.3), the fastest version of Android yet."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 7,
|
||||||
|
"carrier": "Cellular South",
|
||||||
|
"id": "lg-axis",
|
||||||
|
"imageUrl": "img/phones/lg-axis.0.jpg",
|
||||||
|
"name": "LG Axis",
|
||||||
|
"snippet": "Android Powered, Google Maps Navigation, 5 Customizable Home Screens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 8,
|
||||||
|
"id": "samsung-galaxy-tab",
|
||||||
|
"imageUrl": "img/phones/samsung-galaxy-tab.0.jpg",
|
||||||
|
"name": "Samsung Galaxy Tab\u2122",
|
||||||
|
"snippet": "Feel Free to Tab\u2122. The Samsung Galaxy Tab\u2122 brings you an ultra-mobile entertainment experience through its 7\u201d display, high-power processor and Adobe\u00ae Flash\u00ae Player compatibility."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 9,
|
||||||
|
"carrier": "Cellular South",
|
||||||
|
"id": "samsung-showcase-a-galaxy-s-phone",
|
||||||
|
"imageUrl": "img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg",
|
||||||
|
"name": "Samsung Showcase\u2122 a Galaxy S\u2122 phone",
|
||||||
|
"snippet": "The Samsung Showcase\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance, even outdoors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 10,
|
||||||
|
"carrier": "Verizon",
|
||||||
|
"id": "droid-2-global-by-motorola",
|
||||||
|
"imageUrl": "img/phones/droid-2-global-by-motorola.0.jpg",
|
||||||
|
"name": "DROID\u2122 2 Global by Motorola",
|
||||||
|
"snippet": "The first smartphone with a 1.2 GHz processor and global capabilities."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 11,
|
||||||
|
"carrier": "Verizon",
|
||||||
|
"id": "droid-pro-by-motorola",
|
||||||
|
"imageUrl": "img/phones/droid-pro-by-motorola.0.jpg",
|
||||||
|
"name": "DROID\u2122 Pro by Motorola",
|
||||||
|
"snippet": "The next generation of DOES."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 12,
|
||||||
|
"carrier": "AT&T",
|
||||||
|
"id": "motorola-bravo-with-motoblur",
|
||||||
|
"imageUrl": "img/phones/motorola-bravo-with-motoblur.0.jpg",
|
||||||
|
"name": "MOTOROLA BRAVO\u2122 with MOTOBLUR\u2122",
|
||||||
|
"snippet": "An experience to cheer about."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 13,
|
||||||
|
"carrier": "T-Mobile",
|
||||||
|
"id": "motorola-defy-with-motoblur",
|
||||||
|
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
|
||||||
|
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
|
||||||
|
"snippet": "Are you ready for everything life throws your way?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 14,
|
||||||
|
"carrier": "T-Mobile",
|
||||||
|
"id": "t-mobile-mytouch-4g",
|
||||||
|
"imageUrl": "img/phones/t-mobile-mytouch-4g.0.jpg",
|
||||||
|
"name": "T-Mobile myTouch 4G",
|
||||||
|
"snippet": "The T-Mobile myTouch 4G is a premium smartphone designed to deliver blazing fast 4G speeds so that you can video chat from practically anywhere, with or without Wi-Fi."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 15,
|
||||||
|
"carrier": "US Cellular",
|
||||||
|
"id": "samsung-mesmerize-a-galaxy-s-phone",
|
||||||
|
"imageUrl": "img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg",
|
||||||
|
"name": "Samsung Mesmerize\u2122 a Galaxy S\u2122 phone",
|
||||||
|
"snippet": "The Samsung Mesmerize\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance,even outdoors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 16,
|
||||||
|
"carrier": "Sprint",
|
||||||
|
"id": "sanyo-zio",
|
||||||
|
"imageUrl": "img/phones/sanyo-zio.0.jpg",
|
||||||
|
"name": "SANYO ZIO",
|
||||||
|
"snippet": "The Sanyo Zio by Kyocera is an Android smartphone with a combination of ultra-sleek styling, strong performance and unprecedented value."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 17,
|
||||||
|
"id": "samsung-transform",
|
||||||
|
"imageUrl": "img/phones/samsung-transform.0.jpg",
|
||||||
|
"name": "Samsung Transform\u2122",
|
||||||
|
"snippet": "The Samsung Transform\u2122 brings you a fun way to customize your Android powered touch screen phone to just the way you like it through your favorite themed \u201cSprint ID Service Pack\u201d."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 18,
|
||||||
|
"id": "t-mobile-g2",
|
||||||
|
"imageUrl": "img/phones/t-mobile-g2.0.jpg",
|
||||||
|
"name": "T-Mobile G2",
|
||||||
|
"snippet": "The T-Mobile G2 with Google is the first smartphone built for 4G speeds on T-Mobile's new network. Get the information you need, faster than you ever thought possible."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 19,
|
||||||
|
"id": "motorola-charm-with-motoblur",
|
||||||
|
"imageUrl": "img/phones/motorola-charm-with-motoblur.0.jpg",
|
||||||
|
"name": "Motorola CHARM\u2122 with MOTOBLUR\u2122",
|
||||||
|
"snippet": "Motorola CHARM fits easily in your pocket or palm. Includes MOTOBLUR service."
|
||||||
|
}
|
||||||
|
]
|
5
7-network/index.md
Normal file
5
7-network/index.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
development: true
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Network requests: AJAX and COMET
|
Loading…
Add table
Add a link
Reference in a new issue