JavaScript 是一种灵活的语言,表达力极强,我来举一个例子,保证让很多人大吃一惊。
本文受到了 Kyle Simpson 的文章《Iterating ES6 Numbers》的启发。
首先,在 Number.prototype 对象上,部署一个 add 方法。
Number.prototype.add = function (x) { return this + x; };
上面代码为 Number 的实例定义了一个 add 方法。(如果你对这种写法不熟悉,建议先阅读我写的《JavaScript 面向对象编程》。)
由于 Number 的实例就是数值,在数值上调用某个方法,数值会自动转为实例对象,所以就得到了下面的结果。
8['add'](2) // 10
上面代码中,调用方法之所以写成8['add']
,而不是8.add
,是因为数值后面的点,会被解释为小数点,而不是点运算符。
将数值放在圆括号中,就可以使用点运算符调用方法了。
(8).add(2) // 10
其实,还有另一种写法。
8..add(2) // 10
上面代码的第一个点解释为小数点,第二个点解释为点运算符。为了语义清晰起见,下面我统一采用圆括号的写法。
由于add方法返回的还是数值,所以可以链式运算。
Number.prototype.subtract = function (x) { return this - x; }; (8).add(2).subtract(4) // 6
上面代码在Number对象的实例上部署了subtract方法,它可以与add方法链式调用。
如果使用方括号调用属性,写法会很古怪。
8["add"](2)["subtract"](4) // 6
我们还可以部署更复杂的方法。
Number.prototype.iterate = function () { var result = []; for (var i = 0; i <= this; i++) { result.push(i); } return result; }; (8).iterate() // [0, 1, 2, 3, 4, 5, 6, 7, 8]
上面代码在 Number 对象的原型上部署了 iterate 方法,可以将一个数值自动扩展为一个数组。
总之,现在我们可以在数值上直接调用方法了,但是后面一对圆括号看着有点碍眼,有没有可能去掉圆括号呢?也就是说,能不能将下面的表达式
(8).double().square()
写成另一种样子?
(8).double.suqare
这是可以做到的。
ES5规定,每个对象的属性都有一个取值方法get,用来自定义该属性的读取操作。
Number.prototype = Object.defineProperty( Number.prototype, "double", { get: function (){return (this + this)} } ); Number.prototype = Object.defineProperty( Number.prototype, "square", { get: function (){return (this * this)} } );
上面代码在 Number.prototype 上定义了两个属性 double 和 square ,以及它们的取值方法 get 。
因此,在任一数值上,读取这两个属性,就可以写成下面的样子。
(8).double.square // 256
也可以改用方括号运算符。
8["double"]["square"] // 256
(完)
wangxun 说:
知识点不少,受教了。
2015年2月10日 15:26 | # | 引用
必填 说:
https://www.destroyallsoftware.com/talks/wat
后半段讲的也是javascript有多灵活。
2015年2月10日 15:33 | # | 引用
YTed 说:
感觉只不过是 “往原型添加函数” 和 “调用函数的四种写法” 的组合使用罢了,虽然以后当了技术经理要用,但恐怕造成的混乱也不会少,而且跟 “表达力强” 不沾一点边。
面向对象的本质是数据和方法的绑定,(8).square 跟 square(8) 是一回事,除了可以多打一个点没啥稀奇的,唯一的好处是可以引入链式写法。
要说表达力强,难道不应该首推第一级函数(first-class function)和闭包(closure)吗?
2015年2月10日 15:52 | # | 引用
wittyfox 说:
感觉是在说 Ruby
2015年2月10日 16:28 | # | 引用
blacktulip 说:
这些算什么灵活...
http://pastebin.com/raw.php?i=JHeQ9KLS
这才叫灵活
2015年2月10日 17:21 | # | 引用
kael 说:
js最大的问题在于,没有下面这个东西
class a {}
class b extends a{}
没有简单的办法能在10行代码以内写出这玩意儿。
es6的话等浏览器能用再说
2015年2月10日 18:04 | # | 引用
慕陶 说:
var str = 'str';
str.func = function() { console.log('test');};
str.func() ==> Object str has no method 'func'
这个问题困扰了我不少时间
2015年2月10日 19:01 | # | 引用
matz 说:
ruby的
1.add 1 => 2
2015年2月10日 22:20 | # | 引用
阮一峰 说:
@慕陶:
我的理解是,原始类型(字符串、数值、布尔值)的值,本身都是常量,不能修改,只能在原型prototype对象上添加属性。
var n = 123;
n.x = 456;
n.x // undefined
2015年2月11日 08:32 | # | 引用
cath 说:
感觉就是加入了c#中的属性,以替代get/set方法
ps:不过往原型添加函数恐怕不好吧
2015年2月11日 08:53 | # | 引用
ss 说:
正式javascript的这种灵活特性,使我感觉它极为难学,基本看不懂别人的代码。
2015年2月11日 10:18 | # | 引用
Sunday 说:
的确是很灵活,但这是否属于‘表达能力’怕仍有争议,恐怕这只是当初语言设计者的‘无为而治’。
要论表达能力,个人感觉还是如py、ruby等高过一筹,js的这些用法无非奇技淫巧而已~
2015年2月11日 11:03 | # | 引用
Aki Xavier 说:
function ClassA() {
}
function ClassB() {
ClassA.call(this);
}
ClassB.prototype = new ClassA();
你要super我也能给你搞出来,你要delegate, mixin, reflection我都能给你搞出来
2015年2月11日 11:49 | # | 引用
Aki Xavier 说:
我连自己的代码都看不懂 :)
2015年2月11日 11:54 | # | 引用
天魔人心 说:
正是因为别人看不懂 你的价值才高
2015年2月11日 17:10 | # | 引用
沐风 说:
很不错,长见识了
2015年2月12日 09:58 | # | 引用
shenqiwei 说:
这种灵活不是好事,会导致代码很难被人理解,要知道一个项目的参与者并不见得都是同一水准的。如果大段大段的代码必须要运行后才知道结果,那是多痛苦的事。
2015年2月13日 09:56 | # | 引用
Amio 说:
补充一点,跟 8..add(2) 类似还有另外一种写法,8 .add(2),比双点号看起来略正常一些,不过也还是诡异,(8).add(2)还是看起来最合理的样子。
2015年2月13日 15:56 | # | 引用
rednax 说:
几点错误:
1. 数组不是原始类型,数组对象上添加属性完全没问题(其实length就是在数组对象上)
2. 原始类型除了string外也不是常量,a=5 这样的赋值内部绝对是把a变量的值变成5了,没必要搞花样(其实string也是一样,只是string存的其实是string内存地址,赋值后地址就换成新string的地址了而已,这种行为在C/Java中定义为常量,但其实在JS中完全不能算)
至于为什么不在原始类型上添加属性呢?原因很简单,因为当你在做
x=100; x.a = 100;
的时候,因为遇到了对象方法操作,所以首先JS会用原始类型的对象wrapper把变量包装一下,然后在这个临时对象上操作,操作完这个临时对象就销毁了,自然加上去的属性也没了,相当于:
(new Number(x)).a = 100;
最后修改原始类型的wrapper完全不推荐(除了做兼容性polyfill),作为奇技淫巧看看就好……
2015年2月13日 16:51 | # | 引用
阮一峰 说:
@rednax:
不好意思,我把数值误打成了数组,已经改过来了。其他地方确实说错了,谢谢指出。
2015年2月15日 08:18 | # | 引用
gopher 说:
过度的灵活给程序员带来了极大的心智负担,这就是我不喜欢js的原因
2015年2月18日 01:47 | # | 引用
Karl 说:
你是可以搞出来,我也可以搞出来。搞出来很好吗?如果A里面的函数需要继承,麻烦你写出来?不仅仅是简单的弄个Prototype就好了,类型的识别怎么办?
简单的,你看看Coffeescript编译出来的extends,也超过10行了。这么复杂就为了继承一个类,值得吗?
如今绝大部分语言都有OOP,OOP问题很多,但优势也很明显,JS的短板,就是OOP太弱。
2015年2月20日 14:05 | # | 引用
emu 说:
最后的例子里面干嘛要赋值回去给prototyoe?复制了前面的代码导致?
2015年2月20日 21:27 | # | 引用
兴杰 说:
难学是你学习方式不正确。
我们说 js 灵活,是指它能表现出不同的手法。
其实原理并没有那么复杂。
2015年2月22日 13:00 | # | 引用
aussux 说:
受益匪浅
2015年2月23日 15:27 | # | 引用
小媒体 说:
毫无疑问js语言是所以语言中最灵活的,入门易,精通难,要想游刃有余,必须对语法精通。
2015年2月25日 11:53 | # | 引用
爱国者 说:
怎么搞,求指导
2015年2月25日 22:14 | # | 引用
yukon12345 说:
js的短板不在于oop,它的OOP比较特殊。js在于解释器太混乱。。我只能说js是我见过最任性又最有意思的语言。
2015年3月 2日 00:02 | # | 引用
adved 说:
学习了,评论也很精彩
2015年3月 2日 15:07 | # | 引用
ja 说:
2015年3月 2日 15:26 | # | 引用
RedNax 说:
Coffeescript编译出来的代码那是Coffeescript自己方便,不意味着你也需要这样干。
Prototype比你想象的要有用很多,Prototype chain模拟OOP的单继承完全没有问题,继承没问题,重载没问题,类型识别更是简单的instanceof就能做到,而这些你只需要写一个造class的helper方法(短则数十行长则数百行),使用的时候一行代码搞定,比如我们的写法:
klass.define({
base: baseClass,
public: {
init: function(){},
methodA: function(){}
});
所以OOP不是JS的短板,觉得JS OOP不行是因为你读的写的代码太少。
JS短板在于缺乏静态类型,做大型工程的时候无法通过编译期静态类型检查来保证质量,当然这一点用TypeScript就可以了。
2015年3月 2日 16:53 | # | 引用
limerick 说:
翻开阮老师的博客,看评论区也是受益匪浅
2015年3月 4日 09:28 | # | 引用
xx 说:
后面说的get方法来调用函数,只怕会引起阅读方面的麻烦。
2015年3月 6日 14:17 | # | 引用
Demonbane 说:
@RedNax:
同意!
2015年3月10日 03:16 | # | 引用
yukjin 说:
你应该String.prototype.func=function(){}
2015年3月11日 17:05 | # | 引用
刘客青 说:
不需要说更多,博主真是个人才啊!行文流畅,简单易懂,让我受益良多.
2015年3月12日 09:52 | # | 引用
superwf 说:
最初几年都没弄清楚js是怎么回事,后来用上ruby才更深入的了解了js
应该是在用ruby和js的过程中加深了相互的了解.
这两个语言的类都是开放的,随时添加新属性和方法.
2015年4月 8日 14:03 | # | 引用
digdeep 说:
这绝对不能看做是js的优点!!!恰恰相反这是js的缺点或者说缺陷。js留下的坑,是我见过的所有语言中最多的,比C多多了。入门极其简单,精通就不要指望了...似乎也没有必要精通
2015年4月10日 17:32 | # | 引用
Zhang Visper 说:
2015年4月15日 20:38 | # | 引用
lxjwlt 说:
js很多所谓的坑 在我看来都是js的优势所在
2015年4月17日 11:58 | # | 引用
sandheart 说:
js没有静态类开发时很麻烦,好像从.net回到了asp
2015年4月17日 18:30 | # | 引用
web爱好者 说:
知识点上没得说,博主理解的还是很深入的,刚出来的时候对js不懂,后来在开发中慢慢的学习js。js确实带给了我很多的惊喜。现在还在学习。
2015年4月22日 17:42 | # | 引用
aaa 说:
受益匪浅,js真是太有意思了,我原本是搞Java的,看了这篇文章激发了学习的欲望
2015年4月26日 16:02 | # | 引用
lovenets 说:
佩服,这么深奥的问题你也能捉摸透了。
2015年4月30日 22:20 | # | 引用
CJY 说:
涨见识了!原来是这样的
2015年12月 4日 16:38 | # | 引用
瑞瑞爸爸 说:
这个问题很简单,
var a='abc';
var b=new String('abc');
console.log(typeof a);//string
console.log(typeof b);//object
a是字符串基本类型,b是字符串包装类型,字符串基本类型就不能加属性和方法,你的解释是不对的。
2016年1月 4日 09:43 | # | 引用
朽木 说:
大二开始学js,现在毕业半年,越来越觉得难,不过越来越觉得有意思
2016年3月 1日 10:36 | # | 引用
Martin 说:
刚实践了一下,发现一个有趣的现象,当给 Number.prototype 同时定义了 double 方法和属性时,在调用的时候,似乎是以属性优先的。因为当你可以使用 (8).double 的时候就不能再用 (8).double() 了。
这点还不太明白...
2016年3月 8日 12:15 | # | 引用
zhang 说:
这也就那样吧,有啥体现灵活的。。。并不觉得值得去立篇
2016年4月 1日 12:04 | # | 引用
李佳楠 说:
评论也很精彩,受益匪浅
2017年2月15日 14:56 | # | 引用
飞舞的蝴蝶 说:
这些评论有意思,争论语言本身好坏的已经暴露菜鸡本性。
2017年8月21日 14:07 | # | 引用
leijee 说:
(new String(str)) == (new String(str)) // false
通过new后,两者所指向的内存地址就不一样了,是两个不同的对象,
所以会出现(new String(str)).func()//(intermediate value).func is not a function 这种错误
2017年9月 1日 09:57 | # | 引用
李田所 说:
您这么厉害,搞个模板出来可好?
2018年6月14日 02:58 | # | 引用
建兵 说:
基本包装类型。
每当读取一个基本类型的时候,后台其实完成了一系列操作。给你创建了一个包装类型对象,完了又在销毁。
str.func = function() { console.log('test');}; 这个操作没问题,但是完了后,又被销毁了。
下一行就访问不到这个属性。
可翻阅红皮书基本包装类型。
2018年10月10日 11:29 | # | 引用
马化腾 说:
这种写法最大的问题在于,基本类型访问其属性或方法时,是对一个临时对象进行操作。
也就是说这种写法等价于:
var str = 'str';
(new String(str)).func = function() { console.log('test');};
(new String(str)).func() ==> Object str has no method 'func'
方法声明和方法调用完全不是同一个对象。
下面这种写法,测试通过:
var str = new String('str');
str.func = function() { console.log('test');};
str.func()
2020年10月10日 16:45 | # | 引用