本书作者是jQuery之父John Resig的经典之作,是深入学习JavaScript技术的绝佳教材,涵盖了可重用代码、文档对象模型、Ajax、Web生产工具、AngularJs登内容。作者讲解了JS的发展现状、使用技巧以及未来趋势,辅以诸多代码提示,条理清楚、分析到位。本书展示了更新颖、更巧妙、更具有深度的JavaScript技术,同时理清了JavaScript的发展脉络,既能磨练技能,又可开拓视野。
第2章 特性、函数和对象
对象是JavaScript的基本单元。本章包含了JavaScript语言中最为重要的一些方面,例如引用、作用域、闭包以及上下文。它们未必是建造这门语言的基石,却是支撑、改良JavaScript的优美拱梁。
2.1 语言特性
2.1.1 引用和值
JavaScript采用两种方式来保存数据:值和引用。所有的原始值都直接复制到变量中。原始值包括字符串、数字、布尔值、null 和undefined。原始值最重要的特点是:它们按照值进行赋值、复制、传递函数参数以及返回函数结果。
JavaScript的其余部分则依赖于引用。没有保存原始值的变量中保存的对象是对象的引用。引用是指对象(数组、日期或是其他)所在位置的指针。实际的对象被称为指称目标(referent)。这是一种存在于多种编程语言中的强大特性。他提高了操作效率:两个(或多个)变量不需要各自拥有某个对象的副本,他们只需要指向同一个对象即可。
对象拥有两种特性:属性和方法。这两者通常杯统称为对象的成员。属性包含对象的数据。属性可以是原始值,也可以是对象。方法是作用在数据上的函数。在一些关于JavaScript的讨论中,方法被涵盖在属性之中。但有必要对两者做出区分。
2.1.2 作用域
绝大多数编程语言都采用了某些形式的作用域,差别在于作用域的持续期(duration)。JavaScript中只有两种作用域:函数作用域和全局作用域。函数拥有自己的作用域,而块(如while、if和for语句)则没有。
例2-1 JavaScript中变量作用域的工作方式:
// 将全局变量foo的内容设置为test var foo = 'test' // 在if块中 if (true) { // 将foo设置为new test // 注意:此时处于全局作用域! var foo = 'new test' } //在这里我们看到foo此时等于new test console.log( foo === 'new test' ); //创建一个修改变量foo的函数 function test() { var foo = 'old test' } // 但是在调用函数的时候,变量foo仅存在于函数作用域中 test(); // foo仍然等于new test console.log(foo)
你会发现变量位于全局作用域中。在基于浏览器的JavaScript中,所有的全局变量实际上是作为window对象的属性存在的。在其他环境中,所有的全局变量属于某个全局上下文。在例2-1中,赋值给了处于test()函数作用域中的变量foo。但是在例2-1中,该变量并没有声明它的作用域(使用var foo),当变量foo没有明确声明作用域时,他会被定义成全局作用域,即使他只是打算在函数中使用。
例2-2 隐式全局变量声明:
// 定义函数,设置变量foo的值 function test() { foo = 'test'; } // 调用函数为foo赋值 test(); // foo现在变成了全局作用域 console.log( window.foo === 'test' );
函数中 不加var 声明的变量 是全局变量。
在函数声明变量的时候,要留心变量提升(hoisting)的问题。函数中所有的声明(不包括变量的初始值)都会提升到作用域的顶部。JavaScript以此保证变量名全局可用。
2.1.3 上下文
你可以使用变量this来访问上下文,该变量总是指向代码当前所处的执行环境。
例2-3 在上下文中使用函数,然后将上下文切换到另一个变量:
function setFoo(fooInput) { this.foo = fooInput } var foo = 5; console.log( 'foo at the window level is set to:' + foo ); var obj = { foo: 10 } console.log( 'foo inside of obj is set to:' + obj.foo ); // 修改window对象中的foo setFoo(15); console.log( 'foo at the window level is now set to:' + foo ); // 修改对象obj中的foo obj.setFoo = setFoo; obj.setFoo(20) console.log( 'foo inside of obj is now set to:' + obj.foo ); // foo at the window level is set to: 5 // foo inside of obj is set to: 10 // foo at the window level is now set to: 15 // foo inside of obj is now set to: 20
setFoo函数看起来有点奇怪。我们通常并不会在普通的工具函数中使用this。将setFoo()与obj绑定在一起,利用this来访问obj的上下文。JavaScript有两种方法可以在制定的上下文中运行函数,call()和apply()方法。
例2-4 更改函数上下文:
// 一个简单的函数,可以用来设置其上下文的文字颜色 function changeColor(color) { this.style.color = color; } // 在window对象上调用函数时,该函数无法正常运行,原因在于window对象并不包含style对象 // changeColor('while'); // 创建一个新的div元素,该元素包含style对象 var main = document.createElement('div'); // call方法将div设置为黑色 // call方法使用第一个参数来指定上下文,并将其余的参数传递给函数 changeColor.call(main, 'black'); // 使用console.log检查结果 // 应该输出“blak” console.log(main.style.color); // 定义一个函数,用于设置body元素的文字颜色 function setBodyColor() { // apply方法使用第一个参数将上下文设置为body元素,第二个参数是一个传递给函数的参数数组 changeColor.apply(document.body, arguments); } // 将body元素的文字颜色设置成红色 setBodyColor('red')
2.1.4 闭包
闭包是这样一种手段:通过它,内部函数在其副函数结束之后依然能够引用其外围函数(outer enclosing function)中的变量。
闭包允许你引用存在于父函数中的变量。但是它提供给你的并不是该变量在创建之时的值,而是其在父函数中最后的那个值。
例2-4: