学习JS第四节——作用域、闭包、立即执行函数

1.函数的隐式属性

  • [[scope]]:就是常说的作用域,其中存储了运行期上下文的集合
  • 作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,将这种链式链接叫做作用域链。

每个JavaScript函数都是一个对象,对象中有些属性属性可以访问,但是有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。
运行期上下文:函数每次执行时都会产生一个上下文,这个上下文AO为即时的,因此执行完就被销毁。
查找变量:从作用域链的顶端依次向下查找。

2.作用域链

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

//a被定义时   a. [[scope]]     --> 0:GO{}
//a被执行时   a. [[scope]]		--> 0:AO{}
											-->1:GO{}

在这里插入图片描述
在这里插入图片描述

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

//a被定义时   a. [[scope]]     --> 0:GO{}
//a被执行时   a. [[scope]]		--> 0:a.AO{}
											-->1:GO{}
//b被定义时   b. [[scope]]    --> 0:a.AO{}
											-->1:GO{}
//b被执行时   b. [[scope]]		-->0:b.AO{}
											--> 1:a.AO{}
											-->2:GO{}

在这里插入图片描述
在这里插入图片描述

3.闭包的产生

闭包:当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏。

闭包不是只有外部函数最后加一个return内部函数语句才能产生
也可以通过设置全局变量等于内部函数产生
总而言之,只要内部函数活得比外部函数长,就可以产生闭包

//全局变量等于内部函数的方法
var demo;
function test() {
	var abc = 100;
	function a() {
		console.log(abc);
	}
	demo = a;
}
test();			//若没有这个语句则会报错没有定义demo函数
					//只有test执行,才能将a函数赋值给全局变量
demo();
//输出 100

4.闭包的优点:

  • 实现公有变量
    eg:函数累加器(不依赖于外部变量,且能反复执行,即每次执行结果都不一样)

独立的功能不能依赖于外部变量

function add() {
	var num = 0;
	function a() {
		console.log(++num);
	}
	return a;
}

var myAdd = add();
myAdd();
myAdd();
myAdd();

//输出 1 2 3
  • 可以做缓存
function test() {
	var food = "apple";
	var obj = {
		eatFood : function() {
			if(food != "") {
				console.log("I am eating" + food);
				food = "";
			} else {
				console.log("There is nothing!");
			}
		},
		pushFood : function (myFood) {
			food = myFood;
		}
	}
	return obj;
}

var person = test();

person.eatFood();
person.eatFood();
person.pushFood("banana");
person.eatFood();

//输出
I am eating apple
There is nothing!
I am eating banana
  • 变量私有化
function Deng(name, wife) {
	//这个变量在函数执行时会在函数的执行上下文中
	var prepareWife = "xiaozhang";

	this.name = name;
	this.wife = wife;
	this.divorce = function() {
		this.wife = prepareWife;
		//注意这个prepareWife前没有加this
	}
}

var deng = new Deng('deng', 'xiaoliu');

//控制台输入 deng.divorce
//再输入 deng.wife
//输出 xiaozhang 

//输入 deng.prepareWife
//返回 undefined
//因为这个在构造函数中定义的变量不是对象的方法,因此不能访问
//这样就形成了私有化变量

在对象方法中能够直接使用在对象中定义的变量是因为 new构造函数后,最后一步会返回构造函数对象,因此会形成闭包,就可以使用函数中定义的变量。

  • 模块化开发,防治污染全局变量(参见学习JS第七节命名空间)

5.立即执行函数

定义:多用于只执行一次的函数,因为执行一次后立即销毁,避免占用过多内存(初始化功能的函数)

//括号的两种立即执行函数
(function () {
   
}) ()


//多用这种
(function () {
   
} ())

//括号优先级最高,因此先将其转换成函数表达式,因此两种方式都对

只有表达式才能被执行符号执行
能被执行符号执行的表达式,会放弃名字
!+ - && ()操作符可以将函数声明变成表达式

function test() {
	var a = 123;
}()
//报错,不能执行
//因为只是函数声明,不是表达式

var test = function () {
}()
//可以执行
//执行完之后再输 test()会报错
//因为此时立即执行函数已经销毁,此时test是个空的变量

var test = function() {
}();
//输入:test
//返回:undefined
//因为 被执行符号执行的表达式,会放弃自己的名字

+ var test = function() {
}();
//可以执行

真题解析

var x = 1;
if(function f() {}) {
	x += typeof f;
}
console.log(x);

//输出 1undefined
//括号会将f变成表达式,就不是函数定义了,因此f就没有被定义

6.立即执行函数与闭包的联系

function test() {
	var arr = [];
	for(var i = 0; i < 10; i++) {
		arr[i] = function() {
			console.log(i);
			//arr里面保存的函数的 [[scope]]包括
			//函数test的AO:{ i=10 }
			//全局的GO
		}
	}
	return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
	myArr[j]();
}

//输出 10 10 10 10 10 10 10 10 10 10 10

//加上立即执行函数后
function test() {
	var arr = [];
	for(var i = 0; i < 10; i++) {
	    (function (j) {
			arr[j] = function() {
				console.log(j);
			}
			//arr里面保存的函数的 [[scope]]包括
			//外层立即执行函数的AO:{  形参j=实参i }
			//函数test的AO:{ i=10 }
			//全局的GO
		}(i));
	}
	return arr;
}
var myArr = test();
for(var k = 0; k < 10; k++) {
	myArr[k]();
}
//输出 0 1 2 3 4 5 6 7 8 9

7.逗号表达式:

var a = (表达式1,表达式2);
//a被赋值为表达式2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章