类日常使用的JavaScript Web开发工具如果想要成为一个有吸引力的可选方案,至少需要满足以下三方面的需求:
从用户的角度看,使用工具创建的与原生使用JavaScript开发的Web站点和应用在观感、页面加载时间、页面启动时间和持久速度等方面上应难以区分。
从开发人员的角度看,他们希望借助这些工具能够无缝地访问其他JavaScript库,进行高效的调试。
从商业的角度看,应不断有大量的开发人员愿意接受该工具的专业培训,并在企业中使用,学习工具所花费的时间应能很好地转化为生产力,并且使用该工具创建的应用够满足不断变更的需求。
一个此类转化工具要取得成功,必须要达到上述三个方面的需求。各编译器正尽量在这三个方面需求间达到平衡。对于在日常生产环境中使用的编译器来说,其中任何一个方面都不能被忽略。就Transcrypt而言,这三个方面的需求都在特定的Transcrypt设计决策中起到了决定性作用。
需求一:
Web站点和应用的观感与所用的底层JavaScript库有直接的关系。因此想要具有相同的观感,站点或应用必须正确地使用同一软件库。
虽然快速的网络连接可能会隐藏其中的差异,达到同样的页面加载时间,甚至对于在公共网络或托管主机上运行近似大小代码的移动设备也是如此。这使得在加载每个新页面时,不可能去下载一个编译器、虚拟机或较大的运行时。
只有当代码是在服务器端静态预编译成JavaScript时,才有可能获得与使用原生JavaScript同样的页面启动时间。页面中需要的代码量越大,差别就会变得愈发明显。
要获得相同的持久速度,必须生成高效的JavaScript代码。鉴于JavaScript虚拟机已针对通用的编程模式做了高度的优化,生成的JavaScript应该类似于手工编写的JavaScript,而不是效仿堆栈机器或是任何其他的底层抽象。
需求二:
要实现对所有JavaScript库的无缝访问,Python和JavaScript必须使用一致的数据格式、一致的调用模型和一致的对象模型。一致的对象模型要求JavaScript的基于原型的单继承机制与Python的基于多继承的机制融合在一起。应注意的是,JavaScript近期添加的关键字“class”对于弥合这个根本性的差异需求完全没有影响。
要实现高效调试,必须在源代码层面完成断点设置和代码单步执行这类工作。换句话说,源代码映射是非常有必要的。一旦遇到问题,需要通过检查生成的JavaScript代码来找出原因。因此,所生成的JavaScript应该与Python源代码同构。
利用已有的技术意味着源代码必须是纯Python的,而非一些更改了句法的变体。一种稳健的实现做法是使用Python的原生解析器。同样,在语义上也必须是存Python的,该需求会造成一些实际问题,需要引入编译器指令以维持运行时的效率。
需求三:
要保护企业在客户端Python代码上的投入,工具需要具有持续性。持续可用的客户端Python编译器应具有良好的一致性和卓越的性能。如何维持这两者间的平衡是编译器设计中最关键的部分。
Python至今已连续三年成为排名第一的计算机科学导论课程的教学语言,这一现状足以保证受过培训的Python开发人员持续可用。Python已用于我们所能想到的所有后端计算领域上。如果浏览器端编程可以使用Python实现的话,那么所有的Python开发人员都可以进行浏览器端编程。这些开发人员曾经设计了长期运行的大型系统,而非孤立的、短期运行的前端脚本代码段。
就生产率而言,Python在显著增加产出的同时保持了程序运行时的性能,这一点已得到那些从其它编程语言转到Python的开发人员的公认。对于那些关键运算来说,比如数值处理和3D图形处理,它们所使用的库已经被编译成了本地机器码,这也就是为什么Python能够保持运行时的性能。
最后一点,对不断发生变更的需求应具有开放性,这意味要在各个层级上支持模块化和灵活性。基于类的面向对象编程为此做出了很大贡献,它提供了多继承和复杂的包和模块机制。此外,开发人员可以通过使用命名参数和默认参数在不改变现有代码的情况下改变调用签名(call signature)。
对比一致性和性能:语言趋同发挥了作用
一些Python的构件与JavaScript构件非常近似,尤其是当转译成最新版本的JavaScrirpt时。两个语言间明显趋同。具体而言,越来越多的Python元素已经融入JavaScript中,例如:for...of...、类(以有限的形式)、模块、解析赋值(destructuring assignment)和参数展开(argument spreading)。因为JavaScript虚拟机已经对for...of...这类构件做了高度优化,有利于这类Python构件转化为最近似匹配的JavaScript构件。这样同构转化所生成的JavaScript代码能受益于目标语言的优化机制,也易于阅读和调试。
虽然Transcrypt中很多的调试是通过源映射(source map)在Python中逐步进行的,而不是在JavaScript代码中进行的,但是工具不应该隐匿底层的技术,而应揭示底层技术,让开发人员可以完全知道“事情的真相”。这一点更为可取,因为如果使用了编译器指令,在Python源代码的任何地方都可以插入原生的JavaScipt代码。
下面是一个使用了多继承的代码段,展示了Python与Transcrpyt转化的JavaScript代码之间的同构。原始的Python代码是:
转化后的JavaScript代码是:
侧重同构转化的局限性存在于细微之处,有时两个语言之间的差异是难以处理的。例如,Python中可以使用“+”操作符连接列表,而如果在JavaScript中同构地使用“+”操作符,不仅会导致列表被转化为字符串,而且字符串会粘连在一起。当然,a + b可以被转换为__add__ (a, b),,但是因为a和b的类型在运行时才能确定,这会导致即使对于1 + 1这样简单的事情,也会生成函数调用和动态类型检查代码。再给出一个关于如何解释“真值(truthyness)”的例子。空列表在JavaScript中的布尔值是True(或者true),而在Python中则是False。要在应用中全局地处理这个问题,需要对每个if语句执行一次转换,因为在Python构件if a:中不能判定a是一个布尔型,还是列表等其它类型。因此 if a:必须转换为if( __istrue__ (a))。如果在内层循环如此使用,会再次导致性能不高。
在Transcrypt中,嵌入代码中的编译指令(即编译指示)用于编译本地控制这类构件。这允许了使用标准数学符号编写矩阵计算,例如
M4=(M1 + M2) * M3,同时不会对perimeter=2 * pi * radius这样的语句生成任何额外的开销。从语法上说,编译指示仅是在编译时执行对__pragma__函数的调用,而非在运行时。导入包含def __pragma__ (directive, parameters): pass的桩模块(stub module),可允许该代码无需修改即可在CPython上运行。此外,编译指示可以置于注释中。在避免命名冲突的同时统一类型系统
Transcrypt统一了Python和JavaScript的类型系统,而非让它们毗邻而居并实时转换。数据转换需要花费一些时间,还增大了目标代码的规模以及内存的使用,进而增加了垃圾回收的负担,使得Python代码和JavaScript库间的交互难以处理。
因此,Transcrypt的决策是去拥抱JavaScipt世界,而非创建一个平行的世界。下面提供了一个使用了Plotly.js库的简单例子:
其中的编译指示语句是可选的,它允许字典键值忽略引号,只是为了方便。除此之外,代码看上去非常类似于相应的JavaScript代码。你可以注意一下代码中是如何使用列表解析式的,这是在JavaScipt中依然缺乏的特性。开发人员不用关心Python字典的字面量(literal)是如何映射为JavaScript字面量对象的,他们可以在编写Python代码时使用Plotly.js的文档。转化并非是在幕后完成的。在任何情况下,Transcrypt字典都是一个JavaScript对象。
统一类型系统时会产生命名冲突。例如,Python和JavaScript字符串都具有一个split()方法,但是两者在语义上有很大不同。还存在很多类似的冲突情况,Python和JavaScript仍在发展演化,未来还会有其它的冲突。
为了解决这个问题,Transcrpyt支持别名这一概念。当在Python中使用<string>.split时,就会被翻译成一个具有Python的split语义的JavaScript函数<string>.py_split。在原生JavaScript代码中,split指代的是原生JavaScript的split方法。可以从Python调用JavaScript的原生split方法,这时会称其为js_split方法。虽然在Transcrypt中对这一类方法预定义了可用的别名,但是开发人员可以自定义新的别名,或是取消已有别名的定义。这种方式可以解决所有统一类型系统所导致的命名冲突问题,无需付出运行时代价,因为别名是在编译时进行的。
别名也允许从Python标识符生成JavaScript标识符。例如,在JavaScript中允许将$符号作为命名的一部分,而在Python中是不允许的。Transcrypt严格遵循Python的语法,使用原生CPython解析器做解析,语法与CPython相同。一部分JQuery代码看上去如下:
因为Transcrypt使用编译而非解释,为允许加入极简化(minification)和涉及所有模块的交付,必须在编译前确定导入的库。为此,Transcrypt还支持C风格的条件编译,这可以从下面的代码片段中看到:
在Transcrypt运行时中,对JavaScript 5和6的代码之间的转换使用了同一机制:
这种方式考虑了较新版本JavaScript中的优化,并保持了向后兼容。在一些情况下,优化的优先级要高于同构:
一些优化是可选的,例如是否能激活调用缓存。这会导致直接重复调用继承而来的方法,而非通过原型链(prototype chain)。
对比静态类型与动态类型:脚本语言正走向成熟
对静态类型优点的认可正在复苏,TypeScript就是一个很好的例子。与JavaScript不同,静态类型语法是Python语言不可分割的一部分,Python原生解析器就支持静态类型语法。但是类型检查本身却留给了第三方工具,最著名的就是mypy。这是Jukka Lehtosalo的一个项目,Python的创建者Guido van Rossum也是该项目的贡献者。为实现在Transcrypt中高效地使用mypy,Transcrypt团队也为项目贡献了一个轻量级API,无需经由操作系统直接从另一个Python应用激活mypy。虽然mypy依然在开发中,它已经可以在编译时捕获为数不少的输入错误。静态类型检查是可选的,可以在本地通过插入标准类型注解来激活。一个使用注解的例子是mypy的in-porcess API:
正如上例所示,静态类型可被用于任何适合的位置。在上面的例子中是用在run函数的签名中,因为它是API模块的一部分,可以被另一个开发人员从外部看到。如果有人错误解释了API的参数类型或是返回类型,mypy将显式地给出一个错误消息,指向产生不匹配的文件和行数。
动态类型这一概念依然处于Python和JavaScript这些语言的中心位置,因为它允许灵活的数据结构,并有助于降低执行任务所需的代码量。源代码量是十分重要的,因为要理解和维护源代码,首先要通读代码。就此意义而言,实现同一功能,100KB的Python源代码要优于300KB的C++源代码,还不存在读取类型定义的困难,这些类型定义中可能会使用模块、显式类型检查和转化代码、重载的构造函数和方法、处理多态数据结构和类型依赖的抽象基类。
对于由单个编程人员编写的、源代码在100KB以下的小脚本,动态类型只具有优点,因为只需要非常小的规划和设计,而且编程中所有事情也会有条不紊。但是当应用增大到无法由个人构建而需要团队时,这种平衡就发生了改变。对于这样的应用,即以大约200KB以上源代码为特征,编译时类型检查的缺失会导致如下后果:
很多错误只有在运行时才能被捕获,通常是在整个过程的晚期阶段,修复这些问题需要付出高昂的代价,因为这些错误影响了更多已编写好的代码。
由于缺少类型信息,对模块接口可做多种解释。这意味着为了能够正确使用API,在团队成员间所做的协商需要花费更多的开发时间。
尤其是在大型团队中工作时,动态类型接口会导致不必要的模块耦合。而良好定义的稀疏接口才是我们需要的东西。
即便是只有一个参数的接口,如果参数指向的是一个复杂的、动态类型的对象结构,该接口就无法保证稳定的关注分离。虽然这类“4W”(Who did What,Why and When)编程模式虽然带来了极大的灵活性,但同时也导致了设计的延后,影响到大量已有的代码。
应用“耦合与内聚”范式。模块内部可以在设计决策上具有强耦合,但是模块之间最好是松耦合的,一个更改模块内部结构的设计决策不应该影响到其它的模块。基于上述的原则,在动态类型和静态类型间做出选择时可以参考如下的经验法则:
对于特定的模块内部,设计决策是允许耦合的。将模块设计为内聚实体,会导致更少的源代码量,以及易于对各种实现进行实验。对此,动态类型是一种有效的方法,它可以用最小的设计时间开销换取最大的灵活性。
在模块间的边界上,对于要交换什么信息,开发人员应准确地制定稳定的“合约”。采用这种方法,开发人员可以并行工作,无需经常性地进行协商,他们的目标是固定的,不会发生变化。静态类型适合这些要求,对于哪些信息可以作为API的交互载体,它能给出正式的、经机器验证的一致意见。
因此虽然当前的静态类型浪涌看上去像是一个回归,但事实上并不是。动态类型已取得了一席之地,并不会离开。反之也成立,C#这样的传统静态类型语言也已吸收了动态类型概念。但是考虑到使用JavaScript和Python等语言编写的应用的复杂性与日俱增,有效的模块化、协作和单一验证策略愈发重要。脚本语言正走向成熟。
为什么客户端要选择Python而非JavaScript
由于Web编程的极大普及,JavaScript也正受到很多关注和投资。在客户端和服务器使用同一语言有其明显优点。其中的一个优点是,随着应用规模的增长,代码可以从服务器端移动到客户端。
另一个优点是概念上的一致性,这使得开发人员可以同时在前端和后端工作,无需经常在技术间做转换。Node.js这样平台广受欢迎,正是由于人们希望降低应用客户端和服务器端在概念上的距离。但同时,这也将当前Web客户端编程的“放之四海皆准”风险扩展到服务器端。有人认为JavaScript是一种足够好的语言。近期的版本将开始支持基于类的面向对象(类似于在原型内胆上覆盖了一层装饰)、模块和命名空间这样的特性。随着TypeScript的引入,使用严格类型成为可能,虽然将其集成到语言标准中仍需数年时间。
即使具有这些特性,JavaScript仍不会成为其它所有语言的终结者。对JavaScipt有些言过其实了(译者注:原文借用了习语“骆驼是委员会设计的马”,讽刺委员会喜欢虚张声势)。浏览器语言市场需要的是多样性,事实上所有自由市场需要的都是多样性。这意味着我们能够为手头的工作选择正确的工具,即对钉子选用锤子,对螺丝选用螺丝刀。Python在设计上从一开始就是以清晰性、精确可读性为准则的。其价值不应被低估。
在未来很长时间内,大多数客户端编程可能仍会选择JavaScript。但是对于那些考虑替换语言的开发人员,对持续性有影响的因素正是语言的发展动力,而非语言的具体实现。因此最重要的是使用哪种实现,而非选择哪种语言。出于此考虑,Python无疑是一种有效的、安全的选择。Python有很大的知名度,越来越多的浏览器在实现中考虑了Python,同时Python在保持性能的同时越来越接近CPython的黄金标准。
虽然新的实现会替代现有的实现,但是这个过程会一直遵循一个共识,即Python语言应该蕴含什么。直接切换到另一种语言,要比切换到另一个JavaScript库或预处理器要容易得多。服务器端的格局已经成形,多种客户端Python实现将会继续存在,并展开公平竞争。获胜者将是语言本身。浏览器中的Python将会继续下去。在此所写文章做成笔记记录的形式,书写代码过程中难免取之互联网,有的是本人书写,推崇自己一些见解想法,在看文章之前呢,小编推荐一个群,群里分子非常踊跃交流经验遇坑问题,也有初学者交流讨论,群内整理了也整理了大量的PDF书籍和学习资料,程序员也很热心的帮助解决问题,还有讨论工作上的解决方案,非常好的学习交流地方!群内大概有好几千人了,喜欢python的朋友可以加入python群:526929231欢迎大家交流讨论各种奇技淫巧,一起快速成长
者:然后去远足
转发链接:https://segmentfault.com/a/1190000023017398
一年过去了,JavaScript 也一直在改变。不过有些技巧可以帮助你写出简洁高效可伸缩的代码,即便是(或者说特别是)2019 年。下面 9 条实用小技巧能助你成为一个更好的开发者。
如果你仍深陷回调地狱,那么你应该还在写 2014 年之前的老古董代码吧。除非很有必要,比如遵守代码库要求或者出于性能原因,否则不要使用回调方式。Promise 还行,但如果你的代码日渐庞大,Promise 就显得有些尴尬了。我现在的首选方案是 async / await,它让代码的阅读与改进都变得简洁很多。事实上,你可以 await JavaScript 中的每一个 Promise,如果你用的库函数返回一个 Promise,就可以简单地 await 之。其实,async / await 只是使用 Promise 的语法糖。想让你的代码正常工作起来,你只需要在 funcion 前增加 async 关键字。如下是一个简单例子:
async function getData() { const result=await axios.get('https://dube.io/service/ping') const data=result.data console.log('data', data) return data } getData()
注意,在最顶层没办法使用 await,只能在 async 函数中使用。
async / await 是在 ES2017 中引入的,所以记得转译你的代码。
实际开发中不可避免地经常会遇到这种情况,我们要获取多个数据项然后分别对它们进行某些处理(for…of),或者需要在所有异步调用都得到返回值后再完成某项任务(Promise.all)。
for…of
比方说我们要获取页面中几个 Pokemon 的具体信息,我们并不想等待所有调用全部完成,尤其是有时候并不知道具体有多少次调用,但我们想只要一有返回数据就立即更新数据项。这时候我们就可以用 for...of 来遍历数组,在循环体内执行 async 代码,代码的执行会被暂停,直到每次调用成功。必须注意的是如果你在代码中如示例这样做,可能会带来性能瓶颈,但把这个技巧收藏你的工具箱里还是非常有用的。示例如下:
import axios from 'axios' let myData=[{id: 0}, {id: 1}, {id: 2}, {id: 3}] async function fetchData(dataSet) { for(entry of dataSet) { const result=await axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`) const newData=result.data updateData(newData) console.log(myData) } } function updateData(newData) { myData=myData.map(el=> { if(el.id===newData.id) return newData return el }) } fetchData(myData)
注:这些示例都可有效运行,可随意复制粘贴到你喜欢的代码沙盒内执行(如 jsfiddle、jsbin、codepen)。
Promise.all
如果想并行获取所有 Pokemon 的信息又该如何实现呢?既然 await 可以用在所有 Promise 上,很简单,用 Promise.all:
import axios from 'axios' let myData=[{id: 0}, {id: 1}, {id: 2}, {id: 3}] async function fetchData(dataSet) { const pokemonPromises=dataSet.map(entry=> { return axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`) }) const results=await Promise.all(pokemonPromises) results.forEach(result=> { updateData(result.data) }) console.log(myData) } function updateData(newData) { myData=myData.map(el=> { if(el.id===newData.id) return newData return el }) } fetchData(myData)
注:for...of 与 Promise.all 都是在 ES6+ 引入的,所以(必要的话)记得转译你的代码。
让我们返回到上一示例中,我们是这样做的:
const result=axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`) const data=result.data
有一种更简便的做法,采用解构的方式从某个对象或者数组中获取一个或者一些值。像这样:
const { data }=await axios.get(...)
耶!变成一行代码了。甚至还能把变量重命名:
const { data: newData }=await axios.get(...)
另一个很妙的技巧是解构赋值的时候给出默认值。这样就确保了你再也不会得到 undefined,而且也不必费心去手动检测变量。
const { id=5 }={} console.log(id) // 5
这些技巧同样适用于函数参数,如下所示:
function calculate({operands=[1, 2], type='addition'}={}) { return operands.reduce((acc, val)=> { switch(type) { case 'addition': return acc + val case 'subtraction': return acc - val case 'multiplication': return acc * val case 'division': return acc / val } }, ['addition', 'subtraction'].includes(type) ? 0 : 1) } console.log(calculate()) // 3 console.log(calculate({type: 'division'})) // 0.5 console.log(calculate({operands: [2, 3, 4], type: 'multiplication'})) // 24
这个例子乍一看可能有点令人困惑,别急慢慢来。当我们不传任何参数时,函数将使用默认值。一旦开始传递参数,只有没传的参数会使用默认值。
注:解构赋值在 ES6 中被引入,确保先转译你的代码。
在确定是否要取默认值的时候,我们往往会先对给定的值进行检查,其中的某些检查现在来说已经没有必要了,将成为历史。无论如何,知道如何处理 真值(truthy values)和 假值(falsy values)总是非常好的。它能帮助我们改进代码,省去一些表达式,让代码更清晰明白。我经常看到有人这样做:
if(myBool===true) { console.log(...) } // OR if(myString.length > 0) { console.log(...) } // OR if(isNaN(myNumber)) { console.log(...) }
这些都可以简写成:
if(myBool) { console.log(...) } // OR if(myString) { console.log(...) } // OR if(!myNumber) { console.log(...) }
想要真正用好这些,你需要理解 真值 和 假值 的含义。这里有个小总结:
假值
真值
注意在检测真假值时,这里进行的是非严格比较,也就是说用的是==而不是===。一般说来,二者行为相同,但在某些特定情况下会出现 bug。对我来说,常发生在数字 0 上。
同样,这也是精简代码的好方法。通常都能帮我们简化代码,但也会带来一些混乱,尤其是链式使用时。
Logical operators
逻辑运算符主要用于连接两个表达式,计算返回 true,false 或者与之匹配的值,&& 表示逻辑与,|| 表示逻辑或。如下:
console.log(true && true) // true console.log(false && true) // false console.log(true && false) // false console.log(false && false) // false console.log(true || true) // true console.log(true || false) // true console.log(false || true) // true console.log(false || false) // false
我们把逻辑运算符与上一个知识点真假值结合起来理解。当使用逻辑运算符时,遵从如下规则:
console.log(0 && {a: 1}) // 0 console.log(false && 'a') // false console.log('2' && 5) // 5 console.log([] || false) // [] console.log(NaN || null) // null console.log(true || 'a') // true
Ternary operator
三元运算符与逻辑运算符类似,但有三个部分:
示例如下:
const lang='German' console.log(lang==='German' ? 'Hallo' : 'Hello') // Hallo console.log(lang ? 'Ja' : 'Yes') // Ja console.log(lang==='French' ? 'Bon soir' : 'Good evening') // Good evening
你是否遇到过这种问题,想要访问嵌套对象的属性,然而并不知道该对象或其中一个子属性是否存在?你很可能会写出类似这样的代码:
let data if(myObj && myObj.firstProp && myObj.firstProp.secondProp && myObj.firstProp.secondProp.actualData) data=myObj.firstProp.secondProp.actualData
这太啰嗦了,有个更好的方法,至少是一种更好的提议(继续往下看如何用它)。它就是可选链式调用(optional chaining) ,用法如下:
const data=myObj?.firstProp?.secondProp?.actualData
我认为,这是一种让代码更清晰的检查嵌套属性的有效方法。
注:目前可选链式调用 (optional chaining) 还不是官方规范的一部分,是处于 stage-1 的实验性特性。你需要在你的 balelrc 中添加插件 @babel/plugin-proposal-optional-chaining 来使用。
函数绑定在 JavaScript 中十分常见。随着 ES6 规范中箭头函数的引入,我们现在有办法自动绑定函数到定义时的上下文了,这种方法非常好用,被 JavaScript 开发者广泛使用。Class(类)刚刚引入的时候,你并不能真正的使用箭头函数,因为类方法需要一种特定的声明方式。我们要在其他地方绑定函数,如在构造器中(React.js 的最佳实践)。我一直觉得先定义类方法然后再绑定的流程很烦人,一段时候过后再看更感觉莫名其妙。有了类属性语法,我们又可以用箭头函数获得自动绑定的好处。箭头函数现在可以在类内使用了。示例如下,重点看 _increaseCount 是如何绑定的:
class Counter extends React.Component { constructor(props) { super(props) this.state={ count: 0 } } render() { return( <div> <h1>{this.state.count}</h1> <button onClick={this._increaseCount}>Increase Count</button> </div> ) } _increaseCount=()=> { this.setState({ count: this.state.count + 1 }) } }
注:目前,class properties 并不是正式官方规范的一部分,是处于 stage-3 的一个实验性特性。需要在你的 balelrc 中添加插件 @babel/plugin-proposal-class-properties 来使用。
做为前端开发者,你肯定遇到过打包和转译代码的情况。wepback 成为事实标准已经有很长一段时间了。我最初使用 webpack 时它还处于第一个版本,那时候很痛苦。我花了无数个小时去处理各种不同的配置项,让项目打包运行。一旦能跑起来,我就再也不会去动它们,怕又给弄坏了。几个月前偶然发现的 parcel,让我松了口气。它提供的所有功能开箱即用,同时还允许我们在必要时做出更改。它像 webpack 或者 babel 一样支持插件系统,并且速度极快。如果你还没听过 parcel,墙裂建议去看看!
这是个很好的话题。关于这个问题,我有过很多不同的讨论。即使是 CSS,有很多人也会倾向于使用组件库,比如 bootstrap。JavaScript 的话,也有不少人使用 jQuery 和一些轻量代码库处理验证、滑动效果等。虽然用库也可以理解,但我还是墙裂建议自己编写更多的代码,而不是盲目地安装 npm 包。对于那些整个团队维护构建的大型代码库(或者框架),如 moment.js、react-datepicker,我们个人尝试去编写是没有什么意义的。但可以多写一些只是自己项目使用的代码。这样对自己有三大好处:
一开始,用 npm 包会显得更简单,自己去实现某些功能反而更费时间。但万一这个包并没有像预期的那样工作,然后你不得不换另一个,花更多的时间去阅读如何使用新的 API。如果是自己实现,你可以按自己的使用情况 100% 量身定制。
*请认真填写需求信息,我们会在24小时内与您取得联系。