Webpack 4.x 简易使用指南

本篇来自于之前在团队内部分享webpack时的笔记,内容比较浅显,主要讲webpack基本配置,同时webpack运行原理,自定义loaderplugin也有涉及。

Webpack4.x使用指南

零配置使用Webpack

从Webpack4开始支持零配置,在不进行任何配置的形式下,直接运行webpackwebpacksrc/index.js作为入口,最终结果输出到/dist

在默认情况下,webpack只会对只会从入口开始遍历所有依赖文件,由于没有配置loaders,因此无法处理jsx/css/图片等。

在默认情况下运行webpack,打包完成后控制台会提示在配置打包的模式。在cli中通过设置--mode=production|development可以指定默认,在开发模式|产品模式下,webpack会默认启用不同插件对打包进行优化。

webpack基本配置

  1. 入口(entry)
    入口的配置形式有多种,例如:

    1
    2
    3
    4
    5
    6
    entry: './src/index.js', // #1
    entry: {
    home: './home.js',
    about: './about.js',
    }, // #2
    entry: ['./home.js', './about.js'] // #3

    第一种方式配置单一入口,所有文件将会被打包到同一个文件中,例如默认将会打包到main.js中;第二种方式是多页面(MPA)的配置方式,配置多个入口,最终将会生成多个bundle,配合html-webpack-plugin可以将对应的bundle注入到对应的html中;第三种配置的形式指定了多个入口,但是会将最终结果打包到一个文件中。

  2. 输出(output)
    输出指定了webpack将结果输出到哪里,如何进行输出,甚至是使用何种方式构建等,配置比较复杂。常用配置举例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    output: {
    filename: '[name].[hash].js',
    path: path.resolve(__dirname, 'dist'),
    }, // #1
    output: {
    filename: '[name].[hash].js',
    path: path.resolve(__dirname, 'dist'),
    library: 'xxLibrary',
    libraryTarget: 'amd',
    }, // #2

    第一种配置方式是最基本的输出配置,它配置了输入文件的存放的目录以及文件命名,filename支持模板语法,[name].[hash].js表示使用模块名称(入口配置)以及模块标识符来命名文件。
    第二种配置增加了’library’, ‘libraryTarget’两个属性,而library值的作用取决于libraryTarget的值。例2中配置libraryTarget: 'amd'表示使用amd的方式打包,而library的值将作为模块名。

    1
    2
    3
    define('xxLibrary', [], function() {
    return _entry_return_;
    })

    而当libraryTarget: 'commonjs2'时,表示该模块将使用commonjs的方式打包,用于node环境,而library将不起作用。此外libraryTarget的值还可以是var(默认)this等。

  3. 加载器(loader)
    在webpack中module决定了如何处理项目中不同类型的模块,而加载器(loader)则是具体的处理逻辑。

    noParse:指定不需要进行构建的文件。例如,指定项目中不需要对jquery进行构建,可以进行如下配置:

    1
    2
    3
    noParse: function(content) {
    return /jquery/.test(content);
    },

    规则(rules)的配置同样比较复杂,常用的配置如下:

    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
    rules: [
    {
    test: /\.(js|jsx)$/,
    exclude: /(node_modules|bower_components)/,
    include: /src/,
    use: 'babel-loader'
    }, // #1
    {
    test: /\.(png|jpeg|gif)$/,
    oneOf: [
    {
    resourceQuery: /inline/,
    use: 'url-loader',
    },
    {
    resourceQuery: /external/,
    use: 'file-loader',
    }
    ]
    }, // #2
    {
    test: /\.css$/,
    use: ExtractTextPlugin.extract({
    fallback: 'style-loader',
    use: [
    {
    loader: 'css-loader',
    options: {
    modules: true
    },
    },
    ],
    }),
    },
    ], // #3

    上面列举了常用的配置loader的三种情况,第一种处理js|jsx文件时,使用excludeinclude指定构建目录和不需要构建的目录,对于明确不需要构建的模块进行指定可以加快构建速度。第二种情况则是根据具体情况选择对应的loader进行处理。例如当我们使用import emoj from './emoj.png?inline'时,则会使用url-loader对该png文件进行处理,对于external类型的图片,则使用file-loader处理。第三种情况则是使用多个loader并且可以传递options,例如处理css文件,这里使用了style-loadercss-loader两个加载器,并且配置了css-loader的optionsmodules: true。这里需要关注loader执行顺序,loader执行的顺序是先配置后执行,例如这里先使用css-loader解析css,再使用style-loader配置将生成好的css文件通过<style>标签注入到DOM中。options配置了modules: true,则开启css-modules,写法如下:

    1
    2
    3
    4
    import style from 'index.css';
    let IButton = ({children}) => (
    <span className={style.btn}>{children}</span>
    );
  4. 插件(plugin)
    插件(plugin)用于以各种方式自定义webpack构建过程。webpack附带了非常多的插件,可以直接使用webpack.pluginname使用,也存在非常多的第三方插件。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    plugins: [
    new CleanWebpackPlugin(['dist']), // #1
    new HtmlWebpackPlugin({
    title: 'index',
    chunks: ['index'],
    }), // #2
    new BundleAnalyzerPlugin({
    analyzerMode: 'server',
    analyzerHost: '127.0.0.1',
    analyzerPort: 8889,
    }), // #3
    ],

上例使用了三个插件,第一个插件CleanWebpackPlugin作用是在构建输出前清空文件夹;第二个插件用于生成入口html,例如该htmltitle,指定其需要注入的bundle(MPA)。第三个插件则是用于分析构建结果的插件,它会在本地启动一个开发服务器,并且通过canvas来绘制构建可视化结果,方便进行构建分析和优化。

  1. 开发服务器(devServer)
    webpack-dev-server方便我们进行快速开发应用,它会对代码进行监控,一旦发生更改,它会立即构建,并通过补丁的方式应用更改,使得应用能够快速应用更改。并且其提供了一个基于Express开发的简易服务器,所有资源文件都存在内存中,访问速度极快,并且通过配置可以支持热替换。介绍一下常见的webpack-dev-server配置。
    1
    2
    3
    4
    5
    6
    7
    devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    inline: true,
    historyApiFallback: true,
    allowedHosts: ['host1.com', 'host2.com'],
    }

contentBase主要用于指定静态文件的存放路径,例如所需要图片等,如不指定则无法找到对应文件;

compress表示是否开启Gzip压缩;

inline表示是否启用内联模式,内联模式:实时重载的脚本会被插入到bundle中,构建消息将会出现在控制台。此外还有iframe模式。

historyApiFallback主要针对的是访问不存在的页面时的活动,historyApiFallback: true时访问不存在的页面会直接跳转到index.html,也可以传入一个对象更精确的进行控制跳转。

allowedHosts用于指定允许该devServer的主机。

hot是否开启热替换,如果为true的话,则会使用webpack.HotModuleReplacementPlugin插件。通过CLI的方式传递。

1
webpack-dev-server --hot

Webpack 运行流程

概括上来说:
初始化配置参数 -> 绑定事件钩子回调 -> 确定Entry开始遍历 -> 使用loader编译文件 -> 输出

webpack就像一条生产线,经过一系列处理流程后才能将源文件转换成输出结果。

每个流程的处理职责是单一的,流程之间存在依赖关系,只有当前处理完成后才能交由下一个流程处理。

插件像是插入到生产线上的一个功能,在特定的时机对资源进行处理。

webpack 通过 Tapable来组织这一切。

Webpack在运行过程中会广播事件,插件只监听它关心的事件。

—《深入浅出webpack》

Tapable的核心代码可以简化成。

1
2
3
4
5
6
7
8
9
10
11
12
13
class SyncHook {
constructor() {
this.hooks = [];
}
// 订阅事件
tap(name, func) {
this.hook.push(func);
}
// 发布
call() {
this.hooks.forEach(hook => hook(...arguments));
}
}

Webpack具体的流程如图所示。
流程

  1. 初始化构建参数(依据webpack.config.js),插件实例化,生成Compiler供插件使用,挂载自定义钩子。

  2. 依据入口递归遍历文件,并且使用对应的loader进行编译。

  3. 将编译好的文件解析成AST(抽象语法树),分析依赖逐个拉取济源。

  4. 编译完成,输出。

编写自定义loader

在webpack中,真正起编译作用的就是各种各样的loader。loader其实是一个function,其传入匹配到的文件内容(String),然后对这些内容做处理即可。

一个最简单的loader可以使下面这样:

1
2
3
module.exports = function(content) {
return "{};" + content;
}

config中进行配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
module: {
rules: [
{
test: /\.js$/,
loader: path.resolve(__dirname, './loaders/index.js'),
options: {
param1: 1,
param2: 2,
}
}
]
}

这样在编译JS的时候就会在每个JS文件前加上{};


获取自定义配置

可以使用loader-utils模块来拿到自定义配置。

1
2
3
4
5
6
7
const loaderUtils = require('loader-utils');
module.exports = function(content) {
let options = loaderUtils.getOptions(this);
console.log(options.param1); // 1
console.log(options.param2); // 2
return "{};" + content;
}


数据导出

loader可以通过return来返回处理后的结果;当然,更好的方式是使用this.callback的形式。因为它更加灵活,除了content以外,还可以传递其它参数。

this.callback(error, content, sourceMap, ast)可以传入四个参数:

  • error loader向外抛出一个错误
  • content 经过loader编译后的内容
  • sourceMap
  • ast 本次编译生成的AST,之后执行的loader可以直接使用,而不需要再次生成
1
2
3
4
5
6
7
const loaderUtils = require('loader-utils');
module.exports = function(content) {
let options = loaderUtils.getOptions(this);
console.log(options.param1); // 1
console.log(options.param2); // 2
this.callback(null, "{};" + content);
}

异步loader
对于异步loader,可以使用this.async来获取
callback函数。

1
2
3
4
5
6
7
8
9
10
11
const loaderUtils = require('loader-utils');
module.exports = function(content) {
let options = loaderUtils.getOptions(this);
let callback = this.async();
this.cacheable(false); // 是否缓存结果
console.log(options.param1); // 1
console.log(options.param2); // 2
setTimeout(function() {
callback(null, "{};" + content);
}, 1000);
}

pitch钩子
可以在loader文件中exports一个名为pitch的函数,它会先于所有的loaders执行。可以在这个过程中传参,而当前rule的所有loaders都可以拿到这个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const loaderUtils = require('loader-utils');
module.exports = function(content) {
let options = loaderUtils.getOptions(this);
let callback = this.async();
this.cacheable(false); // 是否缓存结果
console.log(options.param1); // 1
console.log(options.param2); // 2
console.log(this.data.hello); // hello
setTimeout(function() {
callback(null, "{};" + content);
}, 1000);
}
module.exports.pitch = function(remaining, preceding, data) {
data.hello = 'hello';
}

Plugin 初探

Plugin起始是一个简单的class,有一个必须要实现的apply方法。

1
2
3
4
5
6
7
8
9
class LPlugin {
constructor(options) {
this.options = options;
console.log('options', options);
}
apply(compiler) {
console.log('run this plugin');
}
}

使用

1
2
3
4
5
6
7
8
9
10
const LPlugin = require('./plugins/LPlugin');
module.exports = {
...,
plugins: [
new LPlugin({
param1: 1,
param2: 2,
})
]
}

plugin在初始化参数就进行实例化(事件流开始时),因此类似于CleanWebpackPlugin在构建之前进行文件操作删除掉某些目录也是很好实现的。


Tapable & Hook Tapable是Webpack构建过程中的核心所在,其暴露了tap tapAsync tapPromoise等方法,可以使用这些方法,来注入一些逻辑,这些逻辑将会在构建过程的不同时机触发。

例如:

1
2
3
compiler.hooks.compile.tap('LPlugin', params => {
console.log('触发了钩子函数');
})

该钩子函数将会在compile阶段触发。

*** 自定义钩子函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { SyncHook } = require('tapable');
class LPlugin {
constructor(options) {
this.options = options;
console.log('options', options);
}
apply(compiler) {
compiler.hooks.LPlugin = new SyncHook(['data']);
compiler.hooks.enviroment.tap('MyPlugin', function() {
compiler.hooks.LPlugin.call('hello');
})
console.log('run this plugin');
}
}

1
2
3
4
5
6
7
class LLPlugin {
apply(compiler) {
compiler.hooks.LPlugin.tap('LLPlugin', function(data) {
console.log('data', data); // data hello
});
}
}
分享到 评论

使用Webpack DllPlugin

DllPlugin 简易使用指南

  1. 创建webpack.dll.config.js 用于对特定的模块打包成dll
  2. webpack --config webpack.dll.config.js 生成dll以及其描述文件
  3. webpack.common.config.js中使用DllReferencePlugin引入打包好的dll文件。
  4. 打包。此时遇到相应的模块时直接引入而不会重新打包。

创建webpack.dll.config.js

这里以分别打包momentlodash为例

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
// config.js
module.exports = {
entry: {
lodash: ['lodash'],
moment: ['moment'],
}
}
// webpack.dll.config.js
const webpack = require('webpack');
const path = require('path');
const config = require('./config');
module.exports = {
entry: config.entry,
output: {
path: path.resolve(__dirname, '/static/dll'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, '/static/dll', '[name]-manifest.json'),
name: '[name]_library',
context: __dirname,
})
]
}

解释:

  1. 推荐把入口配置信息写在单独文件中,易于维护。
  2. DllPlugin类接受一个配置对象,该对象有三个属性:context(绝对路径), manifest文件中请求上下文;name,暴露的dll函数名;path:manifest文件存放的位置(绝对路径)。

使用DllReferencePlugin

1
2
3
4
5
6
7
8
9
10
11
12
// webpack.common.config.js
const config = require('./config');

module.exports = {
... // 省略其它配置
plugins: [
...Object.keys(dllConfig.entry).map((name) => new new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(`./static/dll/${name}-manifest.json`),
}))
]
}

DllReferencePlugin 接受一个对象用于初始化

  1. context: manifest的上下文(绝对路径),需和DllPlugin中的context一致
  2. manifest: manifest文件,使用require引入或指定绝对路径

可选参数:

  1. content:模块id的映射,默认为 manifest.content
  2. name: dll文件的名称,默认为 manifest.name
  3. scope: dll 内容前缀
  4. sourceType: dll如何暴露的?amd commonjs2 …

scope: ‘abc’, 则该dll中的xyz文件可以通过require(‘abc/xyz’)来引用
例如在一个dll中打包了lodashaxios两个库,并且指定了{scope: 'lib'},则在需要使用axios的时候使用require('lib/axios')即可。

测试1:把echartswinduireact及其周边分别抽取成dll文件。

抽取的dll文件大小分别为:753KB 1603KB,385KB。

构建方式 平均构建时间 包大小
普通 97s 4.1MB
使用Dll 71s 2.6MB
dll文件 24s 2.7MB

经过对比发现,由于无法使用按需加载,所以整个windui打包的大小差不多为1.6MB,而普通的打包方式windui的大小仅为365KB。

在使用windui链接库后生成的打包,发现vendors模块中仍然含有windui,大小为330KB,仅windui中的node_modules文件夹下的rc-triggerrc-dropdown被重用,因此这里可能重复打包了。

windui中依赖的rc-*部分模块仍然被打包。

方式二 仅对react相关模块和echarts进行打包

抽取的两个dll文件的大小为393KB 769KB

构建方式 平均构建时间 包大小
普通 97s 4.1MB
使用DLL 77s 3.08MB
dll 11s 1.16MB

windui的大小基本不变,而windui依赖的rc-*系列组件基本没被打包。

vendors模块从0.61MB1.67MB

使用html-webpack-include-assets-plugin 把dll注入到index.html

1
2
3
4
5
const HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin');
const dllIntoHtml = new HtmlWebpackIncludeAssetsPlugin({
assets: ['./static/dll/echarts.dll.js', './static/dll/react.dll.js'], // 需要注入的dll文件路径
append: false, // 是否尾注入?push : unshift
});

因为先构建dll,再进行项目构建,在项目构建过程中会删除整个build目录,所以在构建完后再把dll文件夹拷贝进build/static/

总结

  1. windui进行dll打包后会因为无法按需加载而导致总大小偏高(且可能会产生冗余);而不进行的话,包的总大小基本不变。

  2. 初始时需要加载的chunk从3个变为6个/5个

升级到webpack4.x

升级到webpak4.x后无论是dll还是最终生成块,都有小幅度的下降。与此同时,构建总时间(dll构建时间+项目build时间)略微减小。

关于使用已存在的模块直接作为dll文件引入的可能性

  1. 目前无相关方面的实践。
  2. DllPlugin抽取特定的模块构建dll文件后会生成一个manifest文件。该存储了各个模块的和公共模块的对应关系。
    该文件会对已经打包成dll的模块中的文件进行描述,会给每个文件指定id,并且该json文件中的name属性对应dll的library
  3. 在进行项目构建的时候,如需要打包某个模块,会在manifest文件中查找,如果该模块已经存在于dll中,依据manifest中的信息进行链接即可,不必重新打包。

构建的dll块和使用splitChunk或者直接使用babel打包出来的块不一致。其依赖构建dll时指定的library等,而manifest文件也是和该块一一对应的。所以从理论上,使用其它方式提供dll文件由于构建方式和无法提供manifest文件,在构建过程中并不能被重用。

PS: 本片文章来自于我在团队内部分享的笔记,任何关于webpack-dll-plugin的理解和使用方式请在该插件的Webpack介绍页查看

分享到 评论

我的这一年

好一个寒冷的假期!

最后一日

早上七点,闹钟开始吵个不停,我习惯性按下锁屏键,并快速蜷缩到被窝中,十分钟后,我再次重复这两个动作,直到不得不起。可是,虽然是周一,可毕竟是假期呢。这次我直接打开了闹钟,取消了激活状态,这下,可以好好睡一觉了。

再次醒来的时候,已经快十一点了,湖人的比赛已经开打,也没了吃早饭的必要。我摸出手机,打开“掘金”,果然,技术社区唯一的小说也停更了;今天是周一啊,我似乎忘记了点什么啊。没错!“龙五”更了,看饿了看进度条99.1%,果然,老贼可不会因为今天是18年最后一天,就爆更的。剧情延续上一章,雪救下了阿巴斯,拯救了整个船上的人,而现在船员都知道雪是一个怪物了,要处决她。现在该由阿巴斯救雪了,阿巴斯夹带着言灵“因陀罗”登场,本章完。按照老贼的尿性,在阿巴斯救下雪这个过程中再水个三五章不无可能。哎,什么时候到个头呢!

好不容易起了床,洗漱完毕,打开电脑,开始了一天的“工作”。打开NBA 2K Online2,先晚上两把,发现我已经11连胜了;打开某鱼直播平台,看狗贼叔叔打两把“沙包战”;同性交友社区GitHub当然也不可少,看看前端娱乐圈,今天是否又有猛料。一切完毕,吃饭的点到了,刚刚好。

要是在上班,直接电梯直下十层,食堂的饭虽然不好吃又凉,但架不住便宜并且还少了奔波,所以单从填饱肚子的角度上来说,还行。而周末或是假期就不一样了,自己做饭是不可能的,那么除了点外卖就只好出去吃了,还是在这寒风刺骨的冬天。

穿好厚重的羽绒服,匆匆下了楼,今天我并不想图近就在附近随便吃点,而是要走过好几个路口去公司旁边的小店吃。虽然说实话,作为一个重庆人,从小色香味俱全的菜吃多了,反而觉得这边的什么都不好吃,并且还贵。可是呢,生活还得继续,所以在吃这方面,我能选的只有分量大/价格便宜,如果能够稍微好吃点,那可就是极好的了。

去公司的有两条街最近一直在施工,这不,路过红绿灯的时候,送外卖的小哥把车开进刚铺好的水泥路上,直接连人带车陷进去了。这么冷的天,弄得双腿都是未干的水泥,车也暂时开不了了,不知又会耽搁几个外卖,损失多少收入呢。前天晚上给母亲打电话,都很晚了父亲还在工地上没下班,并且他们基本上是没有休息日的,连节假日也不例外。在我心中,劳动人民都是伟大的,值得所有人的尊重。

刚开始写这篇文章的时候,我开着窗户,吹着寒风;可是不一会儿,就冻得受不了,只好打开空调,顿时暖风阵阵,僵住的双手得以解冻,恢复了往日的活力,然后电费飙升,令人心疼。

预计我会在写完文章过后玩两把吃鸡,到了饭点,我会叫上一顿丰盛的外卖,整个晚上,会找一些还没看过且是科幻/战争题材的电影看看,往后还有让人拍案叫绝的2K剁手环节。我的2018最后一天,会这样度过。

毕业设计

我已经不记得2018年的第一天,我具体干了什么。按时间来算,应该在准备期末答辩,然后开始享受学生时代的最后一个寒假?

我记得春节刚过,我就来到了学校,那个时候软件园水池里还结着厚厚的冰,健身房的门还没开。我选的毕业设计是一款Web应用的开发,并且要求的是使用Vue开发,那个时候我对Vue的了解仅限于听过它的名字,仔细看完一遍官方文档,我就和导师的研究生们开始开发这个应用了。其实对于这个合作的项目,我多有吐槽,这本是一个为收集用户行为数据并进行进一步分析的应用,从应用本身的规划来说没什么问题,但问题就出在开发者本身上面,效率真的很低。每次9点到实验室结果研究生哥哥姐姐们还没到;约好时间进行接口对接,结果竟然在玩手游;一周下来,一个页面都没做好。鉴于此,我决定给我的导师摊牌,我可以协助完成整个应用的开发,但是对于我的毕业设计,我想自己开发。后来,我花了差不多一周时间完成了管理员端的开发,协助完成了接口开发,解决他们遇到的几乎所有问题,然后,脱离了这个合作项目。

其实,不应该只有吐槽。负责接口开发的学长才研一,跨专业考的软工,并且我们的后端使用的是Spring Boot,对于一个Java都怎么熟悉的人,能够在短时间之内完成任务,并且每次我们交流的时候他都在工作,就冲这股认真劲,给他点赞。另外一个就是负责开发应用的学姐了,在整个应用中,我和她接触最多。她对前端也不怎么了解,一个月下来也能做得有模有样,并且干起活来特别认真,一个特别热心肠的人。认真努力的人,值得我们尊重,并抱有敬意!

我的毕业设计脱胎于这个合作项目,是一款基于位置感知的任务众包平台。至于位置感知,就是对于绝大多数本地线下任务而言,推送给用于是基于位置的。此外,还构建了基于统一交易凭证的支付平台,内容审核,用户论坛等。从前端到接口,完全由我一个人开发。当然,直到最后毕业答辩,拿到优秀后,我仍然觉得这个应用还只是一个demo,只完成了所有设想的功能,页面也仅符合直男审美。

这是我的一次尝试,我设想着把想法付诸行动,并且在二十多个下午,我完全进入了编程状态,带上耳机,排除一切干扰,尽量把工作做好。而这样的体验,也只有15年在西电的一年时光。除此之外,对于在项目中用到的技术/框架,我加深了理解,我开始了解到单页应用服务端渲染,对web前端,以前可能只是开了一户小窗,而现在简直是开了一扇门。

毕业季

在毕业前几天,我来到上海,租好房,开始迎接新的生活,在此之前,似乎还重要的事情等待去做。

其实放在以前这件事是我想都不敢想的,这当然不是向喜欢的女孩子表白,更谈不上惊天动地。说到底,是我一个人的执念罢了。在22岁这个年纪,在开始一段全新生活之前,想要解开的心结。

而来到上海入职的前几天,我买了到福州的火车票,然后在那里呆了一天,见到了熟悉的陌生人和她所见的世界。回上海的那一天晚上,刚下过大雨,我从地铁站回公寓,听见小区里的蛙鸣,在这个寂静的夜里,我把什么都抛到了脑后,一切都在那一刻结束了。

不枉我耿耿于怀那么多年。

工作

我准备好了工作,可是真到了那一天,我发现这不是我想要的。

其实工作说得很多了,在很多周记里,我都提到这周完成什么样的工作。半年下来我的态度也在慢慢发生改变,我不得不去做自己不那么感兴趣的事,我对重复性劳动也没那么排斥。我能够利用工作中空余的时间片段,学到更多的知识。

其实自己对工作还是多有不满的,这种不满最初来自于对工作预期的落差,后又因为莫名其妙的转岗雪上加霜,现在因为重复性的劳动。除此之外,我接触到的同事,都非常nice;还有我们的小伙伴们,真的幸运能够和你们认识。

说说最近吧,部门新来了一名实习生,说是做后端的,但是由于我们项目缺人,所以就过来搞前端。大概工位离我很近的原因,我成了他指定导师。说是导师,其实也就是他遇到工作中的问题,我负责给他解答而已。小明才来了几天,一天趁晚饭时间,和他聊了不少。从在校,学习到面试基本都问了下。这位小明同学给我的印象就是,他仅仅是一张白纸而已,说是做后端,但一问到也说不出个所以然来;前端?抱歉也许以前做课程设计的时候可能写过JSP。所以说,我打心底里根本没想让他在短时间加入到这个项目中。这种他最主要的任务就是学习,从JavaScript语法开始;可刚学一天,他就给我说,学习没有目的性;刚过一周,他不但把JavasScript看得差不多了,React也加入了技能树。可是当我问的时候,我说React的生命周期有没有理解,propsstate是怎么回事,命令式声明式区分开了吗,他却说不出个一二。然后我又花很多时间给他讲。我想到我自己,从大二开始接触前端,很久也对JS不怎么理解,React差不多接触了一年多,写了好多Demo才能达到熟练使用用的程度,是我太笨了,还是他们他浮躁呢?所以,小明在下个迭代会加入我们,并且我给他安排了差不多我1/3的任务量,希望他能够快速成长吧。

关于我正在做的这个项目,经历了刀耕火种的时代,在开发过程中大家逐步填坑,并且为了解决开发过程中的痛点,开发了许多轮子;直到现在,我们打开发效率很高了。除了免不了成为配置工程师或者说流水线工人,我想说,我喜欢这个团队。希望项目早日完成/找到接盘侠,脱离苦海。

生活

生活也在周记中提到很多了,每天除了工作,下班了我基本上不会选择继续学习,可能会看看直播玩玩游戏。周末,多睡睡觉,打打篮球,看看电影放松放松。可不是不知为何,一到周一上班特别累。

本来想着来工作了继续健身,所以办了健身卡,每周4次的量,可是奈何健身房跑路这种奇葩的事情都能让我碰见,所以健身就搁置了。每周会花一些时间利用弹力绳保持下身材,所以到现在还没变成肥宅,可是看见日渐隆起的小腹,心中的担忧又来了~

未来

当初我憧憬魔都才选择来到这里工作,可是半年过去了,我丝毫没有归属感。除了同事和舍友,我基本不认识其它什么人;土著们操着一口你怎么也听不懂的方言;这里的物价水平也高到离谱,房租花费3000+,随便吃点什么也都得花费两倍于家乡甚至更高。最奇葩的是,隔壁的老太遇到过我两次,每次都问很久,结果他担心的是外来租客可能导致不安全的问题。虽然老太并没有恶意,但是我却心底拔凉拔凉的,我们是怀着梦想来到魔都,给魔都带来活力,我们买不起房,上下班骑摩拜,我们蜗居在10平米的小屋里却交着巨额房租,而现在他们不欢迎我们,担心我们打破他们的生活宁静,可那又有什么办法呢,就算是这样,生活还得继续不是吗?

所以我每次都认真回答老太的问题,并且打消老太这方面的顾虑。我喜欢魔都的生活节奏,但我看到每个月到手的工资和交完房租和生活必须花费后存下的钱,我想,我的未来不会在这里。至于下一站在哪里,何时到站,还没有计划,但可以预见的是,不会太久。

无论是到哪里,都要不虚此行,所以珍惜每个平常的一天,让自己能更进一步,变得更加优秀。

尾巴

还有不到7个小时就2019年了,在2018年我结束了学生时代,并且在22岁的年龄开始独挡一面。我以前说过,我从小到大几乎所有的决定都是我自己做的,而家人们总是尊重我的决定并且无条件的支持我。我想,什么时候他们能给我一个决定呢?好吧,就让它在2019年吧,说到做到!

愿好,明年见!

分享到 评论

每周一记?

最近总想着升级,比如玩2K阵容想升级,为了追剧想升级成为XX视频会员,为了得到更好的电竞体验,想升级我这台还不怎么过时的PC。当然,其实写周记也要升级了,升为月记可好?

废话少说,不然电竞时间不够用啦。

学习

项目在迭代后期照例改改bug划划水,所以空出来大把大把的时间,去掉刷微博看NBA,貌似还剩点时间片,所以还是装模做样,假装学习。

  1. DOM Element 位置相关的属性

这可是学了又忘,用到查资料的东西,总是会把这么些属性搞混。

client-*: 不用说,这个还是记得住的。

scroll-*:如果是描述形状的话(width&height),那就是元素(及padding)和溢出区的宽/高度,如果是位置,则表示向下/右滚动的距离。

offset-*: 基于元素的offsetParent来进行形状和位置的计算。而offsetParent则表示距离元素最靠近的上层元素,且其position不为static。如果是表示形状,则表示其水平/竖直方向上的高度(包含padding和border); 如果表示位置,则表示距offsetParent`的距离。

再认识一下getBoundingClientRect()用于获取盒模型的各种属性。
其中x,y表示其相对视口的距离,widthheight等于(content+padding+border)。其余四个表示位置的属性分别表示据左上角/右下角的距离。

  1. Node.js 核心模块回顾

net 用于创建TCP Server和Client.

1
2
3
4
5
6
7
8
9
10
11
net.createServer(function(socket){
socket.on('data', (chunk) => {
console.log('data from client: ', chunk.toString())
})
socket.write('hello');
})
const client = net.createConnection(options)
client.on('data', (chunk) => {
console.log('data from server: ', chunk.toString())
})
client.send('hello server')

path 工具模块

1
2
3
4
5
6
7
path.basename() 文件名
path.dirname() 目录名
path.extname 扩展名
path.format(ooption) 将一个对象格式化为一个路径
path.parse(path) // 将一个路径解析成为一个路径对象
path.join() // 路径拼接
path.resolve() // 将路径片段拼处理成绝对路径
  1. React 新特性学习

现在React存在的问题:包装地狱 庞大的组件 class组件

hook:在function组件中使用到class组件中的很多特性

useState为组件添加状态
useContent 组件上下文
useEffect 用于处理副作用

自定义hook

  1. Webpack 4.x 之 code split

webpack默认的分割规则:

  • 新的代码块被共享,或是这些模块来自于node_modules文件夹
  • 新的块压缩前大于30KB
  • 按需加载的块,并行请求数小于等于5
  • 初始加载的块,并行请求数小于等于3

    默认情况下只会影响到按需加载模块,否则所有模块都会被打包到一起。

    使用ES提案中的动态加载方案,import(),则会进行按需加载并单独打包。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    optimization: {
    splitChunk: {
    chunks: 'all|initial|async'
    minSize: number,
    minChunks: number,
    maxAsyncRequests: number,
    maxInitialRequests: number,
    name: string,
    cacheGroup: {
    test: regexp,
    priority: number,
    reUseExitintChunk: boolean,
    }
    }
    }

指定chunks为all|initial会把node_modules中的模块分配到vendors缓存组,而重复至少两次的代码将会被打包到default缓存组。

initialall的区别是:当按需加载时,initial会分开打包,而all会统一打包。

提取特性的第三方库,可以直接新建一个缓存。

其它

最近在看的番《史莱姆》和《猪头少年》。

最近在看的剧《人不彪悍枉少年》。

值得一看的美剧《地球百子》系列。

特别推荐科幻美剧《无垠的太空》系列。

绝对不推荐的游戏《绝地求生》,打到自闭。

尾巴

一个多月没打篮球了。还记得刚来上海的时候,几乎每周都要去一次甚至闲的时候还会去两次,那个时候打球,特别容易迷失。如果被分到了很强的队伍,很容易隐身,超没存在感;如果分到了较弱的队,需要我进攻的时候,总是犹豫不绝;再加上时准不准的投射水平,突破终结能力实在一般,几乎每次打球都不会体验很好。

后来,既然做不好进攻,那就好好防守。毕竟身体还算强壮,下盘还很稳,又不惧对抗,最近几次打篮球在防守端还是不错的,对位同等身高的人,一点都不虚。防守好了,也带动了进攻,更加坚决的出手,往往可以收到意想不到的效果。

我差不多从上初中就开始打球,天赋平平,基本功又不够扎实,所以这么多年来基本上没什么长进。以前我十分乐意分享球,想成为一名组织者,可慢慢打着变成了神经刀。最近小伙伴叫我罗伯森,因为我的表现的确像。而我也乐意接受这样的角色,专注于自己擅长的事,成为一名好队友,一名好的角色球员

我曾经打篮球打到自闭,一度放弃篮球。可是,我还是喜欢玩篮球游戏,也无时不刻关注NBA,我也有我喜欢的球星(偶像)。说到底,我还是喜欢篮球啊。我的生活可少不了篮球。

生活也是这样,作为一个平庸的人,也要努力找到自己的擅长的点。

分享到 评论

每周一记(十一月)

先说重点

差不多一个小时以前, IG 3-0零封 FNC拿到了 LPL赛区的第一个世界冠军。我目睹了这一切,也曾今是一个英雄联盟玩家。还记得第一次弟弟带着我玩这款游戏,我抽到了一个8折优惠,花了36买了我的第一个英雄–金克斯,后来,买了第一款新年限定皮肤–羊年限定金克斯;也买过源计划--艾希。记得大学的第一个暑假高中同学小聚,网吧开黑玩一整夜也不会觉得累。记得最孤独无助的2015年,一个人在宿舍打出排位36连胜。记得几个月以前,快要离开大学之前,和同学们开黑的那段时光。老实说,当我忘记了出门要买装备,总是忘记交闪现,也经常线上漏刀的时候,英雄联盟早已不属于我了。可我总是会在忙得不可开交的一天后,打开直播看上两局;也全程关注了S8世界总决赛;也会打开网易云音乐,单曲循环RISE。到现在,我明白了,我还是喜欢英雄联盟这款游戏,即便我不怎么会亲自去玩了。

我一直喜欢玩游戏,从最开始的穿越火线,再到陪伴了整个高三的NBA 2KOL,到英雄联盟,在到如今的绝地求生。因为只有在玩游戏的时候,才是那个没有任何防备的自我。

工作&学习

为什么要把工作和学习放到一起来说呢?因为这一周真的很闲,临近项目结项,我最主要的工作,除了处理BUG,大把大把的时间就拿来学习了。

当然还是先将工作。我们的项目遇到了一个BUG,我们有一个嵌套层级很深的菜单,需要实现几个需求:

  1. 当用户切换主菜单的时候自动滚动到顶部。
  2. 当弹出板块树或者是进行滚动的时候,始终保持用户选择的菜单项在可视区域内。
  3. 当用户刷新整个页面的时候,能够准确还原到上一次用户选择的菜单项中。

我们最初并没有将Menu写成一个单独的组件,而是把它放在了DataReport中,并且只是用了scrollIntoViewIfNeeded来让选中的菜单项滚动到可视区域,因为在切换菜单是默认会选中顶部第一项的,所以不需要加任何代码就可以实现滚动到顶部这个功能。

理论上是这样的,并且我们也是这样做的。可我们发现了,如果我们切换菜单的时候,如果默认选中的不是报表,那么就没办法滚动到顶部。而且一旦刷新页面,用户当前的状态也就丢失了。

我第一想法是把Menu作为一个组件独立出来,这个我们可以更好的在组件内部实现逻辑。当切换菜单的时候,我使用了scrollTop=0这种方式来滚动到顶部。并且为了能够在刷新后记住用户状态,我需要反向遍历菜单树,并与当前路由进行对比,确认用户当前打开的菜单项位于的层级,从而控制其祖先菜单的打开和关闭。至此,问题解决了。

可是,在和boss沟通的时候,他第一时间判断我这样做是不正确的,并且现场撸了一个版本出来,且不讨论是否能用,但是太多的if else条件判断,反而让DataReport变得无比臃肿,再加上到下班时间并且我们在他身后看他写代码很久了,我一时口嗨就说了句写得乱七八糟的,这惹怒了我的boss,我也当场为我的不理智做出了道歉。
通过这件事可以看出来我的情商还要加强。

虽然最后还是采用了我的方案。

再讲讲学习。

在掘金上看了很多文章,都是零散的,比如说事件循环Shadow-Dom&Custom-Element,还是CSS mask&clip或是markdown2html,通过这些零散的知识点学习,我觉得我对前端的理解又有了更多的理解。在大方向上的学习上,主要是TypeScriptEcharts自定义系列,以及读源码。

特别是读源码,真的让我收获颇多。最近在看Wind Design的很多组件的源码,大体上是老版本的Ant Design的源码。很早就接触并使用到了Antd组件库,比如经常使用的Form组件却不知道是如何实现的。

我之前也按照自己的想法,封装了一个组件。可当读完源码后发现,真的是自己水平太低,很多问题都没有考虑到。并且作为源码,自己的代码也不够规范。

当然,从下周开始,我不会像现在一样闲了,但源码我还是会一直读下去。如果有时间的话,我会考虑记录记录写成博客什么的?

尾巴

上周末我把和上司闹不愉快的事情告诉了家人,而一到周一下班,妈妈就打来电话,问我的情况,生怕我和上司的关系存在问题什么的。说实话,我很感动,我给她说啊,都是技术上的事,不会带入平时工作的,并且我在意识到自己的错误的时候,也立马进行了道歉,况且我的上司也是一个很nice的人,这样他们终于不用担心了。其实就是这样,最关心我们的,非家人莫属。所以啊,都那么大的人了,做事情一定要考虑更多,让他们和关心你的人,少为你操心。

IG都夺冠了,那我也要戒掉自己的某个陋/懒习了,所以在最后立一个只有一个人知道的FLAG

分享到 评论

每周一记(节后综合征)

我似乎是患上节后综合征了,具体表现为:晚上睡不着,早上起不来。

周一

上周整个周末都因为电竞事业繁忙甚至抽不出来一个小时写周记,毕竟有那么多比赛/直播要看,看完还要打,真的嫌时间不够😂。所以上周的周记只好在周一补上了。

项目进行到最后一周,开发和测试工作已经结束了,所以这周的任务就是摸鱼,比如说今天,上班八小时,摸鱼七小时,好不容易熬到了下班😂。

回来的时候,开始下起雨来,昏暗的路灯下,行人匆匆。我想起了大学最后的一年,那个冬天,同样的傍晚,刚从健身房出来,身体还温热,猛地吸入一口冷空气,冻得直哆嗦。而一年不到,当初健身的小伙伴许久不联系,自己也开始发福?(离开公司前刚上称 139斤)。

工作

上周的工作有点划水前奏的意思,开发快要完成,改完了测试提的bug,剩下的就是琐碎的事了。可是就是这么点事,却出了最多的问题,需要花最多的时间去尝试解决。

  1. 在开发一个图形分析的功能,一个类似图浏览器的功能,包括缩略图和图主体两部分,由于boss强调封装,所以并没有开发一个新的组件,而是融合到reportChart中,这使得原本就有好几百行的echarts配置文件又多出了一大截,代码可读性当然不用说了,一个字形容就是:(虽然不是我开发的)。好了,开发完毕,给到开发们,一开始还好,随着需求越来越多,代码出问题了,并且影响到了先前开发的代码,并且难以追踪。排查了好久,最后是因为一个配置项默认值的问题。当然,bug还很多,比如说yAxis无法正常显示刻度/负数和特殊数据无法绘制等。除了bug以外,性能也是很低下的,基本上达到了不可用级别。因为在初始化时,需要请求数据用于绘制缩略图,这会导致太多的网络请求和组件的更新,最后折中了一下,使用icon代替了缩略图。结果这周刚来,接到通知,图形分析被砍了,也就是说,上周有一半的活就白干了😂。

  2. 项目本身遗留一个很大的问题,就是当存在主子表的时候,如果有筛选条件,那么当更改筛选条件并且主表没有数据的时候,子表会拿到上一个参数并且成功请求到数据,这显然是不对的。这个bug很早就被发现了,但又因为各种各样的原因,还没得到解决,并且在测试把bug算到我头上和boss出差的情况下,我只好硬着头皮上了。首先分析点击提取数据按钮后发生了什么事,结果当然是dispatch了一个action;但这个action并没有去请求数据,而是更新了一个标记,这个标记通过props的方式传入到顶层组件中,该组件在componentWillRecieveProps中对对该标记进行了捕获,并且还做了其它的操作;最后是ReportTable组件更新,重新请求数据,并且把请求到的数据渲染出来。这里使用了高阶组件,用户请求数据的参数和事件捕获函数都在父组件中,并且通过props来传递。分析到这里,已经很明了了,我们需要在父组件更新的时候清空selectedRow(也就是被选中行),那么一旦主表无数据,selectedRow将无数据,字表不会发起请求,也就不会错误渲染了,至此,问题得到解决。

  3. 在我开发的一个名叫QAPanel问答面板的组件中,有一个问题,就是一旦滚动到某个地方后再请求数据,页面并不会滚动到顶端,而仍然停留再上一次滚动的地方。这个问题很好解决,一旦组件渲染,我们需要拿到组件的ref(引用),并且再请求新数据之前,把页面滚动到顶端。这原本不是问题,一顿操作过后,竟然报错,createRef undefined?,一脸懵逼。谁叫我们还用的是React 15呢,又是一顿操作,改成原来的模样,差点记不住😂,因为我刚接触React的时候,就已经是React 15,那年我才20岁😂。

  4. 当然,我们的webpack肯定是3.X的,由于使用了create-react-app脚手架,具体构建细节都被隐藏了,所以eject一下就好了嘛。对的,配置是暴露了,也修改了,可就是无法正常构建了,我承认这是我的问题,但是久经排查,并没有发现bug,所以还是老老实实的用覆盖的形式吧,不需要过多的修改,构建的时候需要分包,把第三方库都打包到一起,运行时代码再分开打包。结果差不多压缩后加起来有3MB,并且构建的时间差不多90秒,慢得让人流泪。果断happyPack走一波,总算happy了。

还有很多很有意思的事情这里就不细说了。总的来说就是,填别人的坑,让别人无坑可填。

生活

  1. 如果说电竞是生活的话,那么吃肯定也少不了。上周末两天,累计叫外卖3次,共计消费90+,贵也就算了,关键是特别难吃。这让我想到了我的家乡,随便一个路边面馆,一碗十块豌豆炸酱面,简直不要太好吃。所以啊,想要逃离,这可能是我需要好好思考的了。

  2. 打篮球也要玩心跳?对的,我指的是这款名叫NBA 2K OL2的篮球游戏,因为它的球员交易市场就像炒股一样,你家有矿你随意。从公测开始,陆陆续续完了一个多月,人民币也花了一百多,总算把自己的阵容弄到500w了,谁知道12号一波更新,出了一个合同费礼包,导致合同费贬值严重,我的阵容在一天时间里涨了了300w,也就我在仅仅的一天里,赚到了60%,除去25%的交易费用,也要净赚35%,这可比A股刺激啊。
    阵容截图

  3. 当然还是打球。NBA有一种很吃香的球员那就是3D球员,例如佛主就是典型的3D球员。随着身体变得更强壮,再加上良好的防守习惯和态度,让我在野球场上成了一个优秀的防守者,可是越来越不自信的投篮,别说三分,就是中距离也都靠蒙,以前还能算个2D球员,这下好了,只剩一个D了。所以啊,整个NBA,萝卜丝(罗伯森)最像我了😂。可是,萝卜有瑞秋,而我的瑞秋呢?😂😂

尾巴

生活还要继续,我爱编程,编程使我快乐。好了,电竞事业繁忙,告辞。

分享到 评论

每周一记(长假前夕)

您的一周六天班体验卡已到期,请尽快续费,否则将会被放假一周。

工作

当开发阶段的工作暂告一段落,那么接下来的工作总会是比较轻松的,我也不例外。这个上六天班的一周,可以被划分为三个阶段,轻松->懵逼->抓狂。

轻松

每天除了等测试反馈bug然后再修改以外,在code review 中提到的代码风格的问题也需要修改,虽然有定制的团队代码规范约束,但是例如变量/属性命名,存在性检测,数据格式验证等都需要修改,按照每个人维持的代码量不一样,我这一改就是三天,并且还发现了一些低级错误,比如中文漏打/错打,生成表格列宽严重不足等。

懵逼

接到新的需求,需要对某些业务做定制化的开发。刚刚拿到任务的时候简直是懵逼,第一眼看上去就不简单。深入Delphi代码了解,发现除了很难画出来的图,连表格都很难去完成。表格表头是动态生成的,需要通过接口去拿到数据生成百表头,再通过其它接口拿到数据,再渲染。限于在既有框架下开发存在许多限制,要实现这样的需求是比较困难的。原本可能只会花2小时的任务,半天才搞完。这里还得吐槽一句,接口返回的数据又是columnX的方式,对应几十个字段的数据没有对照表真的很难完成。

抓狂

上班的最后一天,内心也变得浮躁起来,经过前一天的研究,今天的任务就是开发出图形,原本的框架当然不能使用了,已经想好了两种解决方案,使用echarts自定义系列或者使用canvas来自行绘制。

首先尝试echarts,摸索了好久配合上demo终于完成了单元格的绘制,然后是多xAxis绘制,怎么尝试都无法达到想要的效果。因为在绘制等高等宽单元格的时候,xAxis的单位标度成了单元格宽度,其是value类型,而原本xAxis期望是使用时间作为xAxis,其类型是category,因此基于时间标度的折线图无法绘制成功。这几乎是一个无法跨越的问题,正当我准备舍弃这种方案的时候。突然想到,既然这条xAxis是无意义的,那么我们再配置一条xAxis,并且不渲染这条无意义的xAxis不就可以了。

经过尝试,的确可以!接下来就是接入数据,绘制图的数据需要做整理,首先需要做二维数组的转置,去除无用数据。然后是进行标记,因为业务要求需要标记每天每个行业的涨跌幅和涨跌幅排名,来进行图的颜色渲染和富文本渲染。做到这里,差不多demo就完成了,效果与设想的差距不大,剩下的就是细节的完善了。真是令人抓狂的一天!

其它

  1. 转正答辩。虽然过来人都说走走过场,但真当落到自己头上的时候,还真是这样。可又不是这样,气氛非常融洽的一次答辩,到场的大佬都很nice,答辩完还在会议室里闲聊。后来发现架构部的老大去年就是面我的那位,瞬间无比亲切。又聊到目前的一些新技术在公司的运用,工程技术中心一直走在前。经过这次答辩,我突然又有点看到了未来。这是以前从来没有过的。

  2. 遇到一个问题。我在开发一个公共组件的时候,需要加一个loading的效果,使用了Spin这个组件作为容器,但是开发完成后发现并不能滚动。因为Spin容器的高度取决于其内容的高度,而内部内容容器的高度又是继承至外部容器,所以这里相互冲突了。要解决这个问题也很简单,我们仍然需要Spin组件成为一个容器,当时其不应该在组件最外层,而是只包括内容,这样一来,内容容器继承的高度就是外部容器的高度而不是Spin的高度,当内容高度超过内容容器高度的时候,就可以进行滚动。

  3. 学习TypeScript。一直在花时间学习TS,因为工作需要需要去理解源码和做定制化,除此之外,我想扩充自己的技能树。

小尾巴

我从高中开始关注智能手机,对参数配置了如指掌,也因此选择了成为一名程序员;后来上了学,我关注电脑,无论是是笔记本还是PC,对电脑的配置也颇有见解。这些年来,找我推荐手机电脑的朋友真的不要太多。

大四的时候,因为一档二手车节目关注汽车,目前对车型/配置/售价也是颇为了解,也会花时间去专研发动机/变速箱原理等。我在想,如果除了做程序员,我可能也会去修车?

上班了,也开始关注楼市。虽然比起智能手机电脑,对于楼市的理解真是知之甚少,目前还在加深理解。

人在不同阶段所关注的点也不一样,关注手机电脑我往往是有这个需求,而当没有这个需求的时候,这样的关注就会发生转移。从高中到步入社会,我的关注一直在变,所以在如今的这个阶段,需求不是那么不好满足的时候,能做的只有努力。

废话说完后,匿。

分享到 评论

每周一记(中秋)

放假第一天(上周六),打篮球,用力过猛戳到手指,又肿又痛的,键盘都没法敲,两天过去了,现在是这个样子的。受伤了...肿是消了,但是只要按压就痛的不行,所以每周一记推迟了。

如果真要写的话,可能手指受伤的这几天,受最大影响的莫过于我的“电竞事业”了😂。

再补上。

分享到 评论

每周一记(发工资日)

上周说前段时间进行的的前端资格考试挂了,等待补考,其实不然,我莫名其妙的就过了。

工作之外

昨天晚上和大学舍友聊天,聊到近况,发现大家都有落差但也随遇而安,无论是传说中的996还是节假日加班,抑或是我这种相对较为轻松的工作。现在大家都明白了,当初被听信企业画大饼,总觉得自己壮志未酬能够好好干一番,结果总结起来,就是我们太年轻。

我的作息时间一般为晚上12点睡觉,早上7点起,8点到公司,这样的话我就可以早点下班并且保持一个较高的所谓战斗力了。可是这个平衡还是被打破了,每天到了12点睡不着,早上还是7点起,睡眠时间不到7个小时,再配合上高强度的工作,担心身体真的吃不消。而这种担忧在昨天显现了,下午1点例常开发报表,开始头痛,我不得不停下来休息,喝喝水洗把脸什么的;到了三点半开完会,头痛反而加剧了,趴了好一阵,又去江边溜达了一圈,透透气,这才缓解,差不多到五点。前段时间看一个视频,程序员因为劳累倒在工作岗位上,我庆幸自己没事,但是为了健康着想,以后得早点睡了。

工作

最近一段时间得任务都是开发报表,手头上报表大概有40张,按照每天开发3个得进度,得三周,并且每一张报表都不一样,遇到的问题也不一样,所以进度有可能被搁置。

例如本周在开发过程中就遇到很多有意思的问题,具体的业务是我需要使用一个下拉选择框,每次选择后会请求不同的数据,问题就出在我根本不知道每个选择项具体的值是什么,因为我们能够获取信息的方式只有Delphi代码,而恰巧这份代码中所有涉及到的中文的地方都被编码过了,并且这种编码方式是公司独有的,且我暂时不能通过沟通的方式来解码,所以一切都得靠自己咯。编码方式看起来像#23398#39#19876这样,是不是很熟悉?如果稍微变化&#23398;&#39;现在知道了吧,所以这就是一种去掉了&;unicode啊。那么这就很好解决了,写脚本转就可以了,至此,问题搞定。

还有很有趣的事情,比如接口返回的数据有一个字段本该是_mainColumn却写成了_mainColmn,或者干脆在一个有四十多个字段的接口中使用了columnX的命名方式,这种毫无意义的字段名称,给到对我来说完全黑盒的接口,简直是灾难😂。

学习

这周仍然只有恨碎片化的时间来学习,并且随着内心变得浮躁,碎片化的时间也被消耗殆尽了。

  1. SPA性能优化。tree shaking打包时将无用的代码删除掉;代码分割,分离三方库等;动态导入(按需加载,预加载等);利用缓存,打包成runtimeChunk等。
  2. TypeScript 学习。在TS官网上看了好几章文档,主要包括数据类型,类型断言,接口等。
  3. 块级格式化上下文(BFC),老生常谈的问题了,看文章温习温习。

其它

  1. 安利一款游戏:《NBA2K OL2》,可以看作《2K17》的阉割版,目前能玩的模式只有王朝和街头,对于我这种不花钱也不是很想变得很强的玩家,偶尔玩玩还是不错的。
  2. 一款叫做PDFelement6 Pro的软件,也许是我用过能够看PDF和编辑PDF最好的一款了,这不是免费的,当然在天朝想免费还是能够办到。
  3. 都2018了JetBrains全家桶仍然可以通过修改系统时间的方式无限期使用,所以我想学Fluter,求打醒,小程序还没眉目呢!

尾巴

上午说发工资了,怎么也得请吃顿好的吧。好,肯德基怎么样?好啊,请。帮我叫外卖吧,外面太热了,不想出去。怎么可能,已经点好了,出去取吧。晕。穿好鞋,飞奔。走到一半,电话响起。您好,你的外卖到了,门口拿一下。飞奔回去,衣服湿透了。

吃着三十多一顿的午餐,真香。

分享到 评论

每周一记(9月来了)

我真是一个坐不住的人。

工作

我以前总以为在别人搭好的架子下写代码是体力活,而现在这个观念的的确确需要改变了。

我的这周的前三天我都在开发新的报表,周四被安排了一天“轮休”仔仔细细检查了已经开发的报表,周五由于没有安排活所以还是开发报表。我一直以为开发报表是一件实打实的体力活,我们不需要知道报表/图表组件具体实现的方式,我们甚至不需要关心页面布局,因为用于布局容器组件都写好了,我只需要读懂把Delphi代码,然后把对应的数据传入到props中就可以了。这当然有难点,理解Delphi需要花上一些时间,细节也要想方设法去实现。可是当大多数报表实现十分类似,熟练过后基本没什么难度,那么这项任务也就对我来说就成了不折不扣的体力活了。

真的是这样吗?

刚开始开发的时候,其实犯了很多错,这些错误都来自对项目的不理解和粗心大意。甚至我在很长一段时间内并没有发掘,直到阴差阳错这些隐含着错误的报表被纳入到发布版本,测试检测出错误的那一刻。

这中间还有一个小插曲,前面说到阴差阳错的报表就上线了,其实是因为配置文件被更改了。因为这个事情在晨会上我还和leader争了几句,当然到最后谁都没有再追究。把这件事讲给小伙伴,所有人都说我情商低,可我总是这样,我会忍气吞声,当然如果三番两次,总会忍不了,我是来写代码,可不是来背锅的。

回到正题,修复了很早以前开发留下的bug外,在“轮休”那天,我好好检查了那些后来开发的报表,虽然没有了功能上的bug,但还是由于粗心大意,比如字段描述中的文字写错,数字格式化不正确等。好在这些报表都没上线,不然这个就真得我背锅了。

周五开发了一个十分复杂的报表,它让我意识到,这不仅仅是一份体力活。我差不多花了5个小时来完成这张表,除了非常难懂Delphi代码外,限制条件也十分多,因为需要实现具体的功能,发现已经封装好的组件并不能满足需求的时候,又需要去沟通增加需求,并且在开发的过程中,因为实在架子下工作,反而为了实现某些功能,需要额外的想办法。

好吧,说了这么多,我承认我现在做的工作不仅仅是体力活,当然就算是体力活,我也要把任务做到极致。

学习

这周大块大块空余的时间并不多,在闲散的时间里,我主要通过在掘金github看文章来学习。

文件获取和上传

  1. 使用input[type='file']模拟点击上传
  2. 使用拖动/放置事件来上传drag/drop
  3. 剪切板的粘贴事件

高阶函数(HOC)

函数作为参数传入/作为返回值返回。

  1. AOP面向切面编程。把与业务无关的模块抽离出来,然后动态织入到业务中去。
  2. 柯里化(部分求值)。函数并不会立即求值,而是返回一个另一个函数,已经传入的参数活因为形成的闭包而得以保留。
  3. 函数节流与分时函数。
  4. 惰性加载函数。

其它

  1. 有关于getBoundingClientRect的学习。
  2. 哈希碰撞与生日攻击。

框架/库/工具

  1. store.js 多浏览器实现的本地存储库。
  2. pica 一个很好用的图片压缩工具
  3. tween
  4. Vue.js 基础知识重温

尾巴

周五的前端资格考试已挂,期待下周补考。

最近绝地暖暖开挂的人太多了,把把都有大哥,快要弃游了。

最后:无糖全麦麦片真好吃🤮

分享到 评论