Ajax基础到实战(五)——同源政策

2022-07-28,,,,

1. Ajax请求限制

2. 什么是同源

3. 同源政策的目的

  • 测试同源限制

    项目的文件夹结构如上图所示,在同源政策文件夹下新建s1、s2两个文件夹,两个文件夹对应的服务其接口分别为 3000 和 3001。

在s1文件夹下的public文件夹中新建: 01.测试非同源Ajax请求.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script type="text/javascript" src="/js/ajax.js"></script>
	<script type="text/javascript">
		ajax({
			url: 'http://localhost:3001/test',
			type: 'get',
			success: function (result) {
				console.log(result);
			}
		})
	</script>
</body>
</html>

可以看到s1这个html页面是向3001端口发送请求的

s1文件夹下的 app.js 文件如下:

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 向其他服务器端请求数据的模块
const request = require('request');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

app.get('/server', (req, res) => {
	request('http://localhost:3001/cross', (err, response, body) => {
		res.send(body);
	})
});

// 监听端口
app.listen(3000);
// 控制台提示输出
console.log('服务器启动成功');

可以看到app.js 文件监听的是 3000端口的请求,而s1的页面是向3001端口发送请求的,也就是请求的接口不同,是非同源的。

在 s2文件夹下的 app.js 文件中写入:

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

app.get('/test', (req, res) => {
	// const result = 'fn()';
	// res.send(result);
	res.send('ok');
});

// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

在浏览器中输入:http://localhost:3000/01.测试非同源Ajax请求.html

在浏览器的页面上没有显示ok,且浏览器控制台给出了错误提示信息:

这也就说明这个Ajax是不能实现非同源的请求的。

4. 使用JSONP解决同源限制问题

jsonp是json with padding 的缩写,它不属于AJax请求,但它可以模拟ajax请求。

  1. 将不同源的服务器端请求地址写在 script 标签的 src 属性中
<script src="www.example.com"></script>

<script src="https:cdn.bootcss.com/jquery/3.3.1/jquery.min.js"><script>

因为script标签的 src 属性是不受同源限制的。

  1. 服务器端响应的数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。
const data = 'fn ({name: 'zhangsan', age:'20'})';
res.send(data);

函数以字符串的形式响应给客户端,函数是在客户端被调用的。

  1. 在客户端全局作用下定义函数 fn
function fn (data) { }

注意:函数的定义要放在 script 标签前面,同时定义在全局作用域

  1. 在 fn 函数内部对服务器返回的数据进行处理
function (data) {
	console.log(data);
}

02.使用jsonp向非同源服务器端请求数据(1).html:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script>
		// 函数定义在全局作用域,script标签的前面
		function fn () {
			console.log('客户端的fn函数被调用了')
		}
	</script>
	<!-- 1.将非同源服务器端的请求地址写在script标签的src属性中 -->
	<script src="http://localhost:3001/test"></script>
</body>
</html>

s2文件夹中的app.js :

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

// 测试路由
app.get('/test', (req, res) => {
	// 写在字符串中就不会调用函数
	const result = 'fn()';
	res.send(result); // 当客户端加载完这个函数调用的时候,fn() 这个函数会被立即执行。
});

// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

我们可以通过上面的程序来测试 fn 函数是否被客户端执行了。

  • 当我们在服务器端向 fn 函数传递了实参,实参为一个对象:
    {name: “zhangsan”}

02.使用jsonp向非同源服务器端请求数据(1).html:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script>
		// 函数定义在全局作用域,script标签的前面
		// data 是形参,实参是我们在服务器端定义的对象
		function fn (data) {
			console.log('客户端的fn函数被调用了');
			console.log(data);
		}
	</script>
	<!-- 1.将非同源服务器端的请求地址写在script标签的src属性中 -->
	<script src="http://localhost:3001/test"></script>
</body>
</html>

s2文件中的 app.js :

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

// 测试路由
app.get('/test', (req, res) => {
	// 写在字符串中就不会调用函数
	// {name: "zhangsan"} 是 函数fn 的实参
	const result = 'fn({name: "zhangsan"})';
	res.send(result); // 当客户端加载完这个函数调用的时候,fn() 这个函数会被立即执行。
});

// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

我们可以在客户端看一下打印出的结果:

传递的实参在控制台中输出了。

  • 总结:

    客户端在加载完响应内容之后,函数就会在客户端被调用,此时客户端在提前准备好函数的定义,通过这个函数来接收服务器端返回的数据。

  • jsonp的理解

    将json数据作为填充内容(padding:填充),在服务器端将json数据作为函数的参数,将json数据填充到函数中。

5. JSONP 代码优化

  1. 客户端需要将函数名称传递到服务器端

  2. 将script请求的发送变成动态请求(比如点击按钮再发送请求)

    实现思路:我们只需要在想要发送请求的时候,使用javascript代码动态创建script标签,然后将script标签追加到页面中。这样请求也是可以发送的,请求是在script标签被追加到页面时发送的

04.使用jsonp向非同源服务器请求数据.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点我发送请求</button>
    <script>
        function fn2(data) {
            console.log('客户端的fn2函数被调用了');
            console.log(data);
        }
    </script>
    
    <script>
        // 获取按钮
        var btn = document.getElementById('btn');
        // 为按钮添加点击事件
        btn.onclick = function () {
            // 创建script标签
            var script = document.createElement('script');
            // 设置 src 属性
            script.src = 'http://localhost:3001/better?callback=fn2';
            // 将script标签追加到页面中(body体内部的后面)
            document.body.appendChild(script);
        }
    </script>
</body>
</html>

在页面中添加了一个按钮,获取到按钮元素,为按钮元素添加点击事件,当点击事件触发的时候我们可以动态创建script标签,并设置其src属性,最终可以向3001端口发送请求,在s2下的app.js文件中添加路由:

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

// 测试路由
app.get('/test', (req, res) => {
	// 写在字符串中就不会调用函数
	// {name: "zhangsan"} 是 函数fn 的实参
	const result = 'fn({name: "zhangsan"})';
	res.send(result); // 当客户端加载完这个函数调用的时候,fn() 这个函数会被立即执行。
});

app.get('/better', (req, res) => {
	const result = 'fn2({name: "张三"})';
	res.send(result);
});

// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

点击按钮,可以在控制台看到:

但是上面的程序有一个问题,当我们多次点击的时候,会生成多个script标签,这里需要改进。

我们可以为scrip标签添加 onload 事件,当script标签加载完毕后可以删除掉当前加载完的script标签。

// 为按钮添加点击事件
btn.onclick = function () {
    // 创建script标签
    var script = document.createElement('script');
    // 设置 src 属性
    script.src = 'http://localhost:3001/better?callback=fn2';
    // 将script标签追加到页面中(body体内部的后面)
    document.body.appendChild(script);
    // 为script标签添加 onload事件
    script.onload = function () {
        // 将body中的script标签删除掉
        document.body.removeChild(script);
    }
}

这样,每次script标签加载完之后就会被删除,用户多次点击就不会有多次添加的影响了。

  • 优化1. 客户端需要将函数名称传递到服务端
    优化思路:客户端开发人员只需要将函数的名称通过请求参数发送给服务器端,服务器端只要接收到函数名称,然后再返回函数调用即可。这样无论客户端的函数名称修改多少次都不会影响服务器端了。因为客户端总是将最新修改后的函数的名称传递给服务器端,服务器端只需要直接返回就可以了。

03.使用jsonp向非同源服务器端请求数据(2).html:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script>
		// 函数定义在全局作用域,script标签的前面
		// data 是形参,实参是我们在服务器端定义的对象
		function fn (data) {
			console.log('客户端的fn函数被调用了');
			console.log(data);
		}
	</script>
    <!-- 1.将非同源服务器端的请求地址写在script标签的src属性中 -->
    <!-- 现在我们需要在请求地址的后面加上请求参数:函数名称 -->
    <!-- 到了服务器端需要通过请求参数来接收函数名称 -->
	<script src="http://localhost:3001/better?callback=fn"></script>
</body>
</html>

修改一下s2中的app.js,主要是接收客户端发送的请求参数,也就是得到函数名称,然后将函数名称对应的函数调用代码返回给客户端。

app.js:

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

// 测试路由
app.get('/test', (req, res) => {
	// 写在字符串中就不会调用函数
	// {name: "zhangsan"} 是 函数fn 的实参
	const result = 'fn({name: "zhangsan"})';
	res.send(result); // 当客户端加载完这个函数调用的时候,fn() 这个函数会被立即执行。
});

app.get('/better', (req, res) => {
	// 获取请求参数中的函数名称(接收客户端传递过来的函数的名称)
	const fnName = req.query.callback;
	// 将函数名称对应的函数调用代码返回给客户端
	// const result = 'fn2({name: "张三"})';
	const result = fnName + '({name: "zhangsan"})';
	res.send(result);
});

// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

在浏览器中访问,可以发现成功实现了输出:

我们可以修改一下函数的名称,也就是修改请求参数,再次访问:

将函数的名字修改为fn1,同时修改请求参数的值:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script>
		// 函数定义在全局作用域,script标签的前面
		// data 是形参,实参是我们在服务器端定义的对象
		function fn1 (data) {
			console.log('客户端的fn函数被调用了');
			console.log(data);
		}
	</script>
    <!-- 1.将非同源服务器端的请求地址写在script标签的src属性中 -->
    <!-- 现在我们需要在请求地址的后面加上请求参数:函数名称 -->
    <!-- 到了服务器端需要通过请求参数来接收函数名称 -->
	<script src="http://localhost:3001/better?callback=fn1"></script>
</body>
</html>

可以看到修改后可以成功输出。
我们还可以打开控制台中的 network,可以看到:

可以看到发送的请求的响应确实是函数的调用。

  • 优化2. 封装jsonp函数,方便请求发送

    我们上面的程序发送了一个请求,就已经写了很多的代码,如果我想发送多个请求,这个代码就会被重复很多次。所以我们可以将其封装到一个函数当中,当我们想要使用jsonp方式向非同源服务器发送请求时,只需要调用这个函数就可以了。具体怎么做呢,其实和我们封装Ajax函数的思路是一样的。

05.封装 jsonp函数.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点我发送请求</button>
    <script>
        function fn2(data) {
            console.log('客户端的fn2函数被调用了');
            console.log(data);
        }
    </script>
    
    <script>
        // 获取按钮
        var btn = document.getElementById('btn');
        // 为按钮添加点击事件
        btn.onclick = function () {
            jsonp({
            // 请求地址
            url: 'http://localhost:3001/better?callback=fn2'
        })
        }

        function jsonp (options) {
            // 动态创建script标签
            var script = document.createElement('script');
            // 为 script 标签添加 src 属性
            script.src = options.url;
            // 将 script 便签追加到页面中
            document.body.appendChild(script);
            // 为 script 标签添加 onload事件
            script.onload = function () {
                document.body.removeChild(script);
            }
        }

    </script>
</body>
</html>

s2文件中 app.js(路由部分的代码)

app.get('/better', (req, res) => {
	// 获取请求参数中的函数名称(接收客户端传递过来的函数的名称)
	const fnName = req.query.callback;
	// 将函数名称对应的函数调用代码返回给客户端
	// const result = 'fn2({name: "张三"})';
	const result = fnName + '({name: "zhangsan"})';
	res.send(result);
});

第一件事就是动态的创建一个 script标签,利用script标签的src属性可以向非同源服务器端发送请求的特性来达到要求。

当前src属性的值我们不能确定,这个请求地址只有在发送请求,调用jsonp函数的时候我们才能知道这个请求 要发送到哪里去。实际上,就是我们在调用jsonp这个方法的时候我们要把这个请求地址通过参数的方式传递到函数的内部。

在调用jsonp函数的时候,我们的参数是通过对象的形式传递进去的。

我们可以给函数一个形参 options
通过 options.url 可以获得src属性值

将script标签追加到页面中,这个请求就会被发送了。而服务器端返回的是一个函数的调用。

当script标签被加载之后标签的意义就不复存在的,所以可以为script标签添加一个onload事件,加载完毕后去掉这个标签。

启动这两个服务器,打开html文件,点击按钮,可以看到:

在上面的代码中我们虽然封装了jsonp函数用于发送请求,但是在函数外部的其他地方我们还需要另外定义一个函数用于接收服务器端返回的数据,比如在当前代码的fn2函数,现在是发送一次请求需要调用两个函数,而且两个函数是独立的,这样就破坏了jsonp函数的封装性,我们不能一眼就看出来哪个请求和哪个函数是关联的。如果我们可以像Ajax函数一样,将处理请求结果的函数变成success函数,这样的话函数的封装性就比较好了,这是第一个看起来不爽的点。

还有一个地方也是看起来不太好的,就是在一个项目中我们往往要发送很多次的请求,每一个请求都要对应自己的函数,处理服务器端返回的结果,为很多请求对应的函数取很多的名字也是一件很麻烦的事情。

比如我们点击按钮1,发送请求,处理函数的名字叫fn1,点击按钮2,发送请求,处理函数的名字叫fn2。为了避免函数的名字发生重名的情况,我们只需要让函数的名字随机就可以了。

针对第一个问题,我们可以将处理函数放在jsonp函数的success方法中,success所对应的函数是一个匿名函数,为了是这个函数变成一个全局函数我们将其挂载到了window对象,这样我们就可以去掉外面定义的函数(例如fn2函数)。同时需要将请求参数拼接到script标签的src属性上,而在jsonp函数url这个实参中去掉请求参数。

改进后的05.html文件如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点我发送请求</button>
    <script>
        // function fn2(data) {
        //     console.log('客户端的fn2函数被调用了');
        //     console.log(data);
        // }
    </script>
    
    <script>
        // 获取按钮
        var btn = document.getElementById('btn');
        // 为按钮添加点击事件
        btn.onclick = function () {
            jsonp({
                // 请求地址
                url: 'http://localhost:3001/better',
                success: function (data) {
                    console.log(123);
                    console.log(data);
                }
            })
        }

        function jsonp (options) {
            // 动态创建script标签
            var script = document.createElement('script');
            // 它已经不是一个全局函数了
            // 我们要想办法将它变成全部函数
            window.fn2 = options.success;
            // 为 script 标签添加 src 属性
            script.src = options.url + '?callback=fn2';
            // 将 script 便签追加到页面中
            document.body.appendChild(script);
            // 为 script 标签添加 onload事件
            script.onload = function () {
                document.body.removeChild(script);
            }
        }
   
    </script>
</body>
</html>

app.js 代码无变化,打开html文件,点击按钮可以在浏览器的控制台中看到:

上面的方法确实可以解决将处理函数封装到jsonp函数中,实现关联关系,但是这样函数的名字就写死了,一直会是fn2,这显然是需要进一步改进的。

如果我们也年终有两个按钮,点击之后的处理函数都是fn2的话,如果我们在app.js中使用一个定时器,存在延时的话,点击按钮1之后又快速的点击了按钮2,第一个success函数还没有调用就已经出发了第二个点击事件,这样success函数就会被当前的所覆盖。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn1">点我发送请求1</button>
    <button id="btn2">点我发送请求2</button>
    <script>
        // function fn2(data) {
        //     console.log('客户端的fn2函数被调用了');
        //     console.log(data);
        // }
    </script>
    
    <script>
        // 获取按钮
        var btn1 = document.getElementById('btn1');
        var btn2 = document.getElementById('btn2');
        // 为按钮添加点击事件
        btn1.onclick = function () {
            jsonp({
                // 请求地址
                url: 'http://localhost:3001/better',
                success: function (data) {
                    console.log(123);
                    console.log(data);
                }
            })
        }

        btn2.onclick = function () {
            jsonp({
                // 请求地址
                url: 'http://localhost:3001/better',
                success: function (data) {
                    console.log(456789);
                    console.log(data);
                }
            })
        }

        function jsonp (options) {
            // 动态创建script标签
            var script = document.createElement('script');

            // var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
            // 它已经不是一个全局函数了
            // 我们要想办法将它变成全部函数
            window.fn2 = options.success;
            // window[fnName] = options.success;
            // 为 script 标签添加 src 属性
            script.src = options.url + '?callback=fn2';
            // script.src = options.url + '?callback' + fnName;
            // 将 script 便签追加到页面中
            document.body.appendChild(script);
            // 为 script 标签添加 onload事件
            script.onload = function () {
                document.body.removeChild(script);
            }
        }
    
    </script>
</body>
</html>

s2中的 app.js 使用了定时器:

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

// 测试路由
app.get('/test', (req, res) => {
	// 写在字符串中就不会调用函数
	// {name: "zhangsan"} 是 函数fn 的实参
	const result = 'fn({name: "zhangsan"})';
	res.send(result); // 当客户端加载完这个函数调用的时候,fn() 这个函数会被立即执行。
});

app.get('/better', (req, res) => {
	// 获取请求参数中的函数名称(接收客户端传递过来的函数的名称)
	const fnName = req.query.callback;
	// 将函数名称对应的函数调用代码返回给客户端
	// const result = 'fn2({name: "张三"})';
	const result = fnName + '({name: "zhangsan"})';
	setTimeout( () => {
		res.send(result);
	}, 1000)
});

// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

快速点击两个按钮:

可以看到虽然我们 success方法不同,但是最终输出的结果是一样的,为了对这一点进行改进,就需要使用不同的函数名称。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn1">点我发送请求1</button>
    <button id="btn2">点我发送请求2</button>
    <script>
        // function fn2(data) {
        //     console.log('客户端的fn2函数被调用了');
        //     console.log(data);
        // }
    </script>
    
    <script>
        // 获取按钮
        var btn1 = document.getElementById('btn1');
        var btn2 = document.getElementById('btn2');
        // 为按钮添加点击事件
        btn1.onclick = function () {
            jsonp({
                // 请求地址
                url: 'http://localhost:3001/better',
                success: function (data) {
                    console.log(123);
                    console.log(data);
                }
            })
        }

        btn2.onclick = function () {
            jsonp({
                // 请求地址
                url: 'http://localhost:3001/better',
                success: function (data) {
                    console.log(456789);
                    console.log(data);
                }
            })
        }

        function jsonp (options) {
            // 动态创建script标签
            var script = document.createElement('script');
            // 产生随机的函数名称
            var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
            // 它已经不是一个全局函数了
            // 我们要想办法将它变成全部函数
            window[fnName] = options.success;
            // 为 script 标签添加 src 属性
            script.src = options.url + '?callback=' + fnName;
            // 将 script 便签追加到页面中
            document.body.appendChild(script);
            // 为 script 标签添加 onload事件
            script.onload = function () {
                document.body.removeChild(script);
            }
        }

     
    </script>
</body>
</html>

关于jsonp函数,还有一点需要完善,在发送请求时,现在我们只是传递了一个callback参数,如果现在该请求要求我们传递更多的参数,应该如何做呢?

和Ajax函数一样,我们在调用jsonp函数额时候,传递一个data属性,属性的值是一个对象,对象当中存储的就是我们想要向服务器端发送的请求参数,在jsonp函数内部,我们要将对象转换为 参数名称=参数值,多个参数之间使用 & 隔开的字符串。在这里有一点需要注意,jsonp解决方案的请求属于 get 请求,因为它是通过 script 标签的 src属性发送的请求,它传递的参数也是 get 请求参数,具体的参数还要拼接在请求地址的后面。

我们找到按钮1对应的点击按钮事件处理函数,在这我们调用jsonp函数的时候要传递一个data属性,这个data属性的值是一个对象,在对象中所写的属性就是我们要向服务器端传递的请求参数。比如现在我们传递一个name参数,它的值是 lisi,我们在传递一个age属性,他的值的 20。在jsonp函数内部,我们可以通过options.data拿到这个对象,但是在这儿我们需要把这个对象转换成:参数名称 = 参数值,多个参数之间使用 & 隔开的字符串。所以我们可以先定义一个变量params用来拼接字符串。接下来我们使用for…in 来循环这个对象,在循环内部进行字符串的拼接。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn1">点我发送请求1</button>
    <button id="btn2">点我发送请求2</button>
    <script>
        // function fn2(data) {
        //     console.log('客户端的fn2函数被调用了');
        //     console.log(data);
        // }
    </script>
    
    <script>
        // 获取按钮
        var btn1 = document.getElementById('btn1');
        var btn2 = document.getElementById('btn2');
        // 为按钮添加点击事件
        btn1.onclick = function () {
            jsonp({
                // 请求地址
                url: 'http://localhost:3001/better',
                // data对象:向服务器端发送的请求参数
                data: {
                    name: 'lisi',
                    age: 30
                },
                success: function (data) {
                    console.log(123);
                    console.log(data);
                }
            })
        }

        btn2.onclick = function () {
            jsonp({
                // 请求地址
                url: 'http://localhost:3001/better',
                success: function (data) {
                    console.log(456789);
                    console.log(data);
                }
            })
        }

        function jsonp (options) {
            // 动态创建script标签
            var script = document.createElement('script');
            // 拼接字符串的变量
            var params = '';
            for (var attr in options.data) {
                params += '&' + attr + '=' + options.data[attr];
            }
            // 产生随机的函数名称
            var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
            // 它已经不是一个全局函数了
            // 我们要想办法将它变成全部函数
            window[fnName] = options.success;
            // 为 script 标签添加 src 属性
            script.src = options.url + '?callback=' + fnName + params;
            // 将 script 便签追加到页面中
            document.body.appendChild(script);
            // 为 script 标签添加 onload事件
            script.onload = function () {
                document.body.removeChild(script);
            }
        }
       
    </script>
</body>
</html>

打开html文件,点击第一个按钮,我们可以看到当前请求地址的后面除了有callback,还拼接上了 name属性 和 age属性。

接下来,我们再切换到 app.js 当中。切换到服务器端代码这一部分,实际上对于服务器端的代码也有优化的空间。

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

// 测试路由
app.get('/test', (req, res) => {
	// 写在字符串中就不会调用函数
	// {name: "zhangsan"} 是 函数fn 的实参
	const result = 'fn({name: "zhangsan"})';
	res.send(result); // 当客户端加载完这个函数调用的时候,fn() 这个函数会被立即执行。
});

app.get('/better', (req, res) => {
	// 获取请求参数中的函数名称(接收客户端传递过来的函数的名称)
	// const fnName = req.query.callback;
	// 将函数名称对应的函数调用代码返回给客户端
	// const result = 'fn2({name: "张三"})';
	// const result = fnName + '({name: "zhangsan"})';
	// setTimeout( () => {
	// 	res.send(result);
	// }, 1000)

	res.jsonp({name: 'lisi', age: 20});
	// 接收客户端传递过来的请求参数
	// 将真实的数据转换为字符串。拼接起来,返回给客户端
});

// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

本文地址:https://blog.csdn.net/qq_27575925/article/details/109384557

《Ajax基础到实战(五)——同源政策.doc》

下载本文的Word格式文档,以方便收藏与打印。