0%

本质上,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle。

为了更好地理解模块打包工具背后的理念,以及在底层它们是如何运作的,在观看 Tobias Koppers 放在油管的视频后整理本文,后续将录制视频帮助理解,demo地址将在底部放出。

Demo介绍

视频1从开头介绍了整个项目的大体内容,位于 src 文件中:项目以 React 作为示范,引入一个异步懒加载组件(lazy component)展示到根组件中。

项目目录如下:

1
2
3
4
5
6
7
├── README.md
├── index.html
├── one-day-bundler // 按照打包流程顺序整理
├── react-bundle
├── webpack.config.js // 预打包 webpack 配置
├── src // 源文件
├── webpack.config.js // 主 webpack 配置
  1. webpack 配置

    文件位于 wepback.config.js ,主要内容是使用 babel-loader 解析 js 文件,然后配置了 3 个 alias 解析 reactreact-dom 以及 react-bundle

  2. 预打包 webpack 配置

    文件位于 react-bundler/webpack.config.js 用于将 reactreact-domcommonjs 规范打包到 react-bundler/dist/react-bundle.js 中,这个地址也是主 webpack 中最后的 alias 指定的地址。这一步预打包是为了更方便后续的演示。

阅读全文 »

前言

本篇起因源一个问题,Promise.all的限制并发数是多少?

这个可以通过 V8测试用例得知这个数字不超过 2097151

1
2
3
4
5
6
7
8
9
const a = new Array(2 ** 21 - 1);
const p = Promise.resolve(1);
for (let i = 0; i < a.length; ++i) a[i] = p;
testAsync(assert => {
assert.plan(1);
Promise.all(a).then(assert.unreachable, reason => {
assert.equals(true, reason instanceof RangeError);
});
});

平时在社区中,我们也偶尔会看到这样一道题:

请实现如下函数,可以批量请求数据,所有的 url 地址在 urls 参数中,同时可以通过 max 参数控制请求的并发数,当所有请求结束之后,需要执行 callback 回调函数,发送请求的函数可以直接使用 fetch 即可

这道题我们发现,发送请求的函数使用 fetch 可知这是在在浏览器环境中;浏览器其实对同一域名的并发数量有所限制,比如 Chrome 就限制了 6 个,所以这里的 max 我们可以认为是不超过 10 个。解决这类限制我们可以通过多域名的形式优化。

阅读全文 »

js的异步编程是我们无法避免的话题之一:我们会在平时的编码过程中用到它,也会在面试中遇到它(大雾)

今天就对js的异步发展进行简单的探索,并且顺着流程了解并实现一个符合规范的promise来帮助更好地理解js中异步编程的处理逻辑:

本文第一章节将花费6~8min阅读时间;
第二章如果不着手实现将花费10~15min理解时间;参与实现将花费1h :D
第三章将花费4~6min阅读时间;

行文仓促,如有错误还请指正。

阅读全文 »

在一次项目重构中,我将filter组件需要的InputSelect之类通过Connect包裹后,通过<MyFromItem />这种形式当作参数传入。

大致的数据结构如下,component可以传入自定义组件或者直接传入<Input />这种形式;

1
2
3
4
5
6
7
8
9
[
{
name: 'name',
label: '姓名',
component: <StaffFormItem />,
}
...
]

在本地跑的时候,并没有发现问题;然后打包之后就发现跑不通,报错n is not a function之类的错误;

打开sourcemap之后定位到这个n是rc-form中的getFieldDecorator函数,遂寻找这个值未传入的原因。

调试了多次(打包实在是太慢了……),终于发现是判断类型的时候发生了问题:

1
2
3
4
5
6
// 这里传入了<StaffFormItem />
const type = component.type.name;
swith(type) {
case 'Connect': // 判断受控组件
return //...
}

打包之后,这里的type会从Connect变为n, j这种混淆后的形式,万万没想到。

解决问题之后去搜索了一下,出来了3个概念:

  • Components

  • Component Instances

  • Elements

在其他UI框架中的,一般只有模板(Class)和实例(new Class())这样的概念;而在React中,Component InstancesElements不是同一种类型的东西,它们之间没有一对一的关系;

看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react';

class MyComponent extends Component {
constructor(props) {
super(props);
console.log('这时候是Component Instance', this);
}

render() {
const another_element = <div>Hello World!</div>;
console.log('这里是一个element', another_element);
return another_element;
}
}

console.log('这是一个Component', MyComponent);

const element = <MyComponent />;

console.log('这是一个element', element);

上述这段代码,可以得出:

  • MyComponent(这个class自身)是一个Component

  • element是一个对象,但是它不是MyComponent的一个实例。它仅仅描述了一个对象实例(Component Instance)被创建了,它是一个拥有keypropsreftype这些属性的一个对象。在这里,keyref的值为null,props是一个空对象,type即为MyComponent字符串。

  • 一个MyComponent的实例将会在element被render之后被创建(在上述代码中,实例在构造函数中打印)

  • anthor_element也是一个对象,它的keypropsref和上面所述的一样,但是它的typediv

具体可以看React Components, Elements, and Instances

总结一下,可以看出React官方对这几个概念的定义非常清楚:

  • An element is a plain object describing a component instance or DOM node and its desired properties.

  • A ReactElement is a light, stateless, immutable, virtual representation of a DOM Element

Component可以被用来创建一个Instance,当Instance被render之后就创建了一个Element

创建实例的过程不需要我们手动进行;
多个Element可以描述同一个实例(比如<Parent />组件的render()返回了<Child />,每次触发render时都会返回一个新的element,但是已经存在Child实例可能被复用);
一个Elmenet也可以用于描述多个实例(比如把一个element存储到一个变量中const a = <Child />,然后调用多次<div>{a} {a} {a}</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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 生成一个长度为10的数组
const testArr = Array.from({length: 10}).map(()=> Math.round(Math.random() * 10));

// 一个冒泡排序?(大雾) 会返回每次的排序的移动方式 和 排序完之后的数组
function bubble(testArr) {
const arr = testArr.slice();
const operationList = [];
for(let i = 0; i < testArr.length; i++) {
for(let j = i + 1; j < testArr.length; j++) {
if(arr[i] > arr[j]){
[arr[i], arr[j]] = [arr[j], arr[i]]
// 记录了每次移动的方式
operationList.push({
from: i,
to: j,
array: arr.slice(),
})
}
}
}
return operationList;
}

var container = document.getElementById('container');
// 放入fragment统一插入 减少dom操作
var collect = document.createDocumentFragment('div');
var spanList = [];
for(let i = 0; i < testArr.length; i++) {
var span = document.createElement('span');
span.className = 'square';
span.innerText = testArr[i];
span.style.left = i * 40 + 'px';
spanList[i] = span; // 保存对各个span的引用
collect.appendChild(span);
}

container.appendChild(collect);

for(let j = 0; j < result.length; j++) { // 定时任务
setTimeout(function() {
const fromIndex = result[j].from;
const toIndex = result[j].to;
const tmp = spanList[toIndex].style.left;

spanList[toIndex].style.left = spanList[fromIndex].style.left;
spanList[fromIndex].style.left = tmp;
[spanList[toIndex], spanList[fromIndex]] = [spanList[fromIndex], spanList[toIndex]]
}, 1000 * j)//0.5秒动画 1秒延迟
}

配合上css的transition属性,就可以做到简单的动画效果啦

演示地址:点击查看
// 打开控制台 数据显示更清晰

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
/* 
请使用JavaScript实现一个getIntersection函数,可获取多个区间的交集,规则如下:
区间用长度为2的数字数组表示,如[2, 5]表示区间2到5(包括2和5);区间不限定方向,如[5, 2]等同于[2, 5];
该方法可接受多个区间,并返回所有区间的交集(用区间表示),如空集用null表示。并能正确通过以下测试用例
*/
function getIntersection(...args) {
// your code here
const minArr = [];
const maxArr = [];
// 验证输入
const length = args.length;
for(let i = 0; i < length; i++) {
if(typeof args[i][0] !== 'number' || typeof args[i][1] !== 'number') return null;
}
// 取左边和右边
args.forEach(v => {
minArr.push(Math.min(...v));
maxArr.push(Math.max(...v));
});
const left = Math.max(...minArr);
const right = Math.min(...maxArr);
if(left <= right) return [left, right];
return null;
}

getIntersection([1, 4], [3, 5]); // [3, 4]
getIntersection([5, 2], [4, 9], [3, 6]); // [4, 5]
getIntersection([1, 7], [8, 9]); // null
getIntersection(['x', 7], [4, 9]); // null
getIntersection([1, 2]); // [1, 2]
getIntersection([1, 2], [2, 3]); // [2, 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
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
/*
请使用 JavaScript 编写一个树的深度优先遍历函数(节点最深的最先访问到,依次类推),满足以下测试用例:
假设树的结构如下
*/
const tree = [
{
id: 1,
name: '张三',
children: [
{
id: 2,
name: '李四',
children: [
{
id: 5,
name: '张五'
}
]
}
]
},
{
id: 6,
name: '玛丽'
}
]

// 测试用例:

// 1. 生成一颗新树,将所有节点的id,加1
console.log(treeMap(tree, node => {
let newNode = { ...node }
newNode.id = node.id + 1
return newNode
}))
// 打印的新树,应该与tree的结构一致,只是每个id自增1,老的tree,应该没有任何改动

// 2. 打印每个节点的id
treeMap(tree, node => {
console.log(node.id)
return node
});
// 应打印顺序应该是: 5,2,1,6

// 3. 对于非法输入,应直接返回第一个入参
console.log(treeMap(null)) // 输出null
console.log(treeMap(tree, true/*不是函数*/)) //输出tree


// 解答:
const deepClone = tree => {
let newTree = [];
tree.forEach(node => {
let newnode = {}
newnode.id = node.id;
newnode.name = node.name;
if(node.children) {
newnode.children = deepClone(node.children);
}
newTree.push(newnode);
})
return newTree;
}

const treeMap = (root, fn) => {
// 验证是否为数组
if(!Array.isArray(root)) {
return root;
}
// 验证是否是函数
if({}.toString.call(fn) !== '[object Function]') {
// 不是函数
fn = f => f;
}

const deepCloneTree = deepClone(root);
deepCloneTree.forEach((child, index, array) => {
if(child.children) {
dfs(child, fn);
}
array[index] = fn(child);
})
return deepCloneTree;
}

const dfs = (root, fn) => {
if(root.children){
root.children.forEach((child, index, array) => {
dfs(child, fn);
array[index] = fn(child);
})
}
}

前言

这本由程墨编写的《深入浅出React和Redux》是从我入门React之初,在翻阅官方文档之后开始看的,第一次看时一头雾水,仅为了了解React怎么用;

再翻一次开始看得懂性能优化和Redux这类状态应用管理的使用。

随着对React不断的深入,课余的拓展阅读,发现即使是17年年初出版,在React版本更新迅速、API不断变动的情况下,这本书依旧可以带来不一样的阅读体验。

本书的总共12章节,1-2章讲了React的核心理念,3-4章重点讲解状态管理的演化过程,第5章讲解性能优化,提出了虚拟DOM和调和过程,第6章提出了高阶组件(HOC),注重于抽象和组合,第7,9章立足于第3章引入的Redux,对副作用的处理引入了解决方案,第8章讲解了单元测试,第10章及之后,除去第11章对于路由的使用,其余的动画、服务器同构等因为暂未实践理解不到位就不予讨论了。

如果要对React一步一步分析下来,它可以讲解的知识点怕是可以出好几本动物书。但是React的重要理念或是思想是简单的,主要是以下两点:

  • 数据驱动的响应式编程思想,概况为UI = render(data)
  • 一切基于组件
阅读全文 »

# 类型

## 内置类型

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol
1
typeof null === "object": // true

历史遗留问题,用复合条件来检测null值的类型:

1
2
var a = null;
(!a && typeof a === "object"); // true

变量是没有类型的,只有值才有,对变量执行typeof操作时,得到的结果是该变量持有的值的类型

已在作用域中声明但还没有赋值的变量,是undefined的。相反,还没有在作用域中声明过的变量是undeclared的。

1
2
3
4
5
var a;

typeof a; // undefined

typeof b; // undefined (其实应当返回undeclared, typeof的安全防范机制)

typeof undeclared

1
2
3
4
5
6
7
8
9
10
11
12
// 这样会抛出错误
if(DEBUG) {
console.log("Debugging is staring");
}

// 这样是安全的
if(typeof DEBUG !== "undefined") {
console.log("Debugging is starting");
}

<!--more-->

在没有全局变量可用的时候,使用typeof的安全防范机制做检查;或是使用“依赖注入”(dependency injection)设计模式将参数显式地传递到函数中

1
2
3
4
5
function doSomethingCool(Feature) {
var helper = Feature || function () { /* .. default feature .. */ };
var val = helper();
// ..
}

小结

  • 变量没有类型,但它们持有的值有类型。类型定义了值的行为特征。
  • undeclared表示变量还没有被声明过
  • 访问undeclared变量时会报错”ReferenceError: a is not defined”,并且typeof因为安全防范机制,对于undeclared、undefined都返回undefined

数组

使用delete运算符可以将单元从数组中删除,但是单元删除后,数组的length属性不会发生变化。

创建“稀疏”数组时,要注意其中的空白单元”empty slot”

数组通过数字进行索引,又去的是他们也是对象,所以也可以包含字符串键值和属性,但是不计算在数组长度内。

1
2
3
4
5
6
7
8
var a = [];

a[0] = 1;
a["foobar"] = 2;

a.length; // 1
a["length"]; // 2
a.foobar; // 2

特别注意如果字符串键值能够被强制类型转换为十进制的数字,它会被当作数字索引来处理
所以建议使用对象来存放键值/属性值,用数组来存放数字索引值。

类数组

有时候需要将类数组(一组通过数组索引的值)转换为真正的数组,这一般是通过数组工具函数实现

工具函数slice(..)经常被用于这一转换

1
2
3
4
5
function foo() {
var arr = Array.prototype.slice.call( arguments ); // slice返回的是一个数组的副本
arr.push( "bam" );
console.log( arr );
}

用ES6中的内置工具函数Array.from(..)可以实现同样的功能

字符串

字符串和数组很相似,都是类数组,都有length属性以及indexOf(..)和concat(..)方法

1
2
var a = "foo";
var b = ["f", "o", "o"];

字符串不是字符数组,字符串是不可改变的,而数组是可以改变的。
1
2
3
4
5
6
a[1] = "0"; // 应当使用a.charAt(1) 获取
b[1] = "0";


a; // "foo
b; // ["f", "o", "o"]

字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。而数组的成员函数都是在其原始值上进行操作。

1
2
3
4
5
6
7
c = a.toUpperCase();
a === c; //false
a; // "foo"
c; // "FOO"

b.push( "!" );
b; // ["f", "o", "o", "!"]

许多数组函数用来处理字符串很方便,虽然字符串没有这些函数,但可以通过“借用”数组的非变更方法来处理字符串

1
2
3
4
5
6
7
8
9
10
a.join; // undefined
a.map; // undefined

var c = Array.prototype.join.call(a, "-");
var d = Array.prototype.map.call(a, function(v) {
return v.toUpperCase() + ".";
}).join( "" );

c; // "f-o-o"
d; // "F.O.O."

字符串反转

1
2
3
4
5
6
Array.prototype.reverse.call(a); // 返回值仍然是"foo"的一个封装对象

// other way

var c = a.split("").reverse().join("");
c; // oof

数字

.运算符是一个有效的数字字符,回被优先识别为数字常量的一部分,然后才是对象属性访问运算符。

1
2
3
4
5
42.toFixed(3); // SyntaxError

(42).toFixed(3); // "42.000"
0.42.toFixed(3); // "0.420"
42..toFixed(3); // "42.000"

0.1+0.2 != 0.3问题

最常见的方法是设置一个误差范围值,通常称为机器精度(machine epsilon)

对于JS的数字来说,这个值通常是2^-52

ES6中内置了这个数字Number.EPSILON,或者写成Math.pow(2, -52);

1
2
3
4
//polyfill
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2, -52);
}

于是我们可以解决0.1+0.2 != 0.3这个问题了
1
2
3
4
5
6
function numbersCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual(a, b); // true

整数的安全范围

数字的呈现方式导致整数的安全范围远小于Number.MAX_VALUE (1.7976931348623157e+308)
能够被安全呈现的最大整数是2^53 - 1(去除1符号位,11位指数,还有52位) Number.MAX_SAFE_INTEGER (9007199254740991)

整数检测

ES6 中 Number.isInteger(..)

1
2
3
Number.isInteger(42); // true
Number.isInteger(42.000); // true
Number.isInteger(42.3); // false

polyfill方法
1
2
3
4
5
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
}
}

32位有符号整数

虽然整数最大能够达到53位,但是有些数字操作(如数位操作)只适用于32位数字,所以在这些操作中数字的安全范围就回从

Math.pow(-2,31)~Math.pow(2,31)

-2147483648 ~ 2147483648

a | 0可以将变量a中的数值转换为32位有符号整数,因为数位操作符|只适用于32位整数。因此与0进行操作可以截取a中的32位数位。

某些特殊的值并不是32位安全范围的,比如NaN和Infinity

特殊数值

undefined

在非严格模式下,可以为全局标识符undefined赋值,undefined是一个内部标识符,它的值为undefined(可以被改变)

通过void运算符可以获得该值

void并不改变表达式的结果,只是让表达式不返回值。

一些应用

1
2
3
4
5
function doSomething() {
if (!APP.ready) {
return void setTimeout( doSomething, 100); // 不返回timeId而是返回undefined
}
}

一般会分开操作,效果都一样。
1
2
3
4
5
6
function doSomething() {
if (!APP.ready) {
setTimeout( doSomething, 100); // 不返回timeId而是返回undefined
return;
}
}

未完待续…