JS作用域链与闭包

词法作用域

var num = 123;
function foo1 () {
	console.log( num );
	var num = 456;
	console.log( num );
}
foo1();

1、预解析的过程
2、代码的执行过程
程序在执行过程中,会先将代码读取到内存中检查一下,会将所有的声明在此时进行标记,所谓的标记就是让js 解释器知道有这个名字,后面在使用名字的时候,不会出现未定义的错误。这个标记过程就是提升。
声明:
1、名字/标识符/变量名的声明

  • 名字的声明就是让我们解释器知道有这个名字
  • 名字没有任何数据与之对应

2、函数的声明

  • 函数声明包含两部分
  • 函数声明与函数表达式有区别
  • 函数声明是单独写在一个结构中,不存在任何语句、逻辑判断等结构中
function f() {
	//函数声明 函数声明后面不加分号
	function func() {}
	//函数表达式
	if ( true ) {
		function func2() {}
	}
}
  • 首先函数声明告诉解释器有这个名字存在,该阶段与名字声明一样
  • 告诉解释器,这个名字对应的函数体是什么,将名字与函数体绑定
var num = 123;
function num() {
	alert( num );
}
num();

分析:
1、预解析代码,提升名字

  • 首先提升名字 num
  • 再提升函数名,但是名字已经存在,因此只做第二步,让名字与函数体对应上
  • 结论就是,代码中已经有一个 函数 num 了

2、开始执行代码,第一句话从赋值语句开始执行

  • 给num 赋值 1
  • 覆盖了函数

3、调用num 由于num 中存储1 所以报错

问题

function foo() {}
var foo = function () {};
//1、上面的语法是声明,可以提升,因此在函数定义的上方也可以调用
//2、下面的语法是函数表达式,函数名是foo,它会提升(变量名提升)
//提升的不是函数体
//3、函数表达式也是支持名字语法的
var foo = function func() {};
foo();
//函数有一个属性name,表示的是函数名,
//只有带有函数名字的函数定义,才有 name 属性名,否则是""
//但是,函数表达式的名字,只允许在函数内部使用(IE8可以访问)

()可以将数据转换为表达式

结论:

新的浏览器中,写在if, while, dowhile 结构中的函数,都会将函数的声明转换成特殊的函数表达式

if(...) {
	function foo() {};
}
转换成
if(...) {
	var foo = function foo() {};
}

词法(代码)作用域

作用域

域表示范围,即作用范围,就是一个名字在什么地方可以被使用,什么时候不能使用

块级作用域

词法作用域,就是代码在编写过程中体现出来的作用范围,代码一旦写好,不用执行,作用范围就已经确定好了,这就是词法作用域

在js 中词法作用域的规则:

1、函数允许访问函数外的数据

var num = 123;
function foo(){
	console.log( num );
}
foo();

2、整个代码结构中只有函数可以限定作用域

在这里插入代码片

3、作用规则首先使用提升规则分析

if( false ) {
	var num = 123;
}
console.log( num );

4、如果当前作用规则中有名字了,就不考虑外面名字
(进入函数体内,需要再声明一次)

var num = 123;
function foo() {
	var num = 456;
	function func() {
		console.log( num )l
	}
	func();
}
foo();
作用域链

可以发现只有函数可以制造作用域结构,那么只要是代码,至少有一个作用域,即全局作用域,凡是代码中有函数,这个函数就构成另一个作用域,如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域。那么,将这样的所有作用域列出来,可以有一个结构:函数内指向函数外的链式结构
作用域链和DOM数"一模一样"

绘制作用域链的步骤

1、看整个全局是一条链,即顶级链,记为0级链
2、看全局作用域中有什么成员声明,就以方格的形式绘制到0级链上
3、再找函数,只有函数可以限制作用域,因此从函数中引出新链,标记为一级链
4、然后在每一个一级链中再次往复刚才的行为

变量的访问规则

1、首先看变量在第几条链上,在该链上看是否有变量的定义与赋值,如果有直接使用
2、如果没有,到上一级链上去找( n-1 级链),如果有直接用他,停止继续查找
3、如果还没有再次往下找… 直到全局链( 0级 ),还没有就是 is not defined
4、注意:切记同级的链不可混合查找

补充

1、声明变量使用 var,如果不使用var,声明的变量就是全局变量**(禁用)**
2、因为在任何代码结构中都可以使用该语法,那么在代码维护的时候就会有问题,所以除非特殊原因不要这么用
3、下面代码的错误

function foo() {
	var i1 = 1 //局部
		i2 = 2, //全局
		i3 = 3; //全局
}

4、此时注意:

var arr = [];
for( var i = 0; i < 10; i++ ) {
	arr.push( i );
}
for( var i = 0; i < 10; i++ ) {
	console.log( arr[i] );
}
//一般都是将变量的声明全部放到开始的位置,避免因为提升而造成的错误
var arr = [], 
	i = 0;
for( ; i < 10; i++ ) {
	arr.push( i );
}
for( i = 0; i < 10; i++ ) {
	console.log( arr[i] );
}

闭包

闭包的含义就是闭合,包起来。简单的来说,就是一个具有封闭功能和包裹功能的结构。所谓的闭包就是,有一个具有封闭的对外不公开的,包裹结构,或空间
在js 中,函数可以构成闭包,一般函数是一个代码结构的封闭结构,即包裹的特性。同时根据作用域规则,只允许函数访问外部的数据,外部无法访问函数内部的数据。即封闭的,对外不公开的特性,即函数可以构成闭包

闭包要解决什么问题

1、闭包不允许外界访问
2、要解决的问题就是间接访问该数据
函数就可以构成闭包,要解决的问题就是访问到函数内部的数据

function foo() {
	var num = 123;
	return num;
}
var res = foo();
console.log( res );

1、这里的确是访问函数中的数据
2、但是该数据不好二次访问 ,因为第二次访问的时候又要调用一次foo,表示又有一个新的 num = 123 创建出来了

在函数内的数据,不能直接在函数外被访问,那么在函数内如果定义一个函数,那么在这个函数内部是可以直接访问的

function foo() {
	var num = Math.random();
	funciton func() {
		return num;
		//通过调用func函数想获得几次数据就获得几次数据
		//但是函数只能在函数内访问,所以通过return将func return出去
	}
	return func;
}
var f = foo();
//f可以直接访问num 
var res1 = f();
var res2 = f();
//因为foo不再被调用,里面的num就不会变

闭包

1、如何获得超过一个数据

function foo() {
	var num1 = Math.ranfom();
	var num2 = Math.ranfom();
}

一般闭包的问题就是想办法间接地获得函数内数据的使用权,那么我们可以总结出一个基本的使用模型
1、写一个函数,函数内定义一个新函数,返回新函数,用新函数获取函数内的数据

function foo() {
		var num1 = Math.random();
		var num2 = Math.random();
		//用对象来打包可以给函数加上名字
		return {
				num1: function(){
					return num1;
				},
				num2: function(){
					return num2;
				}
		}
	}
	var o = foo();
	var num1 = o.num1();
	var num2 = o.num2();
	console.log( num1 + '\n' + num2 );

2、写一个函数,函数内定义一个对象,对象中绑定多个函数,返回对象,利用对象的方法返回函数内的数据

function foo1() {
		var num = Math.random();
		return {
			get_num: function () {
				return num;
			},
			set_num: function ( value ) {
				num = value;
			}
		}
	}
	var o1 = foo1();
	var num = o1.get_num();
	console.log( num );

	num = o1.get_num();
	console.log( num );

	num = o1.get_num();
	console.log( num );

	o1.set_num( 123 );

	num = o1.get_num();
	console.log( num );

闭包的基本用法

闭包是为了实现具有私有访问空间函数的
1、带有私有访问数据的对象

function Person(){
	this.name = '张三';
	
}
//所谓的私有数据就是指只有函数内部可以访问的数据,或对象内部的方法可以访问的数据
//最简单的实现方式
function createPerson() {
	var __name__ = "";
	return{
		get_name: function(){
			return __name__;
		},
		set_name: function( value ){
			//如果不姓张就报错
			if( value.charAt( 0 ) === '张' ) {
				__name__ = value;
			}else {
				throw new Error( '姓氏不对,不能取名' );
			}
		}
	};
}

2、带有私有数据的函数

var foo = (function(){
	//私有数据
	return function(){
		//可以使用私有数据
	};
})();
闭包的效率问题

函数执行需要内存,函数中定义的变量会在函数执行结束后自动回收。凡是因为闭包结构被引出的数据,如果还有变量在引用这些数据的话,这些数据就不会被回收。
因此在使用闭包的时候,如果不使用某些数据了,一定要赋值为空

var f = (function(){
	var num = 123;
	return function(){
		return num;
	};
})();
//f 引用着函数,函数引用变量 num,因此在不使用该数据的时候,最好写上 
f = null;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值