05.第7章:函数表达式

创建函数的方式有两种:函数声明,函数表达式

  • 函数声明

函数声明的重要特征:函数声明提升,执行代码之前会先读取函数声明。可以将函数声明放在调用它的语句后面。

函数声明可以放在调用语句之后
  • 函数表达式

创建了一个匿名函数并将它赋值给变量functionName
函数表达式和其他表达式一样使用前必须先赋值。

函数表达式

1. 递归

05.第7章:函数表达式_第1张图片
n的阶乘
  • arguments.callee表示一个正在执行的函数的指针,通过使用arguments.callee代替函数名,可以确保无论怎样调用函数都不会出现问题。
  • 但是在严格模式下,不能访问arguments.callee属性,访问会出现错误。使用命名函数表达式来实现


    05.第7章:函数表达式_第2张图片

2.闭包

2.1 回顾作用域

作用域:变量和函数的可访问范围。
js中作用域包括:全局作用域,局部作用域

  • 全局作用域:在代码中任何地方都能访问。

1)最外层函数和最外层函数之外定义的变量拥有全局作用域

var num=0;//直接定义的变量
//最外层的函数
function exp(){
 alert(1);
}

2)所有未使用var进行定义,而直接赋值的变量拥有全局作用域。

function exp(){
//没有使用关键字定义,直接赋值的变量,相当于创建全局变量
  num=0;
}
alert(num);

3)所有window对象的属性拥有全局作用域

一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等

  • 局部作用域:在固定的代码片段内可访问,比如函数内部。
function exp(){
  //定义一个局部变量,只能在exp()函数内部访问
 var num=1;
 alert(num);  

//定义了一个函数,只能在exp()函数内调用
function print(){
  alert(1);
}
//调用该函数
print();
}
//无法访问局部变量num
alert(num); ERROR
//无法访问print()函数
print();ERROR
  • 注意:js中没有块级作用域

对于js,没有块级作用域,if{ }里面定义的变量n就是全局变量,当然可以访问。

2.2 回顾作用域链

  • javascript的一个重要概念是执行环境(execution context),每一个执行环境都有相应的变量对象,执行环境中定义的所有变量和函数都存储在该变量对象中。
  • 在web中全局执行环境的变量对象是window对象。每一个函数都有执行环境,函数的活动对象就是执行环境的变量对象。
  • 当某个函数被调用时,会创建一个执行环境(execution context)及作用域链,作用域链在我理解就是存储变量对象的有序数组,并把作用域链赋值给execution context的内部属性[[scope]]。然后使用this,arguments和其他命名参数的值来初始化活动对象。
  • 在作用域链中,外部函数的活动对象处于第二位,外部函数的外部函数的活动对象处于第三位...,一直到全局对象。

当调用add(3,5)函数时演示上述过程:


05.第7章:函数表达式_第3张图片
  • 在函数中访问一个变量时,就会从作用域链中从第0位开始依次搜索具有相应名字的变量。
  • 内部环境能够通过作用域链访问所有的外部环境,而外部环境不能访问内部环境。

2.3 闭包

我们可以通过作用域链,在内部环境直接访问到外部环境的变量。
但有时我们需要在外部环境中访问到内部环境中的变量,这是就需要用到闭包。
闭包就是能够读取其他函数内部变量的函数,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

function f1(){
//定义了一个局部变量
var n=3;
//我们想要在全局环境中alert(n)这个局部变量,可以定义一个函数
function f2(){
 alert(n);
}
//将函数作为返回值返回
return f2;
}
var result=f1();
result();//就会执行alert(3),3。

闭包的用途

1)能够读取函数的内部变量
2)让这些变量的值始终保持在内存中

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

2.4 this对象

this对象是在运行时基于函数的执行环境绑定的:
全局函数中,this等于window
当函数被作为某个对象的方法调用时,this等于那个对象。
匿名函数的执行环境具有全局性,this等于window。

例1:

05.第7章:函数表达式_第4张图片

分析:
定义了一个全局变量name="The Window"
定义了一个对象object,包含了属性name="My Object",函数getNameFunc()
方法getNameFunc()中返回的是一个匿名函数,上面提到匿名函数的执行环境具有全局性,匿名函数中的this表示window,所以这里返回的是全局变量name:the Window

例2:

05.第7章:函数表达式_第5张图片
05.第7章:函数表达式_第6张图片

这里我们在object对象中,将this对象赋值给了that。如上所述,当函数被作为某个对象的方法调用时,this指向该对象,所以这里的this就是object对象。
然后在匿名函数中返回了that.name,就是object对象的name属性。

例3:


05.第7章:函数表达式_第7张图片

2.5 内存泄漏

在IE9之前的版本中,如果闭包的作用域链中保存着一个HTML元素,那么该元素将无法被销毁。


05.第7章:函数表达式_第8张图片

3.模仿块级作用域

05.第7章:函数表达式_第9张图片

我们可以使用匿名函数来实现块级作用域



上述代码进行简化,直接用函数代替变量名



05.第7章:函数表达式_第10张图片
示例

4.私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
我们将有权访问私有变量和私有函数的方法称为特权方法。
1)在构造函数中定义特权方法


05.第7章:函数表达式_第11张图片
在构造函数中定义特权方法

缺点:构造函数的缺点是针对每个实例都会创建同样一组新方法。
2)使用私有作用域(块级作用域)实现特权方法


05.第7章:函数表达式_第12张图片

05.第7章:函数表达式_第13张图片

05.第7章:函数表达式_第14张图片

你可能感兴趣的:(05.第7章:函数表达式)