如果你最近想要换工作或者巩固一下自己的前端知识基础,不妨和我一起参与到每日刷题的过程中来,如何?
第12天要刷的手写题如下:
-
反转字符串并输出
-
实现一个eventEmitter
-
叙述jsonp原理并实现此功能
下面是我自己写的答案:
1. 反转字符串并输出
思路:将字符出先转成数组,然后进行反转,最后再拼接成新的字符串。这道题看似在考查字符串,实际上是在考察数组上的方法:
function sReverse (source) {
if (!source.length) return '';
return source.split('').reverse().join('');
}
2. 实现一个eventEmitter
分析:此功能的本质是考察函数作为数组元素以及元素上的方法。 方法: - 订阅事件:向话题对应的数组中添加函数元素 - 发布事件:依次执行某个话题对应的数组中的所有函数 - 取消订阅:对话题对应的数组中中的某个函数元素进行删除 - 单次订阅:可以看成由订阅和取消订阅功能组成
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脚本中,如下所示:
前端代码
const scriptUrl = 'http://localhost:6666/my-script.js';
function loadScript(url, callback) {
const script = document.createElement('script');
script.src = url;
script.onload = callback;
document.body.appendChild(script);
}
loadScript(scriptUrl, function() {
console.log(window._info);
});
后端代码
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功能。
function myJsonp ( {url, params} ) {
return new Promise(
res => {
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('&')}`;
document.body.appendChild(_script);
}
);
}
const url = `http://localhost:6666/my-jsonp`;
const _r = myJsonp(
{
url,
}
);
_r.then(d=>void console.log(d));
对应的后端代码示例:
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') {
res.setHeader('Content-Type', 'application/javascript');
const _f = /callback=(.*?)[&]*$/g.exec(req.url);
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}`);
});