alex此举是为了给自己即将出版的新书做个预告,但短短几句总结性的称述提炼出了JavaScript的核心设计概念及编程思想,不可谓不精粹。当我看到这几句话时,暗下思考,再查查犀牛书,才知道自诩精通js的我其实尚是一知半解。以下便来谈谈我对这几句话的重新认识:
-
JavaScript里一切皆是对象,包括函数。(Everything in JavaScript is an Object. Even functions)
"一切皆是对象"是一个诡辩的句子,从不同的角度此句可有不同的解释。所以java也号称是完全面向对象的语言。但是我以为alex这里所指的并非语义上的理解,而是从语法的角度。所以这一句应该理解为—— 在JavaScript语言中,操作的一切都是对象。
我原来以为JavaScript 既然名为 (Java)Script,自然也是继承Java语言的设计————在Java中,数据类型分为基本数据类型(number,boolean,char等)和对象数据类型————而其实JavaScript所操作的数据类型都是对象,类似于Ruby。怎么理解呢? 例如,整型和布尔值在Java代码中是一个基本数据类型,它是没有方法和属性的,在JavaScript中是怎样呢? 看如下的代码:
//画蛇添足的方法,仅作为example
Number.prototype.abs = function() {
returnMath.abs(this);
}
alert((-100).abs()); // 弹出返回值 100
Boolean.prototype.reverse = function() {
return !this;
}
alert(true.reverse()); // 弹出返回值 false
所以, "-100" 和 "true" 在JavaScript中竟然是作为对象来处理的,JavaScript代码中出现的所谓基本数据类型其实都是其Wrapper对象(Number, Boolean等)的直接量,JavaScript在处理它们时实际上是在操作它们的Wrapper对象。从语法上来说,JavaScript是比Java更面向对象的语言。
alex更强调在JavaScript中函数也是对象,这是动态脚本语言的重要特性(作为对比,在Java中函数(方法)只是一种语法)。函数成为一个对象,所以函数完全可以看作数据:赋给变量、设为对象的属性、甚至作为其他函数的参数。函数是对象带来了巨大的灵活性,也是JavaScript面向对象编程的设计核心之一。当然与之而来的复杂性,也让JavaScript倍受误解。
- 所有对象都是可变的。(Every object is always mutable )
此句还好理解,因为JavaScript中虽然有
final 保留字,但并没有提供语法上的实现。所以JavaScript中无法定义一个变量是不可变的。另外一个重要理解就是,JavaScript的对象在创建后可以随意添加或删除属性和方法。
- "." 操作符等同于"[]"操作符。(The dot operator is equivalent to de-referencing by hash (e.g., foo.bar === foo["bar"]) )
此句也是针对JavaScript的对象而言。对象的属性可以用"."加标识符"bar"来存取,也可以使用"[]"配合标识符的字符串形式来存取。这样就给存取对象属性带来了灵活性————可以动态构造一个字符串并以它来存取对象的属性。此句也揭示了:在JavaScript中数组就是对象,对象本质上就是一个关联数组。
- new 关键字实际上是先创建一个对象,再让构造器方法在该对象域中执行。The new keyword creates an object that class constructors run inside of, thereby imprinting them
此句有些难理解,以一个简单的构造器函数举例:
function Dog(name) {
this.name = name;
}
var dog = new Dog("小明");
我以我的理解模拟 new 操作符的执行步骤:
var dog = newObject(); //首先创建一个新的Object对象
dog.prototype = Dog.prototype; // 设置构造器函数的原型对象为 dog 的原型对象
Dog.apply(dog, argumens); //将新对象传递给构造函数,此时构造函数可以用this来初始化新对象
所以 javascript 并非象java/C++的构造器函数那样,先执行然后返回一个对象。而是先创建一个未定义属性的干净的Object对象,接着把构造器函数的原型对象设置为新对象的原型对象(构造器函数的原型对象是JavaScript自动为每个函数创建的)。最后再把这个对象交给构造器函数,在构造器函数中设置该对象其他的属性和方法等等。
通过这样的方式带来的巨大动态特性就是, 可以象操作普通函数一样操作构造器函数。__所有自定义的对象甚至可以使用同一个构造器函数__ 。实际上这也是Prototype和Dojo的核心设计思想。通过这样的方式和原型对象的设计,JavaScript实现了与Java完全不同的面向对象设计。
- 所有函数都是闭句。(Functions are always closures (combine w/ previous rule to create OOP) )
闭句在我的理解中就是函数和其定义时的上下文快照(作用域)关联在一起。然后函数执行的时候,所有变量都在快照中获取。犀牛书上的专业阐述是这样的:
闭包是一个把函数定义和作用域联合在一起的对象...执行函数时使用的是在定义它们时就有效的作用域链,而不是在执行它们时才有效的作用域链...实际上所有函数都是闭包实现的…
顺从这样的理解:任何函数都有作用域,客户端编程中顶层的全局域函数也是有作用域的,那就是window对象,所以全局函数也就是全局域和函数定义组合的闭包。
Alex说此句是JavaScript面向对象编程的重要规则,我就还不能完全参透了。暂时的理解就是如下代码所示:
function Dog(name) {
var name = name;
function getName() {
return name;
}
this.getName = getName;
}
var dog = new Dog("小明");
alert(dog.name); // 弹出"undefined"
alert(dog.getName()); // 弹出"小明"
以上代码中闭句的作用就是让 name 成为了Dog的私有属性,只能通过指定的 getName 方法去获取name。类似于 Java 的 JavaBean。
- this关键字关联于执行时的作用域,而非定义时的作用域。(The this keyword is relative to the execution context, not the declaration context )
此句似乎和上句关于”函数是闭包“的描述相反。但确是好理解的,一个函数中如果出现
this 关键字,那么这个
this 引用的对象就是调用这个函数的对象。也可以通过JavaScript中函数对象提供的apply和call方法来指定
this 引用的对象。这样的设计使得一个函数可以被分配给很多对象作为其方法,大大提高了复用性。Prototype中的bind方法及Dojo的dojo.lang.hitch方法都是利用了此规则。
- 原型对象的属性也是可变的(The prototype property is mutable )
此句算是对第二句的补充,JavaScript中每个对象类型(就是构造器函数)都有原型对象,我们可以在任何时候任意改变原型对象的属性,而且这个改变将影响已经通过new操作符实例化的对象和将来会实例化的对象。
我要感谢这篇短文,它促使我又认真的读了一遍犀牛书的相关章节,同时也有了新的认识和心得。感受正如alex最后所说:
If all of that makes sense to you, JavaScript can be a fun, liberating experience. If not, it’s going to be a world of pain and broken expectations as you shed the baggage of less dynamic languages.