JS学习之路——初探函数、作用域、递归、预编译与闭包及立即执行函数


今天内容比较多,闲话就少叙了,直接进入主题了。昨晚熬夜和今天晚上下课之后看完了渡一成哥的js入门课程的很大一段内容,内容深入浅出,比自己看书理解要透彻得多,但是好记性不如烂笔头,还是一一记下慢慢温习吧。

函数

函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。这是一般得定义,简单翻译成正常人能听懂的话就是函数是一坨可以重复使用的实现内容定义的一系列动作的代码块,当被调用时就作出规定动作。

函数的语法

函数语法由关键字function声明,后接函数名(参数)代码块组成{ },如这就是一个简单的函数声明过程:

function test  ()  {
	doucument.write('abc')
}

这段代码定义了一个test的函数,功能就是当它被调用或触发时向页面打印abc。如此就实现了一个函数的定义,这种方式称为函数声明。函数还有另一种函数的定义方式——函数表达式,分为命名函数表达式和匿名函数表达式,注意函数表达式一般指匿名函数表达式,如:var test = function (){ }.

参数

函数的参数分为形参和实参,形参是函数被定义时在函数名后的括号内声明的参数,实参则是在调用时向函数传递的实际参数。注意形参与实参并不一定要个数相等,不需要严格一一对应。当实参的数量大于形参的数量时,实参会被函数内部的类数组——实参列表arguments保存。实参和实参列表有对应关系时是保持映射关系的(不是同一个),如以下情况,但是没有对应关系时就没有映射关系。

function sum(a, b) {
		a = 2;//改变实参值
		arguments[0] = 3;//改变实参列表第一项的值
		console.log(arguments[0]);
}
sum(1);

函数的返回值(停止条件)

return可以将函数执行的结果返回被调用的地方,同时停止函数的执行。

作用域初探

函数内部声明的变量只能在函数内部使用,称为局部变量。在函数外部声明的变量称为全局变量,他能被所有的函数使用。一般局部函数在函数执行完毕时即被“销毁”,因此在另外的函数中可以使用同样的变量名声明局部变量。

递归

引例:使用函数实现阶乘的算法

//1. 找规律
//2. 找到出口(停止条件)
function mul(n) {
	if(n < 0){
		return '请输入正整数';
	}
	if(n == 1 || n == 0)	{
		return num = 1;
	}
	return n * mul(n - 1);
}
	var num = mul(n);

递归简单来说就是在函数中调用自己,递归的思想就是找到规律,并找到跳出条件即可。

预编译

JS执行三部曲

JS代码在浏览器执行的过程分为三步,分别为通篇语法分析、预编译、解释执行。语法分析就是指js引擎去判断检查你的代码是否存在语法错误,解释执行更不用多说,自然就是执行你的代码,然而重中之重的是预编译,预编译简单理解就是在内存中开辟一些空间,存放一些变量与函数 。

两句口诀:

  1. 函数声明整体提升
  2. 变量 声明提升

预编译前奏

  1. imply global 暗示全局变量:即任何变量,如果未经声明就复制,那么此变量就为全局对象window所有。
  2. 一切声明的全局变量,全是window的属性。

预编译

预编译四部曲(函数执行前)

  1. 创建AO对象(Active Object)(执行期上下文)
  2. 查找函数形参及函数内变量声明,形参名及变量名作为AO对象的属性,值为undefined //变量 声明提升(值为undefined)
  3. 实参形参相统一,实参值赋给形参
  4. 在函数体里面找函数声明,值赋予函数体 //函数声明整体提升

预编译(JS脚本执行前)

  1. 生成GO对象(Global Object)(全局执行期上下文)
  2. 查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined
  3. 查找函数声明,函数名作为全局对象的属性,值为函数引用

说明:上一节提到的window实际上就是GO。

精解作用域

  1. [[scope]]:每个JS函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供JS引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中储存了运行期上下文的几何。
  2. 作用域链: [[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。
  3. 运行期上下文:当函数执行时(执行前一刻预编译环节),会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次被执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文即被销毁。
  4. 查找变量:从作用域的顶端依次向下查找。

举例:

function a() {
	function b() {
		var b = 234;
	}
	b();
}
var glob = 100;
a();

函数a被定义时,发生如下过程(继承全局的环境)

创建a函数生成scope继承GO环境

函数a被执行时,发生如下过程(生成自己的AO)

a函数执行时生成自己的AO
当函数a执行完毕后,产生的AO被销毁,可理解为箭头被砍断了。

函数b被创建时,发生如下过程(站在函数a的肩膀上,继承a的环境,与aAO是同一个AO,只是引用方式不同)

b函数创建时继承a函数的全部环境

b的作用域链中的AO与aAO是同一个AO,只是引用方式不同
如:

function a() {
	function b() {
		var bb = 234;
		aa = 0;
	}
	var aa = 123;
	b();
	console.log(aa);
}
var glob = 100;
a();

以上代码执行后输出的aa值为0,说明了b的作用域链中的AO与aAO是同一个AO,只是引用方式不同。

函数b被执行时,发生如下过程(生成自己的AO)
在这里插入图片描述

b执行完毕后砍断了自己生成的AO,原来a生成的aAO,不会销毁,只有a执行完毕才能销毁。

闭包

引例:

function a() {
	function b() {
		var bbb = 234;
		document.write(aaa);
	}
	var aaa = 123;
	return b;
}
var glob = 100;
var demo = a();
demo();

闭包的产生过程

如图所示,闭包的产生过程即是函数内部函数被抛出到全局中。我们称当内部函数被保存到外部时,就生成了闭包。闭包形成后将会导致原有的作用域链不释放,造成内存泄漏。结合图示理解即为a执行完毕后砍断了自己与AO的联系,但是此时b函数已经到了全局中,a砍断自己与AO的联系不会影响到b与AO的联系。而考虑当b执行完毕后,b只会砍掉自己生成的AO联系,那么a生成的AO就被永久的保存到了b的作用域中

闭包的作用

  • 实现公有变量
  • 可以用作储存结构
  • 可以实现封装,属性私有化
  • 模块化开发,防止污染全局变量

立即执行函数

对于某些特殊的初始化功能的函数,我们希望在它被创建时就执行,而不是等到被调用时才执行,此时立即执行函数可以派上用场。

立即执行函数的格式

(function () {} ());//W3C建议使用
(function () {} )();

立即执行函数的原理

理解立即执行函数只需记住:只有表达式才能被执行符号()执行。最外层的括号相当于将内部的函数转变为一个表达式,接下来即可被执行符号执行(最后一个括号)。我们使用-+都可以达到同样的效果:+ function () {} ()

立即执行函数的应用举例

首先看如下代码,需求是实现使用闭包向页面打印0-9这10个数字。但是执行结果向页面最终输出的却是十个10.

function test () {
	var arr = [];
	for (var i = 0; i <  10; i ++) {
		arr[i] = funtion () {
			document.write(i + " ");
		}
	}
	return arr;
}
var myArr = test();

造成这种结果的原因是:arr对应的内部函数被储存到arr数组中时,函数内容并没有被调用执行,因此打印语句中的i还是i而不是对应的i的值。当arr被抛出到全局中时,调用arr函数时数组中每个函数形成的闭包对应的GO中的i都已经变成了10,因此最后向页面输出的全是10.

使用立即执行函数进行优化:

function test () {
	var arr = [];
		for (var i = 0; i <  10; i ++) {
			(function (j) {
				arr[j] = function () {
					document.write(j + " ");
				}
			}(i));
		}
	return arr;
}
var myArr = test();
for (var j = 0; j < 10; j ++) {
	myArr[j]();
} 

经过立即执行函数,每一个函数在被保存到数组arr中的同时,for循环的函数语句要被执行,函数执行完毕那么将切断作用域中AO的联系,那么在全局中调用这个数组中的函数时,内部函数访问的就是对应的j的值了,也就是每个函数都对应的是自己的AO,而不是十对一。


哈哈哈哈终于总结完了,还有很多例子没有记下来,不过上面这些都是精华了。今晚太累了,明天再继续学习啦!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值