事实上,一个语言是否流行与它是否优秀没有任何关系。JS绑架了整个Web前端,而我们又没有其他的选择。
——题记
最近这两年用JavaScript用的比较频繁。一是2017年时开始制作小程序课程;二是2018年开启了Lin-UI和Lin-CMS,无论哪个开源项目,都严重依赖JS;再加上5.6日上线的NodeJS&KOA新课程,JS就像个502固体胶,越黏越紧。
本篇文字,一方面讲述面向对象对JS编程的重要性,另一方面也必须吐槽一下没有ES新特性加持的JS真的有点“垃圾”。你可以将没有ES新特性支持的JS理解成ES5。
我知道很多同学是JS语言的粉丝,也一定听说过Atwood的名言:凡是能用JS实现的最终都将用JS实现。
但我的确不太理解这句话。
了解我的朋友应该知道,我对语言是没什么感情的,谈不上喜欢亦或是讨厌。生活中有太多值得去”喜爱“的东西,一本好书、一部电影都能让我沉醉,编程语言实在排不上号。所以,当我指出JS的一些问题时,只希望大家能够正视这些问题。一个人成熟的标志是你是否可以客观看待某个问题,而不是一味的袒护与自我麻醉。你大可不必一听到语言的问题就如同坐了穿天猴般蹦的老高老高,语言而已,它欲成仙我们不能左右;它要跌落神界,我们也无能为力。
没有语言没有缺陷。
如果JS那么优秀,为什么现在的ES新标准疯狂迭代,不断借鉴其他语言,越来越面向对象化呢?
抛开作为前端开发者出于保护自己饭碗那点小心思,我们应该理智的承认,JS这个语言在ES新标准普及前,它确实不如现代流行的其他语言。甚至是对于绝大多数的开发者来说,JS引以为傲的“灵活性”和原型链的设计模式反而是一种负担。
作为一个在Jquery还未普及就开始手写原生JS的开发者,今天确实想和大家聊聊JS面向对象的一些问题。
JS是否应该支持面向对象?或者说,我们是否应该用面向对象的思维来编写JS代码?
必须肯定的是JS本身就不是为面向对象而生的语言,这一点同Java是有本质区别的,所以它不能很好的支持面向对象是无可厚非的。
但这里要纠正一个误区:JS是可以支持面向对象的,只不过它和经典的面向对象实现是有差异的。JS用ES5就可以模拟面向对象,它不是用Class来组织的,而是用原型链来“模拟”的。
但用原型链来模拟Java、C#中经典的OO模型是相当丑陋的,所以才有了ES6的Class。但ES6中的Class也仅仅是语法糖,在本质上它依然是用ES5的机制来模拟的类。
在这个ES5像ES6、7、8、9新标准过度的特殊时期,对于JS生态其实是一件很尴尬的事情。
由于ES6标准虽然已经非常普及了,但大量的库由于早期没有Class类的支持,所以依然保持着ES5中半面向对象,半过程式的编程方式。而我们现在在编程时构建复杂的业务时,必然又需要使用Class,当用ES6的Class思维去调用这些“不伦不类”的库时,这就造成了“割裂”。
来看一个使用Sequelize操作MySQL数据库定义Model模型时的典型例子:
以上代码的逻辑非常简单,我们有三个模型对象需要实现,但是这3个模型中有大量的重复属性,就是在ArtBase中定义image、content、pubdate等属性。那么按照通常的思维,我们需要有一个基类,这个基类包含了以上属性,那么子类(Movie、Music、Sentence)需要继承这个基类,才能避免在每个子类中都定义image、content、pubdate的属性;此外,Movie、Music、Sentence还有一个公共的like方法,这个方法也需要定义在基类上。
在Sequelize中,要定义一个模型,必须使用define这种函数调用的方式来产生一个模型。而在define的时候是不能够在模型中直接定义方法的,所以我们需要在define返回模型的prototype上添加like方法。
而Music由于有自己独特的url属性,所以他在继承ArtBase后,还需要在构造函数里用this.url 来添加属性;而根据ES6 的Class规定,在使用this前,必须先调用super。我相信绝大多数熟悉面向对象的同学,都不喜欢这种奇葩的写法。如果你觉得还好,可以对比下下面Python版本的实现:
4个Class,代表了基类、Music、Sentence、Movie。我相信即使你完全不了解Python,这样的定义也能一看就明白,不要说我偏爱Python的风格,这不是语言风格的问题,而是语言机制的问题。换做Java、PHP等任何现代的OO语言,一样是类似于Python这样的写法。
以上两段代码只是节选简化后的示例代码,而在真实的业务中,由于类的复杂性,两个版本的代码优雅性、可读性差距越来越大。通过对比Lin CMS的KOA版和Python版本可以很明显的看到语言特性上的差距。实现整个框架相同的功能,Python版本的代码量只有JS的一半左右。
那为什么两个版本的差距会这么大呢?ES6不是支持Class吗?
仔细观察上面两端代码,你会发现,最根本的原因在于Sequelize是使用define这种工厂模式来创建的Class,但Python版本不同,它是直接可以定义一个Class。
那么为什么Sequelize不能直接定义一个模型基类呢?是现在做不到吗?不是。最本质的原因还是在于Sequelize这个库早期由于ES6没有普及,他本身底层就不是用面向对象的思想来构建的,而现在由于面向对象的普及,它又想要支持面向对象(define返回的就是一个ES6的Class)。这就造成了“割裂”,显得不伦不类。
这样的例子不只是存在于Sequelize中,大量的经典库依然是老旧的ES5的写法。不要小看这个问题,没有很好面向对象支持的库基本不具备扩展性。
当一个被封装好(无法更改源码)的库,如果它的提供的功能不符合我们的需求怎么办?有一定面向对象编程经验的同学应该知道,如果库均是以Class的方式提供的,那么我们可以继承这个Class,然后通过方法重载或者方法覆盖的方式来重写某个关键的方法,从而实现自己的逻辑。
但事实上,由于大量的库都不是以Class的方式来构建的,我们根本无法在库的外部通过这种继承的方式来修改库的默认行为。这是一场灾难,而且短时间无法解决。
那是否由于这些原因,我们在编写JavaScript代码时就不应该使用面向对象的方式来构建?肯定不是。面向对象是一个无法逃避的思维,它是解决复杂应用的必备工程手段。
反过来看,你现在能想到某种替代面向对象的其他有效手段吗?函数式编程?有些鼓吹函数式编程(FP)的人根本没有真实的用函数式编程开发过完整的项目或者应用。JS也根本不是一个函数式编程的语言。
Scala可以算作一个函数式编程语言,但有多少人拿Scala来构建一个大型的应用?面向对象能流行这么久自然是有它的道理的。很多人鼓吹函数式编程只不过是玩弄一些对于绝大多数人陌生的概念,从而体现自己的与众不同。
函数式编程的思维并不是没有用,但函数式编程更多的是用于局部的代码块或者解决某个方向的问题,很少被全面用于整个应用中。比如Map/Reduce就是比较经典的函数式思维。
很多前端开发者认为,面向对象只是服务器端需要使用,前端是不要的。那么,什么是服务端,什么是前端?为什么服务端需要而前端不需要呢?仅仅是服务端比前端多了一个操作数据/数据库的能力?所以前端就不需要?还是因为前端有一部分是做UI层面的所以不需要?
恐怕给出这种结论的人也说不清楚。事实上,前端和服务器的界限已经变的很模糊了,特别是NodeJS让JS脱离了浏览器后,这个界限更加的模糊,你根本无法界定哪些功能必须是前端工程师做,哪些必须是服务工程师来做。
前端的业务已经越来越复杂了,除了不能直接操作数据和数据库,前端几乎和服务端没有太大的区别。面向对象经过几十年的实践,已经被无数经典的案例证明是最有效的代码构建手段。TypeScript的流行一部分原因是因为JS没有类型约束,而TS解决了这个问题;但另一方面却是因为TS非常像C#,而C#的确是比Java还要经典的面向对象语言。使用TS天然的面向对象特性来构建复杂的应用是一个很好的选择。
也许TS不是构建面向对象前端应用的唯一手段,但用面向对象的思维来编写前端代码必定是大势所趋。
有人说JavaScript在设计之初本就只是为了在浏览器里做做简单的”特效“,比如跑马灯、动态色彩等粗糙的效果,是我们太”贪心“赋予了JavaScript太多的责任。这个论述承认了JS在没有普及ES新特性前确实不如其他语言,但依然不承认Web前端其实被JS这个语言“劫持”了。现今的情况下,找不到替代JS的前端方案。
事实上,前端之所以选择JS不是因为它有多优秀,而是因为JS绑架了整个Web前端。即使是NodeJS的流行也不是因为他足够的优秀和不可替代亦或是有非常强的特点,而仅仅是因为JavaScript在前端的统治性,“反推”出来我们需要有脱离浏览器的JS环境,从而诞生了Node。
再拿NodeJS的一大用处——前端工程化来看:前端工程化不能用Python、Java吗?当然能,前端工程化的基础在于对文件的读写能力,哪个服务端语言不具备文件读写能力?纠其原因依然是因为前端最熟悉的是JS,所以需要用JS来读写文件实现前端工程化。
在语法层面上,JS的特性也让很多函数的接口设计显得及其怪异:
其实就是一段普通的函数调用,但JS的语言特性,让它无法避免这种嵌套式的传参形式。可阅读性极差。如果你要昧着良心说,“好”,那我也很无奈。
那我们就不应该深入学习JS或者NodeJS了吗?不,对于前端开发者来说,还是得学。既然JS绑架了整个Web前端,你能有什么办法?我们毕竟是普通开发者,随大流总不会错。我们依然需要面试、找工作,依然需要靠JS生活。
所以,被xx了咋办呢?躺下来享受就好。就像Lin CMS,如果按照我的意愿,我根本不想开发KOA版本,然而人多力量大,前端人就是多,那就必须搞个KOA版本,让大家能够用一个语言完成前端和服务端。
有时候,JS绑架了整个Web前端反而也挺好的。服务端能选择的语言太多了,java、python、php、go、c#,有同学总是问我,我到底学什么语言,我总是说不清楚,挺费事的。
然而前端就没这个烦恼,JavaScript,你没得选择。这真是件很美妙的事儿呢。
我以前总是觉得学一个语言并不是一件太费事儿的事情,所以我总是告诉前端开发者,如果你想做服务端最好的选择是Python而不是JS。但我觉得,我犯了2个错误,一是我不应该拿我10年的开发经验来对标初学编程的前端开发者。二是,我忽略了找工作这个最重要的事情。
NodeJS依然是前端进阶到全栈成本最低的语言。既能加深JS语言的理解,反哺前端开发、又能给前端未来没落后或者竞争太激烈时(我说假如),留给自己一条退路、还能增强找工作的筹码、还能自己独立开发一个完整的项目,关键是还没有语言障碍。
一个语言要想精通还是太难了。至少编程的前五年接触第二语言不是一件太好的事情。所以这次给《旧岛》小程序做的服务端,我选择的还是前端一体化的语言Node.js。详细的课程内容在后续的文章中给出。
我的课依然是我的风格,业务只是个套子,真正让人高潮的还是编程思维和JavaScript语言的本质以及异步编程模型。
最后给大家留个作业,也是在新课程中编写校验器的时遇到的一个问题:
如何查找一个Class实例对象以及其所有父类上指定名称前缀的自定义属性名和自定义方法名?
有点抽象,我们给出具体的题干代码:
A、B、C是三个一次继承的类。你需要编写一个findMembers函数,传入属性名的前缀name,和方法的前缀名validate;查找全部的以name和validate开头的属性和方法名称。函数的返回结果应该是nameA、nameB、nameC和validateA、validateB、validateC。
注意,我们寻找的是自定义方法和属性,所以只能返回这6个名称字符串,不能包含js对象的内置方法。
这个问题不难也不简单,需要对原型链有一定的了解。这个问题放到Java、Python、C#中就是几句代码,因为这些语言已经内置了查找方法。但JS,我没找到内置的方法。
多年编程经验,今年1月整理了一批2019年最新WEB前端教学视频,不论是零基础想要学习前端还是学完在工作想要提升自己,这些资料都会给你带来帮助,从HTML到各种框架,帮助所有想要学好前端的同学,学习规划、学习路线、学习资料、问题解答。只要加入WEB前端学习交流qun:296212562,即可免费获取,学习不怕从零开始,就怕从不开始。
网友评论