以下题目来自掘金等其它博客,但是问题的答案都是根据笔者自己的理解做出的。如果你最近想要换工作或者巩固一下自己的前端知识基础,不妨和我一起参与到每日刷题的过程中来,如何?
第12天要刷的手写题如下:
- 反转字符串并输出
- 实现一个eventEmitter
- 叙述jsonp原理并实现此功能
下面是我自己写的答案:
1. 反转字符串并输出
思路:将字符出先转成数组,然后进行反转,最后再拼接成新的字符串。这道题看似在考查字符串,实际上是在考察数组上的方法:
js
function sReverse (source) {
if (!source.length) return '';
return source.split('').reverse().join('');
}
2. 实现一个eventEmitter
分析:此功能的本质是考察函数作为数组元素以及元素上的方法。 方法: - 订阅事件 :向话题 对应的数组中添加函数元素 - 发布事件 :依次执行某个话题对应的数组中的所有函数 - 取消订阅 :对话题 对应的数组中中的某个函数元素进行删除 - 单次订阅:可以看成由订阅和取消订阅功能组成
js
class EventEmitter {
constructor () {
this.events = {};
}
on (topic, registerCb) {
this.events[topic] = this.events[topic] ?? [];
this.events[topic].push(registerCb);
}
off (topic, registerCb) {
this.events[topic] = this.events[topic].filter(f => f!==registerCb);
}
once (topic, registerCb) {
const _registerCb = () => {
this.off(topic, _registerCb);
registerCb();
}
this.on(topic, _registerCb);
}
emit (topic) {
const _topicCallbacks = this.events[topic];
_topicCallbacks?.forEach(
cb => void cb()
)
}
}
3. 叙述jsonp原理并实现此功能
- jsonp原理:前端构造script标签,和一个数据接受函数f; 然后设置src的值并将其插入到文档中去,以此发送一个绕过跨域检测的get请求;服务器收到这种get请求之后,根据get请求传递的信息(函数f名),将准备好的数据作为调用此约定函数的形参,并将函数调用作为字符串原码写在将要返回的脚本文件中;当script标签加载完毕之后,返回的脚本文件会自动执行,此时数据就通过预设函数f被转发到正确的位置(或被预定操作处理)。
- 用一句话来概括一下:将本来通过接口访问的信息通过js脚本来传递,然后通过函数调用的方法将js脚本中的信息取出来。
- 实际上,数据接受函数f不是重点,重点是将数据写在js脚本中,如下所示:
前端代码
js
const scriptUrl = 'http://localhost:6666/my-script.js';
function loadScript(url, callback) {
const script = document.createElement('script');
script.src = url;
// 在脚本加载完成时触发回调函数
script.onload = callback;
// 将 <script> 标签插入到页面
document.body.appendChild(script);
}
// 加载脚本并定义回调函数
loadScript(scriptUrl, function() {
console.log(window._info); // 输出 "今天中午吃米线"
});
后端代码
js
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/my-script.js') {
// 设置响应头
res.setHeader('Content-Type', 'application/javascript');
// 返回脚本内容
const scriptContent = 'window._info = "今天中午吃米线";';
res.end(scriptContent);
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
const port = 6666;
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
上面的代码已经很好的展示了jsonp的原理了,但是将传递的数据挂载在window上面毕竟是不正规的做法,并且在动态的script标签生效之后并没有及时移除。 下面实现一个基本的通用型jsonp功能。
js
function myJsonp ( {url, params} ) {
return new Promise(
res => {
// 创建动态script标签:创建不会发起get请求,需要设置src的值,type的值然后插入到文档中才会生效
let _script = document.createElement('script');
// 创建一个临时的函数名称,这个函数就是用来被脚本调用并且负责传递/处理数据的
let _f = Math.random().toString(16).replace('0.','tmp_');
// 构建一次性函数并将其挂载到全局对象中去
globalThis[_f] = function (data) {
// 将形参/数据传递出去
res(data);
// 移除动态标签
document.body.removeChild(_script);
// 删除挂载在全局对象下面的临时函数
delete globalThis[_f];
}
// 接下来进行查询参数的序列化
let _arr = [];
for (let key in params) {
if (params.hasOwnProperty(key)) {
arr.push(`${key}=${params[key]}`)
}
}
if (_arr.length) {
_arr.unshift('');
}
// 设置动态标签的类型
_script.type = 'text/javascript';
// 设置动态标签的地址
_script.src = `${url}?callback=${_f}${_arr.join('&')}`;
// 往文档中添加此动态script标签,发起get请求
document.body.appendChild(_script);
}
);
}
const url = `http://localhost:6666/my-jsonp`;
const _r = myJsonp(
{
url,
}
);
_r.then(d=>void console.log(d));
对应的后端代码示例:
js
const http = require('http');
const url = require('url');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
if (parsedUrl.pathname === '/my-jsonp') {
// const callbackName = queryData.callback;
// const data = queryData.data;
// 设置响应头
res.setHeader('Content-Type', 'application/javascript');
// 构造 JSONP 响应内容
const _f = /callback=(.*?)[&]*$/g.exec(req.url); // callback=tmp_0df0608773855
const data = `今天中午吃红烧肉`;
const responseContent = `globalThis.${_f[1]}("${data}")`;
res.end(responseContent);
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
const port = 6666;
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});