分类

JavaScript 有多灵活?

作者: 阮一峰

日期: 2015年2月10日

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

(完)

留言(55条)

知识点不少,受教了。

https://www.destroyallsoftware.com/talks/wat
后半段讲的也是javascript有多灵活。

感觉只不过是 “往原型添加函数” 和 “调用函数的四种写法” 的组合使用罢了,虽然以后当了技术经理要用,但恐怕造成的混乱也不会少,而且跟 “表达力强” 不沾一点边。

面向对象的本质是数据和方法的绑定,(8).square 跟 square(8) 是一回事,除了可以多打一个点没啥稀奇的,唯一的好处是可以引入链式写法。

要说表达力强,难道不应该首推第一级函数(first-class function)和闭包(closure)吗?

感觉是在说 Ruby

这些算什么灵活...

http://pastebin.com/raw.php?i=JHeQ9KLS

这才叫灵活

js最大的问题在于,没有下面这个东西

class a {}

class b extends a{}

没有简单的办法能在10行代码以内写出这玩意儿。

es6的话等浏览器能用再说

var str = 'str';
str.func = function() { console.log('test');};
str.func() ==> Object str has no method 'func'

这个问题困扰了我不少时间

ruby的


class Integer
def add(n)
self + n
end
end

1.add 1 => 2

@慕陶:

我的理解是,原始类型(字符串、数值、布尔值)的值,本身都是常量,不能修改,只能在原型prototype对象上添加属性。

var n = 123;
n.x = 456;
n.x // undefined

感觉就是加入了c#中的属性,以替代get/set方法

ps:不过往原型添加函数恐怕不好吧

正式javascript的这种灵活特性,使我感觉它极为难学,基本看不懂别人的代码。

的确是很灵活,但这是否属于‘表达能力’怕仍有争议,恐怕这只是当初语言设计者的‘无为而治’。

要论表达能力,个人感觉还是如py、ruby等高过一筹,js的这些用法无非奇技淫巧而已~

引用kael的发言:

js最大的问题在于,没有下面这个东西

class a {}

class b extends a{}

没有简单的办法能在10行代码以内写出这玩意儿。

es6的话等浏览器能用再说


function ClassA() {
}

function ClassB() {
ClassA.call(this);
}

ClassB.prototype = new ClassA();

你要super我也能给你搞出来,你要delegate, mixin, reflection我都能给你搞出来

引用ss的发言:

正式javascript的这种灵活特性,使我感觉它极为难学,基本看不懂别人的代码。

我连自己的代码都看不懂 :)

引用Aki Xavier的发言:

我连自己的代码都看不懂 :)

正是因为别人看不懂 你的价值才高

很不错,长见识了

这种灵活不是好事,会导致代码很难被人理解,要知道一个项目的参与者并不见得都是同一水准的。如果大段大段的代码必须要运行后才知道结果,那是多痛苦的事。

补充一点,跟 8..add(2) 类似还有另外一种写法,8 .add(2),比双点号看起来略正常一些,不过也还是诡异,(8).add(2)还是看起来最合理的样子。

引用阮一峰的发言:

@慕陶:

我的理解是,原始类型(字符串、数组、布尔值)的值,本身都是常量,不能修改,只能在原型prototype对象上添加属性。

var n = 123;
n.x = 456;
n.x // undefined

几点错误:
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),作为奇技淫巧看看就好……

@rednax:

不好意思,我把数值误打成了数组,已经改过来了。其他地方确实说错了,谢谢指出。

过度的灵活给程序员带来了极大的心智负担,这就是我不喜欢js的原因

引用Aki Xavier的发言:

function ClassA() {
}

function ClassB() {
ClassA.call(this);
}

ClassB.prototype = new ClassA();

你要super我也能给你搞出来,你要delegate, mixin, reflection我都能给你搞出来

你是可以搞出来,我也可以搞出来。搞出来很好吗?如果A里面的函数需要继承,麻烦你写出来?不仅仅是简单的弄个Prototype就好了,类型的识别怎么办?

简单的,你看看Coffeescript编译出来的extends,也超过10行了。这么复杂就为了继承一个类,值得吗?

如今绝大部分语言都有OOP,OOP问题很多,但优势也很明显,JS的短板,就是OOP太弱。


最后的例子里面干嘛要赋值回去给prototyoe?复制了前面的代码导致?

引用ss的发言:

正式javascript的这种灵活特性,使我感觉它极为难学,基本看不懂别人的代码。

难学是你学习方式不正确。
我们说 js 灵活,是指它能表现出不同的手法。
其实原理并没有那么复杂。

受益匪浅

毫无疑问js语言是所以语言中最灵活的,入门易,精通难,要想游刃有余,必须对语法精通。

引用Aki Xavier的发言:

function ClassA() {
}

function ClassB() {
ClassA.call(this);
}

ClassB.prototype = new ClassA();

你要super我也能给你搞出来,你要delegate, mixin, reflection我都能给你搞出来

怎么搞,求指导

引用Karl的发言:

你是可以搞出来,我也可以搞出来。搞出来很好吗?如果A里面的函数需要继承,麻烦你写出来?不仅仅是简单的弄个Prototype就好了,类型的识别怎么办?

简单的,你看看Coffeescript编译出来的extends,也超过10行了。这么复杂就为了继承一个类,值得吗?

如今绝大部分语言都有OOP,OOP问题很多,但优势也很明显,JS的短板,就是OOP太弱。



js的短板不在于oop,它的OOP比较特殊。js在于解释器太混乱。。我只能说js是我见过最任性又最有意思的语言。

学习了,评论也很精彩

希望不能用

js最大的问题在于,没有下面这个东西

class a {}

class b extends a{}

没有简单的办法能在10行代码以内写出这玩意儿。

es6的话等浏览器能用再说


引用Karl的发言:
你是可以搞出来,我也可以搞出来。搞出来很好吗?如果A里面的函数需要继承,麻烦你写出来?不仅仅是简单的弄个Prototype就好了,类型的识别怎么办? 简单的,你看看Coffeescript编译出来的extends,也超过10行了。这么复杂就为了继承一个类,值得吗?

如今绝大部分语言都有OOP,OOP问题很多,但优势也很明显,JS的短板,就是OOP太弱。


Coffeescript编译出来的代码那是Coffeescript自己方便,不意味着你也需要这样干。

Prototype比你想象的要有用很多,Prototype chain模拟OOP的单继承完全没有问题,继承没问题,重载没问题,类型识别更是简单的instanceof就能做到,而这些你只需要写一个造class的helper方法(短则数十行长则数百行),使用的时候一行代码搞定,比如我们的写法:
klass.define({
base: baseClass,
public: {
init: function(){},
methodA: function(){}
});

所以OOP不是JS的短板,觉得JS OOP不行是因为你读的写的代码太少。

JS短板在于缺乏静态类型,做大型工程的时候无法通过编译期静态类型检查来保证质量,当然这一点用TypeScript就可以了。

翻开阮老师的博客,看评论区也是受益匪浅

后面说的get方法来调用函数,只怕会引起阅读方面的麻烦。

@RedNax:

同意!

引用慕陶的发言:

var str = 'str';
str.func = function() { console.log('test');};
str.func() ==> Object str has no method 'func'

这个问题困扰了我不少时间

你应该String.prototype.func=function(){}

不需要说更多,博主真是个人才啊!行文流畅,简单易懂,让我受益良多.

最初几年都没弄清楚js是怎么回事,后来用上ruby才更深入的了解了js
应该是在用ruby和js的过程中加深了相互的了解.
这两个语言的类都是开放的,随时添加新属性和方法.

这绝对不能看做是js的优点!!!恰恰相反这是js的缺点或者说缺陷。js留下的坑,是我见过的所有语言中最多的,比C多多了。入门极其简单,精通就不要指望了...似乎也没有必要精通

引用慕陶的发言:

var str = 'str';
str.func = function() { console.log('test');};
str.func() ==> Object str has no method 'func'

这个问题困扰了我不少时间

这种写法最大的问题在于,基本类型访问其属性或方法时,是对一个临时对象进行操作。 也就是说这种写法等价于: 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()

引用digdeep的发言:

这绝对不能看做是js的优点!!!恰恰相反这是js的缺点或者说缺陷。js留下的坑,是我见过的所有语言中最多的,比C多多了。入门极其简单,精通就不要指望了...似乎也没有必要精通

js很多所谓的坑 在我看来都是js的优势所在

js没有静态类开发时很麻烦,好像从.net回到了asp

知识点上没得说,博主理解的还是很深入的,刚出来的时候对js不懂,后来在开发中慢慢的学习js。js确实带给了我很多的惊喜。现在还在学习。

受益匪浅,js真是太有意思了,我原本是搞Java的,看了这篇文章激发了学习的欲望

引用Zhang Visper的发言:


这种写法最大的问题在于,基本类型访问其属性或方法时,是对一个临时对象进行操作。
也就是说这种写法等价于:
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()

佩服,这么深奥的问题你也能捉摸透了。

引用lovenets的发言:

佩服,这么深奥的问题你也能捉摸透了。



涨见识了!原来是这样的

引用Zhang Visper的发言:


这种写法最大的问题在于,基本类型访问其属性或方法时,是对一个临时对象进行操作。
也就是说这种写法等价于:
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()

这个问题很简单,
var a='abc';
var b=new String('abc');
console.log(typeof a);//string
console.log(typeof b);//object
a是字符串基本类型,b是字符串包装类型,字符串基本类型就不能加属性和方法,你的解释是不对的。

大二开始学js,现在毕业半年,越来越觉得难,不过越来越觉得有意思

刚实践了一下,发现一个有趣的现象,当给 Number.prototype 同时定义了 double 方法和属性时,在调用的时候,似乎是以属性优先的。因为当你可以使用 (8).double 的时候就不能再用 (8).double() 了。

这点还不太明白...

这也就那样吧,有啥体现灵活的。。。并不觉得值得去立篇

评论也很精彩,受益匪浅

这些评论有意思,争论语言本身好坏的已经暴露菜鸡本性。

引用Zhang Visper的发言:


这种写法最大的问题在于,基本类型访问其属性或方法时,是对一个临时对象进行操作。
也就是说这种写法等价于:
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()

(new String(str)) == (new String(str)) // false
通过new后,两者所指向的内存地址就不一样了,是两个不同的对象,
所以会出现(new String(str)).func()//(intermediate value).func is not a function 这种错误

引用Aki Xavier的发言:

function ClassA() {
}

function ClassB() {
ClassA.call(this);
}

ClassB.prototype = new ClassA();

你要super我也能给你搞出来,你要delegate, mixin, reflection我都能给你搞出来

您这么厉害,搞个模板出来可好?

引用慕陶的发言:

var str = 'str';
str.func = function() { console.log('test');};
str.func() ==> Object str has no method 'func'

这个问题困扰了我不少时间

基本包装类型。

每当读取一个基本类型的时候,后台其实完成了一系列操作。给你创建了一个包装类型对象,完了又在销毁。
str.func = function() { console.log('test');}; 这个操作没问题,但是完了后,又被销毁了。
下一行就访问不到这个属性。
可翻阅红皮书基本包装类型。

这种写法最大的问题在于,基本类型访问其属性或方法时,是对一个临时对象进行操作。
也就是说这种写法等价于:
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()

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接