# 3.28

# TS 枚举

  • ts 枚举写了注释后,在文件外面使用的地方也会有提示。(必须写在前面)

# Jsonp 原理

  • 主要就是利用了 script 标签的src没有跨域限制来完成的。
<script type='text/javascript'>
    window.jsonpCallback = function (res) {
        console.log(res)
    }
</script>
<script src='http://localhost:8080/jsonp?id=1&cb=jsonpCallback' type='text/javascript'></script>
1
2
3
4
5
6
  • 注意的点就是回调函数的名称需要前后端确认好,如这里的 cb
const Koa = require('koa');
const app = new Koa();
const items = [
	{ id: 1, title: 'title1' },
	{ id: 2, title: 'title2' },
];

app.use(async (ctx, next) => {
	if (ctx.path === '/api/jsonp') {
		const { cb, id } = ctx.query;
		const title = items.find((item) => item.id == id)['title'];
		ctx.body = `${cb}(${JSON.stringify({ title })})`;
		return;
	}
});
app.listen(8080, () => {
	console.log('listen 8080...');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

参考链接 (opens new window)

# 4.2

# JavaScript 里面的数组下标一定是数字吗?

  • 不一定,数组也是一个对象,可以通过 [].key 的形式来赋值。

# 4.7

# react 中的 setState() 函数的第二个参数

- setState的第二个回调函数会在更新state,重新触发render后执行。

# React 中的高阶组件(HOC)与 Render Props 的概念与对比。

Render Props 更灵活。

# 4.9

# 大图片的线性加载和渐进式加载

  • 线性加载其实就是在我们浏览网页时常看见的那种 —— 网速足够慢或者图片尺寸过大时,可以看到图片的加载方式由上至下,一点一点的加载出来。
  • 渐进式 则如同名字一般, 它会先显示低分辨率的近似图像,再逐步的增加图片分辨率(模糊到清晰)。

# 4.12

# webpack 热更新原理

  • img 如上图所示,右侧 Server 端是 webpack-dev-server 启动本地服务,内部实现其实是用 express 起的服务。

  • 1、服务端和客户端使用的是 websocket 实现长连接。

  • 2、webpack 在本地开发用 webpack-dev-server 起服务同时开启了热更新之后, webpack 会监听源文件的更改。 - 当你保存文件时,webpack 会重新打包编译一次代码同时生成一个 hash 值、改动模块的 json 文件、改动模块的 js文件。 - 编译完成后通过 websocket 把当前编译后文件的 hash 值发给客户端。

  • 3、客户端收到推送过来的 hash 值,会和上次的 hash 值比较 - 一样的话会读取缓存 - 不一样则通过 ajaxjsonp 从服务器端获取最新的文件

  • 4、最后通过 内存文件系统 去替换有修改的内容,实现局部刷新。

参考——https://juejin.cn/post/6844903933157048333#heading-32

# 4.13

# React JSX 怎么变成真实 DOM 的

  • 首先我们在 react 中写的 jsx/tsx 会通过 babel 被编译为 React.createElement()

Drawing 0.png

  • React.createElement() 到底做了什么?

    • 这个函数的入参如下:

      export function createElement(type, config, children)
      
      1
    • React.createElement() 有三个参数,这三个参数包括了创建一个元素所要知道的所有信息。

    • type:用来标识节点类型,可以是一些原生的 HTML 标签,也可以是 React 中的组件。

    • config:这个参数是以对象形式传入的,里面存放的是节点所有的属性。(key、className 等等)。

    • children 以对象形式传入,它记录的是组件标签之间嵌套的内容。(以 createElement() 的形式)。

    比如下 DOM 结构:

    <ul className="list">
    	<li key="1">1</li>
    	<li key="2">2</li>
    </ul>
    
    1
    2
    3
    4

    对应的是如下形式的 createElement() 调用:

    React.createElement(
    	'ul',
    	{
    		// 传入属性键值对
    		className: 'list',
    		// 从第三个入参开始往后,传入的参数都是 children
    	},
    	React.createElement(
    		'li',
    		{
    			key: '1',
    		},
    		'1'
    	),
    	React.createElement(
    		'li',
    		{
    			key: '2',
    		},
    		'2'
    	)
    );
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    • createElement 函数在对传进来的参数进行一顿处理之后,最后 return 了一个ReactElement 函数,并传入处理过后的参数。
  • 其实在 React.createElement 中没有十分复杂的涉及算法或真实 DOM 的逻辑,它的每一个步骤几乎都是在格式化数据。说得更直白点,createElement 就像是开发者和 ReactElement 调用之间的一个“转换器”、一个数据处理层。它可以从开发者处接受相对简单的参数,然后将这些参数按照 ReactElement 的预期做一层格式化,最终通过调用 ReactElement 来实现元素的创建。

    Drawing 5.png

# typescript 中的枚举 enum 类型其实是数字 number 的子类型

  • number 类型可以理解为所有的数字,而枚举类型 enum 可以理解为有限个的数字。

# 4.14

# 关于 React 中的合成事件的事件池

  • 在 React 17 之前,合成事件对象 会被放进一个叫 事件池 的地方统一管理。
  • 这样做的目的是能够实现 事件对象 的复用,进而提高性能。
  • 但是这样处理的话,每当事件处理函数执行完毕之后,对应的合成事件对象就会被"格式化",为下一次复用做准备,这就意味着我们在事件处理函数执行完毕之后就拿不到事件对象了。如下这个例子(官方提供)
function handleChange(e) {
	// This won't work because the event object gets reused.
	setTimeout(() => {
		console.log(e.target.value); // Too late!
	}, 100);
}
1
2
3
4
5
6
  • 如果你在一个 DOM 元素上绑定上面这个事件处理函数,触发之后控制台会提示如下信息:
Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property `target` on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use `event.persist()`. See https://fb.me/react-event-pooling for more information.
1
  • 根据这个提示信息能够看到解决方法:要想拿到目标事件对象,必须显式地告诉 React——我永远需要它,也就是调用 e.persist() 函数,像下面这样:
function handleChange(e) {
	// This won't work because the event object gets reused.
	event.persist();
	setTimeout(() => {
		console.log(e.target.value); // Too late!
	}, 100);
}
1
2
3
4
5
6
7
  • React 17 拥抱了新时代的潮流,重新在研发体验和向下兼容性能之间做了选择,这一次,它选择了前者——放弃事件池,为每一个合成事件创建新的对象。因此在 React 17 中,我们不需要 e.persist(),也可以随时随地访问我们想要的事件对象。

# 4.15

# ES6 字符串新增方法

# includes()、startsWith()、 endsWith()

传统上,JavaScript 只有indexOf方法,可以用来**确定一个字符串是否包含在另一个字符串中**。ES6 又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!';

s.startsWith('Hello'); // true
s.endsWith('!'); // true
s.includes('o'); // true
1
2
3
4
5
  • 这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';

s.startsWith('world', 6); // true
s.endsWith('Hello', 5); // true
s.includes('Hello', 6); // false
1
2
3
4
5
  • 上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

# repeat()

  • repeat方法返回一个新字符串,表示将原字符串重复n次。
  • 参数如果是小数,会被取整。
  • 如果参数是负数或者Infinity则会报错。
  • 但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0repeat视同为 0。参数NaN等同于 0。如果repeat的参数是字符串,则会先转换成数字。
'jing'.repeat(3); // "jingjingjing"
'hao'.repeat(2); // "haohao"
'bao'.repeat(0); // ""

'hao'.repeat(2.9); // "haohao"

'haha'.repeat(Infinity);
// RangeError
'haha'.repeat(-1);
// RangeError
1
2
3
4
5
6
7
8
9
10

# padStart()、padEnd()

  • ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab'); // 'ababx'
'x'.padStart(4, 'ab'); // 'abax'

'x'.padEnd(5, 'ab'); // 'xabab'
'x'.padEnd(4, 'ab'); // 'xaba'
1
2
3
4
5
  • 上面代码中,padStart()padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
  • 如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
  • 如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
  • 如果省略第二个参数,默认使用空格补全长度。
  • padStart()的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
'1'.padStart(10, '0'); // "0000000001"
'12'.padStart(10, '0'); // "0000000012"
'123456'.padStart(10, '0'); // "0000123456"
1
2
3

# trimStart()、trimEnd()

ES2019 (opens new window) 对字符串实例新增了trimStart()trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

const s = '  abc  ';

s.trim(); // "abc"
s.trimStart(); // "abc  "
s.trimEnd(); // "  abc"
1
2
3
4
5
  • 上面代码中,trimStart()只消除头部的空格,保留尾部的空格。trimEnd()也是类似行为。
  • 除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
  • 浏览器还部署了额外的两个方法,trimLeft()trimStart()的别名,trimRight()trimEnd()的别名。

# matchAll()

  • matchAll()方法返回一个正则表达式在当前字符串的所有匹配。

# replaceAll

历史上,字符串的实例方法replace()只能替换第一个匹配。

'aabbcc'.replace('b', '_');
// 'aa_bcc'
1
2
  • 如果要替换所有的匹配,不得不使用正则表达式的g修饰符。
  • 正则表达式毕竟不是那么方便和直观,ES2021 (opens new window) 引入了replaceAll()方法,可以一次性替换所有匹配。
'aabbcc'.replaceAll('b', '_');
// 'aa__cc'
1
2

详见阮一峰 ES6 (opens new window)

# 4.27

# 对象中的 key

  • 任何对象作为对象的 key 的时候,都会被转成 '[object Object]'
  • 如果是函数作为对象的 key 的时候,会把整个函数字符串化作为 key

看下面代码:

var a = {};
var b = { jing: 'hao' };
var c = { test: 'jing' };
function jing() {
	var jing = 999;
}
a[b] = 'b';
a[c] = 'c';
a[jing] = 'jing';
console.log(a);

// 输出结果如下:
{
  '[object Object]': 'c',
  'function jing() {\n\tvar jing = 999;\n}': 'jing'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Symbol 类型作为 key

再看下面这段代码

var a = {};
var b = Symbol('123');
var c = Symbol('123');
a[b] = 'b';
a[c] = 'c';
console.log(a);
console.log(a[b]);
console.log(a[c]);
console.log(Object.keys(a));

// 对应输出结果分别如下:
// { [Symbol(123)]: 'b', [Symbol(123)]: 'c' }
// b
// c
// []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Symbol 定义的属性不会出现在下面循环中:

  • for in:可获取原型属性,不可获取不可枚举属性
  • for of:不可遍历对象,可遍历数组
  • Object.keys:原型属性和不可枚举属性都不能获取
  • Object.getOwnPropertyByNames:不可获取原型属性,可获取不可枚举属性
  • JSON.stringify:原型属性和不可枚举属性都不能获取
  • Reflect.ownKeys:可获取不可枚举和 Symbol,不可获取原型

所以上面 Object.keys(a) 的结果是 []