原文地址: http://howtonode.org/object-graphs
成为一个高效的JavaScript开发人员的秘诀之一是真正理解语言的语义。本文将使用一些简单的图表来解释JavaScript语言的基本要素。
引用无处不在
JavaScript中的变量是引用内存某个值的简单标记。这些值可以是字符串,数值和布尔值等原生类型,也可以是对象或函数。
局部变量
下面这个例子,我们新建了四个顶级作用域内的局部变量并且将它们指向一些原生类型的值。
// Let's create some local variables in the top scope var name = "Tim Caswell"; var age = 28; var isProgrammer = true; var likesJavaScript = true; // Test to see if the two variables reference the same value isProgrammer === likesJavaScript;
输出 true
请注意两个布尔值指向内存中的相同值。这是因为原生类型是不可变的,并且虚拟机(VM)优化使得同一原生值的引用共享一个实例。
在这个代码片段,我们使用===操作符来测试两个引用是不是指向同一个值,结果是true
盒子代表最外层的闭包作用域。这些变量都是顶级局部变量,不要跟global/window对象的属性混淆。
对象和原型链
对象是指向新对象和原型的引用集合。它带来的唯一特殊的是原型链,即当你访问一个属性时,它不在局部对象中而是在父对象中。
// Create a parent object var tim = { name: "Tim Caswell", age: 28, isProgrammer: true, likesJavaScript: true } // Create a child object var jack = Object.create(tim); // Override some properties locally jack.name = "Jack Caswell"; jack.age = 4; // Look up stuff through the prototype chain jack.likesJavaScript;
输出true
这里tim变量引用一个包含四个属性的对象,我们新建一个继承第一个对象的新对象,并且让变量jack指向它。然后我们覆盖两个局部对象中的属性。
现在当查找jack.likesJavaScript时,首先查找jack引用的对象,然后查找likesJavaScript属性。因为该对象没有这个属性,再查找父对象找到这个属性,然后找到它引用的true这个值。
全局对象
有没有想过为什么像jslint这样的工具总是提示我们不要忘记在变量前面写var语句。我们看看如果不写会发生什么。
var name = "Tim Caswell"; var age = 28; var isProgrammer = true; // Oops we forgot a var likesJavaScript = true;
请注意,likesJavascript现在是全局对象的一个属性而不是外层闭包的属性。这会在你准备混合一些脚本的时候产生影响。但是在任何真实程序中它都能正确工作。
记住在变量前面写var语句可以把变量作用于控制在当前闭包及其子对象之内。这会给你带来好处。
当你不得不将变量放到全局对象中时,可以在浏览器环境中显式的写window.woo或者node.js中写global.goo。
函数和闭包
JavaScript不仅仅是一系列链接的数据结构。它包含可执行,可调用的代码——函数。这些函数产生链接的作用域和闭包。
可视化的闭包
函数可以被描绘成包含可执行代码和属性的特殊对象。每个函数包含一个特殊的 [scope] 属性代表了函数定义时所在的环境。如果一个函数是另一个函数返回值,这个指向旧环境的引用会被新函数用"闭包“封闭起来。
这个例子,我们将新建一个简单工厂方法来产生闭包并返回一个函数。
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());
输出Cloe the ClosureAlbert the Awesome
当调用description1()时,虚拟机查找函数的引用并执行它。因为函数要寻找一个叫name局部变量,它在闭包作用域内查找它。这个工厂方法使得每个产生的函数拥有自己的局部变量空间。
深度阅读见这篇文章why use closure
共享函数和this
有时候因为性能原因或因为你喜欢这种风格,JavaScript提供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" }));
输出Lane the LambdaFred the FunctorundefinedZed the Zetabyte
这个图中我们看到,即使Fred.description被设置成Lane.description,它只引用这个函数。因此所有三个引用都指向同名的匿名函数。
这篇文章有关于this的详细内容what is this
结论
我画了很多有趣的图来可视化这些数据结构。我希望这些可以帮助初学者抓住JavaScript的语义。我有着前端设计开发和服务端架构的经验。希望我的独特视角对学习JavaScript这门语言的人们提供帮助。
ps:作者共写了三篇文章用图来描述JavaScript中的一些概念。后续将翻译其他两篇。