深入了解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 相关文章推荐
javascript 短路法代码精简
Aug 20 Javascript
jQuery在iframe中无法弹出对话框的解决方法
Jan 12 Javascript
深入分析escape()、encodeURI()、encodeURIComponent()的区别及示例
Aug 04 Javascript
简介JavaScript中Boolean.toSource()方法的使用
Jun 05 Javascript
JS实现的新浪微博大厅文字内容滚动效果代码
Nov 05 Javascript
jquery attr()设置和获取属性值实例教程
Sep 25 Javascript
基于angularJS的表单验证指令介绍
Oct 21 Javascript
基于bootstrap实现多个下拉框同时搜索功能
Jul 19 Javascript
XMLHttpRequest对象_Ajax异步请求重点(推荐)
Sep 28 Javascript
Vue 按键修饰符处理事件的方法
May 04 Javascript
在weex中愉快的使用scss的方法步骤
Jan 02 Javascript
基于JavaScript实现十五拼图代码实例
Apr 26 Javascript
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
ajax+php控制所有后台函数调用
2015/07/15 PHP
关于php中一些字符串总结
2016/05/05 PHP
PHP获取访问页面HTTP状态码的实现代码
2016/11/03 PHP
PHP封装的page分页类定义与用法完整示例
2018/12/24 PHP
Laravel框架自定义分页样式操作示例
2020/01/26 PHP
yii2.0框架实现上传excel文件后导入到数据库的方法示例
2020/04/13 PHP
jquery.Jwin.js 基于jquery的弹出层插件代码
2012/05/23 Javascript
jQuery数据缓存功能的实现思路及简单模拟
2013/05/27 Javascript
js改变文章字体大小的实例代码
2013/11/27 Javascript
IE8 内存泄露(内存一直增长 )的原因及解决办法
2016/04/06 Javascript
vue 实现 tomato timer(蕃茄钟)实例讲解
2017/07/24 Javascript
jQuery Collapse1.1.0折叠插件简单使用
2017/08/28 jQuery
vue的token刷新处理的方法
2018/07/17 Javascript
jquery判断滚动条距离顶部的距离方法
2018/09/05 jQuery
基于Vue+elementUI实现动态表单的校验功能(根据条件动态切换校验格式)
2019/04/04 Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
2020/05/31 Javascript
[02:05]2014DOTA2西雅图国际邀请赛 BBC第二天小组赛总结
2014/07/11 DOTA
python encode和decode的妙用
2009/09/02 Python
python使用mailbox打印电子邮件的方法
2015/04/30 Python
python获取list下标及其值的简单方法
2016/09/12 Python
ubuntu16.04制作vim和python3的开发环境
2018/09/23 Python
python实现简单多人聊天室
2018/12/11 Python
Django使用django-simple-captcha做验证码的实现示例
2021/01/07 Python
解决img标签上下出现间隙的方法
2016/12/14 HTML / CSS
Shopee马来西亚:随拍即卖,最佳行动电商拍卖平台
2017/06/05 全球购物
波兰在线运动商店:YesSport
2020/07/23 全球购物
Fenty Beauty官网:蕾哈娜创立的美妆品牌
2021/01/07 全球购物
Python里面search()和match()的区别
2016/09/21 面试题
工地门卫岗位职责
2013/12/30 职场文书
父亲生日宴会答谢词
2014/01/10 职场文书
中学生国旗下讲话稿
2014/04/26 职场文书
车间班组长竞聘书
2015/09/15 职场文书
晶体管来复再生式二管收音机
2021/04/22 无线电
【海涛dota】偶遇拉娜娅 质量局德鲁伊第一视角解说
2022/04/01 DOTA
Java 超详细讲解十大排序算法面试无忧
2022/04/08 Java/Android
mysqldump进行数据备份详解
2022/07/15 MySQL