深入了解JS之作用域和闭包


Posted in Javascript onJune 16, 2020

作用域和闭包

ECMAScript5: JS 的代码没有代码块;使用函数运行的机制进行创建闭包;闭包就是作用域的意思;

ES5中,JS中只有函数才可以创建能操作的作用域;

JavaScript中的内存也分为栈内存和堆内存。一般来说,栈内存中存放的是存储对象的地址,而堆内存中存放的是存储对象的具体内容。对于原始类型的值而言,其地址和具体内容都存在与栈内存中;而基于引用类型的值,其地址存在栈内存,其具体内容存在堆内存中。堆内存与栈内存是有区别的,栈内存运行效率比堆内存高,空间相对推内存来说较小,反之则是堆内存的特点。所以将构造简单的原始类型值放在栈内存中,将构造复杂的引用类型值放在堆中而不影响栈的效率。

函数执行时形成私有作用域

函数执行的时候(直接目的:让函数体中的代码执行)会形成一个新的私有的作用域(栈内存),供函数体中的代码执行;

  1. 给形参赋值
  2. 私有作用域下的预解释
  3. 私有作用域下的代码执行

形成的新的私有的作用域还保护了里面的私有变量不受外界的影响,我们把函数的这种保护机制叫”闭包”:为什么要有作用域;因为变量要规定活动范围的,为便于管理不同范围的变量;所以要给变量设置活动范围;

预解释也是在各自的作用域里进行预解释的;

function fn(){
 var a=1;
}
fn();
fn();
console.log(a);//Uncaught ReferenceError: a is not defined;

因为a没有声明和定义过,所以报错了;

上面的fn运行了两次,所以产生了两个堆内存;两个作用域(作用域也就是闭包)各自分别有一个a的变量;a的值都是数字1,但是两个变量是不相等的;两个a之间是没有任何关系的;

就好比我们都属于人类;我们都继承了人类这个对象所具有的特征;我有一双手,你也有一双手,但是我们两个人的手是没有关系的;我的手不等于你的手;

全局变量和私有变量

  • 在全局作用域下声明的变量是全局变量;
  • 在私有作用域中声明的变量是私有变量;函数的形参也是私有的变量;

如何区分函数中出现的变量是私有的还是非私有的?

首先看是否为形参,然后看是否在私有作用域中声明过(有没有var过),两者有其一就是私有的变量,那么在当前函数中不管什么位置出现都是私有的,和全局的没有半毛钱的关系;如果两者都没有,说明不是私有的;如果一个函数中出现的变量不是私有的,那么会往其上级作用域查找,上级没有则继续查找,一直找到window为止,如果window也没有呢?

如果是获取:会报错

function fn() {
 console.log(num);//Uncaught ReferenceError: num is not defined
}
fn();

如果是设置:不是私有的,找全局,全局没有的话相当于给全局加一个

function fn() {
 num = 13;//相当于给window增加了一个叫做num的属性名,属性值是13 window.num=13;
}
fn();
console.log(num);//13

如何查找当前作用域的上级作用域?

当前作用域的上级作用域是谁和函数在哪执行的没有任何的关系,我们只需要看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁;

下面是查找上一级作用域,一直找到window的案例;

var total = 100;
function fn() {
 var total = 10;
 return function () {
  console.log(total);
 }//如果返回的是一个引用数据类型的值(对象、数组、函数...),首先是把这个值开辟一个内存空间,有一个地址,然后把内存地址返回 ->例如在这里其实返回的就是 return xxxfff111;
}
var f = fn();//f=xxxfff111;
f();
// 10

//输出的结果是 10 还是 100 ? 为什么 ?

如果是在自执行函数里呢?

var total = 100;
function fn() {
 var total = 10;
 return function () {
  console.log(total);
 }
}
var f = fn();//f=xxxfff111;
~function () {
var total = 1200;

f();//->;里面变量的上级作用域是谁
}();
// 10

下面代码输出的是什么?

var a=0;
var b="0";
function fn(){
 console.log(a);
console.log(b);

console.log(typeof a);
 console.log(typeof b);

a="1";

var b=1;

console.log(a);

console.log(b);

console.log(typeof a);

console.log(typeof b);
}
fn();//运行后会输出什么?如果没有这行代码;函数fn的定义是没有意义的;函数只声明定义,而不运行是没有任何意义的;
//0,undefined,"number","undefined","1",1,"string","number"

预解释是作用域中的预解释;js里是可以函数里面套函数的;都运行的时候,是在函数创建的作用域里再创建一个作用域;

下面是作用域的范围;

var a="window";
console.log(a);//window
function father(){
console.log(a);//undefined

var a="father";

console.log(a);//father

function children(){
 
console.log(b);//undefined
 
console.log(a);//father
 
var b="flag";
 
a="children";
 
console.log(a);//children
  }
  children();
  console.log(a);//children
}
father();
console.log(a);//window

JavaScript中的代码执行顺序是从上到下逐条运行的;遇到function定义函数的代码块;直接跳过;遇到函数执行的代码;就找到引用的函数地址;开始跳到执行函数产生的作用域中执行代码;等函数执行完以后,再回到当前作用域执行下面的代码;

上面的代码运行后,输出的是

“window”,undefined,“father”,undefined,“father”,“children”,“children”,“window”

作用域链查找:当作用域套作用域的时候,children内找不到某个变量;会到children的父作用域father中找;当father中找不到的时候;会到father的父作用域找;一直找到window这个根作用域;属于作用域链式查找;

函数运行产生的作用域

函数的运行是一个有生命周期的内存地址;

函数运行时,会创建一个内存地址(产生堆内存,函数保存的就是这个堆内存的地址),当此函数运行结束时,此内存地址又会销毁;这个地址,我们无法保存;它的灵活的,活动的;有生命周期的;我们也没有办法给这个作用域起一个变量名字,也没办法保存这个作用域,JS不提供这种机制;

也就是说:在作用域外面是没办法控制作用域内部的数据的;只能在作用域内部控制;而且作用域内部的代码可以控制外部的数据;这种机制就叫做闭包,闭包与作用域链和函数的运行有关系的;

函数里的变量,就在这个内存里创建;我们可以把这个内存当成一个对象;那函数里的变量就是这个内存对象的属性;

函数的定义和函数的执行是两码事(fn和fn()的区别);函数的执行与函数的定义地方无关;这个一定要理解!

闭包

作用域就是闭包;我理解的是相同的意思;只是不同人对这个机制的叫法不同;闭包是一种机制;并不是某种形式或者概念;最大的闭包就是window,我们可以把window当做一个闭包;

权威指南182页中对闭包的解释;

权威指南解释:函数的执行依赖于变量作用域,这个作用域是函数定义时决定的,而不是函数调用时决定的

注意:函数对象可以通过作用域链相互关联起来;函数体内部的变量都可以保存在函数作用域内,这种特性叫闭包; 批注:这里和作用域链有关系的,和闭包没有关系的;函数的作用域是谁,和在哪运行没有关系;只和在哪儿定义有关系;

如下代码;

var a=0;
function fn(){
 var a=1;
 function fm(){
  console.log(a);
 }
 return fm;
}
var testFn1=fn();//hanshu这个变量就相当于fm函数;
testFn1();//相当于fm函数运行;此时输出的是1;而不是0;虽然是在window中运行的;但是在fn中定义的;所以a找到的是fm上一级作用域fm的a;而不是window中的a;

作用域不销毁的情况|内存释放

作用域不销毁的总结:当函数内return一个引用数据类型;并且函数外面有一个变量接收这个引用数据类型;此时的作用域是不销毁的;

堆内存

对象数据类型或者函数数据类型在定义的时候首先都会开辟一个堆内存,堆内存有一个引用的地址,如果外面有变量等指到了这个地址,我们就说这个内存被占用了,就不能销毁了;

堆内存释放的问题->堆内存用来存储引用数据类型值的

[谷歌]:浏览器会每隔一段时间,看我们的堆内存是否还有其他的东西引用着,如果还在被占用着,浏览器不会进行处理;但是如果我们的堆内存已经没有任何的东西占用了,那么浏览器会把这个堆内存进行回收释放

[IE和火狐]:开辟了一个堆内存,我们有一个占用的时候浏览器记一个数(记录有多少个占用这个内存),当我们减少引用的时候,浏览器会把记数减1,当记的数字减为0的时候,浏览器会把我们的堆内存回收释放

var obj1 = {name: "张三"};
var obj2 = obj1;
//我们想要让堆内存释放/销毁,只需要把所有引用它的变量值赋值为null即可,如果当前的堆内存没有任何东西被占用了,那么浏览器会在空闲的时候把它销毁...
obj1 = null;
obj2 = null;

以上就是深入了解JS之作用域和闭包的详细内容,更多关于JavaScript 作用域和闭包的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
如何快速的呈现我们的网页的技巧整理
Jul 01 Javascript
JavaScript 字符串连接性能优化
Dec 20 Javascript
Jquery iframe内部出滚动条
Feb 11 Javascript
jquery插件制作 自增长输入框实现代码
Aug 17 jQuery
jquery实现盒子下拉效果示例代码
Sep 12 Javascript
深入理解JQuery keyUp和keyDown的区别
Dec 12 Javascript
js判断字符是否是汉字的两种方法小结
Jan 03 Javascript
JavaScript 里的类数组对象
Apr 08 Javascript
整理关于Bootstrap列表组的慕课笔记
Mar 29 Javascript
微信小程序图表插件wx-charts用法实例详解
May 20 Javascript
微信小程序后端(java)开发流程的详细步骤
Nov 13 Javascript
Vue+TypeScript中处理computed方式
Apr 02 Vue.js
JS数组及对象遍历方法代码汇总
Jun 16 #Javascript
浅谈Vue 函数式组件的使用技巧
Jun 16 #Javascript
原生JS实现天气预报
Jun 16 #Javascript
vue实现公告栏文字上下滚动效果的示例代码
Jun 16 #Javascript
原生JS实现无缝轮播图片
Jun 24 #Javascript
如何利用Node.js与JSON搭建简单的动态服务器
Jun 16 #Javascript
js+css实现全屏侧边栏
Jun 16 #Javascript
You might like
日本收入最高的漫画家:海贼王作者版税年收入高达8.45亿元
2020/03/04 日漫
详解PHP安装mysql.so扩展的方法
2016/12/31 PHP
javascript 设置文本框中焦点的位置
2009/11/20 Javascript
javascript 鼠标拖动图标技术
2010/02/07 Javascript
js跨域访问示例(客户端/服务端)
2014/05/19 Javascript
向JavaScript的数组中添加元素的方法小结
2015/10/24 Javascript
利用jQuery设计一个简单的web音乐播放器的实例分享
2016/03/08 Javascript
ionic 自定义弹框效果
2017/06/27 Javascript
webpack学习笔记之代码分割和按需加载的实例详解
2017/07/20 Javascript
requireJS模块化实现返回顶部功能的方法详解
2017/10/16 Javascript
Vue实现商品飞入购物车效果(电商项目)
2019/11/26 Javascript
Vue-axios-post数据后端接不到问题解决
2020/01/09 Javascript
JavaScript forEach中return失效问题解决方案
2020/06/01 Javascript
python计算对角线有理函数插值的方法
2015/05/07 Python
python处理Excel xlrd的简单使用
2017/09/12 Python
网红编程语言Python将纳入高考你怎么看?
2018/06/07 Python
对pandas中Series的map函数详解
2018/07/25 Python
Python零基础入门学习之输入与输出
2019/04/03 Python
在python中使用pymysql往mysql数据库中插入(insert)数据实例
2020/03/02 Python
华为旗下电子商务平台:华为商城
2016/08/06 全球购物
保加利亚运动鞋购物网站:SneakerStudio.bg
2020/12/23 全球购物
杭州时比特电子有限公司SQL
2013/08/22 面试题
大学生自我评价怎样写好
2013/10/23 职场文书
企划专员岗位职责
2013/12/09 职场文书
请假条的格式
2014/04/11 职场文书
2014年教研活动总结范文
2014/04/26 职场文书
酒店仓管员岗位职责
2014/04/28 职场文书
交通事故赔偿协议书怎么写
2014/10/04 职场文书
领导个人查摆剖析材料
2014/10/29 职场文书
大学生实训报告总结
2014/11/05 职场文书
出差报告格式模板
2014/11/06 职场文书
投资合作意向书范本
2015/05/08 职场文书
暑期社会实践新闻稿
2015/07/17 职场文书
2015质检员个人年终工作总结
2015/10/23 职场文书
go goroutine 怎样进行错误处理
2021/07/16 Golang
Java并发编程之原子性-Atomic的使用
2022/03/16 Java/Android