我来说一句反对typescript的话
世界上刚有typescript的时候,我就开始关注它了。我很明确,它是一门新语言,只是和javascript是近亲。在它的介绍文档中,我看到private关键字,甚是兴奋。但我开始去用它的时候,发现它的private的骗人的。除了private骗人外,它的所谓类型检查系统也有欺骗性。现在很多人已经叫嚣着,不懂typescript就是不会前端。说实话,除了对typescript不满意,我连对ES标准的#符也不满意。不是我挑剔,是有些东西人会天然不喜欢。
读完这个答案之后,我更加确信,原来自己的反感是有根源的,并非我有问题,而是typescript有问题。有什么问题呢?先来说一些旁的不喜欢点:
- 繁琐的类型定义方式,明明是一门新语言,非要按照js的语法来写,但是又发明一些莫名其妙的新语法
- 类型声明实在受不了,我知道使用 : 是受了其他语言的启发,但是真正好的类型声明很明显要放在变量前面
- 对象属性的类型声明?呵呵,太恶心。: 和 as 能让人写作烦躁10倍。
- any?哎。。。
- 结构类型检查!WTF
- 据说类型检查能减少bug,但是,你怎么能确保你编写的类型本身没有bug?
- 降低效率,用在解决(编写)一个类型问题上的时间,可能够我写完2个需求
- 使代码可读性降低,你需要在阅读代码过程中,跳到类型定义的部分去阅读,阅读完再跳回来来,一去一来,我是谁,在哪里?
- 增加运行难度,据说deno支持直接运行ts,但是实际上,还是先翻译为js后执行
我们来看上面那篇回答中的一个经典案例:
interface A { x: number; } let a: A = { x: 3 } let b: { x: number | string } = a; b.x = "unsound"; let x: number = a.x; a.x.toFixed(0);
恶心死你。你不是静态类型检查吗?给你查,查破了你能告诉我 bug 来自哪里?人生啊,不要相信所谓了“在准确和效率之间找平衡”,忽悠。别的类型系统,之所以成立,是因为别的语言需要先编译,后执行,有健全类型系统,类型声明不用慌,而且类型本身就是运行时的。从我的不成熟的想法来看,不在运行时的类型系统,都是扯虎皮。使用typescript嘛,和语句末尾使用使用;结束一样,技术不到位,有;也避免不了各种错,技术到家,没;照样优雅健壮。
我理想中的js变量类型声明:
int a = 1 bigint b = 299300002390809238n float c = 2.2 bigfloat d = 4.394085943789534809830543l string e = 'xxx' Date date = new Date() Promise p = new Promise(...) // 多层结构的对象,可以在内部属性上独立确定类型 object o = { string name: 'my name', int age: 10, // 用<>表达其内部结构 array<[ object<{ string name, number price, }> ]> books: [ // 纯值,当然,也可以在内部进行类型定义,类型系统自己推演 { name: 'book name', price: 6.6, }, ], } let x = 1 // 相当于 any const y = 'ok' // 不可变 var z = null // 带变量提升的 let
如果一个变量想要发生类型转化。。。不可以的。
int a = 10 string b = a + ''
这样操作是唯一的途径吧。
但是实际上,在运行时,你怎么知道后端接口会返回给你什么?大部分情况下,null 值无法避免。
// 用 type 关键字声明和定义类型 type ResponseType = object<{ int code, object data: { string name, number|null age, // 支持 null array<[ // array 内部可以有多种结构,只要元素满足其中之一,就可以通过类型检查 object<{ string name, number price, }>, object<{ string name, int pages, }>, ]> books, }, ?string error, // ? 开头表示可能不存在,存在的情况下才遵循类型 }>
现在我要使用它:
fetch('/api').then(res => res.json()).then(string (ResponseType data) => { // 如果data的类型检查不通过,直接抛出 TypeError })
看看函数:
function a(int x, int y) int { return x + y } object o = { a(int x, int y) int { return x + y }, } class Some { string _name = 'my name' static get name() string { return this._name } } // 直接使用原生 function 范型(借鉴 python 的 lambda 表达式) function<int, int: int> sum(a, b) { return a + b }
// 声明一个函数类型(借鉴 python 的 lambda 表达式) type func = int, int: int // 遵循将类型放在变量前面的规则,func 是类型名,sum 是函数(变量)名 func sum(a, b) { return a + b }
再搞一个泛型耍耍。我在这篇文章中已经说过了,泛型,实际上就是函数:
// 用 <> => 定义泛型,其中 book<a, b> 中的 book 是泛型的名字,<a, b> 是替代符,=> 后面是返回的形式化结果
type book<a, b> => object<{
a name,
b price,
}>
怎么用?这样子啊:
book<string, number> book = {
name: 'xxx',
price: 12,
}
看看泛型的演化:
// 定义一个泛型,泛型的结果是一个 object 描述,(而非一个类型) type a<x, y> => { x a, y b, } type b<z> => a<z, z> // 直接运行 a<z, z> 将得到的结果作为 b<> 泛型结果 // b<string> 的结果,作为 object<> 的参数 object<b<string>> o = { a: 'xxx', b: 'yyy', }
实际上,我不大推崇这种声明,因为通过这样的声明,我并不能马上看清楚在声明 book 这个变量时,它的结构,我更喜欢:
object<{ string name, number price, }> book
即使这个时候,我并不给 book 赋值,这样声明 book 这个变量我就知道它的内部结构是啥。虽然这样我可能需要写很多重复代码,但是相对来说,理解成本更低。
当然,在函数上,泛型确实还是有好处:
function a(int|string x, int|string y) int|string { return x + y }
这种声明明显不好,因为你怎么知道 x=1, y='a' 这种情况不会发生。所以,这种情况,要使用泛型:
// 通过泛型,实现函数参数和返回值统一类型 type f<T> => T, T: T f<int|string> a(x, y) { return x + y } // 或者通过一个匿名的泛型来简化写作 function<(T => T, T: T)<int|string>> a(x, y) { return x + y }
作为一个特殊结构,<> 内是一个复杂体。因为任何 <> 前面的东西,会被认为是一个范型而被运行,因此 (T => T, T : T) 将会被作为范型,传入 int|string 运行,得到的结果作为 function 范型的参数。
type some = function type some<T> => T, T: T
上面这段代码表示,我们可以直接使用 some 作为类型,也可以使用 some<xxx> 作为类型,some 既是类型,也是范型。
这一套东西,是在运行时做的,这样的定义,岂不是爽歪歪。当然,我依靠 tyshemo 还是有可能把这套东西给实现的。对了,有兴趣可以看下 C# 的类型系统。
重新理解 rxjs 的事件流
在做上一期 robust 的时候,受到这篇文章的启示,重新理解了 rxjs 的事件流这个概念。结合响应式编程范式的两个目标,对 rxjs 的理解又有了新的认识。
rxjs 是观察者模式、迭代器模式、函数式编程的结合体。其中,我一直对迭代器模式这个点不是很理解。在阅读上面那篇文章之后,我的思维豁然开朗了。在此之前,我只能强迫性的理解,new Observable 本质上是在决定什么时候进行迭代动作,也就是调用 next。但是,迭代器本身是怎么来的呢?我刚开始默认认为,迭代器需要用户自己构造。但在读完上面那篇文章之后,有了新的见解。rxjs 将一个对象的某个事件(例如 DOM 节点的 click 事件),在时间上的发生,抽象为一个虚拟的数组,它的示意图:
这个虚拟数组,和普通数组不一样,它不是在一开始就确定的,而是随着时间的流逝,不断往数组中新加元素,知道该对象被销毁,该数组也自动被销毁。而往数组中新增元素的过程,就是 next 被调用的时候。你可以想象成,这是一个无限个数坑位的列表,一个 click 发生,一个坑位就被填了,这个填坑位的过程,就是 next。而这个虚拟的数组,就是事件流的抽象表示。
既然是一个数组,那么就可以调用迭代器方法,rxjs 内置了非常多迭代器方法,比如常见的 map, filter, reduce。而 new Observable 本质上就是在构造这个迭代器数组。叫 Observable 很傻,还是叫 Stream 比较直观。
纯库
随着时间的流逝,越来越对很多看似花里胡哨的库感到失望,其中以 react 和基于 react/vue 所做起来的库为最。对 vue 报以热烈的喜爱,在于,它的开箱即用性和易用性。虽然是一个框架,但你完全可以把它当作一个库来使用。这就是我认为的纯库。所谓纯库,就是 cdn 加载进来,立即可用的库,不需要复杂的构建环境,不需要上下游的 babel 插件,只需要一个 <script src> 就可以使用其功能的库,而这样的库,以 jquery 为最,以 bootstrap 为最,这才是 web 开发的本质,简洁的声明式编程。react 给 web 开发开了一个坏头,但这和 react 自己没有太多关系,而是类 react 的 web 框架,以及趴在 react 上的 web 组件。react 不是最好的 react 库。但要知道,react 的设计理念,是要抽象视图层为 virtual dom,从而可以在跨平台上发挥作用,因此,它有自己的一套事件系统,如果不是为了跨平台,没必要自己做事件系统,而那些只管用 react 做 web 开发的开发者,实际上,和 flutter 做 web 应用一样憋足。而 vue 一个 cdn 挂载进来,把各类插件用 Vue.use 加载进来,马上就可以用。这就像 jquery 一样,插件加载进来就可以用,真正做到的 web 开发的便捷性。失去便捷性的 web 开发,应该交给另外一门语言去做,比如 ts,甚至 go,总之,便捷性是我认为 web 开发的真谛。为了工程的稳定和可持续发展,引入庞大的构建体系,这没有错,但是如果随便一个东西,都一定要走这么庞大的构建体系,那么真的是背道而驰。
-
是的,现代前端的工具链依赖真的是太重了,所以吾辈选择 vue-cli/cra 这种脚手架工具绕过去
-
同是前端,多交流呀#920 回复给#919 否子戈 2020-04-10 13:15
有生之年,很难想象,那个曾经在社区仰望的技术大神,就这么草草的离开了人世。回顾司徒正美一生,最主要的成就或许在于亮点:1.推出 avalon 框架,并且基于该框架思想后续发展出来的 anu 等框架或 ui 库;2. 在 DOM 编程上所展现的黑魔法技巧,以及传道授业所写下的大量技术文章。
avalon 是在 virtual dom 出现之前,能够兼容到 ie6 的 mvvm 响应式 js 框架。当然,要兼容一些特定浏览器(包含低版本 css),还需要加载一些额外的 js 文件,但是,它自己会自动加载。它在用法上很像 angularjs,但是采用了 Object.defineProperty 来实现响应式。在 react, vue 出现之前,avalon 是一个令人感到神奇的框架。但是随着 virtual dom 的统治,他也在 avalon2 中采用这项技术。虽然 avalon 早期版本令人惊叹,但后续紧跟潮流,也不免让人有些唏嘘,脱离了 DOM,他的想象力也被当红的技术所框定。
36 岁去世,未婚。假如,假如时间允许,他或许还会出一本新书,或者在框架上突发奇想提供一个新的思路。但是,总归是没有假如。这世界来过,也留下了些东西,等待年月渐渐将这些痕迹抹去。再过几年,便不会再有人记得这个名字,随之而去的,还有 avalon 框架。
在感叹年轻的开发者们,要注意自己的身体,要用于对 996 说不的同时,作为档案专业毕业的学生,我一直对“档案是人类社会的记忆”这一点耿耿于怀。为什么我们不能把这个人给我们带来的都留住?再过一段时间,他的博客域名过期了,他的开源项目停更了,那他就像未曾来过这个世界一样,一切烟消云散。有没有一种方法,让人类的思想瑰宝可以永世长存?
(data.key = value) !== value
JavaScript 这门语言,让开发者感受编程乐趣的方式多种多样。今天掌握了一个新技能。我们来看下:
data.key = value var a = data.key value === a
这段代码平淡无奇对吗?但是,如果我告诉你,最后返回的结果是 false,是不是很好玩。
很显然,在这段代码前面,我还干了一些其他事情,导致最后的比较返回 false。我们来看看我都做了什么:
Object.defineProperty(data, 'key', { get() { return this._originalData.key }, set(v) { this._originalData.key = { ...v } }, })
当前,在这之前,我还处理 _originalData 等,但这一段代码就可以了。也就是说,我规定了 key 属性在被赋值的时候,都要做怎么样的处理。这是已经到元编程的层面了,所以说 js 好玩。
这不单单是好玩的问题,而是在实际编程中,我们可能遇到的问题。你可能会问,谁会在自己的项目中搞这种蛋疼的事?不过很抱歉,很多处理数据的库,都会这么干,用来解决一些特别的逻辑。我想有一天你肯定也会这么干。我们来看一个可能出现 bug 的实用场景。
const files = [] const file = input() files.push(file)
if (files.find(item => item === file) { // do something }
类似的代码我们经常用吧。把一个元素加入到一个数组中,需要的时候,又在数组中,去找这个元素。这么干,说实话,经常出事。所以,我建议你最好 files.find(item => item.id === file.id),用一个字符串或数字作为唯一标志。不然翻车别怪我没提醒过。
为什么会这样呢?
你怎么知道 push 没有被修改过?
files.push = function(v) { return Array.prototype.push.call(this, { ...v }) }
所以,你敢随便自认为吗?js 编程就是这么任性。甚至有些人直接在原型链顶端搞事情,你扛得住吗?不要用一个没看过源码的不可靠第三方库!!!这是老人言。现在有些人,非蠢即坏,你防不住的。另外,善用 Reflect 或许也是忠告。
虽然你可以
const pushed = files.push(file)
这样可以得到 push 之后的真实数据,然而,然而,你怎么知道在类似 angular 之类的框架中,这些数据有没有被篡改过。总之,少年,我劝你善良。
Robust 登陆 iTunes,支持泛用型播客
今天收到苹果推送的邮件,Robust 已经可以在 PodCast 上搜索到了,赶紧去试了一下,真的可以了。如果你一直关注 Robust,可以直接在 iPhone 上找到 “PodCast” 或者叫 “播客” 这个应用,打开之后,搜索 “Robust” 关键字,就可以找到我们的音频了。大家订阅关注吧~
另外,你可以使用泛用型博客客户端,订阅如下 RSS 地址:
http://www.ximalaya.com/album/34869197.xml
这样也可以及时获得我的更新。我对自己的要求是,Robust 不闲聊生活,也不传递负能量。让我们专注聊技术,专心关注技术话题,我不会去追热点事件,但我会去聊背后的技术,专注于技术,也许不会获得很多关注,但是我可能也不需要那么多关注吧。
.nojekyll 原来是告诉 git 仓库文档系统不要忽略 _ 开头的文件。之前在做 docsify 的时候,一直没法加载 _sidebar.md 最后改成 index.md 之后搞定了,但是为啥 _sidebar.md 就不行呢?原来如此。
wordpress 模板层次结构和优先级
因为想要体验yarn的优点,于是想安装yarn来使用,也可以顺带装逼。然而,当我使用官方的安装教程,一一试了一遍,全告失败。最后,使用brew安装它之后,不但没安装上,还把npm给干掉了,node好像也出了问题(看提示是要升级成一个新版本的node的样子),我mlgb,我就想试一试你,没想你还要接管我的node,劳资就只要这个版本的node啊!npm命令直接被搞死了,报错。
最后,直接放弃yarn,什么玩意儿。还得重新去node官网下载对应的版本来安装,艹!另外,百度网盘离线下载加超级会员,下载真香。
準確就是效率,先刷掉了一大把靜態檢查會產生錯誤,然後才是執行時期的錯誤再另外排除不就好了?
ts本來的任務就是輔助ts,所以在類型系統上本來就會有妥協,所以才會出現混合的類型,所以在實務上還是要做好類型把關,針對多類型混合狀態就做好執行時期檢查本來就是工程師的該做的事情,檢查沒做再把鍋甩改ts沒檢查到,幹嘛呢?
除了复杂的类型系统,ts也不能辅助js开发,而是阉割js开发,说是js的超级,实际上只能用js的子集。在大部分情况下,你不仅要在为业务逻辑撰写代码过程中,浪费大量的时间在非业务之外的代码上(当然,收益也是有的,而且是持续性的),全是噪音,而且有的时候,为了解决一个IDE报错问题,你需要花费小半天的时间去搞明白逆变原理,再来思考我要做怎样的体操才能得到我想要的类型检查,说真的,扯淡!
当然,不得不承认在前端领域,ts占有一席之地,有好的地方,但是你要说有绝对地位统治地位,我觉得是瞎扯,es标准一升级,你ts可能就要靠边站,比如你private,我来一个#,你怎么办?你得private #?不是瞎扯淡吗?
我写了一套js的运行时类型检查系统,你可以通过 import { Dict } from 'tyshemo' 这样的形式来使用,虽然运行时类型检查消耗性能,但可以在特定情况下再做。可以说,表达类型更直观。