Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[JavaScript] 红宝书整理 -- 函数 #15

@zgfang1993

Description

@zgfang1993

函数

  • 定义函数
  • 递归
  • 闭包
  • 模仿块级作用域
  • 私有变量

定义函数

1.函数声明

function funname(arg1,arg2){
}
funname.name === "funname"

非标准的name属性,
等于function后面的标识符
只在Firefox、Safari、Chrome、Opera有效

函数声明提升
执行代码之前会先读取函数声明
函数声明可以放在调用它的语句后面

2.函数表达式

var funname = function(arg1,arg2){
}

在使用前必须先赋值

递归

定义: 函数通过名字调用自身

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

问题:

var factorial1 = factorial;
factorial = null;
factorial1(4); //报错

解决:
arguments.callee()
指针:指向正在执行的函数

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

解决(严格模式):
严格模式下,不能访问 arguments.callee
可以使用 命名函数表达式
创建名为f()的命名函数表达式

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

闭包

1.定义:

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

2.本质:

一个指向变量对象的指针列表,只引用不包含变量对象

一般而言:
函数执行完毕后,局部活动对象会被销毁
内存中仅保存全局作用域(全局执行环节的变量对象)

但是在闭包中:
在另一个函数内部定义的函数将会把外部函数的活动对象添加到它的作用域中。
外部函数返回内部匿名函数后执行完毕,但外部函数的活动对象并没有被销毁,
因为匿名函数的作用域链中还在引用这个活动对象。
即外部函数的执行环境的作用域链会被销毁,但它的活动对象仍然会在内存中。

function createComparisonFunction(propertyName){
	return function(obj1,obj2){
		var val1 = obj1[propertyName];
		var val2 = obj2[propertyName];

		if(val1<val2){
			return -1;
		}else if(val1>val2){
			return 1;
		}else{
			return 0;
		}
	}
}

var result = createComparisonFunction("name")({name:"A"},{name:"B"})  ["name"]

解释:
匿名函数从createComparisonFunction()函数中返回后,它的作用域链初始化为:包含的createComparisonFunction()函数的活动对象和全局变量对象。
匿名函数可以访问在createComparisonFunction()中定义的所有变量。
在createComparisonFunction()执行完毕后,其活动对象也不会被销毁(因为匿名函数的作用域链仍然在引用这个活动对象)。
即:createComparisonFunction()执行完毕后,其执行环境的作用域链会被销毁,但它的活动对象任然会留在内存中,直到匿名函数被销毁后,
createComparisonFunction()的活动对象才会被销毁。
图解:
image

var compareNames = createComparisonFunction("name");

var result = compareNames({name:"A"},{name:"B"});
//解除对匿名函数的引用,释放内存
compareNames = null;

将compareNames设置为null,解除对该函数的引用
等于通知垃圾回收例程将其清楚,匿名函数的作用域链被销毁,
其他作用域也都销毁了。

慎重使用闭包:
由于闭包会携带包含它的函数的作用域,会占用更多的内存。

3.副作用

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

function createArr(){
	var res = new Array();
	for(var i=0;i<10;i++){
		res[i] = function(){
			return i;
		}
	}
	return res;
}

createArr()[0](); //10 每个函数返回的都是10

解决方法:
没有直接把闭包赋值给数组,而是定义了一个匿名函数,
并立即执行匿名函数,将结果赋值给数组

function createArr(){
	var res = new Array();
	for(var i=0;i<10;i++){
		res[i] = (function(num){
			return function(){
				return num;
			};
		})(i);
	}
	return res;
}

createArr()[0](); //0

2) this对象

匿名函数的执行环境具有全局性,
因此其this对象通常指向window

var name = "window";

var obj = {
	name:"obj";
	getName:function(){
		return function(){   //闭包
			return this.name;
		}
	}
}

obj.getName()(); //"window" 非严格模式

为什么匿名函数没有取得外部作用域的this对象
解释:
每个函数在被调用时都会自动取得两个特殊变量:this和arguments
内部函数在搜索到这两个变量时,只会搜索到其活动对象为止。
永远也不会直接访问外部函数的这两个变量。

解决:
把外部作用域中的this对象保存在一个闭包能够访问的变量里。
arguments也同理。

var name = "window";

var obj = {
	name:"obj";
	getName:function(){
		var that = this; //保存在一个闭包能够访问的变量里

		return function(){   //闭包
			return that.name;
		}
	}
}

obj.getName()(); //"obj" 

特殊情况:
2.加上括号,好像引用一个函数,但是this值得到了维持,obj.getName 和(obj.getName)的定义时一样的。
3.先执行了一条赋值语句,再调用赋值后的结果。赋值表达式的值是函数本身,所以this值不能得到维持。

//特殊情况:
var name = "window";
var obj = {
	name:"obj";
	getName:function(){
		return this.name;
	}
}
 obj.getName();    //"obj" 
(obj.getName)();    //"obj"   this值得到了维持
(obj.getName = obj.getName)();   //"window"  this值不能得到维持。
  1. 内存泄露
    原因:JScript对象和COM对象使用不同的垃圾收集例程(IE9之前版本)

结果:闭包在IE9之前的版本,如果闭包的作用域链中保存着一个HTML元素,该元素将会无法被销毁。

举例:

function assignHandler(){
	var element = document.getElementById("ele");
	element.onclick = function(){  //闭包
		console.log(element.id);
	}
}

这个闭包创建了一个循环引用,闭包保存了对assignHandler()的活动对象的引用,
就会导致无法减少element的引用数。
只要闭包存在,element的引用数至少是1,它占用的内存就会永远不被回收。

解决:

function assignHandler(){
	var element = document.getElementById("ele");
	var id = element.id;
	element.onclick = function(){  //闭包
		console.log(id);
	}
	element = null;
}

解除对DOM对象的引用,减少引用数,确保正常回收其占用的内存。

  1. 把element.id保存在一个变量中,在闭包中引用该变量,消除了循环引用。
  2. element设置为null。闭包会引用外部函数的整个活动对象,即使闭包不直接引用element,
    外部函数的活动对象也会保存一个引用

模仿块级作用域

1. JS没有块级作用域

function nums(count){
	for(var i=0;i<count;i++){
		console.log(i);
	}
	console.log(i);
}

在JavaScript中,变量i在for循环结束后没有被销毁。
变量i是定义在nums()的活动对象,可以在函数内部随处访问。

function nums(count){
	for(var i=0;i<count;i++){
		console.log(i);
	}
	var i; //重新声明变量
	console.log(i);
}

即使重新声明同一个变量,也不会改变。
会对后续的声明视而不见。

2. 匿名函数模仿块级作用域(私有作用域)

(function(){//跨级作用域})();

function nums(count){
	(function(){
		for(var i=0;i<count;i++){
		console.log(i);
	}
	})();
	
	console.log(i); //错误
}

在for循环外部插入了私有作用域,
在匿名函数中定义的变量都会在执行结束时被销毁

经常在全局作用域中被用在函数外部。

  1. 创建私有作用域,限制了向全局作用域中添加过多的变量和函数。
  2. 较少闭包占用内存,没有指向匿名函数的引用,函数执行完毕立即销毁作用域链。

私有变量

1. 定义

任何在函数中定义的变量(参数、局部变量、函数内部定义的函数)

2. 特权方法

访问私有变量的公有方法,创建闭包

function obj(){
	//私有变量和私有函数
	var variable = 10;
	function fun(){
		return false;
	}

	//特权方法
	this.public = function(){
		variable++;
		return fun();
	}
}
//除了使用public()这个途径没有其他方法可以直接访问variable和fun().
function Dog(){
	this.getName = function(){
		return name;
	};
	this.setName = function(val){
		name = val;
	}
}

私有变量name在每个Dog实例中都不相同,
每次调用构造函数都会重新创建这两个方法。

缺点:必须使用构造函数模式,会针对每个实例都创建同样一组方法。
解决方法:静态私有变量

3. 静态私有变量

(function(){
	//私有变量和私有函数
	var variable = 10;
	function fun(){
		return false;
	}
	//构造函数 全局变量 非严格模式
	obj = function(){};
	//公有/特权方法
	obj.prototype.public = function(){
		variable++;
		return fun();
	}
})()
  1. 创建了一个私有作用域
  2. 公有方法在原型上定义
  3. 构造函数使用函数表达式
  4. 构造函数申明没有用var(初始化未经声明的变量,会创建一个全局变量 非严格模式)
    5.私有变量和函数是由所有实例共享的
(function(){
	var name = "";
	Dog = function(val){
		name = val;
	}

	this.prototype.getName = function(){
		return name;
	};
	this.prototype.setName = function(val){
		name = val;
	}
})()

一个实例上调用setName会影响所有实例。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions