0%

问题探究

我在这儿了解了一下 JSONP:知乎 - JSONP 的工作原理是什么?

很简单,就是利用<script>标签没有跨域限制的“漏洞”(历史遗迹啊)来达到与第三方通讯的目的。
当需要通讯时,本站脚本创建一个<script>元素,地址指向第三方的 API 网址,形如: <script src="http://www.example.net/api?param1=1&param2=2"></script> 并提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递)。 第三方产生的响应为 json 数据的包装(故称之为 jsonp,即 json padding),形如: callback({"name":"hax","gender":"Male"}) 这样浏览器会调用 callback 函数,并传递解析后 json 对象作为参数。本站脚本可在 callback 函数里处理所传入的数据。
补充:“历史遗迹”的意思就是,如果在今天重新设计的话,也许就不会允许这样简单的跨域了嘿,比如可能像 XHR 一样按照 CORS 规范要求服务器发送特定的 http 头。

但是这东西我确实没用过,为什么没用过?可能是我经历的项目场景没那么复杂吧?

找到了一个很好的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function jsonp(url, data = {}, cb = 'MusicJsonCallback') {
data[cb] = cb;
// 拼装参数
const params = [];
for (let key in data) {
params.push(`${key}=${data[key]}`);
}
const script = document.createElement('script');
script.src = url + '?' + params.join('&');
script.defer = true;
document.body.appendChild(script);
return new Promise((resole, reject) => {
try {
window[cb] = function (data) {
resole(data);
};
} catch (error) {
reject(error);
} finally {
// 每次删除
script.parentNode.removeChild(script);
}
});
}
jsonp('https://y.qq.com/download/download.js', { format: 'jsonp' }).then(
(res) => {
console.log(res);
}
);

接下来的问题就和 jsonp 关系不大了,但是也引起了我的好奇

由此衍生的另一个疑问 - 什么是 CSP

但是,在知乎页面上我运行了代码,返回了一段错误,而在百度,QQ 页面上却能正常的运行,这让我产生了许些好奇心。

阅读全文 »

实现 mySetInterval

如题,我先是简单的实现了一下 mySetInterval

1
2
3
4
5
6
7
8
9
10
11
12
13
let mySetInterval = (callback, time) => {
let fn = () => {
return setTimeout(() => {
callback && callback();
fn();
}, time);
};
return fn();
};

mySetInterval(() => {
console.log(123);
}, 1000);

实现 mySetInterval

阅读全文 »

问题

由于某些原因,需要忽略某些行的TS报错,比如引入了外部的组件,但是他的Ts声明是有问题的。所以需要强制忽略。

解决问题

解法方法很简单,见官方文档:**ts-ignore**

在代码的上方插入下面的注释就可以了

1
// @ts-ignore

公司项目上线的时候,我写的一个功能在测试环境突然不能用了,吓得我赶紧看了一下客户用的什么浏览器,好家伙,是 Chrome65

翻出 MDN 文档查一下,**Array.prototype.flat()**,好家伙,IE 直接所有版本都不支持。

讲真,真的很怕客户用玄学浏览器,好在只有这个问题需要兼容。

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty(Array.prototype, 'flat', {
value: function (depth = 1) {
return this.reduce(function (flat, toFlatten) {
return flat.concat(
Array.isArray(toFlatten) && depth > 1
? toFlatten.flat(depth - 1)
: toFlatten
);
}, []);
},
});

console.log([1, [2], [3, [[4]]]].flat(2));

赶紧加个垫片压压惊。

朋友给的服务器有个多余的磁盘,但是需要手动去挂载

查看磁盘的几个命令

命令在网上可以很轻松的查到

1
2
df -h
sudo fdisk -l

分区

查看完磁盘信息后首先需要进行分区

1
sudo fdisk /dev/sda ## /dev/sda 为你需要分区的磁盘
  1. 回车进入分区命令
  2. 输入 m 查看 fdisk 分区工具选项
  3. 输入 n 开始分区
  4. 可以选择 p(主分区)或 e(扩展分区)等创建好扩展分区后就可以看到逻辑分区选项。根据提示输入盘符号(按提示的输入或直接回车),分区大小等信息。
  5. 可以多次执行 n 操作创建多个分区,创建好后输入 w 保存退出。
  6. 执行 partprobe 重新读取整个分区表。

创建文件系统

1
sudo mkfs -t ext4 /dev/sda

创建挂载点

1
sudo mkdir /mydisk

挂载

1
sudo mount /dev/sda /mydisk

自动挂载

修改/etc/fstab 文件实现自动挂载

添加如下信息

/dev/sda /mydisk ext4 defaults 1 2

共 6 个字段分别代表:

  1. 分区设备文件名或 UUID
  2. 挂载点
  3. 文件系统名称
  4. 挂载参数,挂载权限
  5. 指定分区是否被 dump 备份,0 代表不备份,1 代表每天备份,2 代表不定期备份。
  6. 指定分区是否被 fsck 检测,0 代表不检测,其他数字代表检测的优先级,比如 1 的优先级比 2 高。根目录所在的分区的优先级为 1,其他分区的优先级为大于或等于 2

验证

1
sudo mount -a

转载来源

朋友给的服务器网不太好,老是掉线,我想起了 screen。但是我也不是很熟悉,对 screen 的了解仅限于 screen -S xxxscreen -R xxx

安装

命令在网上可以很轻松的查到

CentOS/Redhat

1
yum -y install screen #for CentOS/Redhat

Debian/Ubuntu

1
apt-get -y install wget screen #for Debian/Ubuntu

使用

参数说明

1
2
3
4
5
6
7
8
9
10
11
12
-A   将所有的视窗都调整为目前终端机的大小。
-d <作业名称>将指定的screen作业离线。
-h <行数>指定视窗的缓冲区行数。
-m 即使目前已在作业中的screen作业,仍强制建立新的screen作业。
-r <作业名称>恢复离线的screen作业。
-R 先试图恢复离线的作业。若找不到离线的作业,即建立新的screen作业。
-s 指定建立新视窗时,所要执行的shell。
-S <作业名称>指定screen作业的名称。
-v 显示版本信息。
-x 恢复之前离线的screen作业。
-ls 显示目前所有的screen作业。
-wipe 检查目前所有的screen作业,并删除已经无法使用的screen作业。

常用 screen 参数及快捷操作

1
2
3
4
5
screen -S session_name          # 新建一个叫session_name的session
screen -ls # 列出当前所有的session
screen -r session_name # 回到session_name这个session
screen -d session_name # 远程detach某个session
screen -d -r session_name # 结束当前session并回到session_name这个session

在每个 screen session 下,命令都以 ctrl+a、ctrl-a,常用的几个操作如下:

1
2
3
ctrl-a x # 锁住当前的 shell window,需用用户密码解锁
ctrl-a d # detach,暂时离开当前 session,将当前 screen session 转到后台执行,并会返回没进 screen 时的状态,此时在 screen session 里,每个 shell client 内运行的 process (无论是前台/后台)都在继续执行,即使 logout 也不影响
ctrl-a z # 把当前 session 放到后台执行,用 shell 的 fg 命令则可回去。

转载来源

screen 命令使用

题目

你能在你最喜欢的那天吃到你最喜欢的糖果吗?

题解

官方题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var canEat = function (candiesCount, queries) {
const n = candiesCount.length;

// 前缀和
const sum = new Array(n).fill(0);
sum[0] = candiesCount[0];
for (let i = 1; i < n; ++i) {
sum[i] = sum[i - 1] + candiesCount[i];
}

const q = queries.length;
const ans = new Array(q).fill(0);
for (let i = 0; i < q; ++i) {
const query = queries[i];
const favoriteType = query[0],
favoriteDay = query[1],
dailyCap = query[2];

const x1 = favoriteDay + 1;
const y1 = (favoriteDay + 1) * dailyCap;
const x2 = favoriteType == 0 ? 1 : sum[favoriteType - 1] + 1;
const y2 = sum[favoriteType];

ans[i] = !(x1 > y2 || y1 < x2);
}
return ans;
};

let candiesCount = [7, 4, 5, 3, 8],
queries = [
[0, 2, 2],
[4, 2, 4],
[2, 13, 1000000000],
];

console.log('canEat :>> ', canEat(candiesCount, queries));

收获

JS 的技巧

1
const ans = new Array(q).fill(0);

输出长度为 q 的数组,用 0 填充

了解了前缀和这个东西

[7, 4, 5, 3, 8]

用前缀和得到

[7,7+4 7+4+5,7+4+5+3,7+4+5+3+8]

然后拿到区间进行判断。

题目

连续的子数组和

题解

官方题解

如果事先计算出数组 nums 的前缀和数组,则对于任意一个子数组,都可以在 O(1) 的时间内得到其元素和。用 prefixSums[i] 表示数组 nums 从下标 00 到下标 i 的前缀和,则 nums 从下标 p+1 到下标 q(其中 p<q)的子数组的长度为 q−p,该子数组的元素和为 prefixSums[q]−prefixSums[p]

如果 prefixSums[q]−prefixSums[p] 为 k 的倍数,且 q−p≥2,则上述子数组即满足大小至少为 2 且元素和为 k 的倍数。

prefixSums[q]−prefixSums[p] 为 k 的倍数时,prefixSums[p]prefixSums[q] 除以 k 的余数相同。因此只需要计算每个下标对应的前缀和除以 k 的余数即可,使用哈希表存储每个余数第一次出现的下标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var checkSubarraySum = function (nums, k) {
const m = nums.length;
if (m < 2) {
return false;
}
const map = new Map();
map.set(0, -1);
let remainder = 0;
for (let i = 0; i < m; i++) {
remainder = (remainder + nums[i]) % k;
if (map.has(remainder)) {
const prevIndex = map.get(remainder);
// 2 - 0 >= 2
if (i - prevIndex >= 2) {
return true;
}
} else {
map.set(remainder, i);
// 5 0
// 1 2
}
}
return false;
};

let nums = [23, 2, 4, 6, 7],
k = 6;

console.log(checkSubarraySum(nums, k));

// 0 23%6
// 5
// 1 7%6
// 1
// 2 5%6
// 5
// 11%6
// 5
// 12%6
// 0

收获

我看到解题方法(上面的加粗划重点)我已经麻了,这是数学规律吗?

prefixSums[q]−prefixSums[p] 为 k 的倍数时,prefixSums[p]prefixSums[q] 除以 k 的余数相同。因此只需要计算每个下标对应的前缀和除以 k 的余数即可,使用哈希表存储每个余数第一次出现的下标。

数组为 [23, 2, 4, 6, 7] k6

prefixSums[q]−prefixSums[p] 为 k 的倍数

1
((23 + 2 + 4) - 23) % 6 = 0

只要下面这两个余数相同就可以了

1
2
23 % 6 = 5
(23 + 2 + 4) % 6 = 5

记一下获取图片宽高的方法

1
2
3
4
5
6
7
8
9
10
function getMeta(url) {
var img = new Image();
img.onload = function () {
let { width, height } = img;
console.log('{width, height} :>> ', { width, height });
};
img.src = url;
}
getMeta("https://www.xxxx.com/images.png")

大名鼎鼎的 request 库不维护了,虽然还能使用,但是作者不建议使用,翻了一下打算用 got 作为替代。

具体见

安装 got 依赖

1
yarn add got

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
import { promisify } from 'util';
import stream from 'stream';
import fs from 'fs-extra';
import got from 'got';

const pipeline = promisify(stream.pipeline);

pipeline(
got.stream('http://xxx.com/xxx.zip'),
fs.createWriteStream('xxx.zip')
).then(() => {
console.log('文件下载成功');
});

后记

发现 got 官方 demo 里的有些方法没用过,后面要抽时间了解一下。

  • util.promisify(original) 将回调转换为promise返回,看上去不错啊
  • stream.pipeline 一种在流和生成器之间传递管道的模块方法,用于转发错误并正确清理并在管道完成时提供回调。