js笔记--函数表达式

函数表达式

1.递归

函数通过名字调用自身。

	function  factorial(num){
		if(num <= 1){
			return 1;
		}else{
			return num*factorial(num-1);
		}
	}
	var anotherFactorial = factorial;
	var factorial = null;   //factorial赋值null后会导致递归调用出错(factorial  is  not a function)
	console.log(anotherFactorial(4)); 

使用callee属性解决(指向拥有arguments属性的函数,该函数),严格模式下无法使用

	function  factorial(num){
		if(num <= 1){
			return 1;
		}else{
			return num*arguments.callee(num-1);
		}
	}

使用命名函数表达式来实现

	function  factorial(function f()){
		if(num <= 1){
			return 1;
		}else{
			return num*f(num-1);
		}
	}

创建一个命名为f的函数表达式,然后赋值给变量factorial

2.闭包

指有权访问另一个函数作用域中的变量的函数,创建闭包常见方式:在一个函数内部创建另一个函数

function createComparisonFunction(propertyName){
	//返回一个匿名函数
	return function(obj1,obj2){
		var value1 = obj1[propertyName];
		var value2= obj2[propertyName];

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

在匿名函数中访问了外部函数的propertyName参数,即使这个匿名函数被返回了,并且是在其他地方被调用,仍然可以访问到该参数。因为内部函数的作用域链包含了createComarisonFunction()的作用域。

当某个函数第一次被调用时,会创建一个执行环境(execution context),及相应的作用域链,并把作用域链赋值给一个特殊的内部属性(即[[Scope]]),然后使用this,arguments和其他命名参数的值来初始化函数的活动对象(activation  object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,直到作用域链的终点--全局执行环境。

在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量

	function compare(value1,value2){
		if(value1<value2){
			return -1;
			return 1;
		}else {
			return 0;
	}
	var result = compare(1,2);//在全局作用域中调用compare()函数

第一次调用compare()时,会创建一个包含this,arguments,value1和value2的活动对象。全局执行环境的变量对象包含this,arguments和compare。在compare()执行环境的作用域链中处于第二位。

如上图所示:作用域链本质上是一个指向变量对象的指针列表,只引用但不实际包含变量对象。无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名称的变量。一般当函数执行完后,局部活动对象就被销毁了,内存中仅保存全局作用域(全局执行环境的变量对象)。闭包除外!

在函数内部定义的函数会将外部函数的活动对象添加到它的作用域链中。

因此,在createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象。

//创建函数
var compare = createComparisonFunction("name");
//调用函数
var result = compare({name:"Ethan"},{name:"Zhangsan"});
//解除对匿名函数的引用(以便垃圾回收,释放内存)
compare = null;

闭包与变量

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,建议只在绝对必要时再考虑使用闭包。

问题:闭包只能取得包含函数中任何变量的最后一个值,闭包所保存的是整个变量对象,而不是某个特殊的变量。

	function createFunction(){
		var result = new Array();

		for(var i=0;i<10;i++){
			result[i] = function(){//声明10个函数
				return i;
			}
		}
		return result;
	}

	var functionArr = createFunction();
	functionArr.forEach(function(item,index){
		console.log(item());  // 10  输出十次,每次都是同一个结果
	});

createFunction()函数返回一个函数数组,每个函数作用域链中都保存着createFunction()函数的活动对象,所以它们引用的是同一个变量i。当createFunction()函数返回后,变量i的值是10,所以每个函数内部i的值都是10。

通过创建另一个匿名函数强制让闭包行为符合预期。

	function createFunction(){
		var result = new Array();
		for(var i=0;i<10;i++){
			result[i] = function(num){ //创建并立刻执行当前匿名函数
				return function(){//返回一个函数
					return num;
				}
			}(i);
		}
		return result;
	}
	var functionArr = createFunction();
	functionArr.forEach(function(item,index){
		console.log(item());  // 10  输出十次,每次都是同一个结果
	});

定义一个匿名函数,并将立即执行该匿名函数的结果赋给数组。匿名函数有一个参数num。在调用每个匿名函数时,都传入参数变量i。由于函数参数(基本类型)是按值传递的,所以会将变量i的当前值复制给参数num。在这个匿名函数内部,又创建并返回了一个访问num的闭包。这样,result数组中的每个函数都有自己num变量的一个副本,因此返回的就是各自不同的数值了。

this对象

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

	var name = "Window";
	var object = {
		name : "Ethan",
		getNameFun:function(){
			return function(){//返回一个匿名函数结果
				return this.name;//this指向window
			}
		}
	};
	console.log(object.getNameFun()()); // Window
每个函数在被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止(内部函数自己的this对象),因此永远不可能直接访问外部函数中的这两个变量,但是把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

	var name = "Window";
	var object = {
		name : "Ethan",
		getNameFun:function(){
			var outThis = this;//将外部函数的this对象赋值给匿名函数能访问到的变量
			return function(){//返回一个匿名函数结果
				return outThis.name;//访问的外部函数的name属性
			}
		}
	};
	console.log(object.getNameFun()()); // Ethan

在定义匿名函数前将this赋值给outThis变量,定义闭包后,闭包也可以访问这个变量。即使函数返回后,outThis也引用着object。

因此:如果想访问外部函数作用域中的this和arguments对象,必须将该对象的引用保存到另一个闭包能访问的变量中。

特殊情况:

	var name = "window";
	var object = {
		name:"Ethan",
		getName:function(){
			return this.name;
		}
	};
	console.log(object.getName()); // Ethan
	(object.getName)();//Ethan
	(object.getName = object.getName)(); // window

第三种调用方法中:先执行赋值语句,然后再调用赋值后的结果。赋值表达式的值是函数本身,this的值不能维持,所以返回window。

内存泄漏

如果闭包中保存着html元素(com对象使用的引用计数机制回收垃圾),那就以为着该元素将无法被销毁。

	function assignHandler(){
		var element = document.getElementById("hehe");
		element.onclick = function(){
			alert(element.id);
		}
	}

在匿名函数中保存了对保存了对外部函数的活动对象的引用,只要匿名函数存在,element的引用数至少是1。因此它所占用的内存将无法被回收。

解决方法: 手动断开对html元素的引用。

	function assignHandler(){
		var element = document.getElementById("hehe");
		var id = element.id;
		element.onclick = function(){
			alert(element.id);
		}
		//函数执行完后,闭包的作用域链依然会引用外部函数的整个活动对象(活动对象中有element引用)。所以需手动将html元素的引用置空。
		element = null;
	}

3.模仿块级作用域

JS中没有块级作用域的概念。所以在块语句中定义的变量,实际上是包含在函数中而非语句中的。

	for(var i=0;i<10;i++){
		console.log(i);
	}
	alert(i); //10,仍然可以访问,这里i是在全局作用域中的
	var i;//多次声明同一变量,JS会忽视后面的声明
	alert(i);//10

使用闭包解决:

	var name = "Ethan";
	(function(){//匿名函数,私有作用域,执行完后变量i被销毁
		for(var i=0;i<10;i++){
			console.log(i);
		}
		console.log(name);  //匿名函数是一个闭包,可以访问包含作用域中的变量
	})();
	alert(i); //报错,i未定义
 ()会将function(){} 由函数声明变成函数表达式,在后面加上()就会立刻调用这个匿名函数。

这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。

4.私有变量

JS中没有私有成员的概念,所有对象属性都是公有的。但是在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数,局部变量和在函数内部定义的其他函数。

有权访问私有变量和私有函数的公有方法叫做特权方法(privileged  method),有两种在对象上创建特权方法的方式。

一:在构造函数中定义特权方法

function Person(name){
	this.getName = function(){//闭包,通过作用域链可访问包含函数的name参数
		return name;
	};
	this.setName = function(value){
		name = value;
	}
}
var p = new Person("Ethan");
console.log(p.getName());// Ethan
p.setName("Zhangsan");
console.log(p.getName()); // Zhangsan

缺点:构造函数模式,每个实例都会创建同样的一组新方法。

改进:静态私有变量,通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法

	(function(){
		var privateVariable = 10; //声明私有变量

		function privateFunction(){//声明私有函数
			return false;
		}

	//	function MyObject(){ 如果使用函数声明的方式定义构造函数,就成私有函数了,无法被外部访问

	//	}
		//构造函数
		MyObject = function(){//省略var,使用表达式方式给构造函数赋值,可以在全局被调用。

		};

		//特权方法,在构造函数原型上定义,可被多个实例对象共享
		MyObject.prototype.publicMethod = function(){
			privateVariable ++;
			return privateFunction();
		}
	})();

与在构造函数中定义的区别:私有变量和函数是所有实例共享的,特权方法在原型上定义,所有实例调用的都是同一个函数。而这个特权函数作为一个闭包,总是保存着对包含作用域的引用(外部匿名函数--私有作用域)

	(function(){
		var name = "";
		Person = function(value){
			name = value;
		};
		Person.prototype.getName = function(){
			return name;
		}
		Person.prototype.setName = function(value){
			name = value;
		}
	})();
	var person1 = new Person("Ethan");
	console.log(person1.getName()); //Ethan
	person1.setName("Zhangsan");
	console.log(person1.getName()); //Zhangsan

	var person2 = new Person("Collen");
	console.log(person1.getName()); //Collen
	console.log(person2.getName()); //Collen
name成了一个静态,由所有实例共享的属性。在一个实例上调用setName()会影响所有的实例。

多查找作用域链中的一个层次,就会在一定程度上影响查找速度。正式使用闭包和私有变量的一个明显不足之处。

二:模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的。而模块模式则是为单例创建私有变量和特权方法。

	var singleton = function(){
		//私有变量和私有函数
		var privateVariable  = 10;
		function privateFunction(){
			return false;
		}

		//特权方法和属性
		return {//返回一个对象
			publicVariable : 20,
			publicFunction:function(){
				privateVariable++;      //操作私有变量
				return privateFunction();//返回调用私有方法的结果,false
			}
		};
	}();
	console.log(singleton.publicFunction());  //false
	console.log(singleton.publicVariable);  //20

如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式。这种模式创建的每个单例都是Object的实例

增强的模块模式

这种模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况。

	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;
	}();

小结:

在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量。

  • 在后台执行环境中,闭包的作用域链包含着它自己的作用域,包含函数的作用域和全局作用域
  • 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁
  • 但是,当函数返回了一个闭包时,这个函数的作用域将会一直保存到闭包不存在为止

注意:创建闭包必须维护额外的作用域,所以过度使用它可能会占用大量内存

时间: 2024-08-30 01:00:22

js笔记--函数表达式的相关文章

JS中函数表达式与函数声明的区别

hello,沐晴又来更新啦,今天呢,跟大家讲讲让人头疼的函数表达式和函数声明,反正我当初看那本高级程序的时候,是没怎么看太透,哈哈.我是个比较重基础的人,跟我一起探讨函数表达式和函数声明的世界吧. 首先呢,先看看他们的颜值:   函数声明:function 函数名(){}   函数表达式:function 函数名(可写可不写)(){}; (不写名叫做匿名函数表达式.写名叫做命名函数表达式.推荐用匿名的.) 这么一看长的好像没啥区别,反正颜值也都不高.那么我们如何区别是函数表达式还是函数声明呢?答

javascript中函数声明和函数表达式的区别

1.js中函数表达式的定义 表达式(expression)JavaScript中的一个短语,javascript会将其计算(evaluate)出一个结果.程序中的常量是一个最简单的表达式.变量名也是一种简单的表达式,它的值就是赋值给变量的值.复杂表达式是由简单表达式组成. --摘自<javascript权威指南> 一个经典的函数表达式的定义方法: //函数表达式的声明,表达式的值就是这个新定义的函数 var expressFunc = function(){}; 函数名称是函数声明语句必须得部

JS学习笔记3_函数表达式

1.函数表达式与函数声明的区别 函数声明有“提升”(hoisting)的特性,而函数表达式没有.也就是说,函数声明会在加载代码时被预先加载到context中,而函数表达式只有在执行表达式语句时才会被加载 2.闭包 有权访问另一个函数作用域中的变量的函数.闭包可以访问另一个作用域中的变量,因此闭包得到的变量值是最终值,而不是该变量在某一时刻的值,有一个很经典的例子: function createFuns(){ var result = new Array(); for(var i = 0;i <

&lt;深入理解JavaScript&gt;学习笔记(4)_立即调用的函数表达式

前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.(小菜理解:的确看到好多,之前都不知道这是自执行匿名函数) 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法也不一定完全对,主要是看个人如何理解,因为有的人说立即调用,有的人说自动执行,所以你完全可以按照你自己的理解来取一个名字,不过我听很多人都叫它为“自执行”,但作者后面说了很多,来说服大家称呼为“立即调用的函数表达式”.(小菜理解:不管叫什么,大概意思都是一样的

笔记:IIFE 立即执行的函数表达式 +function ($) { }(window.jQuery);

在Bootstrap源码(具体请看<Bootstrap源码解析1>)和其他jQuery插件经常看到如下的写法: +function ($) { }(window.jQuery); 这种写法称为:IIFE 2(Imdiately InvokedFunction Expression 立即执行的函数表达式). 解析: 先弄清函数表达式(function expression)3和 函数声明(function declaration)的区别: 函数表达式  var test = function()

重操JS旧业第九弹:函数表达式

函数表达式,什么概念,表达式中的函数表达式. 1 函数申明 function 函数名([函数参数]){ //函数体 } js中无论像这样的显示函数什么放在调用之前还是调用之后,都不影响使用,因为js解释引擎会将函数声明提前化,这点很好理解: 2 函数表达式 var functionV=function 函数名([函数参数]){ //函数体 }: 这种方式的一个变量指向了一个函数,仅此而已.js引擎只会把他当作一个函数类型的变量来处理,仅此而已. 3 递归调用 我们知道函数名仅仅是指向函数的指针,

关于JS中的函数定义及函数表达式

在初学JS的过程中,老是不能区别什么是函数表达式什么是函数定义,这对基础知识的夯实是不利的.因此查阅资料,认真对其进行了区别. 简单总结如下: 1.区别一:以function开头的函数纪委函数定义,其他则为函数表达式: 2.区别二:函数表达式可以省略函数名. function FunctionName(FormalParameterList) { FunctionBody } //函数定义 function [FunctionName](FormalParameterList) { Functi

【JS笔记系列】浅谈JavaScript函数

JavaScript作为一种基于对象(非严格面向对象)的语言,函数在JS中的地位非同一般:用函数声明类和对象.甚至函数本身也是对象. 一.函数的三种声明方式辨析. 1.function命令 function printAbc(){ console.log('ABC'); } 2.函数表达式(变量赋值形式) var printAbc = function (){ console.log('ABC'); }; 注意:1.使用函数表达式形式时务必要有分号. 2.此时function后函数名可有可无,且

js函数表达式和函数声明的区别

我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义"隐 藏"起来,外部作用域无法访问包装函数内部的任何内容. 例如: var a = 2; function foo() { // <-- 添加这一行 var a = 3; console.log( a ); // 3 } // <-- 以及这一行 foo(); // <-- 以及这一行 console.log( a ); // 2 虽然这种技术可以解决一些问题,但是它并不理想,因为会导致一些额外的