成为超级给力的Javascript程序员的诀窍之一,就是真正理解这门语言的语义。本文将通过使用浅显易懂的图表来说明Javascript的基本元素。
Javascript中的变量只是对内存中某个数值的引用。 这些数值即可以是字符串、数字以及布尔这类的基本数据类型,也可以是对象或者函数。
在下面的例子中,我们将在顶级作用域内建立4个本地变量并使他们指向基本类型。
//让我们在顶级范围内建立一些本地变量 var name = "Tim Caswell"; var age = 28; var isProgrammer = true; var likesJavaScript = true; //看一下这两个变量是否引用了相同的内容 isProgrammer === likesJavaScript;
Output
=> true
请注意,这两个布尔类型变量指向了内存中同一个数据。这是因为部分基础类型的字面量是一成不变的(原文直译为:“基础类型的的变量基本上是一成不变的“。疑有误),所以VM会将单一实例共享给所有对指定变量的引用者以作优化。
在上面的代码片段中我们看到,如果对两个引用指向同一个内存数据使用===做比较则会返回true。
外框(The outer box)所代表的是最外层的作用域。这些变量都是顶层的本地变量,不要和global或windows对象的属性混淆。
对象其实就是对新对象以及原型引用的集合,唯一要注意的是,在访问一个父对象中存在而本地对象中不存的属性时,原型链所起的作用。
// Create a parent object var tim = { name: "Tim Caswell", age: 28, isProgrammer: true, likesJavaScript: true } // Create a child object //建立子对象(注1:这种建立方式没有用到第二个参数:注2:tim将会替代Object作为新生成对象的jack.__proto__,也就是jack的构造器的原型对象) var jack = Object.create(tim); //改写本地的一些属性 jack.name = "Jack Caswell"; jack.age = 4; //通过原型链查看成员 jack.likesJavaScript;
Output
=> true
现在我们手里有了一个具有4个属性的对象,它被Tim变量引用。同样我们创建了一个继承于第一个对象的新对象,并且通过jack变量引用。然后为jack添加了两个属性。
当访问jack.likesJavaScript的时候,我们首先找到jack所引用的对象,然后查找likesJavaScript属性。由于他不存在,我们就去他的父对象中查找并找到了这个变量。最后成功访问到这个属性所引用的数值”true”。
有没有想过为什么像jslint这类工具总是提示你别忘记使用var声明你的变量,恩。。。。如果你忘记了会咋样呢
var name = "Tim Caswell"; var age = 28; var isProgrammer = true; // @@,我们忘记了VAR likesJavaScript = true;
请注意likesJavaScript现在已经是全局对象中的一个属性而非一个顶层作用域中的变量。只有在你准备混用几段脚本时,这个才是需要注意的。但是不得不说,在实际应用中,大家经常这么做。
谨记要用var声明你的变量以保证变量及其子孙作用于正确的闭包中。你应该会非常乐意遵循这个简单的规则。
如果你必须在全局对象中放些东西,如果是在浏览器中就使用window.woo,如果在node.js就使用global.goo
JavaScript不仅仅是连接成串的数据结构,他包含了可执行部分,被称为函数的可调用代码,这些函数建立了作用域链以及闭包。
函数可以被描述成包含可执行代码以及属性的特殊对象,每个函数都有一个[scope]属性,代表函数被定义时的环境。如果一个函数是作为其他函数的返回值返回的,则老的环境会被新函数放在“闭包”中。
在这里例子中我们演示了简单的工厂方法(factory method,应用了工厂模式的方法),它会建立闭包并返回函数。
function makeClosure(name) { return function () { return name; }; } var description1 = makeClosure("Cloe the Closure"); var description2 = makeClosure("Albert the Awesome"); console.log(description1()); console.log(description2());
Output
Cloe the Closure
Albert the Awesome
当调用description1()时,VM找到被引用的函数并执行。然后函数在闭包中寻找一个名为name的本地变量。这样的工厂函数很好,因为每个生成的函数都会有个私有空间存放变量。
请参阅“为什么使用闭包(why use closure)” 以便更深入此话题.。
有时为了性能的原因,或者仅仅为满足大家对编程风格的喜好。JavaScript提供了一个根据不同的调用方式在不同作用域中重用某个函数对象的“this”关键字。
下面我们建立了几个对象,并共享使用同一个函数。改函数在内部使用this,以便演示this在不同的调用中是如何变化的。
var Lane = { name: "Lane the Lambda", description: function () { return this.name; } }; var description = Lane.description; var Fred = { description: Lane.description, name: "Fred the Functor" }; // Call the function from four different scopes console.log(Lane.description()); console.log(Fred.description()); console.log(description()); console.log(description.call({ name: "Zed the Zetabyte" }));
Output
Lane the Lambda
Fred the Functor
undefined
Zed the Zetabyte
在上图中我们看到,即使Fred.description被赋值为为Lane.description,它实际也只是对这个函数的引用。因此所有三个引用都对匿名函数有相同的所有权。这就是我不在构造器的原型方法上调用次方法的原因,因为那意味着函数将和构造器或它的“class”之间产生某种绑定。(参看“this的动态本质(the dynamic nature of this)“以获取更多信息。)
用图表来演示这些数据结构给我带来了很多快乐,我希望这些能使我们更清晰的认识JavaScript的语义。我同时拥有前端设计开发和服务端架构的经验,我希望以独特的视角使人们能够更好的理解Javascript这本优秀的语言。
(所有这些图片都是由graphviz生成的,原始文件可以在这里找到。)
[转自:http://www.grati.org/?p=495]