Skip to main content

· One min read
const config = {
label: '',
fields: 'datas',
Component: <Component />,
rules: [
{
required: true,
message: '请选择数据',
}
],
initialValue: [],
}

这种情况下type默认是string不会校验数组,应该改成

const config = {
label: '',
fields: 'datas',
Component: <Component />,
rules: [
{
type: 'array',
required: true,
message: props.t('请选择成员'),
transform(value) {
if (value) {
return JSON.parse(value);
}

return value;
}
}
],
initialValue: [],
}

type: 'array' transform 则是校验之前转换成数组再进行校验

· One min read
  1. Promise.all 返回的也是一个promise
  2. Promise.all 其中有一个error,则直接返回reject
  3. Promise.all 是并发执行的
  4. Promise.then(value)
  • 如果value是promise, 则返回promise
  • 如果value是thenable,则在then()函数中,value准备好后,回掉两个callback
  • 否则,使用一个fulfilled状态的value作为promise返回
function PromiseAll(arr) {
return new Promise((resolve, reject) => {
const result = [];
for (let i = 0; i < arr.length; i++) {
Promise.resolve(arr[i])
.then(res => {
result.push(res);
if (i === arr.length - 1) {
return resolve(result);
}
}).catch(err => reject(err))
}
});
};

function promise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
}

function promise2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('p2');
}, 1000);
});
}

const p1 = promise();
const p2 = promise2();
const test = [1, p1, p2];
PromiseAll(test).then(res => console.log('res', res)).catch(err => console.log('kkk', err));

参考链接:

MDN

面试官让我手写Promise.all

· 3 min read

在正式环境,前端代码一般做了压缩和混淆,debug比较困难,但是如果有sourceMap则更便于查找bug。

Proposed Format

{
"version" : 3,
"file": "out.js",
"sourceRoot": "",
"sources": ["foo.js", "bar.js"],
"sourcesContent": [null, null],
"names": ["src", "maps", "are", "fun"],
"mappings": "A,AAAB;;ABCDE;"
}
  • Line 1: The entire file is a single JSON object
  • Line 2: File version (always the first entry in the object) and must be a positive integer.
  • Line 3: An optional name of the generated code that this source map is associated with.
  • Line 4: An optional source root, useful for relocating source files on a server or removing repeated values in the “sources” entry. This value is prepended to the individual entries in the “source” field.
  • Line 5: A list of original sources used by the “mappings” entry.
  • Line 6: An optional list of source content, useful when the “source” can’t be hosted. The contents are listed in the same order as the sources in line 5. “null” may be used if some original sources should be retrieved by name.
  • Line 7: A list of symbol names used by the “mappings” entry.
  • Line 8: A string with the encoded mapping data.

The “mappings” data is broken down as follows:

  • each group representing a line in the generated file is separated by a ”;”
  • each segment is separated by a “,”
  • each segment is made up of 1,4 or 5 variable length fields.

mappings 内容表示 ";"用来区分构建后的文件中的每一行 ","用来区分构建后的文件中的每一个部分 每个部分由1,4,5个可变长度的字段构成

例如:"mappings": "AAAAA,BBBBB;CCCCC;"

AAAAA 每一位都是使用VLQ编码 从左到右分别表示:

  • 第一位,表示这个位置在(转换后的代码的)的第几列。

  • 第二位,表示这个位置属于sources属性中的哪一个文件。

  • 第三位,表示这个位置属于转换前代码的第几行。

  • 第四位,表示这个位置属于转换前代码的第几列。

  • 第五位,表示这个位置属于names属性中的哪一个变量。

index.js original

  var name = 'ssss';
var age = 'test';
console.log(name, age);

index_bundle.js generated

  console.log("ssss","test");
//# sourceMappingURL=index_bundle.js.map

index_bundle.js.map

  {
"version": 3,
"file": "index_bundle.js",
"mappings": "AAGAA,QAAQC,IAHG,OACD",
"sources": [
"webpack://webpack-test/./src/index.js"
],
"sourcesContent": [
"var name = 'ssss';\nvar age = 'test';\n\nconsole.log(name, age);"
],
"names": [
"console",
"log"
],
"sourceRoot": ""
}

利用在线网站转换source-map-visualization

"mappings": "AAGAA,QAAQC,IAHG,OACD" 转换成了

0->4:0 8->4:8 12->1:11 19->2:10

· 2 min read

问题:

  const TooltipWrap = (props) => {
const { value } = props;
if (value === 1) {
return <Tooltip type="type1" />;
}
return <Tooltip type="type2" />;
};

上述代码在入参value变化时,Tooltip是否会重新初始化? 在实际运行中会发现Tooltip只会被初始化一次 根据react的VirtualDom Diff算法

ReactElement.createElement = function(type, config, children) {
var propName;
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;

for (propName in config) {
if (hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
// ...
}
props.children = childArray;
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
// ...
}
return ReactElement(
type, // 组件type
key, // 组件key
ref,
self,
source,
ReactCurrentOwner.current,
props
);
};

virtualDom diff算法 针对key和type判断是否能复用组件的逻辑

  1. key不同,重新初始化组件
  2. key相同,type不同,重新初始化组件
  3. key相同,type相同,组件可以复用

· One min read

template.md

---
title: Title
date: 2018-06-06 11:32:22
tags: []
---

# Title
#!/usr/bin/env node
const { program } = require('commander');
const fs = require('node:fs/promises');
const matter = require('gray-matter');
const moment = require('moment');
const { stringify } = require("yaml");

async function createFile() {
const filename = 'file.md';
let fh = await fs.open(filename, 'a');
const { data: frontMatter, content } = matter.read('./template.md');
const now = moment().format("YYYY-MM-DD HH:mm:ss");
frontMatter.date = now;
const newContent = `---\n${stringify(frontMatter)}---\n${content}`;
fh.write(newContent);
await fh.close();
}

program
.option('--first')
.action((commandAndOptions) => {
createFile();
if (commandAndOptions.debug) {
console.error(`Called ${commandAndOptions.name()}`);
};
});

program.parse();

const options = program.opts();
const limit = options.first ? 1 : undefined;

参考链接

· 3 min read

数据加密 的基本过程,就是对原来为 明文 的文件或数据按 某种算法 进行处理,使其成为 不可读 的一段代码,通常称为 “密文”。通过这样的途径,来达到 保护数据 不被 非法人窃取、阅读的目的。

对称加密和非对称加密

对称加密: DES、3DES、AES 非对称加密: RSA、DSA 散列算法: SHA-1、MD5

MAC和HMAC的区别

  1. HMAC技术可是说是MAC技术的一种,HMAC使用两轮散列而MAC只使用一轮散列
  2. HMAC比MAC更安全。 使用哈希函数,更改消息(不知密钥)并获得另一个有效的MAC相对容易,我们称此为长度扩展攻击。 但没有针对当前HMAC规范的已知扩展攻击

AES/DES/3DES算法

AES、DES、3DES 都是 对称 的 块加密算法,加解密 的过程是 可逆的。常用的有 AES128、AES192、AES256

RSA算法

RSA 加密算法是目前最有影响力的 公钥加密算法,并且被普遍认为是目前 最优秀的公钥方案 之一。RSA 是第一个能同时用于 加密 和 数字签名 的算法,它能够 抵抗 到目前为止已知的 所有密码攻击,已被 ISO 推荐为公钥数据加密标准。

RSA加密原理

使用M和C分别表示明文和密文,则RSA加密、解密过程如下:

C=MemodnC = M^e mod \, n

M=CemodnM = C^e mod \, n

由于公钥公开,即e、n公开。

  因此破解RSA私钥,即为已知e、n情况下求d。

  因ed mod (p-1)(q-1) = 1,且n=pq,因此该问题演变为:对n质因数分解求p、q。

  目前已被证明,已知e、n求d和对n质因数分解求p、q两者是等价的。实际中n长度为2048位以上,而当n>200位时分解n是非常困难的,因此RSA算法目前仍被认为是安全实用的。

· 2 min read

开发过程中发现 fun(opts...) 的用法,于是学习函数可变数量参数传参

package main

import (
"fmt"
)

type Foo struct {
Number int
Text string
Name string
}

func print(list ...int) {
for index, a := range list {
fmt.Printf("%d: %v \n", index, a) // print 0:1 1:3 2:4
}
}

func main() {
arr := []int{1, 3, 4}
print(arr...)
}

如果是复杂的多种类型入参

涉及知识点

  1. 数组对象类型定义
  2. interface用法
  3. []interface{} 和 []T{}类型的区别

notices: a variable with type []interface{} is not an interface! 注意: 变量类型[]interface{} 不是 一个interface

package main

import (
"fmt"
)

type Foo struct {
Number int
Text string
Name string
}

func print(list ...interface{}) {
for index, a := range list {
fmt.Printf("%d: %v \n", index, a)
}
}

func main() {
arr := []Foo{{Number: 1, Text: "pie"}, {Name: "ssss", Number: 15, Text: "fish"}, {Number: 30, Text: "orange"}}
print(arr...) // compiler error: cannot use arr (variable of type []Foo) as []interface{}
}

[]Foo 并不能当成[]interface{}类型, 原因是[]Foo长度不固定 []interface{}编译时并不知道参数数量

参考文章1

参考文章2

修改后

package main

import (
"fmt"
)

type Foo struct {
Number int
Text string
Name string
}

func print(list ...interface{}) {
for index, a := range list {
fmt.Printf("%d: %v \n", index, a)
}
}

func main() {
arr := []Foo{{Number: 1, Text: "pie"}, {Name: "ssss", Number: 15, Text: "fish"}, {Number: 30, Text: "orange"}}
var interfaceSlice []interface{} = make([]interface{}, len(arr)) // 增加[]interface{}类型变量,并确定长度
for i, d := range arr {
interfaceSlice[i] = d
}
print(interfaceSlice...)
}

· 4 min read
  一个task(宏任务) -- 队列中全部job(微任务) -- requestAnimationFrame -- 浏览器重排/重绘 -- requestIdleCallback

setTimeout

如果运行死循环函数调用setTimeout会阻塞页面运行吗?

  function loop() {
setTimeout(loop, 0);
}

loop();

答案是不会阻塞页面正常运行,因为主线程会不断创建宏任务放进宏任务队列里面,而主线程都只会执行一个宏任务,然后进入下一次循环

requestAnimationFrame

  button.addEventListener('click', () => {
box.style.display = 'none';
box.style.display = 'block';
box.style.display = 'none';
box.style.display = 'block';
box.style.display = 'none';
box.style.display = 'block';
box.style.display = 'none';
});

主线程会执行修改box的样式,但是渲染的时候只有最后 box.style.display = 'none' 会生效

  button.addEventListener('click', () => {
box.style.transform = 'translateX(1000px)';
box.style.transition = 'transform 1s ease-in-out';
box.style.transform = 'translateX(500px)';
})

浏览器渲染时会忽略初始值 translateX(1000px),动画会从0-500px执行过渡动画。

  button.addEventListener('click', () => {
box.style.transform = 'translateX(1000px)';
box.style.transition = 'transform 1s ease-in-out';

requestAnimationFrame(() => {
box.style.transform = 'translateX(500px)';
});
})

因为requestAnimationFrame会在浏览器渲染之前执行,所以动画还是会从0-500px 执行

  button.addEventListener('click', () => {
box.style.transform = 'translateX(1000px)';
box.style.transition = 'transform 1s ease-in-out';

requestAnimationFrame(() => {
requestAnimationFrame(() => {
box.style.transform = 'translateX(500px)';
})
});
})

只有在第二帧修改translate,动画才会从 1000px-500px执行

Animation callbacks

Animation callbacks队列会一次性执行完成,执行过程中新增的任务会放到下一帧去执行。

microtasks

  for (let i=0; i < 100; i++) {
const span = document.createElement('span');
document.body.appendChild(span);
span.textContent = 'Hello';
}

document.body.appendChild(span); 会向微任务队列提供100条消息 span.textContent = 'Hello'; 会向微任务队列提供100条消息 所以一共会有200条微任务被添加

微任务执行过程会不断运行,直到所有微任务清空。 如果微任务执行过程中,不断新增微任务,会不断执行微任务,直到清空微任务队列,会阻塞后面线程任务。

promise

  button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 1'));
console.log('Listener 1');
});

button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 2'));
console.log('Listener 2');
});

在页面上点击后 打印顺序是 Listener 1 => Microtask 1 => Listener 2 => Microtask 2

第一次 js stack 放入 Listener 1 执行输出 Listener 1 然后执行微任务 Microtask 1 第二次 js stack 放入 Listener 2 执行输出 Listener 2 然后执行微任务 Microtask 2

  button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 1'));
console.log('Listener 1');
});

button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 2'));
console.log('Listener 2');
});

button.click();

用js执行点击 在页面上点击后 打印顺序是 Listener 1 => Listener 2 => Microtask 1 => Microtask 2

这里会同步派发button的点击事件执行Listener 1 => Listener 2,接下来才会执行微任务

参考视频链接:Jake Archibald: 在循环 - JSConf.Asia

· One min read

问题:IOS 上1080p 高清视频无法播放

原因:转码码率太高 系统对手机内存调用有限制

解决办法:

  • 使用云服务的编码解码
  • 压缩并限制视频文件大小

· One min read

题目

const obj = { obj: { name: 123 }, target: [{a: 111}], };

使用字符串 'obj.target[0].a' 读取value

分析

题目的关键点是考察从字符串中读取关键词 可以使用正则匹配实现

const str = 'target[0].a';
const arr = str.split('.');
let params = [];
for (let i=0; i< arr.length; i++) {
const arrMatch = arr[i].match(/\[\d+\]/);
if (!!arrMatch) {
const numStr = arrMatch[0];
const index = arrMatch.index;
params.push(arrMatch.input.slice(0, index));
params.push(numStr.match(/\d+/)[0]);
} else {
params.push(arr[i]);
}
}

console.log(params);