第7章 函数表达式

171 阅读6分钟

浏览器给函数都定义了个name属性,就是直接跟在function后面的函数名。

函数声明最重要的特征:函数声明提升。

函数表达式创建的,function后面没有名字的叫匿名函数(也叫拉姆达函数),因为name属性为空字符串。

递归

递归函数是一个函数通过名字调用自身的情况下构成的。

为了降低耦合,可以用命名函数表达式来到达相同的结果。

闭包

闭包指有权访问另一个函数作用域中变量的函数。以下都讲的是为什么有权访问。

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

函数中的执行环境、作用域链、变量对象如下图产生与销毁:


[[scope]]属性是在函数声明的时候就存在了。

在一个函数内部定义函数,会将外部函数的变量对象添加到内部函数的作用域链上。

包含闭包的函数,在执行完后,活动对象也不会被销毁(闭包的[[scope]]属性还引用着呢),直到闭包被销毁了才销毁。因此创建闭包会占用内存,要谨慎使用

P180图很重要。

闭包与变量

由于[[Scope]]声明的时候就存在了,所以闭包的作用域链我们要看函数创建的时候外部函数执行完的活动对象,里面有什么就是什么。但是闭包的代码我们要等调用的时候再去翻译。

var changeIndex;function createFunction(){    var i = 0;    var array = [];    changeIndex = function changeIndex(index){        i = index;    }    for(i = 0; i < 10; i++){  //块级作用域,这里let i=0;        array[i] = function(){ //array[i]=……这条语句已经被执行了,i编译为0,而function里面的语句没有执行,i不用翻译。            return i;        }    }    return array;}var array = createFunction();changeIndex(5);console.log(array[0]()) //5var array2=createFunction();changeIndex(2);console.log(array[0]());//5

函数每执行一次都会重新初始化一个活动对象,即使在有闭包的情况下,上个活动对象不消失,这个活动对象和上次执行的活动对象不是同一个。

只要函数不重新执行,里面的变量都是保存在同一个活动对象里的,所有闭包引用这个函数的同一个标识符都是同一个变量。

如果有for循环,没有块级作用域的情况下,for里面的变量,是循环的最终值,也就是函数调用完毕的值。当有块级作用域的时候吗,每次for循环都会为for创建一个变量对象,保存在闭包声明的时候的[[scope]]属性中,所以每个function返回的i不一样。

闭包中[[scope]]保存的是声明的时候调用的那次直接包含函数执行结果的活动对象。

一个作用域中的代码,运行一遍就会创建一个变量对象。块级作用域就是那一块代码运行一遍就一个活动对象。

包含闭包(除了立即执行函数表达式)的函数执行完后里面的变量还存在(私有变量),因为活动对象没销毁。执行一遍就创建一个活动对象,活动对象里有那么多变量,因此占内存。

关于this

this、arguments是每个函数自带的属性,变量对象初始化时就包含了,所以根据作用域链,闭包里找这两个属性的值时,只会从自己的活动对象找,不会从外部函数的活动对象找。

内存泄漏

由于闭包会让包含函数的活动对象一直存在,所以如果活动对象中有没有的引用对象,可以设置为null解除下,以便减少内存。【IE9之前的版本用引用计数,IE9之后用的标记清除:环境中的变量以及被环境中变量引用的变量都会清除标记,不回收】

模仿块级作用域

其实还是函数作用域

JavaScript如果你多次声明同一个变量,它只会对后续的声明视而不见,不过,会继续执行声明中变量的初始化。

(function(){    alert("ni");    })(); //没有变量引用,执行完直接销毁

对于引用对象,没有变量引用的时候便会被销毁。所以闭包如果没有变量引用,就会被销毁,[[scope]]也就销毁了。

(function(){    var i=0;  function a(){      alert(i)  }    a();})(); //外面的函数可以销毁,但是它的变量对象不能销毁,闭包执行完还有引用它的变量(function(){    var i=0;  (function (){      alert(i)  })();  })();  //闭包执行完没有引用它的值也就销毁了,所以外层函数的变量对象也销毁了

私有变量

函数中的参数、arguments、this以及变量、函数都可以认为是私有变量。

有权访问私有变量和私有函数的公有方法称为特权方法。(在外部能调用的方法)

有两种方法创建特权方法:

①利用构造函数

new之后,this会绑定到创建的对象上

function Person(name){    pp=name;    this.sayName=function(){        alert(pp);    }   //构造函数中放方法会导致方法不能复用,但方法能访问函数中私有变量}var p=new Person(22);p.sayName();//22   访问了私有变量pp
var p2=new Person(33);p.sayName();//33   重写调用了一遍Person,重新声明了一个sayName()方法,作用域链的变量对象是本次调用函数的活动对象

静态私有变量 ——只调用了一次外部函数,变量在一个活动对象中

(function(){    var value="";    Person=function(name){ value=name;}; //前面没有var,全局的,严格模式下报错    Person.prototype.sayName=function(){   //实例共用方法        alert(value);                             }    })(); //里面有闭包,执行完变量都存在活动对象中,不消失var p1=new Person("ni");p1.sayName(); //nivar p2=new Person("jjj");p1.sayName(); //jjj
p2.sayName(); //jjj

②模块模式

使用场景:必须创建一个对象并以某些数据初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。(一些需要初始化的插件)

var a=(function(){    var components=new Array();    components.push(new Object());    return {        getComponentsConut:function(){            return components.length;        },        setComponents:function(component){            components.push(component);        }    }})(); //里面有闭包,执行完变量都存在活动对象中,不消失

增强的模块模式:适合单例必须是某种类型的实例

var a=(function(){    var components=new Array();    components.push(new Object());    var o=Object();                        //这里可以创建各种类型的对象    o.getComponentsConut=function(){        return components.length;    };    o.setComponents=function(component){        components.push(component);    };    return o;})(); //里面有闭包,执行完变量都存在活动对象中,不消失