JavaScript变量作用域及内存问题实例分析


Posted in Javascript onJune 10, 2019

本文实例讲述了JavaScript变量作用域及内存问题。分享给大家供大家参考,具体如下:

学习要点:

1.变量及作用域
2.内存问题

JavaScript的变量与其他语言的变量有很大区别。JavaScript变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。

一.变量及作用域

1.基本类型和引用类型的值

ECMAScript变量可能包含两种不同的数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。

将一个值赋给变量时,解析器必须确定这个值是基本类型值,还是引用类型值。基本类型值有以下几种:UndefinedNullBooleanNumberString。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。

PS:在某些语言中,字符串以对象的形式来表示,因此被认为是引用类型。ECMAScript放弃这一传统。

如果赋值的是引用类型的值,则必须在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时,先从栈中读取内存地址,然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。

2.动态属性

定义基本类型值和引用类型值的方式是相似的:创建一个变量并为该变量赋值。但是,当这个值保存到变量中以后,对不同类型值可以执行的操作则大相径庭。

var box = new Object(); //创建引用类型
box.name = 'Lee'; //新增一个属性
alert(box.name); //输出

如果是基本类型的值添加属性的话,就会出现问题了。

var box = 'Lee'; //创建一个基本类型
box.age = 27; //给基本类型添加属性
alert(box.age); //undefined

3.复制变量值

在变量复制方面,基本类型和引用类型也有所不同。基本类型复制的是值本身,而引用类型复制的是地址。

var box = 'Lee'; //在栈内存生成一个box 'Lee'
var box2 = box; //在栈内存再生成一个box2 'Lee'

box2是虽然是box1的一个副本,但从图示可以看出,它是完全独立的。也就是说,两个变量分别操作时互不影响。

var box = new Object(); //创建一个引用类型
box.name = 'Lee'; //新增一个属性
var box2 = box; //把引用地址赋值给box2

在引用类型中,box2其实就是box,因为他们指向的是同一个对象。如果这个对象中的name属性被修改了,box2.name和box.name输出的值都会被相应修改掉了。

4.传递参数

ECMAScript中所有函数的参数都是按值传递的,言下之意就是说,参数不会按引用传递,虽然变量有基本类型和引用类型之分。

function box(num) { //按值传递,传递的参数是基本类型
  num += 10; //这里的num是局部变量,全局无效
  return num;
}
var num = 50;
var result = box(num);
alert(result); //60
alert(num); //50

PS:以上的代码中,传递的参数是一个基本类型的值。而函数里的num是一个局部变量,和外面的num没有任何联系。

下面给出一个参数作为引用类型的例子。

function box(obj) { //按值传递,传递的参数是引用类型
  obj.name = 'Lee';
}
var p = new Object();
box(p);
alert(p.name);

PS:如果存在按引用传递的话,那么函数里的那个变量将会是全局变量,在外部也可以访问。比如PHP中,必须在参数前面加上&符号表示按引用传递。而ECMAScript没有这些,只能是局部变量。可以在PHP中了解一下。

PS:所以按引用传递和传递引用类型是两个不同的概念。

function box(obj) {
  obj.name = 'Lee';
  var obj = new Object(); //函数内部又创建了一个对象
  obj.name = 'Mr.'; //并没有替换掉原来的obj
}

最后得出结论,ECMAScript函数的参数都将是局部变量,也就是说,没有按引用传递。

5.检测类型

要检测一个变量的类型,我们可以通过typeof运算符来判别。诸如:

var box = 'Lee';
alert(typeof box); //string

虽然typeof运算符在检查基本数据类型的时候非常好用,但检测引用类型的时候,它就不是那么好用了。通常,我们并不想知道它是不是对象,而是想知道它到底是什么类型的对象。因为数组也是object,null也是Object等等。

这时我们应该采用instanceof运算符来查看。

var box = [1,2,3];
alert(box instanceof Array); //是否是数组
var box2 = {};
alert(box2 instanceof Object); //是否是对象
var box3 = /g/;
alert(box3 instanceof RegExp); //是否是正则表达式
var box4 = new String('Lee');
alert(box4 instanceof String); //是否是字符串对象

PS:当使用instanceof检查基本类型的值时,它会返回false。

5.执行环境及作用域

执行环境是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。

全局执行环境是最外围的执行环境。在Web浏览器中,全局执行环境被认为是window对象。因此所有的全局变量和函数都是作为window对象的属性和方法创建的。

var box = 'blue'; //声明一个全局变量
function setBox() {
  alert(box); //全局变量可以在函数里访问
}
setBox(); //执行函数

全局的变量和函数,都是window对象的属性和方法。

var box = 'blue';
function setBox() {
  alert(window.box); //全局变量即window的属性
}
window.setBox(); //全局函数即window的方法

PS:当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。

PS:每个执行环境都有一个与之关联的变量对象,就好比全局的window可以调用变量和属性一样。局部的环境也有一个类似window 的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(我们无法访问这个变量对象,但解析器会处理数据时后台使用它)

函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。

var box = 'blue';
function setBox() {
  var box = 'red'; //这里是局部变量,出来就不认识了
  alert(box);
}
setBox();
alert(box);

通过传参,可以替换函数体内的局部变量,但作用域仅限在函数体内这个局部环境。

var box = 'blue';
function setBox(box) { //通过传参,替换了全局变量
  alert(box);
}
setBox('red');
alert(box);

函数体内还包含着函数,只有这个函数才可以访问内一层的函数。

var box = 'blue';
function setBox() {
  function setColor() {
var b = 'orange';
    alert(box);
alert(b);
  }
  setColor(); //setColor()的执行环境在setBox()内
}
setBox();

PS:每个函数被调用时都会创建自己的执行环境。当执行到这个函数时,函数的环境就会被推到环境栈中去执行,而执行后又在环境栈中弹出(退出),把控制权交给上一级的执行环境。

PS:当代码在一个环境中执行时,就会形成一种叫做作用域链的东西。它的用途是保证对执行环境中有访问权限的变量和函数进行有序访问。作用域链的前端,就是执行环境的变量对象。

6.没有块级作用域

块级作用域表示诸如if语句等有花括号封闭的代码块,所以,支持条件判断来定义变量。

if (true) { //if语句代码块没有局部作用域
  var box = 'Lee';
}
alert(box);

for循环语句也是如此

for (var i = 0; i < 10; i ++) { //没有局部作用域
  var box = 'Lee';
}
alert(i);
alert(box);

var关键字在函数里的区别

function box(num1, num2) {
  var sum = num1 + num2; //如果去掉var就是全局变量了
  return sum;
}
alert(box(10,10));
alert(sum); //报错

PS:非常不建议不使用var就初始化变量,因为这种方法会导致各种意外发生。所以初始化变量的时候一定要加上var。

一般确定变量都是通过搜索来确定该标识符实际代表什么。

var box = 'blue';
function getBox() {
  return box; //代表全局box
} //如果加上函数体内加上var box = 'red'
alert(getBox()); //那么最后返回值就是red

PS:变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜索作用域链。

二.内存问题

JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其他语言比如C和C++,必须手工跟踪内存使用情况,适时的释放,否则会造成很多问题。而JavaScript则不需要这样,它会自行管理内存分配及无用内存的回收。

JavaScript最常用的垃圾收集方式是标记清除。垃圾收集器会在运行的时候给存储在内存中的变量加上标记。然后,它会去掉环境中正在使用变量的标记,而没有被去掉标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。

垃圾收集器是周期性运行的,这样会导致整个程序的性能问题。比如IE7以前的版本,它的垃圾收集器是根据内存分配量运行的,比如256个变量就开始运行垃圾收集器,这样,就不得不频繁地运行,从而降低的性能。

一般来说,确保占用最少的内存可以让页面获得更好的性能。那么优化内存的最佳方案,就是一旦数据不再有用,那么将其设置为null来释放引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。

var o = {
  name : 'Lee'
};
o = null; //解除对象引用,等待垃圾收集器回收

更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
Add Formatted Text to a Word Document
Jun 15 Javascript
JavaScript 图像动画的小demo
May 23 Javascript
javascript中打印当前的时间实现思路及代码
Dec 18 Javascript
JavaScript实现Java中StringBuffer的方法
Feb 09 Javascript
IE8利用自带的setCapture和releaseCapture解决iframe的拖拽事件方法
Oct 25 Javascript
vue 通过下拉框组件学习vue中的父子通讯
Dec 19 Javascript
解决vue多个路由共用一个页面的问题
Mar 12 Javascript
jQuery+ajax实现批量删除功能完整示例
Jun 06 jQuery
js生成1到100的随机数最简单的实现方法
Feb 07 Javascript
Javascript执行流程细节原理解析
May 14 Javascript
原生js拖拽功能制作滑动条实例代码
Feb 05 Javascript
JavaScript实现一键复制内容剪贴板
Jul 23 Javascript
JavaScript内置对象math,global功能与用法实例分析
Jun 10 #Javascript
vue通信方式EventBus的实现代码详解
Jun 10 #Javascript
Vue将页面导出为图片或者PDF
Aug 17 #Javascript
基于Bootstrap和JQuery实现动态打开和关闭tab页的实例代码
Jun 10 #jQuery
发布订阅模式在vue中的实际运用实例详解
Jun 09 #Javascript
为vue项目自动设置请求状态的配置方法
Jun 09 #Javascript
解决vue单页面应用中动态修改title问题
Jun 09 #Javascript
You might like
php 如何获取数组第一个值
2013/08/06 PHP
非常有用的9个PHP代码片段
2016/04/06 PHP
javascript编程起步(第七课)
2007/02/27 Javascript
『jQuery』名称冲突使用noConflict方法解决
2013/04/22 Javascript
如何用JavaScript动态呼叫函数(两种方式)
2013/05/03 Javascript
JS中typeof与instanceof之间的区别总结
2013/11/14 Javascript
javascript读取xml实现javascript分页
2013/12/13 Javascript
js制作简易年历完整实例
2015/01/28 Javascript
JavaScript 性能优化小结
2015/10/12 Javascript
jQuery实现本地预览上传图片功能
2016/01/08 Javascript
浅谈js中StringBuffer类的实现方法及使用
2016/09/02 Javascript
AngularJS  ng-table插件设置排序
2016/09/21 Javascript
Bootstrap如何激活导航状态
2017/03/22 Javascript
react.js 父子组件数据绑定实时通讯的示例代码
2017/09/25 Javascript
实现一个完整的Node.js RESTful API的示例
2017/09/29 Javascript
解决jQuery使用append添加的元素事件无效的问题
2018/08/30 jQuery
ES6 let和const定义变量与常量的应用实例分析
2019/06/27 Javascript
js将URL网址转为16进制加密与解密函数
2020/03/04 Javascript
Vue实现跑马灯效果
2020/05/25 Javascript
Python基于Socket实现的简单聊天程序示例
2017/08/05 Python
Django--权限Permissions的例子
2019/08/28 Python
python SocketServer源码深入解读
2019/09/17 Python
DJango的创建和使用详解(默认数据库sqlite3)
2019/11/18 Python
python+adb+monkey实现Rom稳定性测试详解
2020/04/23 Python
keras 两种训练模型方式详解fit和fit_generator(节省内存)
2020/07/03 Python
python 通过pip freeze、dowload打离线包及自动安装的过程详解(适用于保密的离线环境
2020/12/14 Python
canvas基础之图形验证码的示例
2018/01/02 HTML / CSS
SISLEY希思黎官方旗舰店:享誉全球的奢华植物美容品牌
2018/04/25 全球购物
Furla官网:意大利著名的皮革品牌
2019/08/06 全球购物
应届生骨科医生求职信
2013/10/31 职场文书
行政助理岗位职责范文
2013/12/03 职场文书
护理专业优质毕业生自荐书
2014/01/31 职场文书
周年庆促销方案
2014/03/15 职场文书
移风易俗倡议书
2014/04/15 职场文书
2015年宣传工作总结
2015/04/08 职场文书
sentinel支持的redis高可用集群配置详解
2022/04/01 Redis