JAVASCRIPT高程笔记-------第 七章 函数表达式
7.1递归
经典递归例子
function factorial(num){ if(num <= 1){ return 1; }else{ return num * factorial(num - 1); } } var a = factorial; //① factorial = null; //② alert(a(3)); // ③ factorial is not a function //原因 栈内存中存放一个factorial变量 指向堆内存中的函数体 第①句代码 执行后 变量a 亦指向 堆内存中的函数体 第③句代码执行后 factorial 变量 不再指向堆内存中的函数体 而执行第③句代码时候 函数体内部调用了 factorial变量 此时的factorial已经为null 所以提示错误 //改造 arguments.callee函数 是一个指向正在运行的函数的指针 因此采用此方式可以避免 因名称更换导致的错误 此方式不适用严格模式 function factorial1(num){ if(num <= 1){ return 1; }else{ return num * arguments.callee(num - 1); } } var b = factorial1; factorial1 = null; alert(b(3)); // 6 // 命名函数表达式 var factorial2 = (function f(num){ if(num <= 1){ return num; }else{ return num * f(num - 1); } }); var c = factorial2; factorial2 = null; alert(c(3)); // 6
7.2 闭包------是指有权访问另一个函数作用域中的变量的函数 个人理解 闭包就是函数A体内返回B函数 ,B函数在外部环境执行时还依赖函数A的作用域链(无关于执行环境而依赖定义的环境)
function a(x){ return function b(y){ return x + y ; } } var a1 = a(5); //① var b1 = a1(5); //② console.log(b1); //10 代码①处 执行时 传入变量x为5 代码②则a1引用函数b 然后再传入5 给a1 实际执行a函数内返回的b函数 此时b函数依旧能访问a函数内部的变量x 所以结果 为10
闭包的副作用------闭包只能取得函数体内变量的最后一个值,如下示例
function createFunctions1(){ var result = new Array(); for(var i=0; i< 10; i++){ result[i] = function(){ return i; } } return result; } console.log(createFunctions1()[5]()); //10 //============改造============== function createFunctions2(){ var result = new Array(); for(var i=0; i< 10; i++){ result[i] = function(num){ return function(){ return num; } }(i); } return result; } console.log(createFunctions2()[5]()); //5
改造后并没有直接把闭包赋值给数组而是采用匿名函数的方式,并且立即执行这个匿名函数,返回匿名函数内部的闭包 得以访问每个参数的值
7.2 .1关于this对象 --- this对象通常指的是整个函数执行环境, 而函数做为某个对象的方法调用时this则等于那个对象,由于闭包和匿名函数具有全局性 因此this对象通常指向window对象---如果以call或者apply去改变函数执行环境,那么this就指向其他对象
7.2.2 内存泄漏 --- IE9 之前的版本采用的是引用计数的方式回收内存 ,而闭包是一个保留函数运行结果的全局变量,因此如果在IE9之前的浏览器内运行使用了HTML元素对象的引用,那么意味着该对象无法被销毁
7.3 模仿块级作用域
JS中没有块级作用域的概念,因此块语句中定义的变量,实际上是包含在整个函数体内的,js解析器不会告知是否多次声明了同一变量(不过,会执行变量的初始化)如下示例
function output1(){ for(var i=0; i< 2; i++){ continue; } var i; console.log(i); } output1(); //2 访问了for循环体内的执行结果 所以等于2 function output2(){ for(var i=0; i< 2; i++){ continue; } console.log(i);//2 访问了for循环体内的执行结果 所以等于2 var i = 1; //对变量进行初始化 console.log(i); //1 } output2();
匿名函数可以模仿块级作用域来避免这样的命名冲突--适合大型项目中采用 此方式在执行完内部的匿名函数后内存会被释放掉 因此访问 i 的时候出错
//模仿块级作用域 function output3(){ (function(){ for(var i=0; i< 2; i++){ continue; } })(); console.log(i);// Uncaught ReferenceError: i is not defined } output3();
这种技术常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数,
7.4 私有变量 -------严格的说 js中没有私有成员的概念;所有对象属性都是公有的, 但是有私有变量的概念,任何在函数内部定义的变量,都可以认为是私有变量,因为不能在函数外部访问此变量 私有变量包括函数参数,局部变量和函数内部定义的其他函数
能够访问私有变量和私有函数的方法称之为特权方法 -- 如下示例:在构造函数内部定义了所有的私有变量和函数,然后创建来可以访问这些私有成员的特权方法 !
function MyObject(){ var privateName = 'zhangsan'; var privateFunction = function(){ return false; } this.publicFunction = function(){ console.log(privateName); return privateFunction(); } } var person = new MyObject(); console.log(person.publicFunction()); // zhangsan ; false;
7.4.1静态私有变量-------构造函数内部定义特权方法的缺点,就是必须使用构造函数模式来达到这个目的,而构造函数模式的缺点则是针对每个实例都会创建通用的方法,如果对象过多则会占用大量内存,基于此问题可以采用静态私有变量来避免这个问题
(function(){ var privateName = 'zhangsan'; var privateFunction = function(){ return false; }; MyObject = function(){}; //注意此处未使用var声明 因此MyObject是一个全局变量 MyObject.prototype.publicFunction = function(){ console.log(privateName); return privateFunction(); }; })(); var person = new MyObject(); console.log(person.publicFunction()); // zhangsan ; false;
7.4.2 模块模式-为单例创建私有变量和特权方法
//js中单例的定义是以字面量来创建的 var singleton = { name : 'zhangsan', method : function(){ return name; } } console.log(singleton.method()); // zhangsan //模块模式通过为单例添加私有变量和特权方法使其增强 var singleton = function(){ var privateName = 'zhangsan'; function privateFunction(){ return privateName; } return { publicProperty : true, publicMethod : function(){ return privateFunction(); } }; }(); console.log(singleton.publicProperty); //true console.log(singleton.publicMethod()); // zhangsan
7.4.3 增强的模块模式 -----此模式适用于单例必须是某种类型的实例,同时还必须添加其他的属性或方法对其加强
function CustomType(){ this.age = 18; }; var singleton = function(){ var privateName = 'zhangsan'; function privateFunction(){ return privateName; }; var Object = new CustomType(); Object.publicProperty = true; Object.publicMethod = function(){ return privateFunction(); }; return Object; }(); console.log(singleton.age); // 18 原实例中的年龄 console.log(singleton.publicProperty); //true 增强的属性 console.log(singleton.publicMethod()); // zhangsan 增强的方法
小结: 使用函数表达式可以无需对函数命名,从而实现动态编程,匿名函数也称拉达姆函数 !
函数表达式不同于函数声明,函数声明要求有名字,而函数表达式不需要,没有名字的函数表达式也叫做匿名函数
递归函数应该始终使用arguments.callee来递归调用自身,而不要使用函数名称;因为函数名称可能会改变
函数内部定义了其他函数,就创建了闭包。闭包有权访问包含函数内部的所有变量 ,闭包的作用域链上包含这它自身的作用域,函数的作用域和全局作用域 ,通常函数的作用域以及其中所有变量都会在函数执行完毕后进行销毁,但是由于闭包对函数的引用,这个函数的作用域将一直保存直到闭包不存在为止;
块级作用域 --创建并且立即调用一个函数,这样既可以执行函数,又不会在内存中留下函数的引用,其函数内部所有变量都会立即销毁