# JavaScript 函数表达式

# 函数定义

函数存在两种定义的方式:函数声明 + 函数表达式

方式1:函数声明

function function name(参数){ };

note: 这种定义的方式会使函数存在一个特性 函数声明提升:执行代码之前会先读取函数的声明; //这意味着可以把函数的声明放在函数的调用的后面

方式2:函数表达式

var functionName = function(参数){ };

note: 这种方式创建的函数叫做匿名函数或者拉姆达函数 并且这种方式定义的函数不存在函数声明提升

# 闭包

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

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。

创建方式:在一个函数内部创建另一个函数

执行环境(execution context)定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之相关联的变量对象。环境中定义的所有的变量和函数都保存在这个对象中。

首先呢,理解函数的作用域链:

一般函数

函数在被调用时(不是声明时),会创建一个执行环境及相应的作用域链,然后使用arguments和其他命名参数的值来初始化函数的活动对象(执行环境是函数,则将其活动对象作为变量对象)。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个来自下一个包含环境。这样一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

eg:

function compare(value1, value2) {
  if(value1 < value2) {
    return -1;
  } else if(value1 > value2) {
    return 1;
  } else {
    return 0;
  }
}

var result = compare(5, 10);

js-005

上图中,其作用域链包含两个变量对象:本地活动对象和全局变量对象。

作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

每个执行环境都有一个表示变量的对象-变量对象。全局环境的变量对象始终存在,而像函数调用这样的局部环境的变量对象,则只在函数执行的过程中存在。无论什么时候,在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来说当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是闭包的情况有所不同

闭包

//闭包
function createComparisonFunction(propertyName) {
  return function(Object1, Object2) {
    var value1 = Object1[propertyName];
    var value2 = Object2[propertyName];

    if(value1 < value2) {
      return -1;
    } else if(value1 > value2) {
      return 1;
    } else {
      return 0;
    }
  }
}

var compareName = createComparisonFunction("name");

var result = compareName({name: 'Yixing'}, {name: 'xiaoya'});

分析: 在另一个函数内部定义的函数会将包含函数(外部函数)的活动对象添加到它的作用域中。因此,在函数内部定义的匿名函数的作用域链中将会包含包含函数(外部函数)的活动对象。这样在匿名函数内部就可以访问包含函数中定义的所有变量。更为重要的是,即使外部函数执行完毕,其活动对象也不会被销毁,因为匿名函数的作用域链依旧在引用这个活动对象。

js-006

闭包的本质:

在另一个函数内部定义的函数会将包含它的函数(外部函数)的活动对象添加到它的作用域链中, 在外部函数执行完毕后,其活动对象并不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。

缺点:由于闭包会携带包含它的函数的作用域链,因此会比其他函数占用更多的内存

# 函数 & 变量 & 作用域链

  • A.在某个函数被调用时,会创建一个执行环境及相应的作用域链 每个执行环境都有一个与之关联的变量对象(环境中定义的所有变量和函数都保存在这个对象中) 如果这个执行环境是函数,则其变量对象是活动对象
  • B.使用arguments或者其它参数初始化函数的活动对象
  • C.在作用域链中,变量对象根据内外层次排序,内部的最先,外部的逐层递减(全局环境变量对象在最外层)
  • D.无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量
  • E.当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)

# 闭包与this指针

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

Note:匿名函数执行环境具有全局性

var name = "This is window";

var obj = {
  name: 'this is obj name',

  getNameFunc: function() {
    return function() {
      return this.name;
    }
  }
};

console.log(obj.getNameFunc()());  //"This is window"

分析: 每个函数在被调用时都会被创建一个执行环境,而且自动的取得两个特殊变量:thisarguments。内部函数在搜索这两个变量时,只会搜素到其活动对象为止,在上面的代码中,匿名函数并不存在外部函数(getNameFunc函数)的引用,所以不会取得obj对象的值,取而代之的搜索到全局对象上的name的值。

所以,如果想让取得obj上的name的值,那么只需扩大匿名函数的作用域链,即在执行匿名函数时存在外部obj对象的引用即可。

可以更改为

var name = "This is window";

var obj = {
  name: 'this is obj name',

  getNameFunc: function() {
    var that = this;
    return function() {
      return that.name;
    }
  }
};

console.log(obj.getNameFunc()());  //"this is obj name"

# 模仿块级作用域(私有作用域)

JavaScript中没有块级作用域的概念,这意味着在块语句中定义的变量实际上是包含在函数中而非语句中的。

可以用匿名函数来模仿块级作用域

(function (){ 
  //这里是块级作用域
})()

var fun = function(){
//这里是块级作用域
}

Note: 好处:这种技术通常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。同时因为没有指向匿名函数的引用,只要函数完毕,就可以立即的销毁其作用域链了。

# 私有变量

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

私有变量包括函数的参数、局部变量和函数内部定义的其他函数

//私有变量
function Person(name) {
  var gender = 'male';  //私有属性
  this.name = name;  //特权属性

  function getName() {
    return 'name';
  }  //私有方法

  this.setName = function(name) {
    this.name = name;
  };  //特权方法

  this.setGender = function(gd) {
    gender = gd;
  };  //特权方法

  this.getGender = function() {
    return gender;
  };  //特权方法
}

在构造函数中,可以通过利用私有成员和特权成员来隐藏那些不应该被直接修改的数据,并通过暴露特权方法来间接的修改其值。 在构造函数中定义特权方法也会存在缺陷,就是我们必须使用构造函数模式来实现,缺点是每个通过构造函数生成的实例都会创建同样的一组方法。

# 静态私有变量

//静态私有变量
(function() {
  var value = 0;

  function getVaule() {
    return value;
  }

  //构造函数  初始化未经声明的变量,总是会创建一个全局变量
  Obj = function() {

  };

  Obj.prototype.method = function() {
    value++;
    return value;
  }

})();

分析: 在定义构造函数时并没有使用函数声明,而使用了函数表达式,因为函数声明只能创建局部函数。我们在声明Obj时使用了var关键字。在这里利用初始化未经声明的变量,总是会创建一个全局变量的方式暴露了一个全局变量Obj。这种方式与在构造函数中定义特权方法的区别是,私有变量和函数是由实例共享的。由于特权方法在原型上定义的,因此所有的实例都使用一个函数。而这个特权方法,作为一个闭包总是保存着对包含作用域的引用

这种方式初始化的实例没有自己的私有变量。

# 模块模式

# 增强的模块模式

这两种模式用于支持单例特权方法

陕ICP备20004732号-3