调试npm包

通过软链接使用第三方包

进入本地npm包文件夹,或通过 git clone拉去的第三方包文件夹

执行 yarn linknpm link 连接到全局(yarn 不会污染全局)

在项目中使用 yarn link [第三方包]npm link [第三方包]

在项目中通过 yarn unlink [第三方包]npm unlink [第三方包] 解除链接

通过一下命令去掉全局安装的包

1
2
npm rm --global foo 
npm ls --global foo // 检查包是否被安装

webpack核心概念

安装

推荐就近安装,即安装在项目中,不要安装在全局中

通过 npx webpack -v 查看项目中 webpack 版本

nrm 镜像源管理

yarn add nrm

查看镜像源列表

nrm ls

测速

nrm test taobao

常用工具

clean-webpack-plugin

https://www.npmjs.com/package/clean-webpack-plugin

source-map

cheap-module-source-map 用于生产环境,不能暴露源码

eval-cheap-module-source-map 开发环境中使用

1
2
3
{
devtool:'cheap-module-source-map'
}

devServer 和热模块更新

安装devServer

1
yarn add -D webpack-dev-server

webpack.config.js 中添加配置项

contentBase 只有需要在访问静态文件时候使用,默认下面三个配置项都可以不写

1
2
3
4
5
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
}

package.json 中添加启动命令

1
2
3
4
5
{
"scripts":{
"server": "webpack-dev-server --open"
}
}

开启hmr

1.配置webpack-dev-server
2.devServer配置hot:true
3.plugins hotModuleeReplaceMentPlugin
4.js 文件中添加hmr代码

webpack.config.js 中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
const webpack = require('webpack')

module.exports = {
devServer: {
...
hot:true
},
plugins: [
...
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
]
}

index.js 增加代码

1
2
3
4
5
6
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
printMe();
})
}

entry

string : 所有的资源打包成一个chunk,输出一个bundle文件,默认的名称是main.js

array: 多入口,所有的文件也只会被打包成一个chunk,通常只在配置html的HMR时使用

object 多入口,有几个入口文件就可以形成几个chunk,输出几个bundle文件,文件的名称时对象中的key,每个key后面可以写一个数组,可以将数组中的文件打包成一个bundle,(参照dll的用法)

output

filename 输出的文件名称,可以指定目录和文件名 js/[name].js

publicpath 所有资源引入时候的公共路径,会拼在资源路径的前面作为基础路径, publicpath:/,img/a.png => /img/a.png, 注意:不是资源的输出路径

chunkFilename 非入口chunk的名称,通过动态import引入的文件名称通过id的形式命名,从0开始,依次递增,通常会使用webpackchunkname来重命名

library 会将chunk文件用一个变量接受,暴露给全局使用

libraryTarget 指明以那种方式引入,window把输入的变量添加到浏览器全局环境window[name]=xxx, global把输入的变量添加到node全局环境global[name]=xxx,commonjs以commonjs模块化规范引入,通常配合dll使用

与 devserver 中的 publicpath 区别

output 中的 publicpath

这是一个在使用按需加载和引入外部资源(图片,文件等)非常重要的属性,如果设置了一个错误的值,当加载这些资源时会报404错误

这个配置项指定了输出目录在浏览器中引用时的公共路径(publicpath),一个相对路径被解析为相对于HTML页面或<base>标签

标签为页面上的所有链接规定默认地址或默认目标。

相对服务器的路径,相对与协议的路径,或绝对路径都是有可能的甚至有时是必须的,换句话说,在CDN 托管静态资源

在运行时或loader处理时,每一个URL的前缀都会被色设置配置项中的值,这就是为什么在很多例子中这个配置项被设置为 / 的原因

webpack-dev-server 也需要从publicPath获取信息,使用它来确定从何处提供输出文件。

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
//...
output: {
// One of the below
publicPath: 'https://cdn.example.com/assets/', // CDN (always HTTPS)
publicPath: '//cdn.example.com/assets/', // CDN (same protocol)
publicPath: '/assets/', // server-relative
publicPath: 'assets/', // relative to HTML page
publicPath: '../assets/', // relative to HTML page
publicPath: '', // relative to HTML page (same directory)
}
};

devServer 中的 publicpath

打包的文件可以在浏览器的这个目录下面得到

如果服务跑在 http://localhost:8080,打包的文件为bundle.js,publicPath为 /, 可以在 http://localhost:8080/bundle.js访问到打包文件

如果 publicPath 改为 /assets/, 那么可以在 http://localhost:8080/assets/bundle.js访问,也可以把 publicPath 改为 http://localhost:8080/assets/

这说明了 devServer.publicPath 与 output.publicPath 是一致的

@babel/polyfill @babel/plugin-transform-runtime @babel/runtime-corejs2

@babel/preset-env 只会转换新语法,但是不会转换新的api,比如 Array.from

需要 @babel/polyfill 转换新的api,但是 @babel/polyfill 会全量引入,不能按需引入

可以通过 babel.rc 配置文件来实现

1
2
3
4
5
6
7
8
9
10
11
{
"presets": [
[
"@babel/preset-env",
// This option was removed in v7 by just making it the default.在新版本中已经移除,无需添加
// {
// "useBuiltIns": "usage"
// }
]
]
}

但是@babel/preset-env也存在问题,虽然会按需引入但是每个文件如果有重复的方法,都会被编译成相同的代码引入,文件多的时候会让冗余的代码越来越多

@babel/runtime-corejs2 是一个随着 @babel/plugin-transform-runtime 一起时使用的运行时依赖,会把重复的函数覆盖为 @babel/runtime-corejs2 中的引用

@babel/runtime-corejs2 仅仅是一个包含着函数的包,把函数以模块化的形式引入, 要安装到生产依赖中

.babelrc

1
2
3
4
5
6
7
8
9
10
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}

treeshaking

webpack4 production 默认开启,需要引入的库使用commonjs 模块化规范

如 loadsh-es

全局引入

provide-plugin

1
2
3
4
5
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]

多入口文件的每一个都会被引入jquery,所以需要提取公共代码

动态加载

@babel/plugin-syntax-dynamic-import

Dynamic Imports

需要指明webpackChunkName才能被单独打包

1
2
3
4
5
6
7
8
import(
/* webpackChunkName: "my-jquery" */
'jquery'
)
.then(({ default: $ }) => {
console.log($)
return $('body');
})

代码分割

SplitChunksPlugin 代替原来的 commonChunksPlugin

  • splitChunks.chunks

async表示只从异步加载得模块(动态加载import())里面进行拆分
initial表示只从入口模块进行拆分
all表示以上两者都包括

  • splitChunks.maxInitialRequests

每个入口的并发请求数, 如果拆出的包的个数大于maxInitialRequests,则不会把较小的包单独拆出

  • splitChunks.maxInitialRequests

动态引入的模块,最多拆分的数量

css分割

css-minimizer-webpack-plugin

压缩css代码

MiniCssExtractPlugin

可视化分析

webpack-bundle-analyzer

Ramda Object方法源码分析

keys

Safari 可能存在 argument.length 可枚举的bug

1
2
3
4
var hasArgsEnumBug = (function() {
'use strict';
return arguments.propertyIsEnumerable('length');
}());

IE9一下toString方法存在可以枚举的bug

1
2
3
4
5
6
// cover IE < 9 keys issues
var hasEnumBug = !({toString: null}).propertyIsEnumerable('toString');
var nonEnumerableProps = [
'constructor', 'valueOf', 'isPrototypeOf', 'toString',
'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'
];
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
// 如果有原生 Object.keys 实现且Safari不存在bug
var keys = typeof Object.keys === 'function' && !hasArgsEnumBug ?
_curry1(function keys(obj) {
// 参数错误处理
return Object(obj) !== obj ? [] : Object.keys(obj);
}) :
_curry1(function keys(obj) {
if (Object(obj) !== obj) {
return [];
}
var prop, nIdx;
var ks = [];
// 如果传入的对象时argument,而且有bug
var checkArgsLength = hasArgsEnumBug && _isArguments(obj);
// 循环对象每一项检查
for (prop in obj) {
if (_has(prop, obj) && (!checkArgsLength || prop !== 'length')) {
ks[ks.length] = prop;
}
}
// 如果有toString可枚举的bug
if (hasEnumBug) {
nIdx = nonEnumerableProps.length - 1;
// 排除不可枚举的属性
while (nIdx >= 0) {
prop = nonEnumerableProps[nIdx];
if (_has(prop, obj) && !contains(ks, prop)) {
ks[ks.length] = prop;
}
nIdx -= 1;
}
}
return ks;
});

Ramda 私有方法源码分析

_objectIs

Object.is 用于判断两值是否项等

1
2
3
4
5
6
7
8
9
10
11
12
function _objectIs(a, b) {
// MDN polyfill
if (a === b) {
// Steps 6.b-6.e: +0 != -0
return a !== 0 || 1 / a === 1 / b;
} else {
// Step 6.a: NaN == NaN
return a !== a && b !== b;
}
}
export default typeof Object.is === 'function' ? Object.is : _objectIs;

_arrayFromIterator

1
2
3
4
5
6
7
8
export default function _arrayFromIterator(iter) {
var list = [];
var next;
while (!(next = iter.next()).done) {
list.push(next.value);
}
return list;
}

_functionName

function.name 可能被压缩工具修改,不能依赖函数名来判断函数

作者通过正则方式判断

1
2
3
4
5
6
export default function _functionName(f) {
// String(x => x) evaluates to "x => x", so the pattern may not match.
var match = String(f).match(/^function (\w*)/);
return match == null ? '' : match[1];
}

_has

判断属性在对象的实例上,而不是在原型上

1
2
3
export default function _has(prop, obj) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}

等价于

1
Reflect.has(obj, prop)

_isArguments

实现了 callee 方法数组也可以当作是形式参数

1
2
3
4
5
6
var toString = Object.prototype.toString;
var _isArguments = (function() {
return toString.call(arguments) === '[object Arguments]' ?
function _isArguments(x) { return toString.call(x) === '[object Arguments]'; } :
function _isArguments(x) { return _has('callee', x); };
}());

_concat

通过双循环,涵盖了array-like类型,

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
/**
* Private `concat` function to merge two array-like objects.
*
* @private
* @param {Array|Arguments} [set1=[]] An array-like object.
* @param {Array|Arguments} [set2=[]] An array-like object.
* @return {Array} A new, merged array.
* @example
*
* _concat([4, 5, 6], [1, 2, 3]); //=> [4, 5, 6, 1, 2, 3]
*/
export default function _concat(set1, set2) {
set1 = set1 || [];
set2 = set2 || [];
var idx;
var len1 = set1.length;
var len2 = set2.length;
var result = [];

idx = 0;
while (idx < len1) {
result[result.length] = set1[idx];
idx += 1;
}
idx = 0;
while (idx < len2) {
result[result.length] = set2[idx];
idx += 1;
}
return result;
}

_dispatchable

dispatchable翻译为调度单元,这个方法用在了类似 R.all 遍历每一项的方法上

如果如参是一个数组会按照常规循环方式处理,如果不是数组但是对象携带了传入的方法s数组([all])其中的一个,将会调用对象携带的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default function _dispatchable(methodNames, transducerCreator, fn) {
return function() {
if (arguments.length === 0) {
return fn();
}
var obj = arguments[arguments.length - 1];
if (!_isArray(obj)) {
var idx = 0;
while (idx < methodNames.length) {
if (typeof obj[methodNames[idx]] === 'function') {
return obj[methodNames[idx]].apply(obj, Array.prototype.slice.call(arguments, 0, -1));
}
idx += 1;
}
if (_isTransformer(obj)) {
var transducer = transducerCreator.apply(null, Array.prototype.slice.call(arguments, 0, -1));
return transducer(obj);
}
}
return fn.apply(this, arguments);
};
}

_arrayFromIterator

1
2
3
4
5
6
7
8
export default function _arrayFromIterator(iter) {
var list = [];
var next;
while (!(next = iter.next()).done) {
list.push(next.value);
}
return list;
}

_equals

用在了涉及到比较的方法上,例如 R.equals

_objectIs

type

_arrayFromIterator

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

// 处理map set 集合

function _uniqContentEquals(aIterator, bIterator, stackA, stackB) {
var a = _arrayFromIterator(aIterator);
var b = _arrayFromIterator(bIterator);

function eq(_a, _b) {
return _equals(_a, _b, stackA.slice(), stackB.slice());
}

// if *a* array contains any element that is not included in *b*
return !_includesWith(function(b, aItem) {
return !_includesWith(eq, aItem, b);
}, b, a);
}

export default function _equals(a, b, stackA, stackB) {
// 引用相同,基本类型的值相同
if (_objectIs(a, b)) {
return true;
}

// 类型不同
var typeA = type(a);

if (typeA !== type(b)) {
return false;
}

// 引用不同的情况
// ??????
if (typeof a['fantasy-land/equals'] === 'function' || typeof b['fantasy-land/equals'] === 'function') {
return typeof a['fantasy-land/equals'] === 'function' && a['fantasy-land/equals'](b) &&
typeof b['fantasy-land/equals'] === 'function' && b['fantasy-land/equals'](a);
}

// 如果自身实现了equals方法
if (typeof a.equals === 'function' || typeof b.equals === 'function') {
return typeof a.equals === 'function' && a.equals(b) &&
typeof b.equals === 'function' && b.equals(a);
}

// 其他引用类型
switch (typeA) {
case 'Arguments':
case 'Array':
case 'Object':
// 引用类型如果为Promise实例,看作是项等的
if (typeof a.constructor === 'function' &&
_functionName(a.constructor) === 'Promise') {
return a === b;
}
break;
// 基本类型的装箱对象
case 'Boolean':
case 'Number':
case 'String':
if (!(typeof a === typeof b && _objectIs(a.valueOf(), b.valueOf()))) {
return false;
}
break;
case 'Date':
// 时间类型转为事件戳
if (!_objectIs(a.valueOf(), b.valueOf())) {
return false;
}
break;
case 'Error':
return a.name === b.name && a.message === b.message;
case 'RegExp':
// 正则类型的所有实例方法的实现相同
if (!(a.source === b.source &&
a.global === b.global &&
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline &&
a.sticky === b.sticky &&
a.unicode === b.unicode)) {
return false;
}
break;
}
// 处理map set集合
var idx = stackA.length - 1;
while (idx >= 0) {
if (stackA[idx] === a) {
return stackB[idx] === b;
}
idx -= 1;
}

switch (typeA) {
case 'Map':
if (a.size !== b.size) {
return false;
}

return _uniqContentEquals(a.entries(), b.entries(), stackA.concat([a]), stackB.concat([b]));
case 'Set':
if (a.size !== b.size) {
return false;
}

return _uniqContentEquals(a.values(), b.values(), stackA.concat([a]), stackB.concat([b]));
case 'Arguments':
case 'Array':
case 'Object':
case 'Boolean':
case 'Number':
case 'String':
case 'Date':
case 'Error':
case 'RegExp':
case 'Int8Array':
case 'Uint8Array':
case 'Uint8ClampedArray':
case 'Int16Array':
case 'Uint16Array':
case 'Int32Array':
case 'Uint32Array':
case 'Float32Array':
case 'Float64Array':
case 'ArrayBuffer':
break;
default:
// Values of other types are only equal if identical.
// 其他类型只有引用相同才算项等
return false;
}

// 处理数组或对象的每一个值是否项等
var keysA = keys(a);
if (keysA.length !== keys(b).length) {
return false;
}

var extendedStackA = stackA.concat([a]);
var extendedStackB = stackB.concat([b]);

idx = keysA.length - 1;
while (idx >= 0) {
var key = keysA[idx];
if (!(_has(key, b) && _equals(b[key], a[key], extendedStackA, extendedStackB))) {
return false;
}
idx -= 1;
}
return true;
}

Ramda中的柯理化

_curry1

内置接口,保证了参数的个数,无需判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Optimized internal one-arity curry function.
*
* @private
* @category Function
* @param {Function} fn The function to curry.
* @return {Function} The curried function.
*/

export default function _curry1(fn) {
return function f1(a) {
if (arguments.length === 0 || _isPlaceholder(a)) {
return f1;
} else {
return fn.apply(this, arguments);
}
};
}

_curry2

如果以值的真假为纬度分支会比较复杂,而且不能正确参数的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const _curry2 = function (fn) {
return function f2(a, b) {
return a !== undefined && b !== undefined
? _isPlaceholder(a) && _isPlaceholder(b)
? f2
: isPlaceholder(a)
? _curry1(function (_a) { return fn(_a, b) })
: _isPlaceholder(b)
? _curry1(function (_b) { return fn(a, _b) })
: fn.apply(this, arguments)
: a !== undefined
? _curry1(function (_b) { return fn(a, _b) })
: f2
}
}

作者以参数的个数为纬度考虑

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
/**
* Optimized internal two-arity curry function.
*
* @private
* @category Function
* @param {Function} fn The function to curry.
* @return {Function} The curried function.
*/
export default function _curry2(fn) {
return function f2(a, b) {
switch (arguments.length) {
case 0:
return f2;
case 1:
// 如果a传入的是undefined也是一个参数
return _isPlaceholder(a)
? f2
: _curry1(function(_b) { return fn(a, _b); });
default:
return _isPlaceholder(a) && _isPlaceholder(b)
? f2
: _isPlaceholder(a)
? _curry1(function(_a) { return fn(_a, b); })
: _isPlaceholder(b)
? _curry1(function(_b) { return fn(a, _b); })
: fn(a, b);
}
};
}

_curry3

同理增加分支判断

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
/**
* Optimized internal three-arity curry function.
*
* @private
* @category Function
* @param {Function} fn The function to curry.
* @return {Function} The curried function.
*/
export default function _curry3(fn) {
return function f3(a, b, c) {
switch (arguments.length) {
case 0:
return f3;
case 1:
return _isPlaceholder(a)
? f3
: _curry2(function(_b, _c) { return fn(a, _b, _c); });
case 2:
return _isPlaceholder(a) && _isPlaceholder(b)
? f3
: _isPlaceholder(a)
? _curry2(function(_a, _c) { return fn(_a, b, _c); })
: _isPlaceholder(b)
? _curry2(function(_b, _c) { return fn(a, _b, _c); })
: _curry1(function(_c) { return fn(a, b, _c); });
default:
return _isPlaceholder(a) && _isPlaceholder(b) && _isPlaceholder(c)
? f3
// 其中两个是占位符的情况
: _isPlaceholder(a) && _isPlaceholder(b)
? _curry2(function(_a, _b) { return fn(_a, _b, c); })
: _isPlaceholder(a) && _isPlaceholder(c)
? _curry2(function(_a, _c) { return fn(_a, b, _c); })
: _isPlaceholder(b) && _isPlaceholder(c)
? _curry2(function(_b, _c) { return fn(a, _b, _c); })
// 其中一个是占位符的情况
: _isPlaceholder(a)
? _curry1(function(_a) { return fn(_a, b, c); })
: _isPlaceholder(b)
? _curry1(function(_b) { return fn(a, _b, c); })
: _isPlaceholder(c)
? _curry1(function(_c) { return fn(a, b, _c); })
: fn(a, b, c);
}
};
}

curry

共有方法curry的实现, 对共有方法curryN的封装,在不确定参数个数时候通过 fn.length 获取参数长度

1
2
3
var curry = _curry1(function curry(fn) {
return curryN(fn.length, fn);
});

curryN

需要指定参数的个数, 抽象出参数个数的判断,最多个数为10个,防止参数过多造成栈溢出

_arity 对参数个数的处理

1
2
3
4
5
6
7
8
9
10
11
12
export default function _arity(n, fn) {
/* eslint-disable no-unused-vars */
switch (n) {
case 0: return function() { return fn.apply(this, arguments); };
case 1: return function(a0) { return fn.apply(this, arguments); };
case 2: return function(a0, a1) { return fn.apply(this, arguments); };
// ...
case 10: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { return fn.apply(this, arguments); };
default: throw new Error('First argument to _arity must be a non-negative integer no greater than ten');
}
}

1
2
3
4
5
6
var curryN = _curry2(function curryN(length, fn) {
if (length === 1) {
return _curry1(fn);
}
return _arity(length, _curryN(length, [], fn));
});

在参数较少时使用其他的内置方法

1
2
3
4
5
6
7
8
9
10
11
function curryN(length, fn) {
return _curry2(function (length, fn) {
return length === 1
? _curry1(fn)
: length === 2
? _curry2(fn)
: length === 3
? _curry3(fn)
: _arity(length, _curryN(length, [], fn))
})
}

_curryN 核心方法

下面是一段糟糕的代码

receive保持了源数组的引用,不符合函数式编成数据不可变的思想

每次添加新参数,都要查询可添加的位置

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
// 1. 真实参数个数和length项等 则执行函数
// 2. 实时记录占位符的位置, 新参数优先放到占位符
function _curryN(length, receive, fn) {
return function fN() {
var index = 0;
var zw = 0;
function findzw(zw, receive) {
while (zw < receive.length) {
if (_isPlaceholder(receive[zw])) {
//指向占位符的前一位, 方便后面处理
return zw - 1
}
zw++
}
return zw - 1
}
// 找到占位符的位置
zw = findzw(zw, receive);
while (index < arguments.length) {
receive[zw + 1] = arguments[index];
//更新之后重新查找占位符, 条过当前的占位符
zw = findzw(zw + 1, receive);
index++;
}
// 没有占位符 则位置+1为参数长度
if (zw + 1 === length) {
return fn.apply(this, receive)
} else {
// 计算剩余参数
var j = 0;
var len = 0
while (j < receive.length) {
if (!_isPlaceholder(receive[index])) {
len++;
}
j++
}
return _arity(length - len, _curryN(length, receive, fn))
}
}
}

源码的实现

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
function _curryN(length, receive, fn) {
return function () {
// 复制已接受参数的数组
var combined = [];
// 遍历入参的索引
var argsIdx = 0;
// 剩余参数
var left = length;
// 遍历已接受参数索引
var combinedIdx = 0;

// 如果参数没有复制完成,或新如参没有添加完成则继续执行
while (combinedIdx < received.length || argsIdx < arguments.length) {
var result;
// 通过条件语句处理了占位符的问题
// 如果老参数没有复制完成,且复制的值不是占位符,或者入参已经都处理了,直接取出当前的老参数,用于复制到新的数组中
// 通过双指针解决占位符的问题
if (combinedIdx < received.length &&
(!_isPlaceholder(received[combinedIdx]) ||
argsIdx >= arguments.length)) {
result = received[combinedIdx];
} else {
// 如果老参数都复制完,或者时占位符,或者还有新参数没有复制
// 取一个新参数加到数组中
result = arguments[argsIdx];
argsIdx += 1;
}
// 添加到新数组中
combined[combinedIdx] = result;
// 如果不是占位符剩余参数 -1
if (!_isPlaceholder(result)) {
left -= 1;
}
combinedIdx += 1;
}
// 如果没有剩余参数直接执行
return left <= 0
? fn.apply(this, combined)
// 否则通过_arity固定新函数的参数个数
: _arity(left, _curryN(length, combined, fn));
};
}

TypeScript 面向对象

定义类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class C {
constructor(n: string) {
this.name = n;// 构造函数,为定义的属性赋值
}
name: string; // 定义属性 省略前面的public关键字

readonly age:number = 18 // 只读属性

static readonly age: number = 1;

run():void{ // 定义方法
console.log('void')
}

}

接口

定义类的结构,声明包含的属性和方法,接口也可以当作类型声明去使用

接口可以在定义类的时候限制类的结构,接口中所有属性都不应该有实际的值

接口只定义对象的结构,接口中的所有方法都是抽象方法

同名结构声明的类型会合并,如果一个对象使用了该接口,必须包含所有的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
interface MyObj {
prop1: string,
prop2: number
}
interface MyObj {
prop3: Boolean
}

const obj: MyObj = {
prop1: '1',
prop2: 2,
prop3: true,
}

接口可以描述类的行为
定义类时可以让类去实现一个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface PersonInterface {
// 只读属性
readyonly name: string,
age: number,
say(method: string): boolean
}

class JiaZhen implements PersonInterface {
name: string = '1';
age: number = 1;
say(method: string): boolean {
return true;
}
}

使用接口描述一个类的实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface PersonInterface{
name:string,
new(name:string):Person
}

class Person{
constructor(name:string) {
}
}

function createPerson(Clsss:PersonInterface,name:string) {
return new Clsss(name);
}

createPerson(Person,'aaa');

属性的封装

类的属性如果可以随意被修改,可能导致运算时的错误

TS提供了三种属性的修饰符

public: 公有 在类,子类 类外面都可以访问

1
2
3
4
5
6
class Person {
constructor(pubilc name:string){
// 表示添加到实例上。可以通过属性访问
}
}
new Person().name

protected: 保护类型 在类里面,子类里面可以访问,在类外部没法访问

private: 私有 在类里面可以方法,子类,类外都不能访问

属性如果不加修饰符就是共有

泛型

定义函数或者类时遇到类型不确定时使用泛型,可以使用多个

1
2
3
4
5
6
7
function jiazhen<Z>(age: Z): Z {
return age;
}

jiazhen(19);

jiazhen<Number>(19) // 主动指明泛型

指明泛型Z必须是interface的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Zhen {
age: number,
love(who: string): boolean
}

function jiazhen<Z extends Zhen>(obj: Z): any {
return obj.love;
}

jiazhen<Zhen>({
age: 1,
love(a) {
return true;
}
})

指明类初始化时的泛型

1
2
3
4
5
6
7
8
class A<T>{
name: T;
constructor(a: T) {
this.name = a
}
}

new A<string>('10')

类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function enhancer(target:any){
target.prototype.name = '11';
}

interface Person{
name:string
}

@enhancer
class Person {
constructor(){

}
}

const p = new Person();
p.name;

属性装饰器

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

// 如果装饰的是普通的属性,target指向类的原型
// 如果是static属性,target指向类的定义
function upperCase(target:any,propname:string) {
let v = target[propname];
const getter = ()=>v;
const setter = (newValue:string)=>{
v = newValue.toUpperCase();
}
delete target[propname];
Object.defineProperty(target,propname,{
set:setter,
get:getter,
enumerable:true,
configurable:true
})
}

interface Person{
name:string
}

class Person {
@upperCase
name = 'girl'
constructor(){

}
}

const p = new Person();
console.log(p.name);
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
function unEnumber(enumConfig:boolean) {
// 属性描述器
return function unEnumber(target:any,propname:string,propertyDescriptor:PropertyDescriptor) {
propertyDescriptor.enumerable = enumConfig;
}
}

interface Person{
name:string
}

class Person {
name = 'girl'
constructor(){}

// getname 不能枚举
@unEnumber(false)
getName(){}
}

const p = new Person();
console.log(p.name);

for(let key in p){
console.log(key)
}

可以对方法的逻辑进行包装

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
function trans(target:any,propname:string,propertyDescriptor:PropertyDescriptor) {
const oldValue = propertyDescriptor.value;
console.log("1------------")
propertyDescriptor.value = function getNumber(...args:any[]) {
args = args.map(item=>Number(item));
console.log("2------------")
return oldValue.apply(this,args);
}
}

interface Person{
name:string
}

class Person {
name = 'girl'
constructor(){}

@trans
getNumber(...args:any[]){
console.log("3------------")
return args.reduce((sum,item)=>{ sum+=item; return sum },0);
}

}

const p = new Person();

console.log(p.getNumber('1',2,'3'));

参数修饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//              类的原型    方法名             被修饰参数的索引
function dubble(target:any,methodName:string,index:number) {
target.num = 'num'
}

class Person {
getNumber(@dubble num:number){
console.log(num);
console.log(this.num);
}
}

const p = new Person();

p.getNumber(2);

抽象类

不能当作构造函数,抽象方法必须被子类实现

1
2
3
4
5
6
7
abstract class Person {
abstract say(): void
}

class Lisa extends Person {
say() { }
}

重写和重载

重写:子类中重写继承自父类的方法

1
2
3
4
5
6
7
class Person {
say(){}
}

class Boy extends Person{
say(){}
}

重载:为一个函数提供多个类型的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
// 函数重载表示同名的函数如果参数不同函数会重载

// 但是JS中没有重载的概念,下面同名的函数会覆盖上面的函数

// TS中模拟函数重载, 通过不同的参数类型校验

function fn(a: number): number;
function fn(b: string): string;
function fn(c: any): any {
if (typeof c === 'number') {
return c + 1;
}
};

TypeScript 基本概念

  • Q1: TypeScript 可以直接在浏览器或 Node.js 中运行吗?
  • Q2: 使用 TypeScript 会影响代码在生产环境的运行性能吗?
  • Q3: 现有的 JavaScript 项目必须重写才能切换到 TypeScript 吗?
  • Q4: 既然最终运行的是 JS,为什么不直接写 JS?

TypeScript 概述

TypeScript 是由微软开发并维护的开源编程语言,旨在解决 JavaScript 在大型应用开发中的局限性。

技术背景与语言特性

TypeScript 由 Anders Hejlsberg(C# 与 Delphi 的核心设计者)主导架构。其设计深受 C# 与 Java 等强类型语言的影响:

  1. 语法特性:引入了接口(Interface)、泛型(Generics)、枚举(Enum)及访问修饰符(Access Modifiers)等特性,这些设计与 C# 和 Java 保持了高度一致。
  2. 静态类型检查:提供了严格的类型推断与检查机制,使开发者能利用 IDE 的智能提示(IntelliSense)与重构工具,降低了开发时的出错率。
  3. 面向对象编程:增强了 JavaScript 的面向对象(OOP)能力,支持类、继承、多态等特性,规范了代码结构。

设计初衷

JavaScript 原生设计的动态弱类型特性,在构建复杂 Web 应用(如大型 SPA)时,常导致代码维护成本高、重构困难以及运行时类型错误频发。TypeScript 通过引入静态类型系统,在编译阶段即可进行类型检查,显著提升了代码的健壮性与可维护性,使其更适用于企业级应用开发。

编译器演进:Project Corsa (Go 重写计划)

2025 年初,微软公开了由 Anders Hejlsberg 亲自带队的 Project Corsa,旨在用 Go 语言对 TypeScript 编译器进行原生重写。

  1. 技术选型逻辑 (Why Go?)

    • Go 的优势:官方评估认为 Go 的并发模型(Goroutines)和内存管理(GC)与现有的 TypeScript 编译器架构高度契合,代码平移(Porting)成本远低于 Rust。
    • Rust 的挑战:尽管 Rust 性能卓越,但其严格的内存所有权模型与 TS 编译器中复杂的 AST(抽象语法树)循环引用结构存在冲突,重写成本过高且不利于快速迭代。
  2. 性能预期
    官方测试数据显示,使用 Go 重写后的原生编译器(Native tsc)在大型项目上的编译速度预计提升 10 倍左右。

  3. 当前生态格局

    • 官方路线:全力推进 Go 原生编译器,预计将在 TypeScript 7.0 版本左右成为稳定选项。
    • 社区路线:现有的 SWC (Rust) 和 esbuild (Go) 主要解决“转译/类型擦除”问题。社区中尝试用 Rust 复刻完整类型检查器(如 stc 项目)的难度极大,官方方案有望填补这一高性能类型检查的空白。

TypeScript 在保留 JavaScript 灵活性的基础上,融合了静态类型语言的工程化优势。

TypeScript 核心优势

  1. 静态类型系统与编译期验证
    通过静态类型检查,TypeScript 能够在代码编译阶段识别类型不匹配及潜在的逻辑错误。相比 JavaScript 的运行时错误机制,这种提前规避风险的能力显著提升了代码交付质量。

  2. 卓越的工具链支持
    类型系统赋予了 IDE 强大的代码分析能力。开发者能获得精准的智能补全、参数提示、即时错误反馈以及安全的自动重构功能,大幅提升了开发体验与效率。

  3. 增强的可维护性与架构能力
    显式的类型定义起到了文档的作用,降低了代码阅读与理解的门槛。同时,TypeScript 提供的接口、泛型及访问控制等特性,为构建高内聚、低耦合的大型应用提供了必要的语言层面支持。

  4. 拥抱未来标准与向下兼容
    TypeScript 支持最新的 ECMAScript 语法特性,并通过编译器将其转换为兼容主流浏览器的 JavaScript 代码(如 ES5/ES6)。这使得开发者可以提前使用新特性,而无需担心环境兼容性问题。

问题解答 (FAQ)

为了更深入地理解 TypeScript,我们可以先思考以下几个关键问题:

Q1: TypeScript 可以直接在浏览器或 Node.js 中运行吗?
不能。 浏览器和 Node.js 运行时仅能原生执行 JavaScript 代码。TypeScript 代码(.ts)必须经过编译(Compilation) —— 或更准确地说是转译(Transpilation) —— 转换为标准的 JavaScript 代码(.js)后才能运行。这也就是为什么在工程中通常包含 build 流程的原因。

Q2: 使用 TypeScript 会影响代码在生产环境的运行性能吗?
不会。 TypeScript 的类型检查仅发生在编译阶段。一旦编译完成,生成的 JavaScript 代码中不再包含任何类型信息(类型擦除)。因此,TypeScript 既不会拖慢运行速度,也不会增加运行时包的体积。

Q3: 现有的 JavaScript 项目必须重写才能切换到 TypeScript 吗?
不需要。 由于 TypeScript 是 JavaScript 的超集,所有合法的 JS 代码天然就是合法的 TS 代码。你可以选择从新模块开始逐步引入 TS,或者通过配置 allowJs 选项让 TS 和 JS 文件共存,实现渐进式迁移。

Q4: 既然最终运行的是 JS,为什么不直接写 JS?
这类似于“为什么有了汇编语言还需要高级语言”。虽然最终执行的是机器码(类比 JS),但 TypeScript 提供了更高维度的抽象(接口、泛型)和安全保障(静态检查),是用来管理开发复杂度的工具,而非改变运行机制的工具。

co模块

简介

基于生成器的nodej和浏览器控制流,使用promises,让您以一种很好的方式编写非阻塞代码。

可以让Generator函数的自动执行。不用编写Generator函数的执行器。

API

co(fn*).then( val => )

通过解析一个Generator,Generator函数,或任何返回Generator的函数,并返回一个Promise

1
2
3
4
5
6
7
8
co(function* () {
return yield Promise.resolve(true);
}).then(function (val) {
console.log(val);
}, function (err) {
console.error(err.stack);
});

var fn = co.wrap(fn*)

generator转换为一个普通函数并返回Promise

1
2
3
4
5
6
7
var fn = co.wrap(function* (val) {
return yield Promise.resolve(val);
});

fn(true).then(function (val) {

});

执行逻辑

  • co(fn) 执行传入co模块的 generator 函数 fn

  • 把 fn 执行的结果 res,包装成 promise 对象

const gen = fn(); const res = gen.next() const rp = toPromise(res.value)

  • 如果是合法的 promise 这继续调用 fn 的 next 方法

rp.then((value)=> res.next())

co其实是一个返回promise对象的函数, 内部通过递归的方式调用 generator 的 next 方法

源码分析

工具方法

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
// sllice 引用
var slice = Array.prototype.slice;

// isObject
function isObject(val) {
return Object == val.constructor;
}

//导出方法
module.exports = co['default'] = co.co = co;

//判断是否是promise对象,只要是实现了thenable的对象都可以视为promise对象
function isPromise(obj) {
return 'function' == typeof obj.then;
}

function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}

// 是否是generator函数
function isGeneratorFunction(obj) {
// 一定是GeneratorFunction构造函数的实例
// (function(){}).constructor === Function => true
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorF方法
function thunkToPromise(fn) {
var ctx = this;
return new Promise(function (resolve, reject) {
fn.call(ctx, function (err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
}

// arrayToPromise yeildable数组转换为promise
function arrayToPromise(obj) {
// 每一项都转换为promise
// 第二个参数为toPromise执行时的this值
return Promise.all(obj.map(toPromise, this));
}

// objectToPromise yieldables对象转为promise
function objectToPromise(obj){
// 借用 function Object(){}
var results = new obj.constructor();
var keys = Object.keys(obj);
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// 每一项转为promise
var promise = toPromise.call(this, obj[key]);
if (promise && isPromise(promise)) defer(promise, key);
// 其他情况直接返回key值
else results[key] = obj[key];
}
return Promise.all(promises).then(function () {
return results;
});

function defer(promise, key) {
// 提前在results中定义key
results[key] = undefined;
promises.push(promise.then(function (res) {
// 提前一步拿到结果
// 通常自己的写法为这里直接返回promise
// 在promise.all 中在遍历结果生成result,这样写法更简洁一点
results[key] = res;
}));
}
}

//把yeild转为Promise对象
function toPromise(obj) {
// 如果为假直接返回
if (!obj) return obj;
//如果已经是promise直接返回
if (isPromise(obj)) return obj;
// 如果是generator函数 通过co函数栈为promise
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
// this通过next方法中toPromise.call(ctx, ret.value)来绑定
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}

co 核心方法

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
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1)

// 把任何东西包装成promise, 从而避免promise调用链

// 内存泄漏的问题
// https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {

// 执行Generator函数,返回Generator
if (typeof gen === 'function') gen = gen.apply(ctx, args);

// 如果没有next方法,则直接返回
if (!gen || typeof gen.next !== 'function') return resolve(gen);


onFulfilled();

/**
* @param {Mixed} res
* @return {Promise}
* @api private
* 通过 try catch 捕获执行时错误
*/

function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}

/**
* @param {Error} err
* @return {Promise}
* @api private
*/

function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}

/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/


function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
// 如果可以包装成promise, 通过thenable方法继续调用onFulfilled,执行下一个next, 并把promise的值传入,当作上一个next的结果
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// 抛出错误
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}

co.wrap(fn*)

1
2
3
4
5
6
7
8
9
// 通过柯理化函数,返回一个包含co执行结果的正常函数

co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};
  • Copyrights © 2015-2026 SunZhiqi

此时无声胜有声!

支付宝
微信