第七章 函数表达式(js高级程序设计)

  • 函数变量提升:在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。
  • 函数声明:
say()
function say(){

}
  • 函数表达式
fun ();//error 函数表达式不存在函数变量提升
var fun = function(){
  //这个function是匿名函数
}

递归

  • 使用arguments.callee()实现递归。在编写递归函数时,使用 arguments.callee 总比使用函数名更保险。
function factorial(num){ 
 if (num <= 1){ 
 return 1; 
 } else { 
 return num * arguments.callee(num-1); 
 } 
} 
  • 命名函数表达式的形式创建(在严格模式中arguments.callee会报错)
var factorial = (function f(num){ 
 if (num <= 1){ 
 return 1; 
 } else { 
 return num * f(num-1); 
 } 
}); //即使是修改了factorial变量名函数内部也能调用到f()命名函数

闭包

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

  • 捋一下一般函数的一生:

    1. 当创建一个函数时,会生成一个预先包含全局变量对象(变量对象包括作用域链上的所有对象,全局对象永远在变量对象上,而局部环境的变量对象只在函数执行过程中存在)作用域链,存放在函数的[[Scope]]属性中
    2. 当调用函数执行时,函数会创建执行环境。首先他会吸收[[Scope]]属性作为执行作用域链,随后再将arguments组成的活动对象推入执行环境作用域链的前端。
    3. 当函数执行完毕,局部活动对象销毁,全局活动对象保存在内存中


      一般函数的执行环境
  • 捋一下闭包函数的一生:
    首先用父函数和匿名函数来表示外层函数和返回的内层函数

    1. 当调用执行父函数创建匿名函数后,即使父函数已经执行完毕,但是其活动对象不会被销毁,因为他被匿名函数引用
    2. 匿名函数执行时,会吸收父函数和全局变量对象作为执行作用域链,随后再将自己局部变量和arguments组成的活动对象推入作用域链前端
      所以匿名函数的作用域链是:
      argument和局部变量组成的活动对象
      父函数的活动对象
      全局变量对象


      父函数和匿名函数执行环境
  • 闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。

闭包与变量

  • 闭包只能取得包含函数中任何变量的最后一个值

  • 闭包父函数的活动对象只会保留父函数执行完毕后的一次。举个栗子:父函数执行 a=1 a=2 a=3,执行完毕后a=3,保存给内部匿名函数的活动对象的a就是等于3。所有返回的匿名函数都是指向相同作用域下的那个a

  function createFunctions() {
    debugger
    var result = new Array();
    for (var i = 0; i < 10; i++) {
      result[i] = function () {
        return i;
      };
    }
    i = 2 //将i重新赋值为2,则返回结果都是2
    return result;
  }
  const a = createFunctions()
  console.log(a[0]())//2
  console.log(a[5]())//2
  • 当createFunctions执行时,父函数创建i变量,i随着程序执行变成0,变成1,变成2。。。变成10,变成2。当createFunctions完全执行完毕后,才会将自己的活动变量交给匿名函数,此时交出去的i是2!!!,所以返回的所有匿名函数得到的i都是2!!
  • 解决方法:
    1. 返回匿名自执行函数,匿名自执行函数再返回闭包函数,匿名自治性函数传参给闭包函数返回。
    2. 将var改成let,每个for循环都是不同的作用域,返回的不再是同一个i

关于this对象

  • this 对象是在运行时基于函数的执行环境绑定的。
    目前知道的this指向
  1. 全局下指向window
  2. 对象中函数执行对象
  3. 匿名闭包函数的this指向全局window(每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量)
  • 获得父函数this的方法,将父函数this赋值给变量传递

闭包带来的内存泄露

  • 闭包会导致父函数局部变量一直保存在内存中,因为闭包函数一直保持着对他的引用
  • 怎么解决?
    1. 将局部变量中需要用到的值取出来引用,例如const age = obj.age
    2. 将父函数的局部变量变为null obj=null(为什么都没有引用obj还不会被垃圾回收呢?因为闭包函数会引用所有父函数的活动对象
function assignHandler(){ 
 var element = document.getElementById("someElement"); 
 var id = element.id; 
 
 element.onclick = function(){ 
 alert(id); 
 }; 
 
 element = null; 
} 

模仿块级作用域

  • 在匿名函数中定义的任何变量,都会在执行结束时被销毁
function outputNumbers(count){ 
 (function () { 
   for (var i=0; i < count; i++){ 
     alert(i); 
   } 
 })(); 
 
 alert(i); //导致一个错误!
} 
  • 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
  • 这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。

私有变量(闭包还可以用在对象创建私有变量)

  • 私有变量:函数中固定的变量(函数参数和函数的局部变量)
  • 特权方法:能访问到私有变量的方法
    1. 使用闭包,匿名函数返回父函数中的局部变量
    2. 构造函数中,通过get(),set()返回私有变量属性

静态私有属性:在私有作用域中用原型返回私有属性

模块模式:如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。和闭包差不多,就是返回了个对象

  var application = function () {
    //私有变量和函数
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    //公共
    return {
      getComponentCount: function () {
        return components.length;
      },
      registerComponent: function (component) {
        if (typeof component == "object") {
          components.push(component);
        }
      }
    };
  }();

增强的模块模式

  • 单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况(就是返回了一个对象,该对象又是某个构造函数的实例)
  var application = function () {
    //私有变量和函数
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    //创建 application 的一个局部副本
    var app = new BaseComponent();
    //公共接口
    app.getComponentCount = function () {
      return components.length;
    };
    app.registerComponent = function (component) {
      if (typeof component == "object") {
        components.push(component);
      }
    };
    //返回这个副本
    return app;
  }();

你可能感兴趣的:(第七章 函数表达式(js高级程序设计))