小王子星球

道虽迩,不行不至,
事虽小,不为不成。

小王子星球
0%

create react app

create-react-app是 react 的官方项目脚手架,可以帮我们快速的搭建一个 react 项目。了解 vue 的同学应该知道,vue的官方脚手架是默认支持@src的绝对路径引用,可以优雅高效的引用其它模块,特别是跨目录,深嵌套的引用。而且vue把webpack的配置文件暴露出来,可以很方便的对webapck进行个性化的配置修改。相比之下,create react app生成的项目,默认还是用相对路径的引入,而且把webpack的配置深度集成到脚本里面,如果需要个性化的修改配置,只能通过react-scripts eject来导出wenpack配置,而且修改之后就只能自己维护了,一些我们不需要修改的react官方默认配置可能就会丢失。我们可以通过以下两个插件来优雅的修改create react app生成的项目配置,以修改导入路径alias为例说明。

react app rewired

react-app-rewired

  1. 安装插件
1
npm install react-app-rewired --save-dev
  1. 创建 config-overrides.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* config-overrides.js */

const path = require('path');

module.exports = {
webpack: function(config, env) {
config.resolve.alias['@'] = path.resolve(__dirname, 'src');
return config;
},
jest: function(config) {
const {
moduleNameMapper = {},
} = config;
config.moduleNameMapper = {
...moduleNameMapper,
'^@/(.*)$': '<rootDir>/src/$1',
};
return config;
}
}
  1. 修改 react-scripts 的默认npm脚本
1
2
3
4
5
6
7
8
/* package.json */

"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}

@craco/craco

@craco/craco

  1. 安装
1
npm i -D @craco/craco
  1. 创建 craco-config.js 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const path = require('path');

module.exports = {
webpack: {
configure: (webpackConfig) => {
webpackConfig.resolve.alias['@'] = path.resolve(__dirname, 'src');
return webpackConfig;
},
},
jest: {
configure: (jestConfig) => {
const {
moduleNameMapper = {},
} = jestConfig;
jestConfig.moduleNameMapper = {
...moduleNameMapper,
'^@/(.*)$': '<rootDir>/src/$1',
};
return jestConfig;
},
}
};

  1. 修改 react-scripts 的默认npm脚本
1
2
3
4
5
6
7
8
/* package.json */

"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
}

总结

我们可以通过 react-app-rewired@craco/craco 两个插件优雅的修改 create-react-app 生成的项目配置。两个插件的使用方法都差不多,也都可以实现我们的需求,至于使用哪一个见仁见智。我建议是用@craco/craco,比较新,react-app-rewired最后的更新已经是两年前了。

Javascript中函数运行时的this和上下文是一个重要的知识点,而且很多文章也做了详细的解析。本文不展开原理进行阐述,只总结结论。

this

  • function定义的函数,运行时的this由调用对象决定,即this指向obj.fn()中的obj,如果无具体的调用方,则为在非严格模式下为global对象,严格模式下为undefined。可通bindcallapply来绑定运行时的this;

  • () => {}定义的箭头函数,无内部的this,由定义时的外部this决定,不可用bindcallapply来绑定运行时的this;

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
function testFn() {
console.log(this);
}

const testNarrowFn = () => {
console.log(this);
}

const a = { name: 'a' };

const b = { name: 'b' };

a.testFn = testFn;

b.testFn = a.testFn;
b.testNarrowFn = testNarrowFn;

a.testNarrowFn = b.testNarrowFn;

console.log(this); // window(web)/undefined(node)

testFn(); // window(web)/undefined(node)

testNarrowFn(); // window(web)/undefined(node)

a.testFn(); // {name: 'a', testFn: ƒ, testNarrowFn: ƒ}

b.testFn(); // {name: 'b', testFn: ƒ, testNarrowFn: ƒ}

b.testNarrowFn(); // window(web)/undefined(node)

a.testNarrowFn(); // window(web)/undefined(node)

作用域

函数运行时的作用域由定义时所在的作用域决定,和调用的位置无关。Javascript中有三个作用域:全局作用域,块级作用域({}),函数作用域。函数运行的作用域层级为:自身作用域 –> 块级作用域(如有)–> 函数作用域(父级函数)–> 全局作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fnArr = [];

function outerFn() {
let outer = 0;
for (let i = 0; i < 3; i += 1) {
outer += 1;
function innerFun() {
console.log(i, outer);
}
fnArr[i] = innerFun;
}
}

outerFn();

fnArr[0](); // 0 3
fnArr[1](); // 1 3
fnArr[2](); // 2 3

参考

defineProperty

defineProperty是ES6之前一种定义对象属性的语法,具体语法为Object.defineProperty(obj, prop, descriptor)

  • 操作的粒度是对象的属性,在原有的对象上进行修改
  • 无法通过set监听数组的length变化,因为数组length属性的Configurable是false
  • vue2.0通过defineProperty进行对象的改造和监听,实现响应式:
    1. 需要遍历对象的属性,对比vue3.0的proxy效率和性能低;
    2. 无法监听数组的length属性和具体的某个index值变化,需要改造原生的数组方法;
    3. 无法动态的添加响应式的属性,需要用vue提供的set方法;
    4. 无法监听子对象的属性,需要深度遍历。

Proxy

Proxy是ES6新增的,用于代理一个对象属性和操作的语法,具体语法为new Proxy(target, handler)

  • 操作的粒度是整个对象,无需修改原有的对象属性
  • 可以更方便,高效的拦截到对象的操作,包括数组的length属性和某个index的变化

联系我

问题描述

pnpm安装create-react-app创建的typescript模板应用依赖,执行pnpm run start会出现错误Property 'toBeInTheDocument' does not exist on type 'Matchers<HTMLElement>'

1
2
3
4
5
pnpm dlx create-react-app --template typescript

pnpm i

pnpm run start

问题原因

问题的原因是pnpm V7以上的版本默认不提升@types的依赖,导致typescript编译时在node_modules/@types目录下找不到testing-library/jest-dom的类型定义,因此报错。参考链接:

问题解决

修改.npmrc配置,把types的依赖提升至根目录,然后重新安装依赖

1
2
## ~/.npmrc
public-hoist-pattern[]=*types*

参考:public-hoist-pattern

联系我

  • Largest Contentful Paint (LCP) :最大内容绘制,测量加载性能。为了提供良好的用户体验,LCP 应在页面首次开始加载后的2.5 秒内发生。
  • First Input Delay (FID) :首次输入延迟,测量交互性。为了提供良好的用户体验,页面的 FID 应为100 毫秒或更短。
  • Cumulative Layout Shift (CLS) :累积布局偏移,测量视觉稳定性。为了提供良好的用户体验,页面的 CLS 应保持在 0.1. 或更少。

参考

联系我

Promise的定义

Promise是ES6提供的一个用于解决异步回调地狱的语法,标准的定义如下:

  • 有三种状态:pending,fulfilled,rejected;
  • 构造函数传入一个执行函数,该函数有两个参数,resolve,成功的回调,reject,失败的回调;
  • 包含一个then函数,支持链式调用;
  • 包含一个catch函数,用户捕获promise的异常

具体实现

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
const stateMap = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected',
}

class MyPromise {
constructor(executor) {
this.id = id;
this.state = stateMap.PENDING;
this.value = undefined;
this.reason = undefined;
this.fulfillCallbackList = [];
this.rejectCallbackList = [];

const resolve = (value) => {
if (this.state !== stateMap.PENDING) return;
this.state = stateMap.FULFILLED;
this.value = value;
this.fulfillCallbackList.forEach((callback) => callback(this.value));
}
const reject = (reason) => {
if (this.state !== stateMap.PENDING) return;
this.state = stateMap.REJECTED;
this.reason = reason;
this.rejectCallbackList.forEach((callback) => callback(this.reason));
}

try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}

then(onFulFill, onReject) {
const thenPromise = new MyPromise((resolve, reject) => {
if (this.state === stateMap.FULFILLED) {
try {
const value = onFulFill(this.value);
resolve(value);
} catch (error) {
reject(error);
}
} else if (this.state === stateMap.REJECTED) {
try {
const reason = onReject(this.reason);
reject(reason);
} catch (error) {
reject(error);
}
} else {
this.fulfillCallbackList.push(() => {
try {
const value = onFulFill(this.value);
resolve(value);
} catch (error) {
reject(error);
}
});

this.rejectCallbackList.push(() => {
try {
const reason = onReject(this.reason);
resolve(reason);
} catch (error) {
reject(error);
}
})
}
});

return thenPromise;
}

catch(rejectCallback) {
return this.then(null, rejectCallback);
}
}

function test(PromiseType = Promise) {
const promise = new PromiseType((resolve, reject) => {
setTimeout(() => {
const num = Math.random() * 10;
if (num < 5) {
resolve(num);
} else {
reject('num is not less than 5');
}
}, 3000);
});

promise.then((value) => {
console.log('fulfill-1', value);
value = `then1-${value}`;
},(reason) => {
console.log('reject-1', reason);
reason = `then1-${reason}`;
return reason;
}).then((value)=> {
console.log('fulfill-2', value);
value = `then2-${value}`;
}, (reason) => {
console.log('reject-2', reason);
reason = `then2-${reason}`;
}).then((value) => {
console.log('promise', promise, value);
});
}

test(Promise);

test(MyPromise);

联系我

一、开启允许任何源

打开终端【启动台】-> 【其它】-> 【终端】,输入:

1
sudo spctl  --master-disable

然后回车,继续输入密码,然后回车。

二、发现还是显示“已损坏,无法打开。 您应该将它移到废纸篓”

在终端输入以下命令,其中具体的软件路径替换成你想要打开的软件路径,按回车输入秘密即可。

1
sudo xattr -r -d com.apple.quarantine /Applications/xxxx.app

联系我

markdown

markdown是一种方便快捷的基于标签的文本格式化语法,类似于HTML超文本标志语言。事实上,很多markdown的解析器就是把markdown转换成html来显示的,特别是web端的markdown编辑器,基本上主流的开源库比如marked都是这样实现的。下面我们自己来实现一个简单的markdown到html的解析器。

markdown标签

首先,我们来看看markdown常用的标签及语义,对应的html标签,如下所示:

markdown 标签 语义 html标签
# ~ ###### 标题 h1 ~ h6
*text* 斜体 em
**text** 加粗 strong
`text` 高亮 a
[text](href) 链接 a

markdown转html

实现思路:

  1. 写markdown标签的正则表达式和对应的html模板规则映射表
  2. 循环规则的正则进行正则匹配的字符串替换

实现代码如下:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
const rules = [
// header rules
[/#{6}\s?([^\n]+)/g, "<h6>$1</h6>"],
[/#{5}\s?([^\n]+)/g, "<h5>$1</h5>"],
[/#{4}\s?([^\n]+)/g, "<h4>$1</h4>"],
[/#{3}\s?([^\n]+)/g, "<h3>$1</h3>"],
[/#{2}\s?([^\n]+)/g, "<h2>$1</h2>"],
[/#{1}\s?([^\n]+)/g, "<h1>$1</h1>"],

//bold, italics
[/\*\*\s?([^\n]+)\*\*/g, "<strong>$1</strong>"],
[/\*\s?([^\n]+)\*/g, "<em>$1</em>"],

//links
[
/\[([^\]]+)\]\(([^)]+)\)/g,
'<a href="$2">$1</a>',
],

//highlights
[
/(`)(\s?[^\n,]+\s?)(`)/g,
'<a style="background-color:grey;color:black;text-decoration: none;border-radius: 3px;padding:0 2px;">$2</a>',
],
]

function markdown2html(markdown = '') {
let html = markdown;
try {
rules.forEach(([rule, template]) => {
html = html.replace(rule, template);
});
} catch (error) {
console.error('转换失败:', error);
}
return html;
}

console.log(markdown2html(`
# heading1
## heading2
### heading3
#### heading4
##### heading5
###### heading6

**strong**
*em*

[百度](https://www.baidu.com)

\`highlight\`
`));

联系我

介绍

跨站脚本攻击(Cross-Site Scripting,简称 XSS)是一种常见的网络攻击方式,攻击者通过在网页中注入恶意脚本,从而获取用户的敏感信息或者控制用户的浏览器。XSS 攻击的危害和影响非常大,例如窃取用户的账号密码、篡改网页内容、发起 CSRF 攻击等,因此需要引起足够的重视。

在 2018 年,Facebook 曾经因为一个 XSS 漏洞而泄露了 5000 万用户的个人信息,这个事件引起了广泛的关注和讨论。这个事件也说明了 XSS 攻击的危害和影响有多么严重。

XSS 的类型

XSS 攻击可以分为三种类型:

  • 存储型 XSS,存储型 XSS 攻击是指攻击者将恶意脚本存储在服务器端,当用户访问包含恶意脚本的页面时,恶意脚本会被执行。
  • 反射型 XSS,反射型 XSS 攻击是指攻击者将恶意脚本作为参数传递给服务器,服务器将恶意脚本反射回用户的浏览器,从而执行恶意脚本。
  • 基于 DOM 的 XSS。基于 DOM 的 XSS 攻击是指攻击者利用浏览器的 DOM 解析机制,通过修改页面的 DOM 结构来执行恶意脚本。

XSS 的攻击方式

攻击者可以通过多种方式利用 XSS 攻击漏洞,例如在表单中注入恶意脚本、在 URL 中注入恶意脚本、在 Cookie 中注入恶意脚本等。攻击者还可以通过构造恶意脚本来实现各种攻击目的,例如窃取用户的敏感信息、篡改网页内容、发起 CSRF 攻击等。

预防 XSS 攻击

为了预防 XSS 攻击,我们可以采取以下措施:

  • 输入验证和过滤:对用户输入的数据进行验证和过滤,防止恶意脚本被注入到网页中。例如,可以使用正则表达式过滤掉 HTML 标签和 JavaScript 代码。
  • 输出编码和转义:对输出到网页中的数据进行编码和转义,防止恶意脚本被执行。例如,可以使用 HTML 实体编码和 JavaScript 转义。
  • 使用 Content Security Policy(CSP):CSP 是一种安全策略,可以限制网页中可以执行的脚本和资源,从而有效地防止 XSS 攻击。例如,可以设置只允许加载来自特定域名的脚本和资源。

实例分析

假设我们有一个简单的博客应用,用户可以在博客中发表文章和评论。我们来看一下如何防止 XSS 攻击。

输入验证和过滤

我们可以对用户输入的数据进行验证和过滤,例如过滤掉 HTML 标签和 JavaScript 代码。这样可以防止攻击者在评论中注入恶意脚本。

例如,我们可以使用 Node.js 的 escape-html 模块对用户输入的数据进行编码,将特殊字符转换为 HTML 实体。这样可以防止恶意脚本被执行。

1
2
3
4
const escapeHtml = require('escape-html');

// 过滤用户输入的评论内容
const comment = escapeHtml(req.body.comment);

输出编码和转义

我们可以对输出到网页中的数据进行编码和转义,例如使用 HTML 实体编码和 JavaScript 转义。这样可以防止恶意脚本被执行。

例如,我们可以使用 JavaScript 的 encodeURIComponent() 函数对用户输入的数据进行编码,将特殊字符转换为 URL 编码。这样可以防止恶意脚本被注入到 URL 中。

1
2
3
4
// 输出评论内容到网页中
var comment = document.createElement('div');
comment.innerHTML = encodeURIComponent(data.comment);
document.getElementById('comments').appendChild(comment);

Content Security Policy(CSP)

我们可以在网页中添加 CSP 头,限制网页中可以执行的脚本和资源。例如,我们可以设置只允许加载来自特定域名的脚本和资源。

例如,我们可以在网页的 HTTP 头中添加 CSP 头,限制只允许加载来自 example.com 域名的脚本和资源。

1
Content-Security-Policy: default-src 'self'; script-src 'self' example.com

这样可以有效地防止 XSS 攻击。

总结

XSS 攻击是一种常见的网络攻击方式,攻击者可以通过注入恶意脚本来获取用户的敏感信息或者控制用户的浏览器。为了预防 XSS 攻击,我们可以采取多种措施,例如输入验证和过滤、输出编码和转义、使用 Content Security Policy(CSP)等。在开发网页应用时,我们应该时刻关注 XSS 攻击的风险,并采取相应的安全措施。

如果你想深入了解 XSS 攻击和预防措施,可以参考以下资料:

innerHTML

innerHTML获取或设置元素的所有内部HTML内容,包括文本和HTML标签,可以理解为元素内部的HTML代码。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 隐藏部分文本 -->
<style>
.hidden {
display: none;
}
</style>

<!-- 测试的HTML内容 -->
<div id="parent">
this is <strong>parent</strong> text
<div id="child">
hello!
how are you? <span></span>

<br>
this is&nbsp;child text
<p class="hidden">this is hidden content by css from child</p>
</div>
</div>

<script>
const parentEl = document.getElementById('parent');
console.log(parentEl.innerHTML);
</script>

输出结果:

1
2
3
4
5
6
7
8
9
this is <strong>parent</strong> text
<div id="child">
hello!
how are you? <span></span>

<br>
this is&nbsp;child text
<p class="hidden">this is hidden content by css from child</p>
</div>

innerText

innerText获取或设置元素的可见文本内容,不包含HTML标签和隐藏不可见的文本内容。如下所示,用css隐藏及\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
<!-- 隐藏部分文本 -->
<style>
.hidden {
display: none;
}
</style>

<!-- 测试的HTML内容 -->
<div id="parent">
this is <strong>parent</strong> text
<div id="child">
hello!
how are you? <span></span>

<br>
this is&nbsp;child text
<p class="hidden">this is hidden content by css from child</p>
</div>
</div>

<script>
const parentEl = document.getElementById('parent');
console.log(parentEl.innerText);
</script>

输出结果:

1
2
3
this is parent text
hello! how are you?
this is child text

textContent

textContent获取或设置元素的所有文本内容,包括换行、空格、css隐藏的内容等,不包括HTML标签。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 隐藏部分文本 -->
<style>
.hidden {
display: none;
}
</style>

<!-- 测试的HTML内容 -->
<div id="parent">
this is <strong>parent</strong> text
<div id="child">
hello!
how are you? <span></span>

<br>
this is&nbsp;child text
<p class="hidden">this is hidden content by css from child</p>
</div>
</div>

<script>
const parentEl = document.getElementById('parent');
console.log(parentEl.textContent);
</script>

输出结果:

1
2
3
4
5
6
7
8
this is parent text

hello!
how are you?


this is child text
this is hidden content by css from child