深入了解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 克隆数组最简单的方法
Feb 12 Javascript
js 跳出页面的frameset框架示例介绍
Dec 23 Javascript
Extjs表单常见验证小结
Mar 07 Javascript
JavaScript设计模式之单件模式介绍
Dec 28 Javascript
基于jQuery仿淘宝产品图片放大镜特效
Oct 19 Javascript
利用JS实现简单的日期选择插件
Jan 23 Javascript
jquery实现input框获取焦点的简单实例
Jan 26 Javascript
深入理解node.js之path模块
May 03 Javascript
微信小程序后台解密用户数据实例详解
Jun 28 Javascript
通过button将form表单的数据提交到action层的实例
Sep 08 Javascript
vue+element表格导出为Excel文件
Sep 26 Javascript
微信小程序实现点击图片放大预览
Oct 21 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
PHP永久登录、记住我功能实现方法和安全做法
2015/04/27 PHP
使用URL传输SESSION信息
2015/07/14 PHP
WordPress主题制作之模板文件的引入方法
2015/12/28 PHP
php 数组随机取值的简单实例
2016/05/23 PHP
laravel excel 上传文件保存到本地服务器功能
2019/11/14 PHP
JQuery扩展插件Validate 1 基本使用方法并打包下载
2011/09/05 Javascript
有关于JS构造函数的重载和工厂方法
2013/04/07 Javascript
JS防止用户多次提交的简单代码
2013/08/01 Javascript
嵌入式iframe子页面与父页面js通信的方法
2015/01/20 Javascript
12306验证码破解思路分享
2015/03/25 Javascript
JavaScript数组操作函数汇总
2016/08/05 Javascript
详解webpack 入门与解析
2018/04/09 Javascript
JavaScript中为事件指定处理程序的五种方式分析
2018/07/27 Javascript
详解npm 配置项registry修改为淘宝镜像
2018/09/07 Javascript
VuePress 快速踩坑小结
2019/02/14 Javascript
微信公众号生成新浪短网址的实现(快速生成)
2019/08/18 Javascript
vue使用高德地图点击下钻上浮效果的实现思路
2019/10/12 Javascript
原生JavaScript实现弹幕组件的示例代码
2020/10/12 Javascript
Python实现统计单词出现的个数
2015/05/28 Python
Python聚类算法之基本K均值实例详解
2015/11/20 Python
Python处理JSON数据并生成条形图
2016/08/05 Python
python 查找字符串是否存在实例详解
2017/01/20 Python
详解如何通过H5(浏览器/WebView/其他)唤起本地app
2017/12/11 HTML / CSS
美国派对用品及装饰品网上商店:Shindigz
2016/07/30 全球购物
《小石潭记》教学反思
2014/02/13 职场文书
团队精神的演讲稿
2014/05/14 职场文书
党风廉政建设个人总结
2015/03/06 职场文书
2015纪念九一八事变84周年演讲稿
2015/03/19 职场文书
河童之夏观后感
2015/06/11 职场文书
遗嘱范文
2015/08/07 职场文书
小学生运动会广播
2015/08/19 职场文书
推广普通话宣传标语口号
2015/12/26 职场文书
2016年暑期社会实践活动总结报告
2016/04/06 职场文书
MySQL pt-slave-restart工具的使用简介
2021/04/07 MySQL
教你怎么用Python实现GIF动图的提取及合成
2021/06/15 Python
Java异常处理try catch的基本用法
2021/12/06 Java/Android