JavaScript王国的一次旅行,一个没有类的世界怎么玩转面向对象?

没有学习前端开发之前,一直以为 JavaScript 跟 Java 有亲缘关系,等了解了 JS 之后才发现原来半毛钱关系都没有。有一次我问一位我的高中同学,他专业是软件工程。我问他“Java 是面向对象语言,C 是面向过程语言,那么 JS 是什么?”。“基于对象”,他的回答让我很郁闷,那基于对象跟面向对象又有什么区别?这不,刚好码农翻身的最新推送解答了我的疑问


来源:码农翻身 微信公众号

作者:刘欣

原文地址:码农翻身-JavaScript王国的一次旅行,一个没有类的世界怎么玩转面向对象?

前言

作为Java 帝国的未来继承人,Java小王子受到了严格的教育, 不但精通Java语言、Java虚拟机、java类库和框架,还对各种官方的Java规范了如指掌。

近日他听说一个叫做Javascript的屌丝逆袭了, 成功地建立了一个独立的王国, 不但成了前端编程之王, 还不断地蚕食Java帝国的领地 !

按照小王子宫廷老师的说法: 想当年, 这家伙只是运行在浏览器中,完完全全是蹭了Java的热度这才发展起来, 现在竟然回过头来要欺负我们, 还有没有天理了? 是可忍孰不可忍? !

(微信公众号码农翻身注:参见文章《Javascript:一个屌丝的逆袭》)

小王子可不这么认为, 存在必然是合理的,javascrip必有独特之处, 俗话说知己知彼,百战不殆,他觉得有必要去Javascript王国刺探一下,搜集一下情报, 看看这个曾经的浏览器中的面向对象语言是怎么回事, 为什么那么多码农趋之若鹜。

初步印象

乔装打扮以后,小王子来到Javascript 王国,这里看起来一派生气勃勃的景象,人们随性而奔放, 不像Java帝国那么严肃而呆板, 让人感觉心情愉悦。

不过令小王子感到不可思议的是, 这里竟然没有官方提供的类库! 人们干活用的工具五花八门,让人眼花缭乱, 什么AngularJS, React , Backbone,Vue, Ember,JQuery, …… 互相之间还吵来吵去,争来争去,煞是热闹。

对比这下,Java帝国有着严密的统治,有着官方提供的庞大类库, 还有一统天下的Web框架 SSH/SSM ,再加上各种各样的Java规范, 码农们只需要拿来学习,干活就行。

没有了选择的烦恼, 但同时也减少了选择的权利, 是好还是坏? 小王子自己也不知道。

小王子还注意到Javascript王国的人写程序几乎没人使用IDE, 找个趁手的文本编辑器就可以开工, 然后扔到浏览器中去运行测试,真是轻量级啊! 唉, 我们Java帝国还在争论IntelliJ IDEA和Eclipse孰优孰劣, 实在是没有必要啊。

没有类怎么创建对象

随着调查的深入,小王子愈发觉得吃惊, 这里竟然没有类的概念! 一个面向对象的语言竟然没有类! 这和小王子从出生就被灌输的概念可是背道而驰!

没有类怎么创建对象 ? 小时候宫廷老师经常说: 先写一个类, 然后才能从这个类new出一个对象出来 。

可是眼前却有着无数的javascript对象, 他们在不断地产生、消亡,一起辛苦地工作,支撑起庞大的、生机勃勃的帝国。

这些对象是从哪里来的? 小王子百思不得其解, 正值正午时分, 小王子看到前面有一家JSON酒馆,决定先歇歇脚,美美地吃一顿再说。

小王子要了二斤熟牛肉,三碗酒,正要开始享用, 只听到旁边桌子的一个穿着长袍的人问道:哎,你说的那个对象的原型是什么? 

另一位戴眼镜的则低声说:嘘,噤声,国王刚颁布命令,原型法是我们帝国的秘密,禁止公开讨论,以防被Java帝国给学了去。

小王子心中一动, 马上把小二叫来,要来上等酒菜, 送到邻桌,请两位吃酒。 一番酒喝下来, 小王子终于获得了两人的初步信任, 原来他们还是负责审查javscript语言规范的官员。

小王子问道: “我家世代经商, 走南闯北,去过C++王国,Java帝国, C#帝国, 他们都是号称面向对象的语言, 都有class 和 object的区分, 可是到了咱们javascript王国, 我怎么连一个class 都没有看到啊? ”

戴眼镜的官员说: “我们不用class, 那玩意儿太不直观了 !”

小王子暗暗称奇, 可是仔细一想, 好像就是这样啊, 当初我学习Java的时候, 费了好大的劲才接受了class这个概念,实际上面向对象的系统,不就是对象之间的交互吗? 要类干什么?

然后小王子问了一个关键问题: “没有class, 怎么创建对象啊”

“外乡人, 没那么复杂,你想想什么是对象啊,不就是属性加上方法吗? 你看看我们这就创建一个对象出来 ” 这位官员说着,手指头沾着酒水在桌子上写了起来:
创建对象

看到没有,这个animal对象定义了一个属性name, 和一个方法 eat , 简单吧?”

的确是简单又明了,完全不需要class, 一个对象就创建了,小王子面前似乎打开了一扇新的大门。

“由于对象并不和类关联, 我们可以随意地给这个对象增加属性:” 眼镜官员补充到。

增加对象

“还能这么玩?!” 小王子被惊到了,没有类的约束,这些对象也太自由了吧。

没有类怎么继承

“那继承怎么实现, 继承可是面向对象的重要概念啊”

眼镜官员说: “简单啊,继承不就是让两个对象建立关联嘛! 在我们javascript王国,每个对象都有一个特殊的属性叫做proto, 你可以用这个属性去关联另外一个对象(这个对象就是所谓的原型了) , 来我给你画一下”

继承

这段酒水写成的代码不长,但是却深深地震撼了小王子, 因为其中信息量非常巨大,隐藏了“原型”的秘密, 小王子不由得陷入了深思:

对象dog 的原型是animal (注意:也是一个对象), 对象cat的原型也是animal 。

无论是dog还是cat ,都没有定义eat()方法, 那怎么可以调用呢?

当eat方法被调用的时候,先在自己的方法列表中寻找, 如果找不到,就去找原型中的方法, 如果原型中找不到, 就去原型的原型中去寻找…… 最后找到Object那里, 如果还找不到, 那就是未定义了。

这里的这几个对象肯定是通过proto建立了一个原型链!

原型链

嗯, 我师父给我讲JVM虚拟机的时候, 也提到了一个对象在执行方法的时候,需要查找方法的定义,这个查找的次序也是先从本对象所属的类开始, 然后父类, 然后父类的父类…… 直到Object, 思路是一模一样的!

只不过Java 的方法定义是在class中, 而这个javascript 的方法就在对象里边, 现在我觉得似乎在对象里更加直观一点啊。

属性和方法应该类似,也是沿着原型链向上查找, 不过这里dog的name属性似乎覆盖了animal的name属性, 还有那个this, 在调用dog.eat()的时候,应该是指向dog这个对象的。

看来面向对象的理念都是想通的啊。 想着想着,小王子脸上竟然露出了笑容。

看到小王子像程序卡住一样,不动了, 穿长袍的官员推了小王子一把: 外乡人, 你怎么了?

小王子意识到自己的失态, 赶紧说: “哦,没啥, 我觉得你们使用的这个’原型‘的办法很精妙啊, 完全不用类就实现了继承。”

眼镜官员一愣: “外乡人, 看来你悟性不错, 帝国的秘密已经被你给洞察了, 不过很多新来的程序员就不容易体会到这一点, 于是我们就做了一个变通, 让javascript可以像Java那样new 出对象出来。说来惭愧, 这完全是为了迁就那些C++,Java, C#程序员啊 ”

向Java靠拢

小王子说:”什么变通办法? 难道你们也开始使用类了吗?“

“不不, 我们提供了一个叫做构造函数的东西。还是给你写点儿代码吧 ” 官员说着,又蘸着酒水写了起来:

构造函数

小王子说道: “那个function 已经有点 class的感觉了啊, 天呐我竟然看到了this这个关键字, 对了那个Student是你故意写的大写吗? ”

“是啊 , 这样以来看起来就像Java的类了。但是,中间有个问题,你看出来了吗? ”

小王子想了一阵:“ 是不是说每个新创建对象都有一个sayHello函数? 在Java中函数都是定义在class 上的。 如果定义对象上, 那就意味着每个对象都有一份, 太浪费了。”

“是的,所以我们得提供一种更加高效的办法, 把这个sayHello函数放到另外一个地方去! ”

“放到哪里? ”

“记得我们刚才说的原型链吗? 当一个对象调用方法的时候,会顺着链向上找,所以我们可以创建一个原型对象,其中包含sayHello函数, 让andy, lisa这些从Student创建起来的对象指向这个原型就ok了。”

“可是你这里只有构造函数Student, 在哪里创建原型对象呢? 怎么把andy,lisa 这些对象的proto指向原型对象呢? 不会让我手工来指定吧。”

眼镜官员瞪了一眼小王子说: “我们javascript帝国肯定不会这么麻烦程序员的, 我们可以把这个原型对象放到Student.prototype这个属性中(注意,不是proto), 这样一来,每次当你创建andy,lisa这样的对象时, javascript 就会自动的把原型链给建立起来!”

小王子面露难色:“唉,这理解起来有点难啊。”

“还是画个图吧, 当你去new Student的时候,javascript会建立这样的关系链:”

关系链

小王子说: “明白了,这个所谓的构造函数Student 其实就是一个幌子啊, 每次去new Student的时候,确实会创建一个对象出来(andy或者lisa) , 并且把这个对象的原型(proto)指向 Student.prototype这个对象,这样一来就能找到sayHello()方法了。”

眼镜官员回答:“没错,这个地方容易让人混淆的就是proto和prototype这两个属性, 唉,我也不知道最早为什么这么干, 实在是不优雅。”

“是啊,这个构造函数再加上prototype的概念,实在是让人费解, 所以我们商量着提供一点语法糖降低程序员的负担。” 长袍官员附和到。

语法糖

听到语法糖,小王子觉的很亲切, 因为 Java 中也提供了很多方便程序员的语法糖。

当长袍官员写出javascript的语法糖的时候, 小王子不由得大吃一惊:

这语法糖已经把javascript变得非常像Java, C#,C++的类了, 看来javascript帝国为了“讨好”程序员, 已经努力的在改变了, 我们java帝国看来得加油啊。

小王子现在明白了Javascript是一个基于原型实现的面向对象的语言, 根本没有类的概念, 新的方式给小王子的思维观念带来了重大的冲击。

在这里待久了,他又了解到javascript强大的函数式编程,越来越喜欢javascript, 都有点乐不思蜀了。

小王子还会回到Java帝国吗?