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

作用域和闭包

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 作用域和闭包的资料请关注我们其它相关文章!

(0)

相关推荐

  • JavaScript静态作用域和动态作用域实例详解

    静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量). 动态作用域–函数的作用域是在函数调用的时候才决定的 JavaScript采用的是词法作用域即静态作用域: // 静态作用域: var a = 10; function fn() { var b = 1; console.log(a + b); } fn(); // 11 在创建fn函数时的时候就已经确定了它可以作用哪些变量,如果函数fn里面有变量a就直接操作变量a,

  • Javascript作用域和作用域链原理解析

    作用域和作用域链在Javascript和很多其它的编程语言中都是一种基础概念.但很多Javascript开发者并不真正理解它们,但这些概念对掌握Javascript至关重要. 正确的去理解这个概念有利于你去写更好,更高效和更简洁的代码,让你成为一个更优秀的Javascript开发者. 因此,在本文中,我将会向大家解释清楚什么是作用域和作用域链,以及Javascript引擎在内部是如何通过它们操作和查找变量的. 事不宜迟,正文开始 :) 什么是作用域 Javascript中的作用域说的是变量的可访

  • javascript中的this作用域详解

    Javascript中this的指向一直是困扰我很久的问题,在使用中出错的机率也非常大.在面向对象语言中,它代表了当前对象的一个引用,而在js中却经常让我觉得混乱,它不是固定不变的,而是随着它的执行环境的改变而改变. 在Javascript中this总是指向调用它所在方法的对象.因为this是在函数运行时,自动生成的一个内部对象,只能在函数内部使用. 下面我们分几种情况深入分析this的用法: 1.全局的函数调用 function globalTest() { this.name = "glob

  • 一次让你了解全部JavaScript的作用域

    前言 作用域决定了变量的生命周期和可见性,变量在作用域范围之外是不可见的. JavaScript 的作用域包括:模块作用域,函数作用域,块作用域,词法作用域和全局作用域. 全局作用域 在任何函数.块或模块范围之外定义的变量具有全局作用域.可以在程序的任意位置访问全局变量. 当启用模块系统时,创建全局变量会变得困难,但仍然可以做到这一点.可以在 HTML 中定义一个变量,这个变量需要在函数之外声明,这样就可以创建一个全局变量: <script> let GLOBAL_DATA = { value

  • 详解JavaScript 作用域

    作用域是可访问变量的集合. JavaScript 作用域 在 JavaScript 中, 对象和函数同样也是变量. 在 JavaScript 中, 作用域为可访问变量,对象,函数的集合. JavaScript 函数作用域: 作用域在函数内修改. JavaScript 局部作用域 变量在函数内声明,变量为局部作用域. 局部变量:只能在函数内部访问. // 此处不能调用 carName 变量 function myFunction() { var carName = "Volvo"; //

  • JS函数本身的作用域实例分析

    本文实例讲述了JS函数本身的作用域.分享给大家供大家参考,具体如下: 函数本身也是一个值,也有自己的作用域.它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在作用域无关. var a = 1 var x = function() { console.log(a) } function f() { var a = 2 x() } f() 上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2. 总之,函数执行时所在的作用域

  • JavaScript 函数用法详解【函数定义、参数、绑定、作用域、闭包等】

    本文实例讲述了JavaScript 函数用法.分享给大家供大家参考,具体如下: 初始函数 Function类型,即函数的类型. 典型的JavaScript函数定义: function 函数名称(参数表){ //函数执行部分 return ; } //注意:参数列表直接写形参名即可 return语句:return返回函数的返回值并结束函数运行 函数也可以看做数据来进行传递 参数列表相当于函数入口,return 语句相当于函数出口 函数可以作为参数来传递. function test ( a ) {

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

    作用域和闭包 ECMAScript5: JS 的代码没有代码块:使用函数运行的机制进行创建闭包:闭包就是作用域的意思: ES5中,JS中只有函数才可以创建能操作的作用域: JavaScript中的内存也分为栈内存和堆内存.一般来说,栈内存中存放的是存储对象的地址,而堆内存中存放的是存储对象的具体内容.对于原始类型的值而言,其地址和具体内容都存在与栈内存中:而基于引用类型的值,其地址存在栈内存,其具体内容存在堆内存中.堆内存与栈内存是有区别的,栈内存运行效率比堆内存高,空间相对推内存来说较小,反之

  • JS页面获取 session 值,作用域和闭包学习笔记

    本文实例讲述了JS页面获取 session 值,作用域和闭包.分享给大家供大家参考,具体如下: Javascript获取session的值: var name= "${sessioScope.变量名}"; 注意这里面需要使用 "" 把 El 表达式给括起来,否则就取不到数据. JSP获取session的值: 可以直接${sessionScope.变量名},在标签里也是一样. JSP获取URL的值: var name = "<%=request.get

  • JS难点同步异步和作用域与闭包及原型和原型链详解

    目录 JS三座大山 同步异步 同步异步区别 作用域.闭包 函数作用域链 块作用域 闭包 闭包解决用var导致下标错误的问题 投票机 闭包两个面试题 原型.原型链 原型对象 原型链 完整原型链图 JS三座大山 同步异步 前端中只有两个操作是异步的: 定时器异步执行; ajax异步请求 编译器解析+执行代码原理: 1.编译器从上往下逐一解析代码 2.判断代码是同步还是异步 同步:立即执行 异步:不执行.放入事件队列池 3.等所有同步执行完毕开始执行异步 同步异步区别 api : 异步有回调,同步没有

  • 浅谈JS封闭函数、闭包、内置对象

    一.变量作用域指的是变量的作用范围,javascript中的变量分为全局变量和局部变量 1.全局变量:在函数之外定义的变量,为整个页面公用,函数的内部外部都可以访问. 2.局部变量:在函数内部定义的变量,只能在定义该变量的函数内部访问,外部无法访问.函数内部访问变量时,先在内部查找是否有此变量,如果有,就使用内部,如果没有,就去外部查找 二.封闭函数封闭函数是javascript中匿名函数的另外一种写法,创建一个一开始就执行而不用命名的函数. 1.一般函数的定义和执行函数 2.封闭函数的定义和执

  • javascript 词法作用域和闭包分析说明

    复制代码 代码如下: var classA = function(){ this.prop1 = 1; } classA.prototype.func1 = function(){ var that = this, var1 = 2; function a(){ return function(){ alert(var1); alert(this.prop1); }.apply(that); }; a(); } var objA = new ClassA(); objA.func1(); 大家应

  • 浅谈JavaScript作用域和闭包

    作用域和闭包在JavaScript里非常重要.但是在我最初学习JavaScript的时候,却很难理解.这篇文章会用一些例子帮你理解它们. 我们先从作用域开始. 作用域 JavaScript的作用域限定了你可以访问哪些变量.有两种作用域:全局作用域,局部作用域. 全局作用域 在所有函数声明或者大括号之外定义的变量,都在全局作用域里. 不过这个规则只在浏览器中运行的JavaScript里有效.如果你在Node.js里,那么全局作用域里的变量就不一样了,不过这篇文章不讨论Node.js. `const

  • JS学习笔记之闭包小案例分析

    本文实例讲述了JS学习笔记之闭包小案例.分享给大家供大家参考,具体如下: 直接上代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <style type="text/css"> *{ list-style

  • JavaScript作用域、闭包、对象与原型链概念及用法实例总结

    本文实例讲述了JavaScript作用域.闭包.对象与原型链概念及用法.分享给大家供大家参考,具体如下: 1 JavaScript变量作用域 1.1 函数作用域 没有块作用域:即作用域不是以{}包围的,其作用域完成由函数来决定,因而if /for等语句中的花括号不是独立的作用域. 如前述,JS的在函数中定义的局部变量只对这个函数内部可见,称之谓函数作用域. 嵌套作用域变量搜索规则:当在函数中引用一个变量时,JS会搜索当前函数作用域,如果没有找到则搜索其上层作用域,一直到全局作用域. var va

  • JavaScript 中的作用域与闭包

    目录 一.JavaScript 是一门编译语言 1.1 传统编译语言的编译步骤 1.2 JavaScript 与传统编译语言的区别 二.作用域(Scope) 2.1 LHS查询 和 RHS查询 2.2 作用域嵌套 2.3 ReferenceError 和 TypeError (1)ReferenceError (2)TypeError (3)ReferenceError 和 TypeError 的区别 小结 三.词法作用域 3.1 词法阶段 3.2 词法作用域 查找规则 3.3 欺骗词法 ——

  • JavaScript三大重点同步异步与作用域和闭包及原型和原型链详解

    目录 1. 同步.异步 2. 作用域.闭包 闭包 作用域 3. 原型.原型链 原型(prototype) 原型链 如图所示,JS的三座大山: 同步.异步 作用域.闭包 原型.原型链 1. 同步.异步 JavaScript执行机制,重点有两点: JavaScript是一门单线程语言 Event Loop(事件循环)是JavaScript的执行机制 JS为什么是单线程 最初设计JS是用来在浏览器验证表单操控DOM元素的是一门脚本语言,如果js是多线程的,那么两个线程同时对一个DOM元素进行了相互冲突

随机推荐