【JS】作用域链及其闭包原理

闭包原理

作用域对象:

  • 当JavaScript在运行的时候,它需要一些空间让它来存储本地变量(local variables)。我们将这些空间称为作用域对象(Scope object) 。
  • 例如,当你调用函数时,函数定义了一些本地变量,这些变量就被存储在一个作用域对象中。
  • 你可以将作用域函数想象成一个普通的JavaScript对象, 但是有一个很大的区别就是你不能够直接在JavaScript当中直接获取这个对象。
  • 你只可以修改这个对象的属性,但是你不能够获取这个对象的引用。
  • 在JavaScript中,作用域对象是在堆中被创建的,所以在函数返回后它们也还是能够被访问到而不被销毁。

作用域链:

  • 当代码试图访问一个变量的时候,解释器将在当前的作用域对象中查找这个属性。
  • 如果这个属性不存在,那么解释器就会在父作用域对象中查找 这个属性。
  • 就这样,一直向父作用域对象查找,直到找到该属性或者再也没有父作用域对象。
  • 我们将这个查找变量的过程中所经过的作用域对象称作作用域链 (Scope chain)

作用域链顶端:

  • 在作用域链的最顶层的元素就是全局对象(Global Object)了。
  • 运行在全局环境的JavaScript代码中,作用域链始终只含有一个元素,那就是全局对象。
  • 所以,当你在全局环境中定义变量的时候, 它们就会被定义到全局对象中。
  • 当函数被调用的时候,作用域链就会包含多个作用域对象。

函数的执行空间

function fn(){
	console.log('我是 fn 函数');
}
fn();
  • 函数执行的时候会在内存中开辟一个执行空间(我们暂且叫他xxff00
  • console.log('我是 fn 函数')这个代码就是在xxff00这个空间中执行
  • 代码执行完毕以后,这个内存中开辟的xxff00执行空间空间就销毁了
  • 每一个函数会有一个存储空间
  • 但是每一个调用都会生成一个完全不一样的执行空间
  • 并且执行空间会在函数执行完毕后就销毁了,但是存储空间不会
  • 那么这个函数空间执行完毕就销毁了,还有什么意义?
    • 我们有一些方法可以让这个空间不销毁
    • 闭包:就是要利用这个 不销毁的空间

执行空间不销毁

  • 函数的执行空间会在函数执行完毕以后销毁
  • 但是,一旦函数内部返回了一个引用数据类型,并且在函数外部有变量接收的情况下
  • 那么这个函数的执行空间就不会销毁了
function fn(){
	const obj= {
		name:'Jack',
		age:18,
		gender:'男'
	}
	return obj;
}
const o = fn();
  • 函数执行的时候,会生成一个函数执行空间(我们暂且叫他xxff
  • 代码在xxff00空间中执行
  • xxff00这个空间中声明了一个对象空间(xxff11
  • xxff00这个执行空间把xxff11这个对象地址返回了
  • 函数外部o接收的是一个对象的地址没错
    • 但是是一个在xxff00函数执行空间中的xxff11对象地址
    • 因为o变量一直在和这个对象地址关联着,所以xxff00这个空间一直不会销毁
  • 等什么时候,执行一句代码o=null
    • 此时,o变量不再关联着xxff00函数执行空间中的xxff11对象地址
    • 那么,这个时候函数执行空间xxff00就销毁了

闭包概述

闭包:有权限访问另一个函数作用域中的变量的函数,通俗的理解就是函数内部的函数

  • 闭包产生的三个必要条件
function A(){
	const num = 100;
	return function B(){
		console.log(num);
	}
}
const fn = A();
1. 在函数 A 中直接或间接返回一个函数 B
2. B 函数中使用着 A 函数的私有变量
3. A 函数外面有一个变量接收着 B 函数
  • 闭包空间
function A(){
	const num = 100;
	return function B(){
		console.log(num);
	}
}
const fn = A();
1. 这个不会销毁的 A 函数的执行空间,叫做 闭包空间
2. 把函数 A 里面返回的 函数 B, 叫做函数 A 的闭包函数
3. 即:闭包 = 函数内部的函数
  • 闭包分析
function fn(){
	const num = 100;
	return function a(){
		console.log(num);
	}
}
const f = fn();
- `fn()`的时候会生成一个`xxff00`的执行空间
-`xxff00`这个执行空间内部,调用了一个`a`函数的存储空间`xxff11`
- 全局 f 变量接收的就是`xxff00`里面的`xxff11`
- 所以`xxff00`就是不会销毁的空间
- 因为`xxff00`不会销毁,所以定义在里面的变量num也不会销毁
- 将来用`f()`的时候,就能访问到num变量

闭包特点

用途:

  • 1、闭包可以读取函数内部变量
  • 2、将函数内部变量的值始终保存在内存中
function f1() {
	var n = 999;
	nAdd = function() {
		n += 1
	}
	function f2() {
		alert(n);
	}
	return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
  • 代码分析
- 在这段代码中,result实际上就是闭包f2函数。
- 它一共运行了两次,第一次的值是999,第二次的值是1000- 这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

- 原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,
- 而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

- 这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,
- 首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。
- 其次,nAdd的值是一个匿名函数,而这个匿名函数本身也是一个闭包,
- 所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

优势:

 1、保护函数内的变量安全。
 以最开始的例子为例,函数`f1``n`只有函数`f2`才能访问,而无法通过其他途径访问到,因此保护了`n`的安全性。

 2、在内存中维持一个变量。会导致内存一直被占用,使用不当会很容易造成内存泄露
 依然如前例,由于闭包,函数`f1``n`的一直存在于内存中,因此每次执行`nAdd()`,都会给`n`自加13、通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)。
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗不甘坠落的流星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值