| |
|
阅读网 -> 历史人文 -> JavaScript 这种语言特性十分糟糕的语言流行起来是不是一场灾难? -> 正文阅读 |
|
[历史人文]JavaScript 这种语言特性十分糟糕的语言流行起来是不是一场灾难? |
[收藏本文] 【下载本文】 |
声明:不要把 TypeScript 拿出来说,因为 TypeScript 并不能解决所有问题,而且 TypeScript 本身为了给 JavaScri… |
泻药。我一向不支持仅拿着 == 运算符的神必行为作为证据来批判 JS 一番。JS 遗留下来的问题有很多,包括但不限于: ==、+ 等运算符的隐式转换问题var 的作用域问题,以及在 Script 模式下可能带来的全局对象污染this 的动态作用域问题,以至于为了规避 this 许多人宁愿从不用 class,只写闭包,往对象里直接塞函数(当然,也不排除有人只是觉得这很酷所以才这么写),而为了缓解创建这么多函数对象的内存占用问题,现代 JS 引擎往往会做生成运行时伪类等特殊处理数组与 object 纠缠不清的联系,length 为什么是可变的、稀疏数组、在数组上 for in,以及 DOM API 中遗留的一些 ArrayLike 对象 typeof null === "object" 争议性特性: 动态类型火葬场为啥我们同时有 undefined 和 null with(严格模式中已弃用) 原型链回调地狱NaN !== NaN 和 typeof NaN === "number"(实话说,这个不是问题,人家 IEEE 754 就是这么规定的,但许多人仍感觉这是 JS 的问题,包括 0.1 + 0.2 !== 0.3 其实也不是 JS 的问题) 我们走过的路: Monkey Patching纠缠不清的模块机制,旧式的 Script(UMD)、CommonJS,与 ES Module过小的标准库,至今连日期处理的现代化支持都仍等待有生之年的 Temporal 成熟 We’re missing(哇这些东西太潮流了虽然我从来没用过但是如果这些特性加入 JS 不是非常酷炫的事情吗虽然即使加入了多半我也不会用但我仍旧要起哄一下告诉所有人 JS 必须加入这些特性): Pipeline operatorPattern matchingSignals 好了,可以具体谈谈这些问题。 关于 == 的众多讨论在我看来一直显得颇为奇怪,原因是用于替代它的严格相等运算符 === 在 1999 年诞生的 ES3 中就已经存在,规避了 == 运算符可能导致的隐式转换。但二十多年的时间仍未使人们放弃 ==——这也许要“归功于”该运算符的长相实在是太具有迷惑性。不过,== 的问题实际上是个非常容易规避的问题,任何一个 Linter 都能很容易识别出来代码中的所有 == 并将提示程序员用 === 替代——对于大多数编程语言来说,上一个 Linter 总是最佳实践,不管你正在编写一门“好的”编程语言还是一门“差的”编程语言,我想引入一个 Linter 总是顺带和理所当然的事情。 与之相对的,同样是隐式转换带来的问题,+ 带来的问题实际上要严重得多——我想再“欢迎”JS 的人也不会认为 {} + [] === "[object Object]" 是一个合理的结果。而 + 又相当常用,一通操作下来返回一个摸不着头脑的带着 "[object Object]" 的对象不是什么不可能发生的事情——如果不让我用 TS 只用纯 JS,我大概也不好说自己不会写出这种东西来。 + 运算符这个将操作数往字符串隐式转换的灵感可能源于 Java——Java 的 + 同样会隐式将对象转换成字符串,并且也为所有对象提供了一个默认的 toString 实现。不过 Java 遇到相关问题大概更容易调试,至少 Java 默认实现的 toString 返回的不是 "[object Object]",而是一个带类名和哈希的字符串,比如 "java.lang.Dog@6e6c3152"。 至于通过 + 的“有趣”特性可以做出来的 jsfuck 这种项目,那纯粹就是个玩笑。如果真有人会把 jsfuck 用于生产,或是用于混淆自己的代码(并能忍受这带来的巨大性能损失),那我只能佩服你也是个乐子人。如果真的有人喜欢研究隐式转换的神秘行为,可以去这里,看看能不能把这里边的题目全部答对,我自己靠直觉是一半题目都做不对的,要想全做对只能去按照标准行为一步步求解。 至于 var 的种种“恶行”,大概已经成为过去式了。自 let/const 普及之后,已经没有道理接着使用 var 了。 而 this 的问题,的确可以说是个相当有问题的特性。this 源于当初设计 JS 时的偷懒,不想去 Java 那边抄一套完整的 OOP 系统过来,但是又想要做一套能 Work 的 OOP 机制——于是一个隐式传入的动态作用域 this 成为了一个非常凑合而有效的解决方案。在熟知 JS 的人看来,this 不难理解,它的规则有且仅有一个——即对于 o.f() 这样的表达式,会将 o 作为 this 传入函数 f 中,否则 this 是 undefined,除此之外再没有其他规则了。只是这一行为对于已经熟悉了 Java 等传统 OOP 语言的用户实在是有些匪夷所思,而 JS 又是个常用回调函数的语言,传着传着丢掉 this 太正常不过了。不得不承认,这是个很糟糕的特性。 关于数组的问题,这个日常使用下其实很难感知到,但是这个问题又的确存在。当初将数组实现为一个带 length 且键为数字的对象纯粹是出于偷懒——既然只是个简单的对象,这个数组自然就可以出现所谓的“空洞(hole)”,即跳过一些元素的下标,干脆有些位置压根就没有对应的元素,连 undefined 都不填充。数组的诸多奇异行为(稀疏数组、length 是最大的下标加一、可变的 length)给 JS 引擎的实现造成了诸多麻烦,并且这些特性可以说压根就没什么存在的意义。好在只要不特意去触碰这些特性,使用中其实除了 arr.length = 0 这个常用操作,其他的奇异行为倒是完全用不上。此外还有一个很容易被新手误用的是数组 for in 会遍历下标而非元素,这也是比较反直觉的。至于 DOM API 中遗留的一些 ArrayLike 对象,处理起来的确也比较头疼,好在我们已经很少直接操作 DOM 了。 然后是 typeof null === "object",同样是一个相当反直觉的特性。这大概源于 JS 早期自身的一个 BUG,被观察到后为了不破坏兼容性就保留下来了。我不认为这是个合理的特性,尽管我如今已经会习惯性补上一个 obj !== null 了。 我想真正被大多数人认为是问题的特性大体上只有这些。有趣的是,尽管题主希望问题底下的回答不要拿 TypeScript 来搪塞这些问题,但它们巧合的都能被 TS 很好解决——无论是 ==、+、var、数组的各种问题,还是 typeof null === "object"。自 TS 打了诸多补丁之后,结合 typescript-eslint,其实大多数 this 带来的问题也能很好检查出来了。我想对于许多纯 JS 项目,简单带上一个 jsconfig.json 也足以提示出来很多问题了。 如果我们忽略 TS,谈谈这些问题的严重性的话,在我的看法里 this 是最大的问题,这需要程序员在处理 this 时记住那一条规则(对于且仅对于 o.f() 这样的表达式,会将 o 作为 this 传入函数 f 中,否则 this 是 undefined),尽管它很短;隐式转换的问题在其次,并且仅对于 + 运算符(毕竟 === 这种可以通过 Linter 直接检查出来的实在很难说是个问题),不过由 + 引起的问题其实也不那么常见,也许几个月才能碰见一次;typeof null === "object" 在我看来是第三个比较严重的问题,这个一般就靠经验习惯性加上 !== null 的检查了,的确不是很有办法;数组的反直觉 for in 可能是第四个比较严重的问题,但这个被坑过一次应该就长记性了,再也不会被坑了。 剩下的要么是已经可以被完全弃用的特性,要么是正常使用时完全碰不上的特性,也不必拿它们说事了。 可以看到,其实最大的问题还是在 this 上,除开它的大多数问题要么很难碰上,要么属于被坑过一次就很容易长记性的问题——每门语言都有自己反直觉的地方,总是会碰见坑的,尽管新手在 JS 上踩到这些坑时可能会惊呼这也太智障了。 其实 this 本身可能也不是一个问题——如果 JS 像 Java 8- 那样不允许直接传递方法,或者像 Java 8+ 那样给传递方法引用加一个有些区别的特殊语法,又或者像 Lua 一样给方法调用加个特殊语法,可能不会有人感觉到有什么不对的。坏就坏在 JS 中回调函数十分常见,而 obj.method 又十分具有迷惑性,让人误以为 this 已经理所当然地绑定到了传递的方法上,实则不然。时到如今,我们只能求助 Linter,帮我们标出这样的方法传递,提示我们加上 .bind,或者干脆保护性地干什么都加上 .bind。又或者,干脆直接不再用 class。 接下来就是许多争议性问题了,像是“动态类型火葬场”、with 是好是坏、该不该同时保留 undefined 和 null,都是各有各说法的话题。即使 with 这种我们曾经以为毫无价值的特性,如今在有些微前端的实现里也仍就找到了它的闪光点,是非功过,都是很难说的。让我们一个个看下来。 关于动态类型的诟病由来已久,我不太愿意具体深入。动态类型有它的立足之地,例如脚本和教学领域(比如用来教一教 Untyped lambda calculus),这也是很符合 JS 最初的脚本定位的——它在诞生时就没有考虑到如今如此庞大的 JS 生态,甚至也没有自己的模块机制。而动态类型确实有一些灵活性优势,比如在动态类型下 Auto-currying 写起来相对轻松(如果语言并未内置 Auto-currying 机制的话),而一旦到了静态类型,就变得束手束脚,这点曾经尝试过用 TS 写 Ramda 的人应该深有体会,而从 Ramda 到 fp-ts 的演变其实也是一个为了类型安全而牺牲一部分灵活性的过程。 而关于 undefined 和 null,主要就是由于 JS 中访问对象不存在的键时不会报错,所以 undefined 的引入几乎是专门用来用来表示不存在的键的,并且也起到了一个作为变量未赋值默认值的作用。关于 JS 选择访问不存在的键时不报错这点,倒很难说是个坏主意——不如说在 Python 以外的大多数语言中,对于默认的键值对数据结构,访问不存在的键时都选择不报错,这其中有 Lua/Ruby/Go/PHP/Java/Kotlin/Swift/Perl/Elixir(仅对于 Keyword) 返回 nil/null/undef、C++ 构造默认值、Rust/Haskell 返回 Option/Maybe。而对于 JS 这样构造对象字面量如此简单的语言来说,如果访问不存在的键时真会直接报错,恐怕在 Optional chaining 出现之前 obj.optionalKey || defaultValue 这样的语法就要写成 optionalKey in obj ? obj.optionalKey : defaultValue 了。 问题是,undefined 和 null 是否可以合并,我们是否真的需要这两个值?如果我们打算去掉 undefined,将缺失值和空值全部统一为 null,想想倒也是可以做的,只是会带来一些问题。关于键的缺失值问题,我们仍可以用 in 和 hasOwnProperty 来判断,只是会变得更麻烦,这也许还可以接受,不会像 Lua 一样完全无法区分不存在的键和值为 nil 的键。但是对于函数返回值和变量的默认值,语义就不那么明确了,我们就无法判断一个函数究竟是没返回值,还是返回了 null 表示空值,对于未赋值的变量同理。 我个人是更偏爱较明确的语义的,所以我会更倾向于仍同时保留 undefined 和 null,毕竟它们的确表达了不同的意思。也许学习 Rust,将空值统一为 Option 这样的结构,然后取消 null,只留下 undefined 表示缺失值,会是一个听上去更干净的解决方案——但 Option/Maybe 流行起来已经是 JS 诞生很久之后的事情了,并且在缺少语法糖的情况下,Option 其实也并不好用。 而关于原型链,这只能说是 JS 独特的地方,是实现共享的另一种手段,不好说是什么坏的特性。原型链的好处是它非常灵活,可以动态地自由修改链上的一切,实现动态继承以至于 Monkey Patching 这样的奇淫巧计,坏处是原型链过深时会导致查找的性能问题,并且意外修改原型链也会造成一些难以调试的问题。不过说到底,关于这点也只能说有优有劣,我们很难评价原型链是个坏的特性,顶多说它是个不那么常规的、不那么为人所知的实现方式而已。 关于回调地狱的问题,我相信自 async/await 可以在大多数主流引擎中使用之后,已经成为了某种意义上的“刻板印象”。尽管我们自己写库的时候完全避不开回调函数,毕竟 JS 主要的异步机制还是基于事件循环,还得从底下开始手搓回调,但一般用户应该已经很难遇到深层次的回调函数了。 至于 NaN,我也不知道自己为什么要把它加上——按理来说它是个完全不该有任何争议的特性,因为 IEEE 754 就是这么规定的,JS 对于 NaN 的实现完全符合标准,奈何这些年来关于 NaN 的讨论还是经久不衰,以至于还能是不是看到有人“哈哈,NaN 居然不等于 NaN,而且 Not a number 居然类型是 number,不知道是哪个天才想出来的!”,我感觉还是得提一嘴这个问题,破除一些奇异的迷思。 让我们谈谈那些纠缠不清的历史——它们不是语言的问题,却是我们走过的路。 第一个是 Monkey Patching——如今许多人大概是从没听过这个词了。但换一个说法,大概人人都知道,那就是 polyfill。Monkey Patching 这个说法在 Ruby 社区非常流行,并且被 Rails 发扬光大,指直接修改已有 class 的定义,在其基础上动态添加方法,甚至直接修改语言的内置对象。 我们如今也可以这么做,比如我们完全可以写个 Array.prototype.clear = function clear() { this.length = 0 } 来避免 arr.length = 0 这样奇异语法,并且连 TS 支持也很好做,只要写个 declare global { interface Array<T> { clear(): void } } export {}——如果你从未接触过这个概念,甚至也许觉得这是个天才的主意,打算立即用到你的代码库中——但这其实是我们走过的弯路。 在十多年前,像 PrototypeJS 这样的库一度非常流行。它们的思路就是直接侵入式地修改原生对象,给它们添加新的方法以增强程序员的幸福度——想象一下我们有了 Array#uniq 和 Array#reject(filter 的相反),还有了全局可访问的 range,再也不用自己定义一个然后手动引入。 但这带来了许多问题。例如同样是做 Monkey Patching 的另一个库 MooTools 引入了一个 Array#include 方法(与 includes 差一个 s),这直接导致了后来 JS 标准引入 Array#includes 时故意加了一个 s 以避免冲突这种听上去很滑稽的事情。Monkey Patching 导致的这种污染问题引起了很大的争议。并且随着 JS 标准库的丰富,很多“应该有的方法”也已经内置到了 JS 中,例如 map/filter/reduce,人们很快转向了 Lodash,后来连 Lodash 也不那么需要了。 如今 Monkey Patching 最大的用途大概是 Polyfill——这也许是 JS 诞生之初根本没人想到的、原型链带来的一个用处,却意外的在这些年里发挥了重要的作用,让语言的大部分兼容问题成为了一个可以解决的问题,也使人们变得乐于尝试新的标准库特性。除此之外,大概只能在 chai.should 这样的测试框架中看到了。 不过 Monkey Patching 的影响其实从未真正消失过。总是偶尔有一些“自作聪明”的框架试图 Monkey Patching 一些相当常见的东西——在不久之前,React 在 RSC 中还会 Monkey Patching fetch 来做自动缓存,这其实导致了相当多潜在的问题,参见此处。Angular 至今也仍依赖于使用 Monkey Patching 的 Zone.js 实现响应式,尽管 Angular 目前已经在尝试转向 Zoneless。 然后我们看到如今 JS 生态中仍纠缠不清的模块机制——自 ES6 提出 ES Module 之后,已经过去近十年了,然而我们难过的看到 CommonJS 仍旧维持着相当大的影响力,并且偶尔死去的 UMD 还会跳出来攻击我们(比如 TS 如今仍旧默认文件使用 Script 作用域,除非它使用了任何 import/export 语句才会被认为是 Module)。 这一个是源于 Node 长期使用 CommonJS 的影响力,另一个是由于原生的 ESM 其实不好用,以至于许多人也许还不知道按照严格的 ESM 标准所有引入的文件是必须加上 .js 后缀的,并且也不支持默认加载 index.js。如今能比较“宽容地”支持 ESM 的运行时大概只有 Bun,而 Node 对于 ESM 是不支持默认加载 index.js 并且必须要求加上 .js 后缀的,Deno 目前则必须开启 --unstable-sloppy-imports 才能支持。如今我们通常使用 Bundler,所以也许没什么感知,但如果你尝试直接把 TS 用 tsc 编译成 JS 之后也许会发现它不能直接用浏览器运行的,因为你大概没有给每个 import 都加上 .js,而且也许还用了默认加载的 index.js 特性。 如今 ESM 能逐渐取代 CommonJS,有两个比较大的功臣,一个是强推 ESM 的 Vite,一个是能够自动转换 require/import 语法的 TS。但是 CommonJS 的影响仍旧非常大,毕竟 Node 是在不久之前才引入的 --experimental-require-module,在此之前一个采用 CommonJS 的应用要使用哪个库就必须要求它也导出了 CJS 版本才行,但反过来却很轻松(Node 直接支持在 CJS 应用中 import ESM)。如今大多数的 JS 后端应用也仍旧使用 CommonJS。并且,也有很多多年未维护的库停留在了 CommonJS 上,它们仍旧被经常使用,但很难期待哪天被迁移到 ESM 上去。如果 Bun 哪天流行起来也许能解决一些问题,但 Node 的历史太长了,大多数应用还没有准备好迁移到 Bun 上,也没有勇气去面对可能的兼容性问题。 偶尔,我们还能看到 jQuery 仍在发光发热——在一个不存在模块机制的 Script 环境下执行 JS,依赖于全局对象导入少数几个库,一般是 jQuery 和 Lodash,有时还会混合 jQuery 和 Vue 2.6。对于许多不那么专业的“前端”来说,jQuery 仍旧是很直观的写法,它可以如此轻松地引入浏览器中,又可以良好地嵌入各种模板引擎中,相比于一个包含了大量包袱的“现代化标准前端项目”,它显得是那样轻盈。 在模块问题仍纠缠不清的同时,JS 也饱受着过于贫瘠的标准库带来的困扰。JS 大概是现有流行语言中少数几个不拖着巨型标准库的成员了。自 Python 以来,battery-included 的语言越来越流行,以至于如今一个不拖着巨型标准库的语言几乎不可能流行起来——大家希望一门新的编程语言能立即拥有完整的工具链,能够马上投入使用,没有了等待一门语言慢慢成长的耐心了——毕竟我们现在有这么多编程语言,而现有的编程语言也仍在努力挣脱过去的镣铐,渐渐引入更多现代化的新特性。 这时,JS 连引入 Set#difference() 这样不大可能有人反对的微小改动也要去 TC39 走一遍完整的标准化流程,就显得有些过于缓慢和小题大做了。同样令人难以忍受的,还有浏览器厂商在实现新标准上的拖沓,以至于本该早早落地的 Temporal,如今在浏览器中连实验性支持都还没有,我们仍只能使用来自二十多年前的 Date,并迷惑于为什么它的月份是 0-indexed 的。 与此同时,我们又那么激进地向往远方,以至于我们想要拥有 Pipeline operator,想拥有 Pattern matching,想拥有 Type annotations,想拥有过去几个月曾是潮流但如今无人问津的 Signals(以前还有 Observable),想拥有 Immutable 的 Record 和 Tuple,甚至想拥有专为性能优化的 Struct——尽管这听上去应该是由 WASM 解决的问题才对。 JS 承载了太多历史,如今又承载了许多不切实际的希望,以至于有些人真切地希望用 JS 解决世间任何问题,也有人对这一团充斥着历史遗骸的诡异造物困惑不解,甚至极其厌恶它的存在。 但这些都构成了 JS。它们不是语言自身的问题,它们是 JS 走过的路。 如果当年哪个不知名的“天才”没有想到发明 Google Maps,JS 不会走到今天这一步。存在于浏览器上的编程语言或许会进一步分裂,又或者浏览器永远只会是个浏览静态网页和查找资料的工具,而不会成为如今的“微型操作系统”。JS 也会呆在一个舒适的角落,继续在网页的角落里充当一个漂亮的时钟,又或者是用来制造漂亮的花瓣特效。 但 JS 既然走到了今天,我们只能接受它并回顾它的生平。它似乎并没有造成什么荼毒——像是早期 BASIC 被毫不客气地批评的那样,“学习过这门编程语言的人这辈子再也无法编写正常的程序”,这样严重的批评没有出现在 JS 身上。它也承担起了人们赋予的任务,尽管这似乎远远超出了它的设计目的——1999年制定ES3标准的人们大概从未想到,JS可以在若干年的修补后成为一门看上去相当“正常”并且用起来也很正常的语言,正如他们当时对 ES4 的梦想一样。JS 还间接催生了 JSON,并且有趣地普及了一些函数式编程的知识,大约在 2009 年开始就被很多人用于一些函数式编程概念的教学目的,尽管一些拥有正经函数式“血统”的用户也许并不喜欢这种说法。 我不认为 JS 的流行是一场“灾难”,而且它除 this 外的大部分特性并不那么糟糕,并且在语言早期就具有了良好的内核——对闭包和函数作为一等公民的支持。直到今天,我们也没有遇到什么问题是需要对 JS 做破坏性改动才能做到的,这个良好的内核发挥了很好的作用。 如果有机会,我们也许可以设计一门“稍微好一些的 JS(Better EcmaScript, Truly,简称 BEAST)”: 删除现在的 ==,将如今的 === 变成 ==删除大多数隐式转换,尤其是 + 上的去除 falsy value 的概念,要求条件语句只能是 true/false 删除 var使用显式 self 而非 this,obj.method 返回 bounded method 设计一个标准的数组删除 for of,for in 改为在迭代器上迭代 typeof null 不再是 "object" 删除 with一套设计良好的 Module 作为一开始的唯一标准 没有增加任何新特性,只是删除特性和将原先 A 语句的作用转移到 B 去,最大的改动来自于 this,其实也不算很大的改动,毕竟没动原型链。 这样看来,这门“BEAST语言”看起来没有什么明显的问题,它完全就是一门标准且实用的动态类型语言,而且同时兼容 Java 式的 class 写法和依赖闭包的“函数式”写法,看起来还是一门多范式语言,没有什么不妥。 |
JavaScript的底子其实没有那么糟糕。JavaScript被人形容是披着C皮的Lisp(或者说Scheme),其实没说错太多。 JavaScript一个大错,就是“讨好新手”。比如弱类型,对明显的错误也不抛异常而是继续想办法往下跑。考虑到它本来的定位,这本来也不是大错。 问题就是,事物会发展的。曾经的新手逐渐变成老手,曾经的“浏览器脚本”的定位变成了“前端应用语言”。这下就劈叉了。 当你真的决定严肃对待你的应用的时候,你是不希望你用的语言“宽恕”你的错误的。你希望你的错误尽早暴露出来,方便你测试和debug。本质上JS这个错误,跟以前的脚本语言没有太大区别——不够严格。 新版的JS正在逐渐修正以前的错误。这些问题不是致命问题。Perl现在会建议加上“use strict ”,这个解决办法看起来不错。JS直接抄过去了。 |
是的 这些年有几个趋势,对浏览器乃至整个javascript生态都是毁灭性的打击 先说第一个,页游的消失 时间倒退回几年前,曾经的游戏生态是四分天下,手机,游戏机,电脑和网页游戏,手机当时最强势,但是没有做到绝对垄断,其市占率大概是三成多,大概三分之一这样,然后游戏机和电脑分别是四分之一,剩下页游大概可以占到六分之一,虽然页游最弱,但已经占有一定比例,也就是放饼状图里,你可以看到 时至今日,这两年,页游基本上销声匿迹了,甚至你连广告都看不到了,之前很多页游都是重营销,买量的做法,所以广告铺天盖地,到处都是,这两年连广告都消失了,各个对于游戏市场的调查报告里面,页游基本上不再出现,或者很少比例,大概1%这样,属于基本上可以忽略的存在,而手游则迅速增长到接近一半的水平,游戏机和电脑的占比变化比较小,换句话说,手游把页游给干掉了,页游的市场被手游吃掉了 之前如果你要做页游的话,js是绕不开的工具,那现在随着页游的消失,js也就变成了一个可有可无的存在,而只要有其他工具选择,基本上不会有人选择js这种脚本去搞研发,因为太混乱,一旦代码量达到一定程度,就看不懂了。像脚本的动态类型,不仅是性能上的桎梏,同时也是阅读的障碍,类型变来变去,谁有办法去给你猜到底是啥意思,增加了不确定性和维护成本 其次呢,Google的转向 javascript的所谓生态,很大程度上依托于Google研发的v8脚本引擎,像node.js的作者,之前做过几个项目,都失败了,他自己技术能力不行,然后Google搞出了v8,他在v8的基础上做了node,依托v8强大的jit功能,使得node得以超过ruby等其他web脚本,得以发展,而且Google持续性对v8的投入,也使得所谓的js生态用户,有了靠山,毕竟Google是超级大厂 而且当时Google的浏览器chrome,也在迅速占领市场,形成了一定程度上的垄断 所以后来Google在chrome vs 安卓的斗争中,Google上层选择了chrome,也就是做chrome浏览器出身的劈叉,成为了Google的ceo,反而是安卓部门的领导鲁宾被赶走了,离开了Google 那Google当时的思路就很清楚了,希望扶持曾经在chrome浏览器上大获全胜的劈叉上位,带领Google,攻城略地,重现它在chrome上的辉煌,一个代表作品就是chromebook,也就是搭载了chrome浏览器的笔记本电脑,v8不仅可以用在node上,同时还被Google用在了chorme浏览器里面,或者说,v8本来设计出来,就是打算给chrome浏览器用的 那时至今日,我们再回头看 chromebook和chromeos,应当被视作一个失败的产物,它并没有像n年前预期的那样,攻城掠地,像干掉ie一样干掉windows,在chromebook上市初期,以及后期,确实形成了几波浪潮,比如后期口罩期间,chromebook确实有过一段销量的上涨 但是,很快就发现,销量很快就下去了,因为市场对于只能用浏览器的电脑,并不怎么感兴趣 哪怕chromebook有价格优势,普通民众并不买账 而与之形成鲜明对比的是,能够运行编译型软件的廉价电脑,反而大行其道,比如树莓派 虽然树莓派的派是python(另外一种脚本)的意思,但是树莓派最大的应用是允许普通用户安装并使用c,c++等编译型编程语言编写的软件,也正是这个特性,所以鬼佬表现出了对于树莓派极大的热情,纷纷把不同编程语言,尤其是编译型的编程语言往树莓派上搬,像java,swift等现在都出现在了树莓派上,甚至后来在树莓派的应用商店里都很快出现了java的ide,也就是集成开发环境,也就是说,用户可以直接在树莓派上编程了 反观chromebook,因为上网本的功能所限,用户只能浏览网页,装一些功能受限的插件,各种高应用迟迟得不到发展,尤其是第三方支持,可谓是寥寥无几,chrome web store跟安卓上的app store数量上形成了鲜明对比 与此同时,安卓随后的发展并没有因为鲁宾的离开而受限,与chromebook表现截然相反并形成鲜明对比的是,安卓操作系统的搭载量,在随后几年,依旧保持了高速增长,并一跃超过windows,成为全球市场占有率第一的操作系统,并在这些年逐步拉开了跟windows的差距 此消彼长,安卓和chrome的表现,Google股东们都看在眼里 随着chrome越来越表现出一种烂泥扶不上墙的特质,Google股东的耐心也被逐步消耗干净,遂开始要求Google对于chromebook做出改变,于是chromebook开始越来越多地表现出兼容安卓,比如允许用户安装google play上的app还有steam 甚至现在已经传出,Google开始把chromeos跟安卓合并,说是合并,其实是安卓干掉了chromeos,迫使chromeos采用安卓的代码,并逐步发展成安卓套壳或者是扩展的一种样态https://www.oschina.net/news/321097/chrome-os-becoming-android" data-tooltip-richtext="1" data-tooltip-preset="white" data-tooltip-classname="ztext-reference-tooltip">[1] 也就是chromeos正在转变成安卓+操作系统,跟原先的chrome浏览器套壳没啥关系了 甚至啊,Google高层已经开始考虑直接搭载安卓操作系统的笔记本电脑,不再拘泥于chromeoshttps://finance.sina.com.cn/tech/roll/2024-11-20/doc-incwteun2139422.shtml" data-tooltip-richtext="1" data-tooltip-preset="white" data-tooltip-classname="ztext-reference-tooltip">[2] 那种种迹象都表面,Google对于chrome为代表的浏览器,已经失去了耐心,不再将其视作未来的真命天子,在chrome vs 安卓的斗争中,开始倾心于安卓,而非chrome,开始表现出废chrome立安卓的姿态 那随着chrome在Google内部斗争的逐步式微,那chrome派系的所有幕僚,也都会得到相应的降温处理,其中之一就是v8,v8本来做出来就是给chrome用的,那现在chrome式微了,那自然v8也很难得到高层的重视 与之相反的就是安卓相关的技术,得以迅速发展 其实像flutter这种工具,本质上就是从浏览器叛逃到安卓阵营的工具,flutter那个组,原先就是做浏览器相关技术也就是web技术优化的,实在做不下去,于是决定,把所有web技术标准和要求,全部废除,就js,html,css,全部不要,重新设计,就有了flutter的第一版,然后做基准测试发现,快了整整20倍,于是wow,就有了后来的flutter,所以flutter虽然用的编程语言dart,在1.0的时候,是想做一个better javascript的(毕竟dart和v8是同一个爹),后来有了flutter之后,dart升级到2.0,就转变成一个类似better java的存在,实际上现在dart的语言特性,你认真看,就是早期学java,后期学swift的这么一个产物,早期的各种特性跟java很像,后来加的特性,几乎都是从swift那边学过去的,比如record语法,就是swift的tuple,extension扩展,空安全等等,也就是dart = java + swift 随着现在Google越来越多地转向安卓,而非浏览器chrome,也就是js阵营最大的靠山有点心不在焉了,那自然这个所谓的生态就开始展现出各种分崩离析的症状 老大都心不在焉了,那下面那些岂不就是群龙无首了? 市场衰弱,工匠心不在焉,那这还搞什么? 就不再有人对这些工具及其可能应用的市场抱有信心和耐心 所以现在这些年,所谓的前端,所谓的js生态,就大不如前 不像以前那样声势浩大,到处都是敲锣打鼓之徒 这是一件好事,一个不合格,混乱的工具,理应被市场所淘汰 参考^https://www.oschina.net/news/321097/chrome-os-becoming-android^https://finance.sina.com.cn/tech/roll/2024-11-20/doc-incwteun2139422.shtml |
题主列举出来的这些特性大部分我是有所耳闻,但是在五六年的职业生涯中,经过了数家企业,实际生产中没见过任何一行代码是这么写的…… 如果你现在的项目都是这种用法,那我的建议是考虑换一家公司。这种乱写的方式,日常维护都会很困难,说不准哪天就给你爆个线上问题。 任何语言你想乱写都能乱写,哪怕是 rust 都存在 unsafe 关键字(当然静态语言肯定是比动态语言强很多的)。 动态语言的关键在于你怎么灵活的使用这些特性。 在 Ruby on Rails 社区中流传的一个 Rails 信条 中提到: Ruby 则在欢迎工具里就附上了自尽的绳子 这种灵活的语言特性用的好那就是提升生产力的工具,用的不好那就是自尽的绳子。 最后如果你是饱受垃圾项目代码困扰的开发者,我附上个人意见,希望能帮助到你: 团队协作,大规模项目,请使用 typescript 和 eslint,拉满规则和限制,什么滥用 any、滥用 as、双等号等等统统禁掉,保证入口的安全,多用 zod、async validator 等库验证 http 入参出参或者其他 json 解析的场景多写单测,多测试边界 case,vitest 是很方便的工具 |
这么多年来,让我改变歧视态度的语言有三个,第一个是Lua,第二个是Javascript,最后一个是Rust。 很长时间以来,我不是Javascript的用户,我是从AS3转过来的,带我入行的一个水平很低的同事告诉我,你会AS3以后,学js很容易的,好多底层是一样的。 其实不一样,相对于AS3,js真是啥啥都不行。OO都是伪装的!!除了有一套差不多的事件系统,其它的js都比AS3儿戏!!那时候flash player要落幕了,我手上许多的基于flash player activex+c++的游戏开发用的工具,需要寻找后续维护技术。于是后来开始用上了node.js+TypeScrip作为替代方向(当时还编译过v8,想用v8+js桥来仿照fp acrivex,发现这个方案真蛋疼,结果node.js做出来了)…… 是的我没选择js而是先选择了ts,因为ts的oo够纯净,也更像AS3。直到后来真的开始用nodejs部署项目,一个egg+mongodb的电商项目,我才真的直接用上了js,才真正了解了js那一大堆的恶心语言特性,以及时不时看到像肠道一样跟stdc++代码一样晦涩的React.createElement垃圾堆还有new array的一串链式调用…… 直到我完全了解清楚什么是es2015,我才开始对js改观,class像样了,这个js像个语言了,以前像个脚本……这时候写js开始不反感了。后来用上了promise,又用上了async,又用上了ArrayBuffer,嗯,它越来越好了,至少没朝着为难人的方向进化,而且基本每年发一次版本,能给出不少减少代码量的方案,还是很有诚意的。至少比我从06年听到c++0x结果等到c++11等了6年结果等来一个没多少糖连string都依然残废的版本要好多了。 js的发展历程才是社会的真相,你可以不优秀甚至就是垃圾,但是只要坚持下去,不断的往正确的方向去迭代,自然这些付出会成为有效的回报。js的流行是他应得的,不是什么灾难。 |
JS 的优点就是能乱写, 如果没有这个优点,实用度大打折扣。 很多缺点和混乱都是 大家公认的, 我这里主要讨论一下3 个 我的观念里是 绝佳,但是主流认为是糟粕的东西 : undefined eval prototype 原型链 先说 undefined 和 eval 重点说 eval. 最后说 原型链 0。 存在 undefined 实际并不是坏事, 你可以把 undefined 当做 未初始化的 | 或者被reset 的来使用。 1。 JS 的灵魂特性是 【捕获】 是天然的, 也就是闭包。 2。 还有两个灵魂 主流都是反对的, 但是我觉得也是JS的灵魂,因为它们和闭包配合才能完全做到 代码既数据 数据既代码。 第一个是 原型链, 你们不觉得它 灵活无比吗? 第二个是 eval, 不过eval 的理想用法是: 只能在编译期使用 不能调用任何 【不安全的】 函数 :
因为JS 脚本语言的特征, 这个 【不安全】 是可以 自动推导出来的,唯一要做的额外工作是定义全局黑名单 和 全局白名单: 这个黑名单有很多种 定义方法, 我这里举个简单例子: 我自己的应用 定义了 3种 基础黑名单 1个基础白名单, 和 3 种扩展ACL: 3 种 基础黑名单如下: REQUIRE_ARG_BLACKLIST module 黑名单 例子:
ID_BLACKLIST identifier 黑名单
AST_TYPE_BLACKLIST ast-节点 黑名单,
1 种默认白名单如下: GLOBAL_WHITE_LIST 全局白名单
另外3种扩展ACL ,需要自己写校验函数, 把语法和用户绑定, 分别是 JS-SYNTAX+用户 更灵魂的 JS API授权 POSTGRES-SQL-SYNTAX+ 用户 数据库 API 授权 C++-SYNTAX+用户 c++ 绑定API 授权 整个效果类似下面,这个授权服务代码比较庞大 因为除了js自己的parser 还依赖 pgquery的parser 和 libclang的parser, 以及 angular-devtool 里面的 css-parser . 不过总的效果类似下面:
类似的 postgres 的ACL的例子:
这样的好处 就是你用户端随便传, 你直接传JS代码也行, 传 sql语句也行, 要什么ORM 脱了裤子放屁, 我们追求的就是 风一样的裸奔。 不但安全而且有规律。 没有eval 你做不到这个。 最后说一下 原型链: 原型链的 缺点在于 它允许在运行时使用, 但是 假设 它只允许在 编译期使用呢? 这个是 meta-programming 的完美API模型: 用和运行时一样的代码 构建 类/struct , 【只要限制只能在编译期合法】,是一个非常好的 生成器, 什么继承 多继承 DAG 只要你无环 + 递归次数有限, 任何结构 都是一个 原型链-like 一把索给你生成出来。 不要被 静态 强类型 束缚了自己。 静态 强类型好是相对的,是相对于哪个阶段。 在 编译阶段 【动态无类型 】才是最 NB的: 用动态无类型 产生 弱类型 再产生 静态弱类型 再产生 静态强类型 不能 【瞎写】的编程语言都是 烂语言。 不能把 【瞎写】的代码 自动|半自动 转化为 【整齐好好写】 的编程语言也烂,但是比前者好一点。 |
JavaScript再怎么不行也比Java行,但我没有办法用这一条给JavaScript洗地,因为Java的流行确确实实是一场灾难。 @圆胖肿 不过,Java的流行是因为缺乏同生态位的竞争对手,但JavaScript的兴起完全是历史的选择,JavaScript并非不存在强力竞争者,JavaScript的确是受web赐福的永世神选,即它最能代表web对一个客户端脚本的期望,但它从来都不是web客户端编程的唯一选项。我们的老朋友ecma 334在ie还没死的时候就一直在变着花样得试图竞争上岗,ecma 334那是有GC静态语言的天花板,你看它成功过吗。 JavaScript的大多数缺点来自于历史包袱,例如过去的浏览器对降级运行带错误页面的尝试。在严格模式中这些缺点中的大多数是被解决了的。 而且,和Java不同的是,JavaScript的优点远远多于缺点,并且它进步得很快。 |
我真的很喜欢js,我也很喜欢C++。 js模糊了数据与代码的边界,补足了静态编译语言缺失的部分。 C++构造对象,得先声明类型,再实例化,太麻烦了。 js可以直接通过json语法构建对象,配置就是程序,程序就是配置。 undefined和null各自都有合适用途,并不多余。反而没了null有些场景得构造出一个类似的null来,反而不方便。 通过xxx!=null,可以直接判断既不是undefined也不是null,所以===和==的存在也有必要。 原型链的property查找方式,与prefab的property查找方式一模一样,先天prefab圣体。 原型链成功模拟了oop(有点像虚表),习惯oop的用起来很平滑。通过原型链也可以构造特殊数据结构,非原型链语言不太能做到。 合理排版对象(monomorphic),可以高效jit,性能也不错。 Promise和await虽然比起C++ sender/receiver弱了点,但绝大多数情景也是够用的。 真心觉的js没什么大缺点,简洁且表达力强,优美。 |
不是,尤其是es6之后 你觉得糟糕是因为js的糟糕特性都是面试题 你真正写谁会用原型链,class,科里化,这些乱七八糟的东西 |
js太可怕了,大概12-13年前后开始用js,很快接触了nodejs,当时拿来做爬虫。sizzle,domtree大概这些名字的包吧。 后来深度应用开始做前后端的项目,坚持到15年-16年初,最后转行也就不再用了,但实在是不喜欢。绝对灾难。 原本想着前后端统一语言,而且还不用序列化反序列化,当时受rest的理念以及nosql,尤其是mongo,redis,solr的影响。来回全是json。看着很美好现实很骨感。 oo的奇葩写法,域的概念,还有特么的异步,简直就是圆环套圆环。换一套框架就像变了一种语言一样。extjs,angular,jquery,还有GitHub上各种知名不知名的库,库与库之间还有兼容性问题。 不用GitHub上的框架,npm上的包,落后的快,自己也折腾,用了的话,版本一变,没准接口都不一样了,其实更折腾,只是说开始就放弃,还是开始一段时间再放弃,只能说资质愚钝,全栈工程师真不好做。 毒药,先喝为敬,从此白首不相见,再也不碰前端,不碰nodejs。 好了,现在连vue都不懂,十年前的努力全部成了沉没成本。 时间久远的我连prototype这个词都忘了[大笑],看前面的帖子才想起这个词。但是当时还是认真的用来组织代码。那个天才js叫什么名字来着?尼古拉斯啥,一本厚厚红宝书,一本薄薄的绿字的奥利莱出版社的,看完觉得都是很好的书。 |
|
[收藏本文] 【下载本文】 |
历史人文 最新文章 |
有点想看豪门霸总小说,有没有朋友们推荐下 |
被称为「人间尤物」的女主,有多绝? |
古代有什么特别毁三观的文学作品吗? |
以色列的道德水平如果放在中世纪算高吗? |
裘千尺只靠枣树活了10多年,现实世界真的可 |
《遮天》中很多大帝都有道统家族,女帝和无 |
人到底可以无知到什么程度? |
南京某商场贴日本元素事件,怎么看待这件事 |
你本人如何评价“满清误我中华三百年”的观 |
如何评价汪精卫? |
上一篇文章 下一篇文章 查看所有文章 |
|
|
古典名著
名著精选
外国名著
儿童童话
武侠小说
名人传记
学习励志
诗词散文
经典故事
其它杂谈
小说文学 恐怖推理 感情生活 瓶邪 原创小说 小说 故事 鬼故事 微小说 文学 耽美 师生 内向 成功 潇湘溪苑 旧巷笙歌 花千骨 剑来 万相之王 深空彼岸 浅浅寂寞 yy小说吧 穿越小说 校园小说 武侠小说 言情小说 玄幻小说 经典语录 三国演义 西游记 红楼梦 水浒传 古诗 易经 后宫 鼠猫 美文 坏蛋 对联 读后感 文字吧 武动乾坤 遮天 凡人修仙传 吞噬星空 盗墓笔记 斗破苍穹 绝世唐门 龙王传说 诛仙 庶女有毒 哈利波特 雪中悍刀行 知否知否应是绿肥红瘦 极品家丁 龙族 玄界之门 莽荒纪 全职高手 心理罪 校花的贴身高手 美人为馅 三体 我欲封天 少年王 旧巷笙歌 花千骨 剑来 万相之王 深空彼岸 天阿降临 重生唐三 最强狂兵 邻家天使大人把我变成废人这事 顶级弃少 大奉打更人 剑道第一仙 一剑独尊 剑仙在此 渡劫之王 第九特区 不败战神 星门 圣墟 |
网站联系: qq:121756557 email:121756557@qq.com |