《Ruby 元编程》对JavaScript 开发者的启示
作为一名JavaScript 开发者,在看本书的过程中每看到一个元编程技巧,都会忍不住将两种语言作一番比较和联想:在JavaScript 可以应用这种技巧吗?可以的话,这种技巧应用得广泛吗?
某程度上JavaScript 比Ruby 更灵活,所以大部分技巧在JavaScript 都可以轻松实现。
动态方法
在ES6 之前JavaScript 连class 都没有(ES6 中的class 实际上也只是语法糖),所谓的对象封装是通过构造函数实现的。没有class 的结果就是JavaScript 的面向对象实现得非常松散,构造函数的作用跟工厂函数差不多。都是用来产出一个实例,只是构造函数为实例提供了一个原型(constructor.prototype),实例会自动继承原型上的属性、方法(JavaScript 中对象上的方法实际上就是对象的属性!),这就是JavaScript 中对象的封装和继承了。
实例需要共享的属性和方法统一定义在原型上,至于实例自己的属性,要么就是构造函数新建实例的时候根据参数指派,要么就是直接给实例赋值。
JavaScript 中同样有一句“一切皆对象”,甚至连函数也是对象,所以在JavaScript 中要定义类方法,一般是把方法赋值为构造函数的属性。
是的,想要类属性就给构造函数的属性赋值,想要实例属性就给实例的属性赋值,JavaScript 支持使用方括号语法进行动态属性赋值:obj[name] = method 。这里name就是一个变量,你甚至可以在方括号里放进一个表达式;因为JavaScript 中对象的属性和方法实质是同一个东西,所以当method 是一个方法的时候,实际上就实现了Ruby 的define_method 了。
另外,JavaScript 中函数的调用需要显式添加括号,因此JavaScript 中类似Ruby 的send 方法的实现也呼之欲出了:objname。
打开类
JavaScript 中的实例的属性和方法继承自原型,而且原型上对属性和方法作出修改,会同步到实例中去,所以要实现打开类,只要直接操作原型即可。
早年有一个prototype.js 的类库,就是大量运用了这个技巧,在字符串原型、数字原型等内置原型上直接作monkey patch,为内置原型添加了不少有用的方法。
问题是JavaScript 语法在发展,某个monkey patch 的方法可能很快就有了官方语法的支持,但如果patch 过的方法的函数签名不同于新语法,混乱就产生了,旧代码变得岌岌可危,需要重构。
时至今天,直接修改内置原型已经成为JavaScript 的反模式。
代码块
JavaScript 中的函数非常灵活,可以完全不依附任何对象直接定义(可以近似地这样认为),可以认为JavaScript 中的函数都是自由方法;函数甚至可以被当做参数直接被传进方法中,这实际上就完全实现了类似代码块的功能了。
方法绑定与绑定对象
JavaScript 的函数里有类似self 的关键词——this。如果JavaScript 中的函数不依赖对象,那函数内的this 指向何处?这个this 是JavaScript 奇葩语法的一抹高光,几乎是面试必问,但其实也没那么复杂,在构造函数里this 指向实例,原型方法中的this 同样如此,其他情况下默认指向全局宿主对象。
指向全局宿主对象实际上作用不大,所以JavaScript 支持把方法抽出来,重新绑到别的对象上去,函数对象上的bind、apply 及call 方法就是干这个的,绑定完成后函数的this 就指向新绑定的对象。是不是和Ruby 中的 UnboundMethod#bind 的概念很像?而这个绑定的对象,不也有点Ruby 中绑定对象的味道么?
作用域门
JavaScript 中的作用域和Ruby 有很大不同,但作用域门方面有一些相似之处,比如JavaScript 中函数引用的变量是定义该函数时就决定了的,后面再调用,引用的都是定义时的变量。不过这个特性非常容易导致混乱,JavaScript 中大量运用bind、apply 及call 均实现预先传进部分参数,待函数真正调用时再传进余下参数,效果和作用域门也差不多。
JavaScript 缺失的特性
Ruby 始终是Ruby,有一些语法实在是太灵活了,连JavaScript 都自叹弗如,比如那个令人咋舌的method_missing,实际上实现了对象属性(方法)的终极拦截器,在JavaScript 中无论如何也实现不了。
另外就是隐式self,这个JavaScript 同样欠奉,未经定义直接引用的变量属于全局变量,造成全局空间污染,一直是JavaScript 的反模式。
比较下来,两门语言虽然在语法上大相径庭,但实现起元编程来却存在不少共性,真正体现了所谓的元编程其实也只是编程而已。