0%

iframe在项目中经常会使用到,而且通常都存在跨域的情况,那么如何才能安全进行跨源通信呢?最常用的方法就是使用window.postMessage() 方法。

postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

以上摘自MDN 原文 postMessage;

1
2
3
4
5
otherWindow.postMessage(message, targetOrigin, [transfer]);
// otherWindow 其他窗口的引用
// message 将要发送到其他 window 的数据
// targetOrigin 指定目标主机的协议、地址、端口,可以为"*"
// transfer (可选)一般不用

postMessage十分强大,既可以子传父,也可以父传子,并且可以突破同源限制。

接下来我们来实际应用一下,想象一下这样一个场景,在主页面中有一个遮罩层,子页面是一个iframe内嵌的页面,子页面中有一个按钮,现在想要在点击子页面按钮的时候实现遮罩层的切换,该如何实现?这里我们就用到了postMessage:

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
// 子页面

<style>
.btn{
width: 100px;
height: 50px;
margin: 60px;
background-color: skyblue;
border-radius: 10px;
text-align: center;
line-height: 50px;
color: azure;
cursor: pointer;
}

.btn:hover {
background-color:cornflowerblue;
}
</style>
<div class="btn">点击按钮</div>

<script>
const btn = document.querySelector('.btn')
btn.addEventListener('click', function(event) {
// 向父页面发送数据
window.parent.postMessage('toggle', '*')
})

// 监听父页面传来的数据
window.addEventListener('message', function(event) {
console.log(event.data.msg)
const span = document.createElement('div')
span.innerText = event.data.msg
document.body.appendChild(span)
})
</script>
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
// 父页面

<style>
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
</style>

<div class="mask">
<iframe frameborder="0" style="width:800px; height: 300px; background-color: white;" src="http://blog.hpf0532.top/test.html" id="authIframe"></iframe>
</div>

<script>
const iframe = document.getElementById('son')
const mask = document.querySelector('.mask')
// 监听子页面传来的数据
window.addEventListener('message', function (e) {
console.log(e.data)
if(e.data === 'toggle') {
mask.classList.toggle('mask')
// 向子页面传输数据
iframe.contentWindow.postMessage({ msg: Date.now() }, 'http://blog.hpf0532.top')
}
}
</script>

上面的代码其实很简单,在子页面里面获取了元素,该元素触发点击事件的时候,向父窗口发送一个消息,传递了一个消息(这个消息参数会在接收页面的e.data查到)。在父页面监听message事件,监听到了就让遮罩层切换。

完成切换后,父窗口也会向子页面发送一个消息。在子页面监听message事件,监听到了就会向子页面的DOM树中添加相应的元素。这样就实现了在跨域的情况下iframe页面之前的数据传递。

冒泡排序

原理

通过比较相邻的两个数,如果后一个数比前一个数小,则交换它们的位置。重复这个过程,直到所有的数都按照从小到大的顺序排列。

代码中使用了两层嵌套的循环。外层循环控制比较的轮数,内层循环用于比较相邻的两个数并交换位置。

下面代码添加了一个标志位来判断是否发生了交换,如果某一轮比较中没有发生交换,说明数组已经有序,可以提前结束循环。

时间复杂度 O(n ^ 2) n为数组长度

空间复杂度 O(1)

算法稳定性 稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let array = [3, 1, 4, 6, 5, 7, 2];

function bubbleSort(arr) {
const len = arr.length;
for(let i = 0; i < len - 1; i++) {
let swapped = false; // 标志位,判断是否发生交换
for(let j = 0; j < len - i - 1; j++) {
if(arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true; // 发生交换
}
}
if(!swapped) {
break;
}
}
return arr;
}

console.log(bubbleSort(array)); // [1, 2, 3, 4, 5, 6, 7]

选择排序

原理

第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。依次类推完成整个数组的排序。

时间复杂度 O(n ^ 2) n为数组长度

空间复杂度 O(1)

算法稳定性 不稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let array = [3, 1, 4, 6, 5, 7, 2];

function selectionSort(arr) {
const len = arr.length;
for(let i = 0; i < len - 1; i++) {
let minIndex = i;
for(let j = i; j < len; j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if(i !== minIndex) {
let temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
// [ arr[i], arr[minIndex] ] = [ arr[minIndex], arr[i] ]; //交换位置
}
}

return arr;
}

console.log(selectionSort(array)); // [1, 2, 3, 4, 5, 6, 7]

插入排序

原理

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序的过程,就如同打扑克牌时,不断整理放入手中的牌一样,即每次拿到一张牌,按大小顺序(从大到小或从小到大,一般都是这样吧)将其插入到合适的位置。

为了方便理解,这里大致模拟一下摸牌过程(从小到大):

  1. 第一次摸到一张 9。
  2. 第二次摸到一张 6,它比 9 小,要放在 9 的前面。
  3. 第三次摸到一张 10,它比 9 大,要放到 9 的后面。

以此类推(仅为便于理解,所以过程不是很严谨),直到所有的牌摸完。

所以,我们可以将插入排序理解为: 每次将一个新数据插入到有序队列中的合适位置里。

时间复杂度 最坏O(n^2) 最好O(n)

空间复杂度 O(1)

算法稳定性 稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let array = [3, 1, 4, 6, 5, 7, 2];

function insertSort(arr) {
const len = arr.length;
for(let i = 1; i < len; i++) {
let temp = arr[i];
let j = i;
for(; j > 0; j--) {
if (temp > arr[j - 1]) {
break; // 当前考察的数大于前一个数,证明有序,退出循环
}
arr[j] = arr[j - 1]; // 将前一个数复制到后一个数上
}
arr[j] = temp; // 找到考察的数应处于的位置
}

return arr;
}

console.log(insertSort(array)); // [1, 2, 3, 4, 5, 6, 7]

归并排序

原理

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个典型的应用。 合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

时间复杂度 O(nlogn)

空间复杂度 O(n)

算法稳定性 稳定

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
let array = [3, 1, 4, 6, 5, 7, 2];

function merge(left, right) {
let result = [];
while(left.length > 0 && right.length > 0) {
if(left[0] < right[0]) {
result.push(left.shift());
}else{
result.push(right.shift());
}
}

return result.concat(left).concat(right);
}


function mergeSort(arr) {
if (arr.length == 1) {
return arr;
}
let middle = Math.floor(arr.length / 2); // 求中点
let left = arr.slice(0, middle);
let right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right)); //递归合并与排序
}

console.log(mergeSort(array)); // [1, 2, 3, 4, 5, 6, 7]

快速排序

原理

采用二分法,取出中间数,数组每次和中间数比较,小的放到左边,大的放到右边。

  • 在数据集之中,找一个基准点
  • 建立两个数组,分别存储左边和右边的数组
  • 利用递归进行下次比较
  • 快速排序的时间复杂度为 O(nlogn),是一种高效的排序算法

时间复杂度 平均O(nlogn),最坏O(n2),实际上大多数情况下小于O(nlogn)

空间复杂度 O(logn)

算法稳定性 不稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let array = [3, 1, 4, 6, 5, 7, 2];

function quickSort(arr) {
if(arr.length === 0) {
return [];
}
const middleIndex = Math.floor(arr.length / 2);
let c = arr.splice(middleIndex, 1);
let left = [];
let right = [];

for(let i = 0; i < arr.length; i++) {
if(arr[i] < c) {
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}

return quickSort(left).concat(c, quickSort(right));

}

console.log(quickSort(array)); // // [1, 2, 3, 4, 5, 6, 7]

基本介绍

我们都知道在CSS中可以使用border属性来绘制不同的三角形,那么具体是怎么来实现的呢?我们一起来看看吧!

首先来绘制一个基本的容器:

1
<div class="triangle"></div>

接下来给他加点边框:

1
2
3
4
5
6
7
.triangle {
width: 80px;
height: 80px;
border-width: 10px;
border-style: solid;
border-color: red green blue pink;
}

目前它长这个样子:

接下来,我们把边框的宽度增加为25px:

1
2
3
4
5
.triangle {
...
border-width: 25px;
...
}

可以清楚看到四个角接合的地方其实是斜角,也就是每一个方向的边其实是梯形,而不是长方形。试想一下,如果我们把中间的空白区域拿掉(长宽各为80px的区域)的话,这张图会变成什么样子呢?现在我们把width和height都改为0看看结果:

1
2
3
4
5
.triangle {
width: 0;
height: 0;
...
}

果然,和预想中一样,变成了一个由四个三角形组成的正方形了。

因此,假如我们需要一个剪头向下的三角形,那么我们只需要将左、下、右的边框颜色改为transparent (透明)即可:

1
2
3
4
5
6
7
8
9
10
11
.triangle {
...
border-color: red transparent transparent transparent;
}

/*等价写法: */
.triangle {
border-top: 25px solid red;
border-left: 25px solid transparent;
border-right: 25px solid transparent;
}

应用案例

一、带有icon的按钮

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
.btn-more {
background-color: #7d3f98;
color: #fff;
padding: 8px 25px;
width: 60px;
text-align: center;
text-decoration: none;
border-radius: 30px;
font-size: 16px;
position: relative;
}

.btn-more:hover,
.btn-more:active,
.btn-more:focus {
background-color: #511378;
cursor: pointer;
}

.btn-more::after {
content: "";
width: 0;
height: 0;
border-style: solid;
border-width: 6px 0 6px 12px;
border-color: transparent transparent transparent #fff;
position: absolute;
right: 13%;
top: calc(50% - 6px);
}
1
<div class="btn-more">more</div>

二、气泡对话框

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
.dialog {
color: #444;
background-color: #ecf5ff;
border: 2px solid #c4e1ff;
border-radius: 10px;
width: 180px;
line-height: 60px;
position: relative;
text-align:center;
margin-top: 30px;
margin-bottom: 30px;
}

.dialog:before,
.dialog:after {
border: solid transparent;
content: "";
width: 0;
height: 0;
position: absolute;
}

.dialog::after {
border-width: 10px 10px 0 10px;
border-top-color: #ecf5ff;
top: 60px;
right: 20px;
}
.dialog::before {
border-width: 13px 13px 0 13px;
border-top-color: #c4e1ff;
top: 60px;
right: 17px;
}
1
<div class="dialog">陷入思考中...</div>

当我们在工作中需要批量处理异步操作时常会用到Promise的一些方法,那么这些方法都有怎样的特点和区别呢?接下来我们逐个的来分析下:

Promise.all()

我们先来看下MDN中的定义:

Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。

上述文档总结如下:

  • Promise.all()参数为包含多个Promise实例的数组
  • 当所有实例都执行成功时,返回他们所有的成功值
  • 如果有任何一个出现错误,则变为rejected状态并返回第一个错误的原因

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const delay = (time, flag, data) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(flag) {
resolve(data)
}else{
reject(`Error ${data}`)
}
}, time)
})
}

const p1 = delay(1000, true, 1)
const p2 = delay(2000, true, 2)
const p3 = delay(3000, true, 3)

Promise.all([p2, p3, p1]).then(res => {
console.log(res) // [2, 3, 1]
}).catch(e => console.log(e))

上面例子为所有promise实例全部成功的例子,因为返回结果为[2, 3, 1],由此我们也可以看到返回结果的顺序和参数传递的顺序是一致的。接下来我们看一个不成功的例子:

1
2
3
4
5
6
7
const p1 = delay(1000, true, 1)
const p2 = delay(2000, false, 2)
const p3 = delay(3000, true, 3)

Promise.all([p2, p3, p1]).then(res => {
console.log(res)
}).catch(e => console.log(e)) // Error 2

上述例子中p2状态为拒绝,所以Promise.all()最终状态也为Rejected并将p2的错误返回。

细心的你一定看出了问题,p1的成功结果去哪了?没错,Promise.all()方法在遇到错误的时候不会返回已经处在fulfilled状态的实例的数据,针对这个问题,我们来看一下Promise.allsettled()方法。

Promise.allSettled()

Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。

简单来说,Promise.allSettled方法不管参数中的promise对象成功与否,他都会等待所有promise状态的确认并且返回一个包含结果的对象数组。

接下来我们还是看刚才的例子:

1
2
3
4
5
6
7
const p1 = delay(1000, true, 1)
const p2 = delay(2000, true, 2)
const p3 = delay(3000, true, 3)

Promise.allSettled([p2, p3, p1]).then(res => {
console.log(res)
}).catch(e => console.log(e))

返回的res内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"status": "fulfilled",
"value": 2
},
{
"status": "fulfilled",
"value": 3
},
{
"status": "fulfilled",
"value": 1
}
]

再来看看错误状态的情况:

1
2
3
const p1 = delay(1000, true, 1)
const p2 = delay(2000, false, 2)
const p3 = delay(3000, false, 3)

返回信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"status": "rejected",
"reason": "Error 2"
},
{
"status": "rejected",
"reason": "Error 3"
},
{
"status": "fulfilled",
"value": 1
}
]

至此,我们看到一旦所指定的 promises 集合中每一个 promise 已经完成,无论是成功的达成或被拒绝,未成功的 Promise将被异步完成。那时,所返回的 promise 的处理器将传入一个数组作为输入,该数组包含原始 promises 集中每个 promise 的结果。

对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value 。如果值为 rejected,则存在一个 reason 。value(或 reason )反映了每个 promise 成功(或拒绝)的值。

Promise.race()

Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。

race的中文含义为”赛跑“,所以这个方法就很好理解了,拿上面的例子来说,3个promise实例谁先完成(不论成功还是失败)都将会以此作为race方法的结果而被确定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const p1 = delay(1000, true, 1)
const p2 = delay(2000, true, 2)
const p3 = delay(3000, true, 3)

Promise.race([p2, p3, p1]).then(res => {
console.log(res) // 1
}).catch(e => console.log(e))

const p1 = delay(1000, false, 1)
const p2 = delay(2000, true, 2)
const p3 = delay(3000, true, 3)

Promise.race([p2, p3, p1]).then(res => {
console.log(res)
}).catch(e => console.log(e)) // Error 1

就这个例子来说,Promise.race()方法的结果成功与否完全取决于p1,因为它总会第一个完成(或失败)。

Promise.any()

Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。

Promise.any()主要是针对只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

他还有一个特点就是:

Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

看下例子:

1
2
3
4
5
6
7
const p1 = delay(1000, false, 1)
const p2 = delay(2000, false, 2)
const p3 = delay(3000, true, 3)

Promise.any([p2, p3, p1]).then(res => {
console.log(res) // 3
}).catch(e => console.log(e))

上述例子中,只有p3是成功状态,因此包装实例的状态就是p3的状态,这里res输出3。

1
2
3
4
5
6
7
const p1 = delay(1000, false, 1)
const p2 = delay(2000, false, 2)
const p3 = delay(3000, false, 3)

Promise.any([p2, p3, p1]).then(res => {
console.log(res)
}).catch(e => console.log(e)) // AggregateError: All promises were rejected

如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promiseAggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。

总结

作用:

  • Promise.all() 全成功我成功 失败一个我失败
  • Promise.race() 谁第一个改变状态就是谁的,无论成功或失败
  • Promise.allSettled() 管你成功或失败,全部都得运行完
  • Promise.any() 一个成功我成功,全部失败我失败

状态成功时返回值:

  • Promise.all() 返回状态成功时的数组
  • Promise.race() 第一个成功的
  • Promise.allSettled() 无所谓成功或失败,返回值都是一个包含状态对象的数组
  • Promise.any() 返回第一个成功的

状态失败时返回值:

  • Promise.all() 第一个失败的
  • Promise.race() 第一个失败的
  • Promise.allSettled() 无所谓成功或失败,返回值都是一个包含状态对象的数组
  • Promise.any() AggregateError: All promises were rejected

是否将参数数组内部的Promise实例全部执行完,才调用

  • Promise.all() 成功是是,失败是否
  • Promise.race() 不是
  • Promise.allSettled() 是
  • Promise.any() 成功是否,失败是是

初始文件名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ls -lh 
total 5.5G
-rw-r--r-- 1 root root 193K Sep 28 09:38 20180908.txt
drwxr-xr-x 2 root root 4.0K Mar 7 16:37 batch
-rw-r--r-- 1 root root 160M Mar 13 16:35 batch_dispatcher.log.1
-rw-r--r-- 1 root root 17M Jan 8 12:00 batch_gen_scheduler.log.1
-rw-r--r-- 1 root root 3.5G Mar 13 16:36 batch_scheduler.log.1
-rw-r--r-- 1 root root 329M Jan 25 10:11 batch_scheduler_test.log.1
drwxr-xr-x 2 root root 4.0K Jan 21 13:54 batch_test
-rw-r--r-- 1 root root 30M Jan 25 10:10 batch_test_dispatcher.log.1
-rw-r--r-- 1 root root 137M Mar 13 16:29 boss_bi_access.log.1
-rw-r--r-- 1 root root 86M Mar 13 16:29 boss_bi_error.log.1
-rw-r--r-- 1 root root 185M Mar 13 14:32 boss_channel_access.log.1
-rw-r--r-- 1 root root 96M Mar 13 14:32 boss_channel_error.log.1
-rw-r--r-- 1 root root 77K Jan 21 14:45 monitor_once.log.1
-rw-r--r-- 1 root root 638M Mar 13 16:35 monitor_scheduler.log.1
-rw-r--r-- 1 root root 26M Dec 10 22:00 yqb_job.log.1
drwxr-xr-x 2 root root 4.0K May 29 2018 zhongcai
-rw-r--r-- 1 root root 348M Mar 13 16:35 zhongcai_scheduler.log.1
-rw-r--r-- 1 root root 688K Jan 28 10:51 zhongcai_test_access.log.1
-rw-r--r-- 1 root root 538K Jan 29 11:23 zhongcai_test_error.log.1
drwxr-xr-x 2 root root 4.0K Aug 21 2018 zhongcai_v1.4

需要将.log.1结尾的文件更改为.log
命令如下:

1
2
3
4
5
6
# for file in $(ls -lh | awk '$NF~/log/{print $NF}');do newfile=$(echo $file | sed 's/\.1//g'); mv $file $newfile; done
# 美化一下
for file in $(ls -lh | awk '$NF~/log/{print $NF}'); do
newfile=$(echo $file | sed 's/\.1//g')
mv $file $newfile
done

法二:

1
#  ls *.1 | sed -r "s#(.*).1#mv & \1#" | bash

注:
sed替换标记:
\1表示前面第一个左括号所表示的内容,\2表示前面第二个左括号中表示的内容,以此类推
&表示前面匹配到的内容