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 相关文章推荐
让 JavaScript 轻松支持函数重载 (Part 2 - 实现)
Aug 04 Javascript
JavaScript 拾碎[三] 使用className属性
Oct 16 Javascript
js判断横竖屏及禁止浏览器滑动条示例
Apr 29 Javascript
js中各种类型的变量在if条件中是true还是false
Jul 16 Javascript
js实现网页标题栏闪烁提示效果实例分析
Nov 20 Javascript
jquery.uploadify插件在chrome浏览器频繁崩溃解决方法
Mar 01 Javascript
理解JavaScript中Promise的使用
Jan 18 Javascript
很全面的JavaScript常用功能汇总集合
Jan 22 Javascript
jQuery 跨域访问解决原理案例详解
Jul 09 Javascript
JS简单实现动态添加HTML标记的方法示例
Apr 08 Javascript
webpack配置打包后图片路径出错的解决
Apr 26 Javascript
浅谈VueJS SSR 后端绘制内存泄漏的相关解决经验
Dec 20 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
我的论坛源代码(九)
2006/10/09 PHP
php 定界符格式引起的错误
2011/05/24 PHP
php数组函数序列之prev() - 移动数组内部指针到上一个元素的位置,并返回该元素值
2011/10/31 PHP
ci检测是ajax还是页面post提交数据的方法
2014/11/10 PHP
php针对cookie操作的队列操作类实例
2014/12/10 PHP
Yii2 GridView实现列表页直接修改数据的方法
2016/05/16 PHP
highchart数据源纵轴json内的值必须是int(详解)
2017/02/20 PHP
详解PHP发送邮件知识点
2018/05/06 PHP
PHP 图片合成、仿微信群头像的方法示例
2019/10/25 PHP
用javascript连接access数据库的方法
2006/11/17 Javascript
JS的数组的扩展实例代码
2008/07/09 Javascript
JavaScript 计算当天是本年本月的第几周
2009/03/22 Javascript
jquery高级编程的最佳实践详解
2014/03/23 Javascript
node.js正则表达式获取网页中所有链接的代码实例
2014/06/03 Javascript
JavaScript让网页出现渐隐渐显背景颜色的方法
2015/04/21 Javascript
BootStrap 附加导航组件
2016/07/22 Javascript
jQuery查找节点并获取节点属性的方法
2016/09/09 Javascript
jQuery如何跳转到另一个网页 就这么简单
2016/12/28 Javascript
vue.js开发环境安装教程
2017/03/17 Javascript
angularjs中ng-bind-html的用法总结
2017/05/23 Javascript
详解angular脏检查原理及伪代码实现
2018/06/08 Javascript
微信小程序实现自定义modal弹窗封装的方法
2018/06/15 Javascript
原生JS实现旋转轮播图+文字内容切换效果【附源码】
2018/09/29 Javascript
[01:38]完美世界DOTA2联赛PWL S3 集锦第四期
2020/12/21 DOTA
深入讲解Java编程中类的生命周期
2016/02/05 Python
实例讲解Python中global语句下全局变量的值的修改
2016/06/16 Python
python读写json文件的简单实现
2017/04/11 Python
Tensorflow限制CPU个数实例
2020/02/06 Python
土耳其家居建材网站:Koçtaş
2016/11/22 全球购物
美国娱乐和流行文化商品店:FYE
2017/09/14 全球购物
美国男士和女士奢侈品折扣手表购物网站:Certified Watch Store
2018/06/13 全球购物
巴西补充剂和维生素购物网站:Natue
2019/06/17 全球购物
学生发电厂实习自我鉴定
2013/09/22 职场文书
工程项目经理任命书
2014/06/05 职场文书
党员个人整改措施
2014/10/24 职场文书
SpringBoot集成Druid连接池连接MySQL8.0.11
2021/07/02 Java/Android