1. Introduction

In the front-end interview, everyone will be asked cross-domain related questions, and those who have memorized the eight-part essay will certainly answer the cross-domain solution fluently. There are many common cross-domain solutions sorted out on the Internet, but if asked about the implementation details, can you write the implementation or in-depth interpretation? In fact, in many cases, the interviewer will not only investigate the concept of the first layer, but also ask the second layer, the third layer content, so it is necessary to grasp the principle of implementation. Today, I will sort out the basic way of cross-domain, focusing on the implementation of JSONP ~

2. Cross-domain solution

There are about eight common cross-domain solutions, and you should be able to answer four or five in an interview

2.1 If we only want to achieve cross-domain operation of different subdomain names under the master domain name, we can use the document.domain setting to solve the problem

Document.domain is set as the master domain to achieve cross-domain operation of the same subdomain. At this time, cookies under the master domain can be accessed by the quilt domain name. If the document contains an iframe with the same primary domain name and different subdomains, we can also operate on this iframe.

2.2 Using the location.hash method

We can dynamically change the hash value of the iframe window on the main page, and then implement a listener function in the iframe window to achieve such a one-way communication. Since there is no way to access the parent Windows of different sources in iframe, we cannot directly modify the hash value of the parent window to achieve communication. We can add another IFrame to the iframe, and the content of this iframe is the same as that of the parent page. So window.parent. Parent can change the SRC of the top-level page to achieve two-way communication.

2.3 Using the window.name method

If window.name is set in the same window, pages of different sources can also be accessed. Therefore, child pages of different sources can first write data in window.name and then jump to a page of the same origin as the parent. In this case, the parent page can access the data in window.name of the child page of the same origin. The advantage of this method is that a large amount of data can be transmitted.

2.4 Using postMessage to solve the problem

This is a new API in H5. Through it, we can realize the information transfer between multiple Windows. By obtaining the reference of the specified window, we can call postMessage to send information. In the window, we can receive information by listening to the message information, so as to realize the information exchange between different sources. If it is a problem like ajax not being able to submit cross-domain requests, we can use JSONP, CORS, WebSocket protocol, server proxy to solve the problem.

2.5 Use JSONP to implement cross-domain requests

Its main principle is to implement cross-domain requests by dynamically building script tags, since there are no cross-domain access restrictions on the introduction of script tags by browsers. Through after the requested url to specify a callback function, then the server at the time of return data, build a json data packaging, the packaging is a callback function, and then returned to the front, front end receives the data, because the request is a script file, so will be executed directly, so that we previously defined callback function can be invoked, Thus, cross-domain request processing is realized. This can only be used for GET requests.

2.6 Using CORS

CORS is a W3C standard, which stands for “Cross-domain Resource Sharing”. CORS requires both browser and server support. Currently, all browsers support this feature, so we only need to configure it on the server side. Browsers classify CORS requests into two categories: simple and non-simple. For simple requests, the browser issues CORS requests directly. In particular, an Origin field will be added to the header information. The Origin field indicates the source from which the request came. Based on this value, the server decides whether to approve the request or not. The server returns a normal HTTP response if Origin specifies a source that is not within the license scope. When the browser sees that the response header does not contain the Access-Control-Allow-Origin field, it knows something has gone wrong, throws an error, and Ajax does not receive the response. If successful, it will contain fields starting with access-Control -. For non-simple requests, the browser sends a pre-check request to check whether the domain name is in the whitelist of the server. The browser sends a pre-check request only after receiving a positive reply.

2.7 Use the Websocket protocol, which has no origin restriction

2.6 Use a server to proxy cross-domain access requests

When there is a cross-domain request operation, send the request to the back end, let the back end request for you, and then finally get the result back.

3. The json explanation

3.1 Basic Principles

Jsonp(JSON with Padding) is a “usage mode” of JSON that allows web pages to fetch data from other domains. As explained above, the basic principle of JSONP is to make use of script tag SRC without cross-domain constraints.

let a = 123;
this.document.getElementById('123')
Copy the code

3.2 Execution Process

  • The front end defines a parsing function (e.g.jsonpCallback = function (res) {})
  • throughparamsForm packagingscriptTag, and declare the execution function (e.gcb=jsonpCallback)
  • The back end gets the execution function declared by the front end (jsonpCallback) and passed to the front end with arguments and calls to execute functions
  • The front-end is executed when the script tag returns the resourcejsonpCallbackAnd get the data through the callback function.

3.3 the advantages and disadvantages

Disadvantages: Only GET requests are made, and the function logic is written with the help of the back end.

Advantages: Good compatibility, can run in some older browsers.

3.4 Case Analysis

To see what we want to achieve, we have the following code in a file called index.html:

<script type='text/javascript'>
    window.jsonpCallback = function (res) {
        console.log(res)
    }
</script>
<script src='http://localhost:8080/api/jsonp? id=1&cb=jsonpCallback' type='text/javascript'></script>
Copy the code

Then I have a local file, server.js, which uses Node to provide a service that emulates the server and defines an interface/API/jSONp to query the data corresponding to the ID.

When I open index.html I load the script tag and execute the cross-domain request.

preparation

  • I’ll create a new folder locallynode-cors
  • And in this directorynpm initInitialization,package.json
  • The installationkoa(nodeA lightweight framework)
  • New FolderjsonpAnd the newindex.htmlandserver.js, one to write front-end code, one to write back-end
mkdir node-cors && cd node-cors
npm init
cnpm i --save-dev koa
mkdir jsonp && cd jsonp
touch index.html
touch server.js
Copy the code

The back-end code

Since the implementation of JSONP requires the coordination of the front and back ends, let’s write the implementation of the back end first.

const Koa = require('koa');
const app = new Koa();
const items = [{ id: 1.title: 'title1' }, { id: 2.title: 'title2' }]

app.use(async (ctx, next) => {
  if (ctx.path === '/api/jsonp') {
    const { cb, id } = ctx.query;
    const title = items.find(item= > item.id == id)['title']
    ctx.body = `${cb}(The ${JSON.stringify({title})}) `;
    return; }})console.log('listen 8080... ')
app.listen(8080);
Copy the code

When you’re done, save. And execute it in jsonp:

node server.js
Copy the code

To start the service, you can see that the editor console will print “Listen 8080…”

Front-end simple implementation OK👌, the back end has been implemented, now let’s look at the front end of the simplest way to implement, that is, write a script and send a request:

In the index.html:

<script type='text/javascript'>
    window.jsonpCallback = function (res) {
        console.log(res)
    }
</script>
<script src='http://localhost:8080/api/jsonp? id=1&cb=jsonpCallback' type='text/javascript'></script>
Copy the code

These two scripts mean:

  • The first one, create onejsonpCallbackFunction. But it hasn’t been called yet
  • Second, loadsrcAnd wait for the requested content to return

The whole process is:

  1. When the second script is executed, we request our port 8080 and put the id and cb parameters in the URL. So the background can get these two parameters in the URL.

  2. Const {id, cb} = ctx.query in the back end.

  3. The back end, once it gets those two parameters, might do some queries based on the ID, but of course I’m just simulating the query here, using a simple find to do a lookup. Find the item with ID 1 and take the title.

  4. The second parameter, cb, gets “jsonpCallback”, which tells the back end that there will be a function called jsonpCallback to receive the data that the back end wants to return, and that the back end will write jsonpCallback() in the return body.

  5. JsonpCallback ({“title”:”title1″}) {title :”title1″}) {title :”title1″}) {title :”title1″}) {title :”title1″}) {title :”title1″}} So the browser console will print {title: ‘title1’}

To achieve a simple cross-domain effect.

In fact, if we replace the second script tag with the following code, would we achieve the same effect?

<! -- <script src='http://localhost:8080/api/jsonp? id=1&cb=jsonpCallback' type='text/javascript'></script> -->
<script type="text/javascript">
    jsonpCallback({ title: 'title1' })
</script>
Copy the code

Jsonp implementation in jQuery

The $.ajax() method in jQuery also provides JSONP.

Let’s take a look:

<script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
<script>
    $.ajax({
        url: "http://localhost:8080/api/jsonp".dataType: "jsonp".type: "get".data: {
            id: 1
        },
        jsonp: "cb".success: function (data) {
            console.log(data); }});</script>
Copy the code

The data is also available in the SUCCESS callback.

3.5 Complete JSONP package implementation

Simple version of

So let’s take a look at what we’re going to implement

Define a JSONP method that takes four arguments:

  • url
  • params
  • CallbackKey: Which field is used with the background convention callback function (e.gcb)
  • Callback: Callback function executed after data is retrieved
<script>
    function JSONP({
        url,
        params = {},
        callbackKey = 'cb',
        callback
    }) {
        // Define the name of a local callback
        const callbackName = 'jsonpCallback';
        // Add the name to the argument: 'cb=jsonpCallback'
        params[callbackKey] = callbackName;
        // Add the callback to the window object so that the callback can be executed
        window[callbackName] = callback;

        / / get 'id = 1 & cb = jsonpCallback'
        const paramString = Object.keys(params).map(key= > {
            return `${key}=${params[key]}`
        }).join('&')
        // Create the script tag
        const script = document.createElement('script');
        script.setAttribute('src'.`${url}?${paramString}`);
        document.body.appendChild(script);
    }
    JSONP({
        url: 'http://localhost:8080/api/jsonp'.params: { id: 1 },
        callbackKey: 'cb',
        callback (res) {
            console.log(res)
        }
    })
</script>
Copy the code

So write to open the page but you can see the effect.

Multiple simultaneous requests

We have implemented JSONP, but there is a problem if I call JSONP multiple times at the same time:

JSONP({
    url: 'http://localhost:8080/api/jsonp'.params: { id: 1 },
    callbackKey: 'cb',
    callback (res) {
        console.log(res) // No.1
    }
})
JSONP({
    url: 'http://localhost:8080/api/jsonp'.params: { id: 2 },
    callbackKey: 'cb',
    callback (res) {
        console.log(res) // No.2}})Copy the code

You can see here I called JSONP twice, just passing different arguments. But instead of printing the results separately in No.1 and No.2, as we expected, both will be printed in No.2. This is because the latter callback overwrites the first callback wrapped in JSONP, and they all share the same callbackName, jsonpCallback. As follows:

Both times the results were printed from line 76.

So we need to modify the above JSONP method:

Let callbackName be unique, you can use increments instead of defining the callback in the window to pollute the global variable, you can throw it in json. XXX OK👌, and take a look at the modified code:

<script>
    function JSONP({
        url,
        params = {},
        callbackKey = 'cb',
        callback
    }) {
        // Define a unique local callbackId, or initialize it to 1 if there is none
        JSONP.callbackId = JSONP.callbackId || 1;
        let callbackId = JSONP.callbackId;
        // Add the callback to the JSON object to avoid contaminating the window
        JSONP.callbacks = JSONP.callbacks || [];
        JSONP.callbacks[callbackId] = callback;
        // Add the name to the argument: 'cb= jsonp.callbacks [1]'
        params[callbackKey] = `JSONP.callbacks[${callbackId}] `;

        / / get 'id = 1 & cb = json. Callbacks [1]'
        const paramString = Object.keys(params).map(key= > {
            return `${key}=${params[key]}`
        }).join('&')
        // Create the script tag
        const script = document.createElement('script');
        script.setAttribute('src'.`${url}?${paramString}`);
        document.body.appendChild(script);
        // the id is automatically increased to ensure that the unique
        JSONP.callbackId++;
    }
    JSONP({
        url: 'http://localhost:8080/api/jsonp'.params: { id: 1 },
        callbackKey: 'cb',
        callback (res) {
            console.log(res)
        }
    })
    JSONP({
        url: 'http://localhost:8080/api/jsonp'.params: { id: 2 },
        callbackKey: 'cb',
        callback (res) {
            console.log(res)
        }
    })
</script>
Copy the code

You can see that the callbacks are now called twice, but jSONP.callbacks [1] and jsonP.callbacks [2] are executed respectively:

Continue to improve

In fact, the above is relatively perfect, but there is a small problem, such as the following situation:

Let me change the code on the back end

const Koa = require('koa');
const app = new Koa();
const items = [{ id: 1.title: 'title1' }, { id: 2.title: 'title2' }]

app.use(async (ctx, next) => {
  if (ctx.path === '/api/jsonp') {
    const { cb, id } = ctx.query;
    const title = items.find(item= > item.id == id)['title']
    ctx.body = `${cb}(The ${JSON.stringify({title})}) `;
    return;
  }
  if (ctx.path === '/api/jsonps') {
    const { cb, a, b } = ctx.query;
    ctx.body = `${cb}(The ${JSON.stringify({ a, b })}) `;
    return; }})console.log('listen 8080... ')
app.listen(8080);
Copy the code

Added a/API /jsonps interface.

The front-end code then adds a request like this:

JSONP({
    url: 'http://localhost:8080/api/jsonps'.params: {
        a: '2&b=3'.b: '4'
    },
    callbackKey: 'cb',
    callback (res) {
        console.log(res)
    }
})
Copy the code

As you can see, the parameter a also contains the string b, so we get the wrong data:

The background doesn’t know that the argument to A is a string, it just intercepts the argument with ampersand.

So to solve this problem, use URI encoding.

That is, using:

encodeURIComponent('2&b=3')

// Result is "2%26b%3D3"
Copy the code

Just change the argument generation in the JSONP method:

/ / get 'id = 1 & cb = json. Callbacks [1]'
const paramString = Object.keys(params).map(key= > {
    return `${key}=The ${encodeURIComponent(params[key])}`
}).join('&')
Copy the code

Finally realize

Take a look at the full JSONP method:

<script>
    function JSONP({
        url,
        params = {},
        callbackKey = 'cb',
        callback
    }) {
        // Define a unique local callbackId, or initialize it to 1 if there is none
        JSONP.callbackId = JSONP.callbackId || 1;
        let callbackId = JSONP.callbackId;
        // Add the callback to the JSON object to avoid contaminating the window
        JSONP.callbacks = JSONP.callbacks || [];
        JSONP.callbacks[callbackId] = callback;
        // Add the name to the argument: 'cb= jsonp.callbacks [1]'
        params[callbackKey] = `JSONP.callbacks[${callbackId}] `;
        / / get 'id = 1 & cb = json. Callbacks [1]'
        const paramString = Object.keys(params).map(key= > {
            return `${key}=The ${encodeURIComponent(params[key])}`
        }).join('&')
        // Create the script tag
        const script = document.createElement('script');
        script.setAttribute('src'.`${url}?${paramString}`);
        document.body.appendChild(script);
        // the id is automatically increased to ensure that the unique
        JSONP.callbackId++;

    }
    JSONP({
        url: 'http://localhost:8080/api/jsonps'.params: {
            a: '2&b=3'.b: '4'
        },
        callbackKey: 'cb',
        callback (res) {
            console.log(res)
        }
    })
    JSONP({
        url: 'http://localhost:8080/api/jsonp'.params: {
            id: 1
        },
        callbackKey: 'cb',
        callback (res) {
            console.log(res)
        }
    })
</script>
Copy the code

Note ⚠ ️ :

Difference between encodeURI and encodeURIComponent

EncodeURI () does not encode special characters that are urIs themselves, such as colons, forward slashes, question marks, and hash points; EncodeURIComponent () encodeURIComponent() encodes any nonstandard characters it finds for example:

var url = 'https://lindaidai.wang'

encodeURI(url) // "https://lindaidai.wang"

encodeURIComponent(url) // "https%3A%2F%2Flindaidai.wang"
Copy the code

In addition, decodeURIComponent can be used to decode.

decodeURIComponent("https%3A%2F%2Flindaidai.wang")
// 'https://lindaidai.wang'
Copy the code