security ❀ Ajax JSON CORS cognate cookie

Posted by qiangxd on Mon, 10 Jan 2022 07:00:56 +0100

AJAX

The same origin policy stipulates that AJAX requests can only be sent to the same origin website, otherwise an error will be reported.

In addition to setting up a server proxy (the browser requests the same source server, and then the latter requests external services), there are three ways to avoid this restriction.

  • JSONP
  • WebSocket
  • CORS

XMLHttpRequest object

brief introduction

The communication between browser and server adopts HTTP protocol. Users type a web address in the browser address bar or submit content to the server through a web form. At this time, the browser will send an HTTP request to the server.

In 1999, Microsoft released version 5.0 of IE browser, which introduced a new function for the first time: allowing JavaScript scripts to send HTTP requests to the server. This function did not attract attention until Gmail was released in 2004 and Google Map was released in 2005. In February 2005, the word AJAX was officially put forward for the first time. It is the abbreviation of Asynchronous JavaScript and XML. It refers to obtaining XML documents from the server through asynchronous communication of JavaScript, extracting data from them, and then updating the corresponding part of the current web page without refreshing the whole web page. Later, the word AJAX became synonymous with JavaScript script initiating HTTP communication, that is, as long as script initiating communication can be called AJAX communication. W3C also released its international standard in 2006.

Specifically, AJAX includes the following steps.

  1. Create XMLHttpRequest instance
  2. Issue HTTP request
  3. Receive the data returned by the server
  4. Update web page data

To sum up, AJAX sends an HTTP request through the native XMLHttpRequest object, obtains the data returned by the server, and then processes it. Now, all the data returned by the server is in JSON format, and the XML format is outdated, but the name Ajax has become a general term, and the literal meaning has disappeared.

XMLHttpRequest object is the main interface of AJAX, which is used for communication between browser and server. Although the name contains XML and Http, it can actually send data in any format (including string and binary) using a variety of protocols (such as file or ftp).

XMLHttpRequest itself is a constructor that can generate an instance using the new command. It has no parameters.

var xhr = new XMLHttpRequest();

Once you create a new instance, you can use the open() method to specify some details of establishing an HTTP connection.

xhr.open('GET', 'http://www.example.com/page.php', true);

The above code specifies to use the GET method to establish a connection with the specified server URL. The third parameter, true, indicates that the request is asynchronous.

Then, specify the callback function to listen for changes in the communication state (readyState property).

xhr.onreadystatechange = handleStateChange;

function handleStateChange() {
  // ...
}

In the above code, once the state of the XMLHttpRequest instance changes, the listener function handleStateChange will be called

Finally, use the send() method to actually issue the request.

xhr.send(null);

In the above code, the send() parameter is null, which means that the request is sent without a data body. If you are sending a POST request, you need to specify the data body here.

Once you get the data returned by the server, AJAX will not refresh the whole web page, but only update the relevant parts of the web page, so as not to interrupt what the user is doing.

Note that AJAX can only send HTTP requests to the same origin web address (protocol, domain name and port are the same). If a cross domain request is sent, an error will be reported (CORS communication).

The following is a complete example of the simple usage of the XMLHttpRequest object.

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function(){
  // When the communication is successful, the status value is 4
  if (xhr.readyState === 4){
    if (xhr.status === 200){
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};

xhr.onerror = function (e) {
  console.error(xhr.statusText);
};

xhr.open('GET', '/endpoint', true);
xhr.send(null);

Instance properties of XMLHttpRequest

XMLHttpRequest.readyState

XMLHttpRequest.readyState returns an integer representing the current state of the instance object. This property is read-only. It may return the following values.

  • 0 indicates that the XMLHttpRequest instance has been generated, but the open() method of the instance has not been called.
  • 1. It means that the open() method has been called, but the send() method of the instance has not been called. You can still use the setRequestHeader() method of the instance to set the header information of the HTTP request.
  • 2. The send() method of the instance has been called, and the header information and status code returned by the server have been received.
  • 3, indicating that the data body (body part) transmitted from the server is being received. At this time, if the responseType property of the instance is equal to text or an empty string, the responseText property will contain some of the received information.
  • 4. It indicates that the data returned by the server has been completely received, or this reception has failed.

During communication, whenever the state of an instance object changes, the value of its readyState property will change. Each time this value changes, the readyStateChange event will be triggered.

var xhr = new XMLHttpRequest();

if (xhr.readyState === 4) {
  // At the end of the request, process the data returned by the server
} else {
  // The prompt "loading..."
}

In the code above, XHR When readyState is equal to 4, it indicates that the HTTP request issued by the script has been completed. Other cases indicate that the HTTP request is still in progress.

XMLHttpRequest.onreadystatechange

XMLHttpRequest. The onreadystatechange property points to a listener function. This property is executed when the readystatechange event occurs (the readyState property of the instance changes).

In addition, if you use the abort() method of the instance to terminate the XMLHttpRequest request, the readyState property will also change, resulting in the call to XMLHttpRequest Onreadystatechange property.

Here is an example.

var xhr = new XMLHttpRequest();
xhr.open( 'GET', 'http://example.com' , true );
xhr.onreadystatechange = function () {
  if (xhr.readyState !== 4 || xhr.status !== 200) {
    return;
  }
  console.log(xhr.responseText);
};
xhr.send();

XMLHttpRequest.response

XMLHttpRequest. The response property indicates the data body returned by the server (that is, the body part of the HTTP response). It can be any data type, such as string, object, binary object, etc. the specific type is determined by XMLHttpRequest The responsetype property determines. This property is read-only.

If the request is unsuccessful or the data is incomplete, the property is null. However, if the responseType property is equal to text or an empty string, the response property contains part of the data that the server has returned before the request ends (the stage where readyState is equal to 3).

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    handler(xhr.response);
  }
}

XMLHttpRequest.responseType

XMLHttpRequest. The responseType property is a string indicating the type of data returned by the server. This property is writable. You can set the value of this property before calling the open() method and calling the send() method to tell the browser how to interpret the returned data. If responseType is set to an empty string, it is equivalent to the default value of text.

XMLHttpRequest. The responsetype property can be equal to the following values.

  • "" (empty string): equivalent to text, indicating that the server returns text data.
  • "ArrayBuffer": an ArrayBuffer object that represents a binary array returned by the server.
  • "Blob": blob object, indicating that the server returns binary objects.
  • "Document": a document object, indicating that the server returns a document object.
  • "JSON": JSON object.
  • "text": string.

Among the above types, the text type is suitable for most cases, and it is more convenient to process text directly. The document type is suitable for returning HTML / XML documents, which means that for websites that open CORS, you can directly grab web pages with Ajax, and then directly perform DOM operations on the retrieved data without parsing HTML strings. blob type is suitable for reading binary data, such as picture files.

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status === 200) {
    var blob = new Blob([xhr.response], {type: 'image/png'});
    // perhaps
    var blob = xhr.response;
  }
};

xhr.send();

If this property is set to ArrayBuffer, binary data can be processed as an array.

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';

xhr.onload = function(e) {
  var uInt8Array = new Uint8Array(this.response);
  for (var i = 0, len = uInt8Array.length; i < len; ++i) {
    // var byte = uInt8Array[i];
  }
};

xhr.send();

If you set this property to JSON, the browser will automatically call JSON on the returned data Parse() method. That is, from XHR The response attribute (note, not the xhr.responseText attribute) does not get text, but a JSON object.

XMLHttpRequest.responseText

XMLHttpRequest. The responseText property returns the string received from the server, which is read-only. This attribute will not contain complete data until the HTTP request is received.

var xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);

xhr.responseType = 'text';
xhr.onload = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
};

xhr.send(null);

XMLHttpRequest.responseXML

XMLHttpRequest. The responsexml property returns the HTML or XML document object received from the server, which is read-only. If the request is unsuccessful, or the received data cannot be parsed into XML or HTML, the attribute is null.

The precondition for this attribute to take effect is that the content type header information of the HTTP response is equal to text/xml or application/xml. This requires XMLHttpRequest. Before sending the request The responsetype property should be set to document. If the content type header information of the HTTP response is not equal to text/xml and application/xml, but you want to get the data from responseXML (that is, parse the data in DOM format), you need to manually call XMLHttpRequest Overridemimetype() method to force XML parsing.

The data obtained by this attribute is the DOM tree of the directly parsed document.

var xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);

xhr.responseType = 'document';
xhr.overrideMimeType('text/xml');

xhr.onload = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseXML);
  }
};

xhr.send(null);

XMLHttpRequest.responseURL

XMLHttpRequest. The responseurl property is a string indicating the web address of the server that sent the data.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/test', true);
xhr.onload = function () {
  // return http://example.com/test
  console.log(xhr.responseURL);
};
xhr.send(null);

Note that the value of this attribute is not necessarily the same as the request URL specified by the open() method. If a jump occurs on the server side, this property returns the URL of the last actually returned data. In addition, if the original URL includes a fragment, this attribute will split the anchor.

XMLHttpRequest.status,XMLHttpRequest.statusText

XMLHttpRequest. The status property returns an integer representing the HTTP status code of the server response. Generally speaking, if the communication is successful, the status code is 200; If the server does not return a status code, this attribute defaults to 200. The property was 0 before the request was issued. This property is read-only.

  • 200, OK, access normal
  • 301, Moved Permanently
  • 302, Moved temporarily
  • 304, Not Modified
  • 307, Temporary Redirect
  • 401, Unauthorized
  • 403, Forbidden, access prohibited
  • 404, Not Found, the specified web address is not found
  • 500, Internal Server Error

Basically, there are only 2xx and 304 status codes, indicating that the server returns to a normal state.

if (xhr.readyState === 4) {
  if ( (xhr.status >= 200 && xhr.status < 300)
    || (xhr.status === 304) ) {
    // Process the returned data from the server
  } else {
    // error
  }
}

XMLHttpRequest. The statustext property returns a string indicating the status prompt sent by the server. Unlike the status attribute, this attribute contains the entire status information, such as "OK" and "Not Found". Before the request is sent (that is, before the open() method is called), the value of this attribute is an empty string; If the server does not return a status prompt, the default value of this property is "OK". This property is read-only.

XMLHttpRequest.timeout,XMLHttpRequestEventTarget.ontimeout

XMLHttpRequest. The timeout property returns an integer indicating how many milliseconds later, if the request still does not get results, it will be automatically terminated. If the property is equal to 0, there is no time limit.

XMLHttpRequestEventTarget. The ontimeout property is used to set a listening function. If a timeout event occurs, the listening function will be executed.

Here is an example.

var xhr = new XMLHttpRequest();
var url = '/server';

xhr.ontimeout = function () {
  console.error('The request for ' + url + ' timed out.');
};

xhr.onload = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // Process the data returned by the server
    } else {
      console.error(xhr.statusText);
    }
  }
};

xhr.open('GET', url, true);
// Specify a 10 second timeout
xhr.timeout = 10 * 1000;
xhr.send(null);

Event listening properties

The XMLHttpRequest object can specify a listener function for the following events.

  • XMLHttpRequest.onloadstart: the listener function for the loadstart event (HTTP request issuance)
  • XMLHttpRequest.onprogress: listener function for progress event (sending and loading data)
  • XMLHttpRequest.onabort: listener function for abort event (request abort, for example, the user called abort() method)
  • XMLHttpRequest.onerror: listener function for error event (request failure)
  • XMLHttpRequest.onload: the listening function for the load event (request completed successfully)
  • XMLHttpRequest.ontimeout: the listening function for the timeout event (the time limit specified by the user has exceeded, and the request has not been completed)
  • XMLHttpRequest. On loadend: the listener function for the load event (request completion, regardless of success or failure)

Here is an example.

xhr.onload = function() {
 var responseText = xhr.responseText;
 console.log(responseText);
 // process the response.
};

xhr.onabort = function () {
  console.log('The request was aborted');
};

xhr.onprogress = function (event) {
  console.log(event.loaded);
  console.log(event.total);
};

xhr.onerror = function() {
  console.log('There was an error!');
};

The listening function of the progress event has an event object parameter, which has three attributes: the loaded attribute returns the amount of data transmitted, the total attribute returns the total amount of data, and the lengthComputable attribute returns a Boolean value indicating whether the loading progress can be calculated. Among all these listening functions, only the listening function of progress event has parameters, and other functions have no parameters.

Note that if a network error occurs (for example, the server cannot be connected), the onerror event cannot obtain the error information. In other words, there may be no error object, so only the error prompt can be displayed.

XMLHttpRequest.withCredentials

XMLHttpRequest. The withcredentials property is a Boolean value indicating whether user information (such as cookies and authenticated HTTP header information) will be included in the request when a cross domain request is made. The default value is false, that is, to example example.com will not be sent when making a cross domain request Com Cookie Set on this computer (if any).

If you need to send cookies for cross domain AJAX requests, you need to set the withCredentials property to true. Note that this property does not need to be set for homologous requests.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);

For this property to take effect, the server must explicitly return the header access control allow credentials.

Access-Control-Allow-Credentials: true

If the withCredentials property is turned on, the cross domain request will not only send cookies, but also set the cookies specified by the remote host. Conversely, if the withCredentials property is not opened, the cross domain AJAX request will be ignored even if it explicitly requires the browser to set cookies.

Note that scripts always follow the same origin policy and cannot be downloaded from document Read a cross domain Cookie in the header information of a Cookie or HTTP response. The withCredentials property does not affect this.

XMLHttpRequest.upload

XMLHttpRequest can not only send requests, but also send files, which is AJAX file upload. After sending the file, use XMLHttpRequest The upload attribute can get an object. By observing this object, you can know the progress of uploading. The main method is to listen to various events of this object: loadstart, loadend, load, abort, error, progress, timeout.

Suppose there is a < progress > element on the web page.

<progress min="0" max="100" value="0">0% complete</progress>

When uploading a file, specify the listening function for the progress event on the upload attribute to obtain the upload progress.

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function (e) {};

  var progressBar = document.querySelector('progress');
  xhr.upload.onprogress = function (e) {
    if (e.lengthComputable) {
      progressBar.value = (e.loaded / e.total) * 100;
      // Compatible with older browsers that do not support < progress > elements
      progressBar.textContent = progressBar.value;
    }
  };

  xhr.send(blobOrFile);
}

upload(new Blob(['hello world'], {type: 'text/plain'}));

Instance method of XMLHttpRequest

XMLHttpRequest.open()

XMLHttpRequest. The open () method is used to specify the parameters of the HTTP request, or to initialize the XMLHttpRequest instance object. It can accept a total of five parameters.

void open(
   string method,
   string url,
   optional boolean async,
   optional string user,
   optional string password
);
  • method: indicates HTTP verb methods, such as GET, POST, PUT, DELETE, HEAD, etc.
  • URL: indicates the destination URL of the request.
  • async: Boolean value indicating whether the request is asynchronous. The default value is true. If it is set to false, the send() method will not proceed to the next step until it receives the result returned by the server. This parameter is optional. Because synchronizing AJAX requests will cause browsers to lose response, many browsers have banned the use in the main thread and only allowed it in the Worker. Therefore, this parameter should not be set to false.
  • User: indicates the user name used for authentication. The default is an empty string. This parameter is optional.
  • Password: indicates the password used for authentication. It is an empty string by default. This parameter is optional.

Note that if you make an AJAX request that has used the open() method, using this method again is equivalent to calling abort(), that is, terminate the request.

The following is an example of sending a POST request.

var xhr = new XMLHttpRequest();
xhr.open('POST', encodeURI('someURL'));

XMLHttpRequest.send()

XMLHttpRequest. The send () method is used to actually issue an HTTP request. Its parameters are optional. If there are no parameters, it means that the HTTP request has only one URL and no data body. A typical example is the GET request; If it has parameters, it means that in addition to the header information, it also has an information body containing specific data. A typical example is a POST request.

The following is an example of a GET request.

var xhr = new XMLHttpRequest();
xhr.open('GET',
  'http://www.example.com/?id=' + encodeURIComponent(id),
  true
);
xhr.send(null);

In the above code, the parameters of the GET request are appended to the URL as a query string.

The following is an example of sending a POST request.

var xhr = new XMLHttpRequest();
var data = 'email='
  + encodeURIComponent(email)
  + '&password='
  + encodeURIComponent(password);

xhr.open('POST', 'http://www.example.com', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);

Note that all listening events of XMLHttpRequest must be set before the send() method call.

The parameter of the send method is the data sent. Data in various formats can be used as its parameters.

void send();
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(String data);
void send(FormData data);

If send() sends a DOM object, the data will be serialized before sending. If binary data is sent, it is better to send ArrayBufferView or Blob objects, which makes it possible to upload files through Ajax.

The following is an example of sending form data. The FormData object can be used to construct form data.

var formData = new FormData();

formData.append('username', 'Zhang San');
formData.append('email', 'zhangsan@example.com');
formData.append('birthDate', 1940);

var xhr = new XMLHttpRequest();
xhr.open('POST', '/register');
xhr.send(formData);

In the above code, the FormData object constructs the form data and sends it using the send() method. Its effect is the same as sending the following form data.

<form id='registration' name='registration' action='/register'>
  <input type='text' name='username' value='Zhang San'>
  <input type='email' name='email' value='zhangsan@example.com'>
  <input type='number' name='birthDate' value='1940'>
  <input type='submit' onclick='return sendForm(this.form);'>
</form>

The following example uses the FormData object to process form data and then send it.

function sendForm(form) {
  var formData = new FormData(form);
  formData.append('csrf', 'e69a18d7db1286040586e6da1950128c');

  var xhr = new XMLHttpRequest();
  xhr.open('POST', form.action, true);
  xhr.onload = function() {
    // ...
  };
  xhr.send(formData);

  return false;
}

var form = document.querySelector('#registration');
sendForm(form);

XMLHttpRequest.setRequestHeader()

XMLHttpRequest. The setrequestheader () method is used to set the header information of the HTTP request sent by the browser. This method must be called before open() and send(). If the method is called multiple times and the same field is set, the value of each call will be combined into a single value and sent.

The method accepts two parameters. The first parameter is a string representing the field name of the header information, and the second parameter is the field value.

xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));

The above code first sets the header information content type to send JSON format data; Then set content length to indicate the data length; Finally, send JSON data.

XMLHttpRequest.overrideMimeType()

XMLHttpRequest. The overridemimetype () method is used to specify the MIME type and override the real MIME type returned by the server, so that the browser can handle it differently. For example, the data type returned by the server is text/xml. Due to various reasons, the browser fails to parse and reports an error. At this time, the data can not be obtained. In order to get the original data, we can change the MIME type to text/plain, so that the browser will not automatically parse, so that we can get the original text.

xhr.overrideMimeType('text/plain')

Note that this method must be called before the send() method.

Modifying the data type returned by the server is not a normal method. If you want the server to return the specified data type, you can tell the server with the responseType attribute, as in the following example. The overrideMimeType() method is used only if the server cannot return a data type.

var xhr = new XMLHttpRequest();
xhr.onload = function(e) {
  var arraybuffer = xhr.response;
  // ...
}
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.send();

XMLHttpRequest.getResponseHeader()

XMLHttpRequest. The getresponseheader () method returns the value of the specified field of HTTP header information. If the server response has not been received or the specified field does not exist, it returns null. The parameters of this method are case insensitive.

function getHeaderTime() {
  console.log(this.getResponseHeader("Last-Modified"));
}

var xhr = new XMLHttpRequest();
xhr.open('HEAD', 'yourpage.html');
xhr.onload = getHeaderTime;
xhr.send();

If there are multiple fields with the same name, their values will be connected into a string, and each field is separated by "comma + space".

XMLHttpRequest.getAllResponseHeaders()

XMLHttpRequest. The getallresponseheaders () method returns a string representing all HTTP header information sent by the server. The format is string, and each header information is separated by CRLF (carriage return + line feed). If no server response is received, this property is null. If a network error occurs, the property is an empty string.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'foo.txt', true);
xhr.send();

xhr.onreadystatechange = function () {
  if (this.readyState === 4) {
    var headers = xhr.getAllResponseHeaders();
  }
}

The above code is used to obtain all header information returned by the server. It could be a string like this.

date: Fri, 08 Dec 2017 21:04:30 GMT\r\n
content-encoding: gzip\r\n
x-content-type-options: nosniff\r\n
server: meinheld/0.6.1\r\n
x-frame-options: DENY\r\n
content-type: text/html; charset=utf-8\r\n
connection: keep-alive\r\n
strict-transport-security: max-age=63072000\r\n
vary: Cookie, Accept-Encoding\r\n
content-length: 6502\r\n
x-xss-protection: 1; mode=block\r\n

Then, the string is processed.

var arr = headers.trim().split(/[\r\n]+/);
var headerMap = {};

arr.forEach(function (line) {
  var parts = line.split(': ');
  var header = parts.shift();
  var value = parts.join(': ');
  headerMap[header] = value;
});

headerMap['content-length'] // "6502"

XMLHttpRequest.abort()

XMLHttpRequest. The abort () method is used to terminate the HTTP request that has been issued. After calling this method, the readyState property changes to 4 and the status property changes to 0.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example.com/page.php', true);
setTimeout(function () {
  if (xhr) {
    xhr.abort();
    xhr = null;
  }
}, 5000);

The above code terminates an AJAX request after 5 seconds.

Event for XMLHttpRequest instance

readyStateChange event

The readyStateChange event is triggered when the value of the readyState property changes.

We can specify the listener function of this event through the onReadyStateChange property to process different states. Especially when the status changes to 4, it indicates that the communication is successful. At this time, the callback function can process the data transmitted by the server.

progress event

When uploading a file, both the XMLHttpRequest instance object itself and the upload attribute of the instance have a progress event, which will continuously return the upload progress.

var xhr = new XMLHttpRequest();

function updateProgress (oEvent) {
  if (oEvent.lengthComputable) {
    var percentComplete = oEvent.loaded / oEvent.total;
  } else {
    console.log('Unable to calculate progress');
  }
}

xhr.addEventListener('progress', updateProgress);

xhr.open();

load event, error event, abort event

The load event indicates that the data transmitted from the server has been received, the error event indicates that the request is in error, and the abort event indicates that the request is interrupted (for example, the user cancels the request).

var xhr = new XMLHttpRequest();

xhr.addEventListener('load', transferComplete);
xhr.addEventListener('error', transferFailed);
xhr.addEventListener('abort', transferCanceled);

xhr.open();

function transferComplete() {
  console.log('Data received');
}

function transferFailed() {
  console.log('Data receiving error');
}

function transferCanceled() {
  console.log('User cancels reception');
}

loadend event

The abort, load and error events will be accompanied by a load event, indicating that the request ends, but it is not known whether it succeeds or not.

xhr.addEventListener('loadend', loadEnd);

function loadEnd(e) {
  console.log('Request ended, status unknown');
}

timeout event

If the server does not return a result after the specified time, the timeout event will be triggered. For specific examples, see the timeout attribute section.

Navigator.sendBeacon()

When users uninstall web pages, they sometimes need to send some data to the server. It is natural to use the XMLHttpRequest object to send data in the listening function of the unload event or beforeunload event. However, this is not very reliable because the XMLHttpRequest object is sent asynchronously. It is likely that the page has been unloaded when it is about to be sent, resulting in sending cancellation or sending failure.

The solution is to add some time-consuming synchronization operations to the unload event. This will allow enough time to ensure that asynchronous AJAX can be sent successfully.

function log() {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
}

window.addEventListener('unload', function(event) {
  log();

  // a time-consuming operation
  for (let i = 1; i < 10000; i++) {
    for (let m = 1; m < 10000; m++) { continue; }
  }
});

In the above code, a double loop is enforced, which prolongs the execution time of the unload event, resulting in the successful sending of asynchronous AJAX.

Similarly, you can also use setTimeout. The following is an example of tracking user clicks.

// The HTML code is as follows
// <a id="target" href="https://baidu.com">click</a>
const clickTime = 350;
const theLink = document.getElementById('target');

function log() {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
}

theLink.addEventListener('click', function (event) {
  event.preventDefault();
  log();

  setTimeout(function () {
    window.location.href = theLink.getAttribute('href');
  }, clickTime);
});

The above code uses setTimeout, delaying 350 milliseconds to make the page Jump, so it gives asynchronous AJAX time to issue.

The common problem with these practices is that the unloading time has been forcibly prolonged, the loading of subsequent pages has been delayed, and the user experience is poor.

To solve this problem, the browser introduces navigator Sendeacon() method. This method still sends the request asynchronously, but the request is decoupled from the current page thread as the task of the browser process, so it can ensure that the data will be sent out without delaying the unloading process.

window.addEventListener('unload', logData, false);

function logData() {
  navigator.sendBeacon('/log', analyticsData);
}

Navigator. The sendeacon method accepts two parameters. The first parameter is the URL of the target server, and the second parameter is the data to be sent (optional), which can be of any type (string, form object, binary object, etc.).

navigator.sendBeacon(url, data)

The return value of this method is a Boolean value. The successfully sent data is true, otherwise it is false.

The HTTP method of sending data in this method is POST, which can cross fields, similar to submitting data in a form. It cannot specify a callback function.

Here is an example.

// The HTML code is as follows
// <body οnlοad="analytics('start')" οnunlοad="analytics('end')">

function analytics(state) {
  if (!navigator.sendBeacon) return;

  var URL = 'http://example.com/analytics';
  var data = 'state=' + state + '&location=' + window.location;
  navigator.sendBeacon(URL, data);
}

JSONP

JSONP is a common method of cross source communication between server and client. The biggest feature is that it is easy to use and has no compatibility problems. All old browsers support it, and the transformation of the server is very small.

Its approach is as follows.

The first step is to add a < script > element to the web page and request a script from the server, which is not limited by the homology policy and can be requested across domains.

<script src="http://api.foo.com?callback=bar"></script>

Note that the requested script URL has a callback parameter (? callback=bar), which is used to tell the server the callback function name of the client (bar).

Step 2: after receiving the request, the server splices a string, puts the JSON data in the function name and returns it as a string (bar({...})).

Step 3: the client will parse the string returned by the server as code, because the browser believes that this is the script content requested by the < script > tag. At this time, as long as the client defines the bar() function, it can get the JSON data returned by the server in the function body.

Let's take a look at an example. First, the < script > element is dynamically inserted into the web page, which sends a request to the cross domain web address.

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

The above code dynamically adds a < script > element to the server example Com. Note that the query string of the request has a callback parameter to specify the name of the callback function, which is required for JSONP.

After receiving the request, the server will return the data in the parameter position of the callback function.

foo({
  'ip': '8.8.8.8'
});

The script requested by the < script > element runs directly as code. In this case, as long as the browser defines the foo function, the function will be called immediately. JSON data as a parameter is treated as a JavaScript object rather than a string, thus avoiding the use of JSON Parse steps.

WebSocket

WebSocket is a communication protocol that uses ws: / / (non encrypted) and wss: / / (encrypted) as protocol prefixes. The protocol does not implement the same source policy. As long as the server supports it, cross source communication can be carried out through it.

The following is an example of the header information of the WebSocket request sent by the browser (excerpted from Wikipedia).

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

In the above code, one field is origin, which indicates the origin of the request, that is, the domain name from which the request is sent.

It is precisely because of the Origin field that WebSocket does not implement the homology policy. Because the server can judge whether to license this communication according to this field. If the domain name is in the white list, the server will respond as follows.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

CORS communication

CORS is a W3C standard, whose full name is "cross origin resource sharing". It allows browsers to issue XMLHttpRequest requests to cross domain servers, thus overcoming the limitation that AJAX can only be used from the same source.

brief introduction

CORS requires both browser and server support. Currently, all browsers support this feature.

The whole CORS communication process is completed automatically by the browser without user participation. For developers, CORS communication is no different from ordinary AJAX communication, and the code is exactly the same. Once the browser finds that AJAX requests cross domains, it will automatically add some additional header information. Sometimes there will be an additional request, but the user will not feel it. Therefore, the key to realize CORS communication is the server. As long as the server implements the CORS interface, it can communicate across domains.

Two requests

CORS requests are divided into two categories: simple request and not so simple request.

As long as the following two conditions are met at the same time, it is a simple request.

(1) The request method is one of the following three methods.

  • HEAD
  • GET
  • POST

(2) The header information of HTTP does not exceed the following fields.

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content type: limited to three values: application/x-www-form-urlencoded, multipart / form data, and text/plain

If the above two conditions are not met at the same time, it is a non simple request. In a word, a simple request is a combination of a simple HTTP method and a simple HTTP header.

The reason for this division is that forms have historically been able to make requests across fields. A simple request is a form request. The browser follows the traditional processing method and does not complicate the behavior. Otherwise, developers may turn to forms to avoid the limitations of CORS. For non simple requests, the browser will adopt a new processing method.

Simple request

Basic process

For simple requests, the browser sends CORS requests directly. Specifically, add an Origin field to the header information.

The following is an example. When the browser finds that the cross domain AJAX request is a simple request, it automatically adds an Origin field to the header information.

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

In the header information above, the Origin field is used to indicate which domain (protocol + domain name + port) the request comes from. Based on this value, the server decides whether to agree to the request.

If the source specified by Origin is not within the license range, the server will return a normal HTTP response. When the browser finds that the header information of this response does not contain the access control allow Origin field (see below), it knows that an error has occurred and throws an error, which is captured by the onerror callback function of XMLHttpRequest. Note that this error cannot be identified by the status code, because the status code of the HTTP response may be 200.

If the domain name specified by Origin is within the license range, several header information fields will be added to the response returned by the server.

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

In the header information above, there are three fields related to CORS requests, all beginning with access control -.

(1)Access-Control-Allow-Origin

This field is required. Its value is either the value of the Origin field at the time of the request, or a *, indicating that the request for any domain name is accepted.

(2)Access-Control-Allow-Credentials

This field is optional. Its value is a Boolean value indicating whether cookies are allowed to be sent. By default, cookies are not included in CORS requests. If it is set to true, it means that the server has explicit permission. The browser can include cookies in the request and send them to the server together. This value can only be set to true. If the server does not want the browser to send cookies, it can not send this field.

(3)Access-Control-Expose-Headers

This field is optional. When CORS requests, the getResponseHeader() method of XMLHttpRequest object can only get six basic fields returned by the server: cache control, content language, content type, Expires, last modified and Pragma. If you want to get other fields, you must specify them in access control expose headers. The above example specifies that getResponseHeader('FooBar ') can return the value of the FooBar field.

withCredentials property

As mentioned above, the CORS request does not contain Cookie information (and HTTP authentication information, etc.) by default, in order to reduce the risk of CSRF attack. However, in some cases, the server may need to get cookies. In this case, the server needs to explicitly specify the access control allow credentials field to tell the browser that cookies can be sent.

Access-Control-Allow-Credentials: true

At the same time, the developer must open the withCredentials property in the AJAX request.

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

Otherwise, even if the server requests to send cookies, the browser will not send them. Alternatively, the server requires cookies to be set, and the browser will not process them.

However, some browsers set the withCredentials property to true by default. This causes these browsers to still send cookies together if the withCredentials setting is omitted. At this point, withCredentials can be explicitly closed.

xhr.withCredentials = false;

It should be noted that if the server requires the browser to send cookies, access control allow origin cannot be set as an asterisk, and a clear domain name consistent with the requested web page must be specified. At the same time, cookies still follow the same origin policy. Only cookies set with the server domain name will be uploaded, cookies of other domain names will not be uploaded, and (cross domain) the document in the original web page code Cookie s also cannot read cookies under the server domain name.

Non simple request

Pre inspection request

Non simple requests are those that require special requirements from the server. For example, the request method is PUT or DELETE, or the type of content type field is application/json.

For CORS requests that are not simple requests, an HTTP query request will be added before formal communication, which is called "preflight". The browser first asks the server whether the domain name of the current web page is in the license list of the server, and what HTTP methods and header information fields can be used. Only when you get a positive reply will the browser send a formal XMLHttpRequest request, otherwise an error will be reported. This is to prevent these new requests from putting pressure on traditional servers without CORS support and giving the server an opportunity to reject them in advance. This can prevent the server from receiving a large number of DELETE and PUT requests. These traditional forms cannot send requests across domains.

The following is a JavaScript script for the browser.

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

In the above code, the HTTP request method is PUT and sends a custom header X-Custom-Header.

When the browser finds that this is a non simple request, it automatically sends a "pre check" request to the server to confirm that such a request can be made. The following is the HTTP header information of the "pre check" request.

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

The request method used for the "pre check" request is OPTIONS, which means that the request is used for inquiry. In the header information, the key field is Origin, indicating which source the request comes from.

In addition to the Origin field, the header information of the "pre check" request includes two special fields.

(1)Access-Control-Request-Method

This field is required to list which HTTP methods will be used in the browser's CORS request. The above example is PUT.

(2)Access-Control-Request-Headers

This field is a comma separated string that specifies the header information field that the browser CORS request will send additionally. The above example is x-custom header.

Response to pre inspection request

After receiving the "pre check" request, the server checks the Origin, access control request method and access control request headers fields and confirms that cross source requests are allowed to respond.

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

In the above HTTP response, the key is the access control allow origin field, indicating http://api.bob.com Data can be requested. This field can also be set to an asterisk to indicate consent to any cross source request.

Access-Control-Allow-Origin: *

If the server denies the "pre check" request, it will return a normal HTTP response, but there is no CORS related header information field, or it clearly indicates that the request does not meet the conditions.

OPTIONS http://api.bob.com HTTP/1.1
Status: 200
Access-Control-Allow-Origin: https://notyourdomain.com
Access-Control-Allow-Method: POST

In the above server response, the access control allow origin field explicitly does not include the user who made the request http://api.bob.com .

At this time, the browser will assume that the server does not agree with the pre check request, so an error is triggered and caught by the onerror callback function of the XMLHttpRequest object. The console will print the following error message.

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

Other CORS related fields responded by the server are as follows.

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods

This field is required. Its value is a comma separated string indicating all cross domain request methods supported by the server. Note that all supported methods are returned, not just the one requested by the browser. This is to avoid multiple "pre check" requests.

(2)Access-Control-Allow-Headers

If the browser request includes an access control request headers field, the access control allow headers field is required. It is also a comma separated string, indicating all header information fields supported by the server, not limited to the fields requested by the browser in "pre check".

(3)Access-Control-Allow-Credentials

This field has the same meaning as simple request.

(4)Access-Control-Max-Age

This field is optional. It is used to specify the validity period of this pre inspection request. The unit is seconds. In the above results, the validity period is 20 days (1728000 seconds), that is, it is allowed to cache this response for 1728000 seconds (20 days). During this period, there is no need to send another pre check request.

Normal requests and responses from the browser

Once the server passes the "pre check" request, every normal CORS request of the browser in the future is the same as a simple request, and there will be an Origin header information field. The server's response will also have an access control allow Origin header information field.

The following is the normal CORS request of the browser after the "pre check" request.

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

The Origin field of the header information above is automatically added by the browser.

The following is the normal response of the server.

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

In the header above, the access control allow origin field must be included in each response.

Comparison with JSONP

CORS is used for the same purpose as JSONP, but it is more powerful than JSONP. JSONP only supports GET requests, and CORS supports all types of HTTP requests. The advantages of JSONP are that it supports old-fashioned browsers and can request data from websites that do not support CORS.

Reference link

Homology restriction

The cornerstone of browser security is the "homology policy"( same-origin policy ). Many developers know this, but they don't fully understand it.

summary

meaning

In 1995, the same origin policy was introduced by Netscape. At present, all browsers implement this policy.

Initially, it means that the Cookie set by page A and page B cannot be opened unless the two pages are "homologous". The so-called "homology" refers to "three identical".

  • Same agreement
  • Same domain name
  • The ports are the same (this can be ignored, see below)

For example, http://www.example.com/dir/page.html The protocol of this website is http: / /, and the domain name is www.example COM, the port is 80 (the default port can be omitted), and its homology is as follows.

  • http://www.example.com/dir2/other.html : homologous
  • http://example.com/dir/other.html : different sources (different domain names)
  • http://v2.www.example.com/dir/other.html : different sources (different domain names)
  • http://www.example.com:81/dir/other.html : different sources (different ports)
  • https://www.example.com/dir/page.html : different sources (different protocols)

Note that the standard stipulates that URLs with different ports are not homologous (for example, port 8000 and port 8001 are not homologous), but the browser does not comply with this provision. In fact, different ports in the same domain can read cookies from each other.

objective

The purpose of homology policy is to ensure the security of user information and prevent malicious websites from stealing data.

Imagine this situation: website A is A bank. After the user logs in, website A sets up A Cookie on the user's machine, which contains some privacy information. After leaving website A, the user visits website B. if there is no homology restriction, website B can read the cookies of website A, then the privacy will be leaked. What's more terrible is that cookies are often used to save users' login status. If users don't log out, other websites can impersonate users and do whatever they want. Because the browser also stipulates that submitting forms is not limited by the homology policy.

It can be seen that the homology policy is necessary, otherwise cookies can be shared, and the Internet will be insecure.

Limit scope

With the development of the Internet, the homology policy is becoming more and more strict. At present, if non homologous, a total of three behaviors are limited.

(1) Unable to read cookies, LocalStorage and IndexedDB of non homologous web pages.

(2) The DOM of a non homologous web page cannot be contacted.

(3) An AJAX request cannot be sent to a non origin address (it can be sent, but the browser will reject the response).

In addition, you can get the window objects of other windows through JavaScript scripts. If it is a non homologous web page, at present, one window is allowed to contact the nine properties and four methods of the window object of other web pages.

  • window.closed
  • window.frames
  • window.length
  • window.location
  • window.opener
  • window.parent
  • window.self
  • window.top
  • window.window
  • window.blur()
  • window.close()
  • window.focus()
  • window.postMessage()

Of the above nine attributes, only window Location is read-write, and the other eight are all read-only. Moreover, even if the location object is not homologous, it is only allowed to call location Replace() method and write location Href attribute.

Although these restrictions are necessary, they are sometimes inconvenient and their reasonable use is also affected. The following describes how to circumvent the above restrictions.

Cookie

A Cookie is a small piece of information written by the server to the browser. Only homologous web pages can be shared. If the primary domain name of two web pages is the same, but the secondary domain name is different, the browser can set document Domain shared cookies.

For example, the URL of page A is http://w1.example.com/a.html , the web address of page B is http://w2.example.com/b.html , then just set the same document Domain, two web pages can share cookies. Because the browser passes document Domain attribute to check whether it is homologous.

// Both pages need to be set
document.domain = 'example.com';

Note that both A and B pages need to set document Domain attribute to achieve the purpose of homology. Because setting document Domain, the port will be reset to null, so if you only set A web page's document Domain, which will cause the ports of the two web addresses to be different, or the purpose of homology cannot be achieved.

Now, A web page sets A Cookie through A script.

document.cookie = "test1=hello";

B web page can read this Cookie.

var allCookie = document.cookie;

Note that this method is only applicable to cookies and iframe windows. LocalStorage and IndexedDB cannot evade the homology policy through this method, but use the PostMessage API described below.

In addition, the server can also specify the domain name of the Cookie as the primary domain name when setting the Cookie, such as example com.

Set-Cookie: key=value; domain=example.com; path=/

In this way, the secondary domain name and tertiary domain name can read the Cookie without any settings.

JSON object

JSON format

JSON format (short for JavaScript Object Notation) is a text format for data exchange. It was proposed by Douglas Crockford in 2001 to replace the cumbersome and cumbersome XML format.

Compared with XML format, JSON format has two significant advantages: simple writing and clear at a glance; It conforms to the native syntax of JavaScript and can be directly processed by the interpretation engine without adding additional parsing code. Therefore, JSON has been quickly accepted, has become the standard format for data exchange among major websites, and has been written into the standard.

Each JSON object is a value, which may be an array or object, or a value of the original type. In short, it can only be one value, not two or more values.

JSON has strict rules on the type and format of values.

  1. The value of a composite type can only be an array or object, not a function, regular expression object or date object.

  2. There are only four original types of values: string, numeric value (which must be expressed in decimal), Boolean value and null (NaN, Infinity, -Infinity and undefined cannot be used).

  3. Strings must be represented in double quotes, not single quotes.

  4. The key name of the object must be enclosed in double quotes.

  5. You cannot add a comma after the last member of an array or object.

The following are legal JSON.

["one", "two", "three"]

{ "one": 1, "two": 2, "three": 3 }

{"names": ["Zhang San", "Li Si"] }

[ { "name": "Zhang San"}, {"name": "Li Si"} ]

The following are illegal JSON.

{ name: "Zhang San", 'age': 32 }  // Property names must be in double quotes

[32, 64, 128, 0xFFF] // Hexadecimal values cannot be used

{ "name": "Zhang San", "age": undefined } // undefined cannot be used

{ "name": "Zhang San",
  "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
  "getName": function () {
      return this.name;
  }
} // Property values cannot use functions and date objects

Note that null, empty arrays, and empty objects are legal JSON values.

JSON object

JSON object is the native object of JavaScript, which is used to process JSON format data. It has two static methods: JSON Stringify() and JSON parse().

JSON.stringify()

Basic Usage

JSON. The stringify () method is used to convert a value to a JSON string. The string conforms to JSON format and can be used by JSON The parse () method restores.

JSON.stringify('abc') // ""abc""
JSON.stringify(1) // "1"
JSON.stringify(false) // "false"
JSON.stringify([]) // "[]"
JSON.stringify({}) // "{}"

JSON.stringify([1, "false", false])
// '[1,"false",false]'

JSON.stringify({ name: "Zhang San" })
// '{"name": "Zhang San"}'

The above code converts various types of values into JSON strings.

Note that for strings of the original type, the conversion results are enclosed in double quotes.

JSON.stringify('foo') === "foo" // false
JSON.stringify('foo') === "\"foo\"" // true

In the above code, the string foo is converted to "\" foo \ ". This is because when restoring in the future, the inner double quotation marks can let the JavaScript engine know that this is a string rather than other types of values.

JSON.stringify(false) // "false"
JSON.stringify('false') // "\"false\""

In the above code, if it is not the inner double quotation marks, the engine will not know whether the original value is a Boolean value or a string in the future.

If the property of an object is undefined, a function, or an XML object, the property is JSON. XML Stringify() filter.

var obj = {
  a: undefined,
  b: function () {}
};

JSON.stringify(obj) // "{}"

In the above code, the a attribute of object obj is undefined, while the b attribute is a function, and the results are all JSON Stringify filtering.

If the members of the array are undefined, functions, or XML objects, these values are converted to null.

var arr = [undefined, function () {}];
JSON.stringify(arr) // "[null,null]"

In the above code, the members of array arr are undefined and functions, which are converted to null.

Regular objects are converted to empty objects.

JSON.stringify(/foo/) // "{}"

JSON. The stringify () method ignores the non traversable properties of the object.

var obj = {};
Object.defineProperties(obj, {
  'foo': {
    value: 1,
    enumerable: true
  },
  'bar': {
    value: 2,
    enumerable: false
  }
});

JSON.stringify(obj); // "{"foo":1}"

In the above code, bar is the non traversable property of obj object, JSON The stringify method ignores this property.

Second parameter

JSON. The stringify () method can also accept an array as the second parameter to specify which attributes of the parameter object need to be converted into strings.

var obj = {
  'prop1': 'value1',
  'prop2': 'value2',
  'prop3': 'value3'
};

var selectedProperties = ['prop1', 'prop2'];

JSON.stringify(obj, selectedProperties)
// "{"prop1":"value1","prop2":"value2"}"

In the above code, JSON The second parameter of the stringify () method specifies that only prop1 and prop2 attributes are converted.

This whitelist like array is only valid for object properties, not for arrays.

JSON.stringify(['a', 'b'], ['0'])
// "["a","b"]"

JSON.stringify({0: 'a', 1: 'b'}, ['0'])
// "{"0":"a"}"

In the above code, the second parameter specifies that the JSON format is only converted to the No. 0 attribute. In fact, it is not valid for arrays, but only for objects.

The second parameter can also be a function to change JSON Return value of stringify().

function f(key, value) {
  if (typeof value === "number") {
    value = 2 * value;
  }
  return value;
}

JSON.stringify({ a: 1, b: 2 }, f)
// '{"a": 2,"b": 4}'

The f function in the above code accepts two parameters, namely the key name and key value of the converted object. If the key value is a numeric value, multiply it by 2, otherwise it returns as it is.

Note that this handler handles all keys recursively.

var obj = {a: {b: 1}};

function f(key, value) {
  console.log("["+ key +"]:" + value);
  return value;
}

JSON.stringify(obj, f)
// []:[object Object]
// [a]:[object Object]
// [b]:1
// '{"a":{"b":1}}'

In the above code, the object obj will be processed by the f function three times, and the last line of output is JSON The default output of stringify(). For the first time, the key name is empty, and the key value is the whole object obj; The second key name is a, and the key value is {b: 1}; The third time, the key name is b and the key value is 1.

In recursive processing, the object processed each time is the value returned the previous time.

var obj = {a: 1};

function f(key, value) {
  if (typeof value === 'object') {
    return {b: 2};
  }
  return value * 2;
}

JSON.stringify(obj, f)
// "{"b": 4}"

In the above code, the f function modifies the object obj, and then JSON The stringify () method recursively handles the modified object obj.

If the handler returns undefined or no value, this property is ignored.

function f(key, value) {
  if (typeof(value) === "string") {
    return undefined;
  }
  return value;
}

JSON.stringify({ a: "abc", b: 123 }, f)
// '{"b": 123}'

In the above code, the a attribute returns undefined after processing, so the attribute is ignored.

Third parameter

JSON.stringify() can also accept a third parameter to increase the readability of the returned JSON string.

By default, a single line string is returned. For large JSON objects, the readability is very poor. The third parameter makes each attribute occupy a separate line, and adds the specified prefix (no more than 10 characters) to each attribute.

// Default output
JSON.stringify({ p1: 1, p2: 2 })
// JSON.stringify({ p1: 1, p2: 2 })

// Branch output
JSON.stringify({ p1: 1, p2: 2 }, null, '\t')
// {
// 	"p1": 1,
// 	"p2": 2
// }

In the example above, the third attribute \ tadditions a tab character to each attribute and displays it in separate lines.

If the third attribute is a number, it indicates the space added before each attribute (no more than 10 at most).

JSON.stringify({ p1: 1, p2: 2 }, null, 2);
/*
"{
  "p1": 1,
  "p2": 2
}"
*/

toJSON() method of parameter object

If the parameter object has a custom toJSON() method, JSON Stringify () takes the return value of this method as a parameter and ignores other properties of the original object.

Here is a common object.

var user = {
  firstName: 'three',
  lastName: 'Zhang',

  get fullName(){
    return this.lastName + this.firstName;
  }
};

JSON.stringify(user)
// "{" firstName ":" San "," lastName ":" Zhang "," fullName ":" Zhang San "}"

Now, add the toJSON() method to this object.

var user = {
  firstName: 'three',
  lastName: 'Zhang',

  get fullName(){
    return this.lastName + this.firstName;
  },

  toJSON: function () {
    return {
      name: this.lastName + this.firstName
    };
  }
};

JSON.stringify(user)
// "{" name ":" Zhang San "}"

In the above code, JSON Stringify () finds that the parameter object has a toJSON() method, so it directly uses the return value of this method as the parameter and ignores other parameters of the original object.

The Date object has its own toJSON() method.

var date = new Date('2015-01-01');
date.toJSON() // "2015-01-01T00:00:00.000Z"
JSON.stringify(date) // ""2015-01-01T00:00:00.000Z""

In the above code, JSON If stringify () finds that it is dealing with an instance of a Date object, it will call the toJSON() method of the instance object and take the return value of the method as a parameter.

One application of the toJSON() method is to automatically convert regular objects to strings. Because JSON Stringify () cannot convert regular objects by default, but you can convert regular objects after setting the toJSON() method.

var obj = {
  reg: /foo/
};

// When the toJSON method is not set
JSON.stringify(obj) // "{"reg":{}}"

// When setting the toJSON method
RegExp.prototype.toJSON = RegExp.prototype.toString;
JSON.stringify(/foo/) // ""/foo/""

The above code deploys the toJSON() method on the prototype of the regular object and points it to the toString() method. Therefore, when converting to JSON format, the regular object first calls the toJSON() method to convert it into a string, and then it is converted by JSON The stringify () method handles.

JSON.parse()

JSON. The parse () method is used to convert the JSON string to the corresponding value.

JSON.parse('{}') // {}
JSON.parse('true') // true
JSON.parse('"foo"') // "foo"
JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
JSON.parse('null') // null

var o = JSON.parse('{"name": "Zhang San"}');
o.name // Zhang San

If the incoming string is not in a valid JSON format, JSON The parse () method will report an error.

JSON.parse("'String'") // illegal single quotes
// SyntaxError: Unexpected token ILLEGAL

In the above code, the double quoted string is a single quoted string. Because the single quoted string does not conform to JSON format, an error is reported.

To handle parsing errors, you can use JSON Put the parse() method in try Catch code block.

try {
  JSON.parse("'String'");
} catch(e) {
  console.log('parsing error');
}

JSON. The parse () method can accept a processing function as the second parameter. The usage is the same as JSON The stringify () method is similar.

function f(key, value) {
  if (key === 'a') {
    return value + 10;
  }
  return value;
}

JSON.parse('{"a": 1, "b": 2}', f)
// {a: 11, b: 2}

In the above code, JSON The second argument to parse () is a function that adds 10 to the key value if the key name is a.

Reference link

Topics: Ajax