第五章讲了函数的使用方式, 要么函数声明, 要么函数表达式.

关于函数声明, 有一个重要的特征, 就是会函数声明提升:

  1. sayHi(); // 这样是可以的, 如果是函数表达式 就会报错
  2. function sayHi(){
  3. alert("Hi!");
  4. }

因为函数声明的这个特性, 千完不要在判断语句中使用函数声明:

  1. // 不要这么做!
  2. if(condition){
  3. function sayHi(){
  4. alert("Hi!");
  5. }
  6. } else {
  7. function sayHi(){
  8. alert("Yo!");
  9. }
  10. }

递归

我们看三个示例.
示例1:

  1. function factorial(num){
  2. if (num <= 1){
  3. return 1;
  4. } else {
  5. return num * factorial(num-1); // 耦合问题
  6. }
  7. }

示例2:

  1. function factorial(num){
  2. if (num <= 1){
  3. return 1;
  4. } else {
  5. return num * arguments.callee(num-1); //严格模式下会报错
  6. }
  7. }

示例3:

  1. // 这里使用了函数表达式, 实际上就是示例1的包装
  2. var factorial = (function f(num){
  3. // 同时使用函数表达式和函数声明, 那么函数声明的函数名只能在本函数中被访问
  4. if (num <= 1){
  5. return 1;
  6. } else {
  7. return num * f(num-1);
  8. }
  9. });

闭包

闭包是指有权访问另一个函数作用域中的变量的函数

我们看这个示例:

  1. function createComparisonFunction(propertyName) {
  2. return function(object1, object2){
  3. var value1 = object1[propertyName];
  4. var value2 = object2[propertyName];
  5. if (value1 < value2){
  6. return -1;
  7. } else if (value1 > value2){
  8. return 1;
  9. } else {
  10. return 0;
  11. }
  12. };
  13. }

其中返回的匿名函数就是一个闭包.

闭包与变量

  1. function createFunctions(){
  2. var result = new Array();
  3. for (var i=0; i < 10; i++){
  4. result[i] = function(){ // 闭包保存的是包含环境的变量, 而这个变量经过多次改变, 根据闭包的特性, 只能获取到最后一个改变的值.
  5. return i;
  6. };
  7. }
  8. return result; // 注意这里返回的是闭包数组
  9. }

发现了这个特点, 我们可以再创建多一层闭包:

  1. function createFunctions(){
  2. var result = new Array();
  3. for (var i=0; i < 10; i++){
  4. result[i] = (function(index){
  5. return function(){ //注意这里是没有参数的
  6. return index; //强行保留起来
  7. };
  8. })(i);
  9. }
  10. return result;
  11. }

this对象

我们在第五章讲过, 函数内部始终有两个特殊对象, 一个是arguments, 一个是this.

this指的是最后调用它的环境对象, 在返回的匿名函数中, this指向window,在构造函数中, this就是将要生成的对象, 在DOM中, this指向当前的html节点(这个后面的章节会讲).

你可能不太理解 this为啥指的是最后调用它的环境对象, 我们来看几个例子:

  1. function a(){
  2. console.log('this in a:',this) //window
  3. function b(){
  4. console.log('this in b:',this) //window
  5. }
  6. b()
  7. return function(){
  8. console.log('this in c:',this) //window
  9. }
  10. }
  11. var c = a();
  12. c();

可以看得出, 无论你函数有没有嵌套, 或者是否为匿名函数, 只要它没有被某一个明确的非window对象显式调用, 那么this都指向window. 这是JS的设计,便于this在函数中的表现一致性.

我们再来看一个例子:

  1. var name = 'window';
  2. var object = {
  3. name:'object'
  4. }
  5. function sayName(){
  6. console.log(this.name);
  7. }
  8. sayName(); // 'window', 没毛病
  9. // 但是我想让object调用sayName怎么办? 前面我们学的call和apply就排上用处了
  10. sayName.call(object); // 'object';
  11. //或者用es5的bind方法, 手动添加到object上
  12. sayName.bind(object)();// 'object';

内存泄露

主要是针对旧版IE, 略

模仿块级作用域

ES5没有块级作用域:

  1. function outputNumbers(count){
  2. for (var i=0; i < count; i++){
  3. alert(i);
  4. }
  5. alert(i); //依然可以访问
  6. }

为了实现块级作用域, 我们通常用立即执行的匿名函数实现:

  1. (fnuction(){
  2. // your code is here
  3. var i =10;
  4. })();
  5. alert(i); //报错

著名的jQuery就是用这种包装形式.

私有变量

严格来讲,JavaScript 中没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。

我们来看一个例子:

  1. function Person(name){
  2. // this.name = name; // 通常我们会这样设置属性
  3. this.getName = function(){
  4. return name;
  5. };
  6. this.setName = function (value) {
  7. name = value;
  8. };
  9. }
  10. var person = new Person("Nicholas");
  11. alert(person.getName()); //"Nicholas"
  12. person.setName("Greg");
  13. alert(person.getName()); //"Greg"

上面的示例可以看出, 对象实例无法直接访问name这个属性, 只能通过方法去访问或者修改.但是要记住本示例的代码也是有缺陷的, 为了访问私有变量而生成许多功能相同的特权方法, 是一种资源的浪费.

静态私有变量

模块模式


后面会另外写一篇文章讲设计模式

本章完