262022.12

随着年龄的增长,学习成为一件越来越奢侈的事情

“活到老,学到老”听上去是一件非常容易的是,它告诉你,只要想学习,什么时候都不晚,但是在现实生活中,这种自以为是的哲学,本质上是钳住人思维的一种毒药。人的主要学习时间是在我们普通人常见的读书阶段,大学毕业之后,很难再持续学习。但当我们意识到这一点的时候,已经回不去了。“人最重要的投资,就是学习。”我觉得这句话及其重要,而且不仅适用于“投资自己学习”,也适用于“投资家人、下一代学习”。当然,投资是有风险的。我以前觉得,小孩子嘛,能否学的好不一定靠成绩体现,具有一种浪漫主义色彩的自由学习论。但随着年龄的增长,我们的很多思想都发生了变化。你可以说随波逐流,但我称之为逐渐意识到了前人的智慧。

学习本质上就是一种能力。在我们青年时期,我们的首要任务是学习“如何学习”这项能力。当我们习得这项能力之后,我们便可以用它“学习”其他的知识。因此,我们可以看到以前很多学渣,现在混的很好,因为我们在学校的时候,我们自以为是的认为考试成绩是以自己掌握的多少知识来进行打分,而实际上,我现在发现,考试成绩是以掌握知识与掌握知识的时间比来进行打分,所以哪些拥有100分的同学,应该思考一个问题,如果自己的100分是建立在100天的基础上,那么那些只用了3天临时抱佛脚得到70分的学渣,真的是学渣吗?本质上,我们的考试结果,是“学习效率比”,而学习效率比的本质,就是“学习的能力”。掌握了“如何学习”的能力与掌握了“通过学习获得的知识”相比,前者不仅简单的多,而且有利的多。任何的知识都会忘记,但是一旦掌握了“如何学习”,那么任何忘记的知识都可以快速重新获得,而如果我们把大量的时间用来掌握“通过学习获得的知识”,那么最后我们只能获得一堆很快就会忘记的知识,而不是获得一项终身受用的能力。

如果我们的工作不是做文学研究,就没有必要研读小说的每一个字,而是知道故事的梗概和作者的核心思想即可;如果我们的工作不是建筑设计师,那么就没有必要挖掘每一页照片和梁柱结构的细节,而是只要像浏览杂志一样从各种精美的构图中找到灵感即可;如果我们的工作不是系统工程师,那么就没有必要对每个自然段的来龙去脉理解的那么透彻,而是能够对章节大标题的主要内容和涉及问题有轮廓的理解即可。书尽读不如不读。我们只需要对自己所在专业,或者自己正在从事的工作所需要的知识,进行深读深专,而不应该纠缠于那些用于扩展思路的书籍中的细节。同样,面对开源项目也是这样。

不过,现在最关键的是,我们没有时间来进行如上的操作。除了平时工作时间占据了过长,生活琐事无休无止外,还有一个不得不面对的问题,就是社交媒体和短视频让我不“吸上两口”没法过好这一天。以上这些问题阻碍了我们在随着年龄增长过程中的持续学习过程。“持续学习”和“学习”本质是不同的,“学习”的本质是“学习如何学习”,而“持续学习”的本质是“通过发挥掌握的如何学习的能力,快速掌握目前自己需要的某些既能或想法,以开阔自己在工作或某项事务中的思路,帮助我解决问题”。如果人生没有“学习”,那么就只会停留在离开学校的那一刻,那一刻你什么样,就什么样;但如果人生没有“持续学习”,那么就没法解决问题,生活问题积累越多,人就会崩溃。我小时候经常听到“学习型社会”,意思是人们通过持续学习解决自己的生活问题,从而让社会和谐。但随着经济发展,这种提法被抹掉了,似乎对于某些人而言,矛盾越多,利益越大。

我前不久买了几本书,觉得应该很快可以看完,可今天发现,半本都没读完。人啊,有的时候对自己太自信,是对自己的侮辱。说来说去,还是因为懒,社交媒体短视频“吸”的不亦乐乎,哪还有心思读书。等到疫情来了,阳了,嗯,没时间刷短视频了,时间都用来照顾家人了,至于学习,早丢到九霄云外。人呐,有的时候有时间,觉得自己很富有,直到事情一来,才发现,自己不是富有,自己是蜉蝣。

23:21:13 已有0条回复
222022.11

有什么是vue能做到而react做不到的?

我在知乎讲react是全量更新脏检查,很多人听不懂,说我在乱说,我也懒得理会。我有一个例子,这个例子里面,我们可以看到react是全量更新,而vue可以做到定点更新(当然,vue是不是实现了另说)。我们来看下结构:

<a>
<b>
<c>
<d>
<e>

在这个结构里,我希望以直观的形式,去监督某个值变化,来实现<d>的更新,注意,这里<d>是一个node。在react里面,我们的写法可能是这样:

<SomeComponent attr={this.props.tableTips} />

如果想要更新SomeComponent的渲染结果,要么SomeComponent内部做了什么处理,让其自身可以根据某些条件重新渲染,要么必需是当前这个组件重新渲染,让SomeComponent被动重新渲染。但是为了重新渲染<d>而把a, b, c, e都重新渲染一遍,代价太大。有没有什么办法,在不重新渲染当前组件的情况下,重新渲染<d>这个片段?

一种方法是把d再封装一层,作为一个高阶组件,把需要监听的内容传入该封装好的组件,由组件来监听和重新渲染,这样就可以做到局部重新渲染,而不需要整个重新渲染。但是,这种方法也很笨,因为不可能存在一种万能的高阶组件来达到这个目的,所以到处去写这样的组件也不好。另外,高阶组件的使用,割裂了阅读组件结构的顺序,让我们在后期的维护中,不得不跳来跳去看代码。

而vue就可以做到基于模板来优化这一需求。例如:

<some-component attr="tableTips" />

就可以在模板编译时把tableTips作为可动态读取的片段,这样,每次在渲染some-component时,读取到的attr都可能是新的,从而带来重新渲染。

通过这个例子,我们就可以发现,虽然jsx具有非常强的表达力,但它始终基于原生语法,而作为模板语法,当然具有更强的能力。

11:41:01 已有3条回复
  1. 首先React肯定能局部渲染,不然官方文档就是笑话(https://reactjs.org/docs/rendering-elements.html#react-only-updates-whats-necessary)。

    Vue、React的渲染都是要通过vnode diff做更新,Vue也没细粒到标签级别的vnode,只能说Vue的tempalte编译期间做个太多的aot优化,Jsx因为要使用js/ts的全部能力,注定做不了很好的优化。
    #1253 断崖上的风 2022-11-22 13:33 回复
  2. 你说的那个局部渲染叫commit,不是一回事,麻烦看懂再来讨论
    #1256 回复给#1253 否子戈 2022-11-22 22:27 回复
  3. 如果你说的是前面的render过程,你说的对,默认情况下调用setState都会导致组件及其子孙的render方法的调用。

    这就是React跟Vue的不同之处。Vue基于订阅发布模型的数据响应可以在数据变化时准确知道应该通知谁要做更新;React推崇函数式,没有依赖收集过程,调用setState后无法知道依赖的数据是否更新,默认只能进行render,也因为粗暴的渲染机制,所以也才有了shouldComponentUpdate这样的api。

    以上是否就是你想说的?

    至于你说的脏检查,应该说的是渲染组件及其子孙吧,毕竟对应state,React没做任何检查。
    #1259 回复给#1256 断崖上的风 2022-11-26 10:32 回复
262022.10

物联网开发指南

072022.9

在前端,你聊建模和分层,总会被人烦

过去两年,我遇到比较尴尬的情况,就是无法很好的解释建模和分层的总要性和便利性,因为前端领域关于这块的东西共同语言比较少,大家都是在专注于视图层的新技术研究,而在更广泛的编程范式、原则上,比较少去探讨,因此,每当我聊到这个东西的时候,无法快速的聊核心思想,必须在外围给听者建立场景,举例子,但是这些例子在日常前端开发中,又不是非得按你这套才能实现,所以听者往往带着自己的想象和经验先对例子进行脑海复盘,然后来评价所谓建模、分层都太复杂,这就是非常尴尬的场面,你还没有开始聊建模和分层本身,就已经在前期预热阶段被听者否定,那么,后续所讲的任何话都没有任何意义。

前端建模和分层是非常重要的,它本身并不复杂,也不会增加工作量,问题在于它所要求开发者具备的思维方式、理念是比较庞大的,因此往往被抵触。如果你在完成一个需求时,提交代码进行review,直接被对方打回来,理由是不符合我们的开发理念,你一定也会很生气。但是这种东西往往需要有一个强有力的人去推动,他会去review每个人写的代码,是否按照分层理念去写,是否符合开放封闭原则,是否遵循整洁设计,是否考虑周期等等。而要确保这些,review人总需要花比较长的时间,除了那些对软件质量要求高的企业,很少有团队这样去做,包括现在国内的一线大厂,都不舍得这样去做,“我花钱来就是请你看别人代码的吗?”对他们来说,快速写出功能上线,才是最重要的,至于代码质量早被抛诸脑后,等到出了事,就“杀一个程序员祭天”。

在这样的环境下,把前端应用当作软件,追求其稳定性健壮性,就非常不合群,因为稳定健壮的软件,会淡化开发者本身的成就,只会把最终的成就摊平到日积月累的时间长河中去,除非是某个强有力的tech leader从古老的开始就把它当孩子般培养,否则没有人愿意做一些短期内看上去根本无法凸显自己价值的事,而且更可悲的是,软件健壮稳定了,用户本身根本没有任何感知,而没有感知就会直接忽略你的劳动成果,相反,那种到处漏洞,你天天救火的,在甲方看来,还觉得你态度好,能力也够。

所以,我已经在考虑避开聊建模分层这个话题了,我想接下来我会去研究一个新的领域,这个领域得有一定的需求,同时又有深度。

20:56:18 已有3条回复
  1. 整体上前端处于鄙视链底层也是有从业人员自身的原因。
    #1221 断崖上的风 2022-09-08 09:58 回复
  2. 太赞同你说的这些了,然后我又想起了这篇文章:http://www.lowcoder.cn/best-practice/detail?fid=BZxGdCrUX2。不知道你怎么看待目前的应用技术发展,比如近几年涌现的微前端,低码和跨端技术。
    #1222 戡玉 2022-09-08 11:09 回复
  3. 这块确实全在细节里,不容易被察觉。 但一个需要天天救火的项目和一个平稳运行的项目,甲方应该也是能看出差别的。
    #1225 1188 2022-09-16 18:29 回复
112022.8

typescript class static静态方法内this类型约束

typescript中只能对class成员进行约束,无法对static方法进行约束,我们通过一些特殊方法来实现。

/**
 * 用于得到某个class的构造函数,例如:
 * class Some {}
 * Constructor<Some> -> Some类型的构造函数,也就是class Some本身
 * 用处:
 * class Some {
 *  static fn<T>(this: Constructor<T>): void; // -> this: Constructor<T> 规定了该静态方法内的this类型,由于类型推导,此处的this被推导为Some本身
 * }
 */
export type Constructor<T> = new (...args: any[]) => T;

通过Constructor这个util,我们可以获得一个class的type,也就是class构造函数本身。所以,当我们在静态方法中进行this约束,或返回this时,可以这样:

class Some {
static create<T>(this: Constructor<T>): T & { a: number } {
// @ts-ignore extends 要求特定结构
return class extends this {
a: number = 1;
}
}
}

通过这样对static方法进行签名,就可以在使用时很好的约束类型。

10:30:33 已有0条回复
252022.7

Javascript如何区分function和class?

JS里面,普通的function也可以通过new进行实例化,成为一个对象。而ES6引入的class是个阉割版本,导致class缺失了作为class的特征,class可以理解为封装的function。所以,在JS里面区分一个值是function还是class是比较麻烦的,目前TC39已经在考虑加入[[FunctionKind]]来进行区分。目前,我们可以通过一些办法来区分,如果是在纯浏览器环境下,我们有如下的一些办法:

  1. 通过字符串

把function或class与空字符串连接,得到字符串就可以看出来。

  1. 通过prototype的discriptor

对于class A而言Object.getOwnPropertyDescriptor(A, 'prototype')的writable为false,而对于function a而言,Object.getOwnPropertyDescriptor(a, 'prototype')的writable为true。

  1. 通过arguments

对于class A而言,A.arguments会报错,而对于function a而言,a.arguments为null。

这些方法都抵挡不住现在很多编译工具会把class编译为ES5的function,这导致这些特性都失效,所以能不能用还要看你项目里面的编译是怎么做的。

18:21:59 已有0条回复
162022.6

浏览器JS拉取远端图片上传到自己的服务器

我们经常遇到这样的情况,我们填写某个表单的时候,需要上传一张图片,但是我们的业务会有这样一个逻辑,我们这个表单会默认拉取一些数据进行填充,用户在这些数据基础上进行修改,这些拉取的数据,有的时候会使用外部数据里面的图片,此时,我们需要把外部数据里面的图片保存到我们自己的服务器。这种情况有两种解决方案,一种是纯后端做,一种是前端来做。我分享一种前端来做的方案。

其实拉取数据进行填充的本质,就是不破坏我们原有的表单交互流程,前后端保持高度一致,避免多态。一般的交互是提交表单的时候提交上传图片,把上传后得到的文件 id 同表单数据一起保存。这个过程是比较顺畅的,前后端理解起来非常简单。但是,如果这里强行改变这个逻辑,为了兼容多态,后端支持传 url 字符串,实际上,这也会给前端带来比较大的处理量。因此,我们的目标是,利用图片 url 转化为本地的 File 再提交到后端,走一个简单的流程,只不过在拉取数据这一步做一个处理。

那么,怎么把外部图片转化为一个浏览器里面的 File 呢?

function getImageFile(src) {
    return new Promise((resolve, reject) => {
      const img = document.createElement('img');
      img.style.position = 'fixed';
      img.style.top = '-10000px';
      img.crossOrigin = 'anonymous';
      img.src = src;
      img.onload = () => {
        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        const context = canvas.getContext('2d');
        context.drawImage(img, 0, 0);
        canvas.toBlob((blob) => {
          // @ts-ignore
          // eslint-disable-next-line no-param-reassign
          blob.lastModifiedDate = new Date();
          const name = src.split('/').pop();
          // @ts-ignore
          // eslint-disable-next-line no-param-reassign
          blob.name = name;
          const file = new File([blob], name, { type: blob.type });
          resolve(file);
        });
        document.body.removeChild(img);
      };
      img.onerror = (e) => {
        document.body.removeChild(img);
        reject(e);
      };
      document.body.appendChild(img);
    });
  }

这里用到了以下知识点:

  • 由于是跨域的图片,所以不能直接通过 fetch 来得到 blob,于是我们利用 canvas 来加载远程图片,再利用 canvas 的接口来转化数据类型
  • canvas 要能够 draw 远程图片,必须加上 crossOrigin = anonymous
  • 通过 toBlob 之后得到 blob,再使用 new File([blob]) 就可以把 blob 转化为一个本地的文件,用在提交中
  • 最后需要注意,提交文件需要使用 content-type: multipart/form-data 进行提交

通过这个处理,我们就可以在浏览器里面把外部图片转化为内部图片进行使用,这样就节省了后端去做类似回源CDN之类的建设。

11:19:11 已有0条回复

nginx获取顶层目录,重写至顶层目录下的index.html

最近再部署测试环境时,考虑到我们的特殊需求,希望按模块进行发布测试,每个模块被放在一个文件夹中进行发布,所以不得不进行重写,来让请求fallback到目录下的index.html. 例如当我们访问 /some-module/xxx/xxx 时,实际上fallback到 /some-module/index.html。而我们发布测试模块时增量的,所以可能过两天会发布一个 any-module 来测试,不大可能写死在 nginx 的 config 中。所以,我的想法是,在 nginx 中实现一种配置,可以实现这种能力。具体实现如下:

location / {
try_files $uri $uri/ index.html @rewrite_uri;
}

location @rewite_uri {
if ($request_uri ~ /([a-z0-9_-:]+)/) {
set $tmp /$1/index.html;
rewrite ^ $tmp break;
}
}

使用 try_files 来在 nginx 中实现 fallback 的能力。增加了一个 @rewrite_uri 的规则,这个规则里面,对 $request_uri 进行匹配,通过正则把顶层目录取出来,在用 rewrite 进行规定。

通过这种方式,我们不仅可以去除顶层目录,而且可以实现我们想要的重写效果。

08:12:06 已有0条回复
112022.5

Typescript从联合类型转化到交叉合并后的对象类型

我们有如下一个联合类型:

type union = { a: string } | { b: number } | { c: boolean }

如何得到最终的类型:

type obj = {
  a: string
  b: number
  c: boolean
}

如果是用js来写,不要太简单,但是typescript没有对union的遍历能力,所以只能走其他办法,经过探索后最终得到如下解:

type GetUnionKeys<Unoin> = Unoin extends any
  ? {
      [key in keyof Unoin]: key;
    } extends {
      [key in keyof Unoin]: infer K;
    }
    ? K
    : never
  : never;

type UnionToInterByKeys<Union, Keys extends string | number | symbol> = {
  [key in Keys]: Union extends any
    ? {
        [k in keyof Union]: k extends key ? Union[k] : never;
      } extends {
        [k in keyof Union]: infer P;
      }
      ? P
      : never
    : never;
};

type UnionToInter<Unoin> = UnionToInterByKeys<Unoin, GetUnionKeys<Unoin>>;

在使用时如下:

type obj = UnionToInter<union>

这样就可以实现我们的目的。我写了两个原子范型,GetUnionKeys用于获取联合类型里面的全部key,UnionToInterByKeys是关键,用于基于前面获得的keys再得到最后的对象。

不过这个方法存在一定的风险,假如联合类型中存在相同的属性,那么可能存在不确定性,比如:

type obj = UnionToInter<{ a: string, b: number } | { b: boolean }>

我们最终就会得到:

type obj = {
  a: string
  b: number | boolean
}

但是我们再开发中,常常会让后面一个覆盖前面的,我们期望得到的是:

type obj = {
  a: string
  b: boolean
}

我之前在读一些博客的时候,有同学提到可以通过冲载机制得到联合类型中的最后一个元素,但是我没有找到这篇文章,如果你看到,请在下方留言给我。

20:55:12 已有2条回复
  1. 深奥, 我只会合并两个, 这种批量的就完全不会了.
    type UnionZ<A> = {
    [k in (keyof A | keyof B)]: k extends keyof B ? B[k] : k extends keyof A ? A[k] : never;
    };
    #1195 1188 2022-05-17 11:30 回复
  2. #1209 千劫 2022-07-21 15:11 回复
102022.5

Nodejs检查端口是否占用,如果被占用使用一个随机端口

在日常开发中,我们常常要自己启动一个server来进行本地调试,但是如果写死端口,就会导致端口冲突,怎么解决呢?

/* eslint-disable @typescript-eslint/no-require-imports */
const net = require('net');

function checkPortUsable(port) {
  return new Promise((resolve, reject) => {
    const server = net.createConnection({ port });
    server.on('connect', () => {
      server.end();
      reject(`Port ${port} is not available!`);
    });
    server.on('error', () => {
      resolve(port);
    });
  });
}

function randomNumByRange(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function findUsablePort(port, minPort = 10000, maxPort = 65536) {
  const retry = () => {
    const port = randomNumByRange(minPort, maxPort);
    return findUsablePort(port, minPort, maxPort);
  };
  return checkPortUsable(port)
    .then(() => port)
    .catch(retry);
}

module.exports = {
  findUsablePort,
  checkPortUsable,
};

具体使用的时候,调用findUsablePort即可:

findUsablePort(4000).then((port) => app.listen(port))

如果4000可用,就会直接用4000,否则就会使用一个随机端口。

11:20:29 已有1条回复
  1. net.createConnection({ port })这种模式是建立客户端尝试连接该端口, 但是如果该端口已经被使用,但防火墙做了限制,应该也会触发连接错误; 会造成误判;
    用创建服务监听的方式应该更完善一些.
    #1220 躁动de气球 2022-08-31 17:33 回复