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 相关文章推荐
JS date对象的减法处理实现代码
Dec 28 Javascript
JavaScript中获取鼠标位置相关属性总结
Oct 11 Javascript
JavaScript实现将xml转换成html table表格的方法
Apr 17 Javascript
详解JavaScript中的表单验证
Jun 16 Javascript
jQuery EasyUI Pagination实现分页的常用方法
May 21 Javascript
javascript正则表达式之分组概念与用法实例
Jun 16 Javascript
JS中innerHTML和pasteHTML的区别实例分析
Jun 22 Javascript
jQuery实现table中的tr上下移动并保持序号不变的实例代码
Jul 11 Javascript
Three.js学习之正交投影照相机
Aug 01 Javascript
Angularjs 动态改变title标题(兼容ios)
Dec 29 Javascript
jQuery中animate()的使用方法及解决$(”body“).animate({“scrollTop”:top})不被Firefox支持的问题
Apr 04 jQuery
Vuerouter的beforeEach与afterEach钩子函数的区别
Dec 26 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代码(星期六,星期日总和)
2009/11/12 PHP
php 文件缓存函数
2011/10/08 PHP
让PHP更快的提供文件下载的代码
2012/06/13 PHP
PHP判断文件是否存在、是否可读、目录是否存在的代码
2012/10/03 PHP
详解WordPress中创建和添加过滤器的相关PHP函数
2015/12/29 PHP
PHP检测用户是否关闭浏览器的方法
2016/02/14 PHP
php类自动装载、链式操作、魔术方法实现代码
2017/07/23 PHP
thinkPHP中钩子的使用方法实例分析
2017/11/16 PHP
用js实现的页面关键字密度查询代码
2007/12/27 Javascript
jQuery select控制插件
2009/08/17 Javascript
jQuery EasyUI API 中文文档 - Form表单
2011/10/06 Javascript
轻松学习jQuery插件EasyUI EasyUI创建树形菜单
2015/11/30 Javascript
Vue中Quill富文本编辑器的使用教程
2018/09/21 Javascript
解决百度Echarts图表坐标轴越界的方法
2018/10/17 Javascript
详解关于JSON.parse()和JSON.stringify()的性能小测试
2019/03/14 Javascript
Vue axios 将传递的json数据转为form data的例子
2019/10/29 Javascript
python对数组进行反转的方法
2015/05/20 Python
浅谈Python中函数的参数传递
2016/06/21 Python
django_orm查询性能优化方法
2018/08/20 Python
对Pycharm创建py文件时自定义头部模板的方法详解
2019/02/12 Python
python 整数越界问题详解
2019/06/27 Python
33个Python爬虫项目实战(推荐)
2019/07/08 Python
Flask框架学习笔记之使用Flask实现表单开发详解
2019/08/12 Python
python 检查数据中是否有缺失值,删除缺失值的方式
2019/12/02 Python
如何在Django中使用聚合的实现示例
2020/03/23 Python
css3 border旋转时的动画应用
2016/01/22 HTML / CSS
英国香水店:The Perfume Shop
2017/03/27 全球购物
Joules官网:女士、男士和儿童服装和鞋类
2018/10/23 全球购物
LN-CC中国:高端男装和女装的奢侈时尚目的地
2019/09/14 全球购物
高级护理专业大学生求职信
2013/10/24 职场文书
财务管理职业生涯规划书
2014/02/26 职场文书
县级文明单位申报材料
2014/05/23 职场文书
上班离岗检讨书
2014/09/10 职场文书
2014年会计个人工作总结
2014/11/24 职场文书
2016年大学生社会实践心得体会
2015/10/09 职场文书
PyQt5结合QtDesigner实现文本框读写操作
2021/06/11 Python