JS 学习笔记--9---变量-作用域-内存相关

时间:2023-03-08 15:23:45
JS 学习笔记--9---变量-作用域-内存相关

JS 中变量和其它语言中变量最大的区别就是,JS 是松散型语言,决定了它只是在某一个特定时间保存某一特定的值的一个名字而已。由于在定义变量的时候不需要显示规定必须保存某种类型的值,故变量的值以及保存的数据类型可以在脚本页面的生命周期中随意的改变,其他语言则不可。

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

  基本类型:指那些保存在栈内存中的简单数据,即这种值完全保存在内存中的一个位置。他们所占据的空间大小是固定的。类型有:Number, Null, Boolean, Undefined, String[其他语言中String类型被认为是引用类型的]。但是这些基本类型如果用new方式来声明变量的话,也是引用类型

  引用类型:指那些保存在堆内存中的对象,这些类型的真正数据是保存在堆内存中的,而同时在栈内存中保存的只是一个指针这个指针指向的是这个对象在堆内存中的一块地址。[Object类型]

  基本类型的复制:基本类型在内存中占有的空间大小是固定的,复制的时候会重新在栈内存中开辟一块空间,是按值来访问的。

  引用类型的复制:由于这种对象所占的大小是不固定的,是放在堆内存中的,但是内存的地址大小是固定的,故栈中存放的是对象在堆内存的地址。这样当查找引用的时候,是先从栈内存中取出地址,然后再到堆内存中找到对应的值,这就是引用访问。复制的时候是复制的栈内存的值,也就是拷贝的一个引用而已,两个变量指向的堆内存中对象还是同一个对象。

2、动态属性    

  引用类型变量可以直接添加属性,box.name='abc';

  值类型的变量(字面量形式声明)如果按照同样的方式添加属性,不会报错,但是按照同样的方式输出属性的时候就是undefined,但是如果通过new的方式来声明的话则效果和Object是一样的

3、复制变量的值

  基本类型:复制的是变量本身的值,复制后是在栈空间重新开辟一块空间,复制完成后两个变量是相互独立的,各不相干,故一个变量的值改变后不会影响另外一个变量的值。

  引用类型:复制的时候只是复制的栈空间的地址而堆内存中还是同一个对象,也就是复制的引用,结果是两个变量指向的是同一个内存块,所以,复制后两个变量中某一个改变了对象的值那么另外一个变量输出的结果也会改变,因为他们是指向的内存中同一个对象。

4、传递参数

  在JS中参数传递都是值传递,不存在引用传递,C语言中引用传递就是在变量前面添加一个&符号。

  值类型:传递的是变量本身的值,和复制是一样的,函数中改变了变量的值,不会影响源变量值

  引用类型:同样是值传递,传递的是变量再栈内存空间中的地址值,如果在函数中改变了对象某一个属性的值,源变量中的值也会改变,因为在堆内存中它们是指向的同一个对象

function func(num){
num.name=123;
} var box={};
box.name='abcd';
alert(box.name); //abcd
func(box);
alert(box.name); //123 值在函数func中被改变了

  ECMAScript 函数中的参数都是局部变量,没有按照引用传递的变量。只有传递引用类型的变量

5、检测类型    

  在 《JS 学习笔记--3---数据类型》中知道,检测变量的类型的时可以用 typeof 来实现,但是这个只是返回的几大基本的数据类型,在检测Object类型的时候则不是那么好用,因为Null,Object,Array,RegExp 等都会返回object,那样就不知道变量到底是什么类型。

  可以 instanceof 来确定变量是某种具体的引用类型。   alert(box instanceof Array);  alert(box instanceof Null);     alert(box instanceof RegExp);  alert(box instanceof String); 等   如果是这种类型就返回true,否则就返回false

  用 instanceof 来检测用字面量形式声明的基本类型的变量返回的是false,但是如果用 new 方法声明的变量则会返回为 true

6、执行环境以及作用域   

  执行环境也就是作用域在很多的编程语言中都是一个很重要的概念,规定了变量或者函数有权访问其他数据的权限,规定了各自的行为。JS 中执行环境和其他语言差不多。

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

  当执行环境中代码执行完成后,就会销毁该执行环境,也会销毁里面的变量和函数等。全局执行环境是需要在网页关闭或者程序执行完毕后才会被销毁。

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

  每当函数被调用的时候都会创建自己的环境对象,当执行这个函数时,函数的环境就会被推到环境栈上面去执行,而环境栈执行完成后,又回弹出栈,把控制权交给上一级的执行环境。

  当代码在一个环境中执行的时候,就会形成一个块级作用链域的东西,作用就是保证对执行环境中有访问权限的函数和对象进行有序访问,处于链域的最前端就是执行环境的变量对象

  一般来说对象只是能够访问它的本身的属性和方法,不能够隔代访问,window不能够访问方法func中定义的方法ziFunc,但是可以通过点访问形式访问对象box中定义的方法ziFunc,参考C#点访问法   

7、没有块级作用域  

  块级作用域表示诸如if等有花括号封闭的代码段块,所以支持条件判断来定义变量。 像 if,for 等代码块中定义的变量再花括号外面是可以访问的,这和其他语言中有很大的差别

8、var 关键字在函数中的区别  

  函数中声明变量的时候,如果不加上关键字 var 那变量会被认为是全局的,函数外面也可以访问它,当然在访问之前要先执行一次函数,加了则是局部的

  最好不要不用var关键字就初始化变量,因为这种情况下可能会导致各种错误,所有初始化变量的时候一定要加上关键字 var

  一般确定某一个变量的时候是通过搜索来确定的,现在本级作用域上找,如果没有,在向上级作用域找,依次类推,故访问局部变量要比访问全局变量的效率更高。因为不需要向上收索作用域链

9、内存相关  

  JS中也存在垃圾回收机制,和C#差不多,我们不需要担心内存的泄露问题,垃圾回收机制会自动的管理内存的分配和无用内存的回收。

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

  垃圾收集器是周期性的运行,不是随时运行,这样可能会遇到一些性能问题,但是一般情况下不需要担心这个问题

  一般来说,确保页面占用的内存更少可以让页面获得更好的性能,最好的减少内存占用量的方式就是,一旦变量或者对象不再使用的时候,将其赋值为空,即:box=null; 来释放引用,这种方式叫做删除引用,这种方式使用大多数的全局变量和全局对象。

练习代码:

 /*
//字面量形式定义的基本类型数据是非引用类型的变量
var box='ABC';
box.name='abc';
alert(box.name); //undefined //通过new的方式声明的各种基本类型的变量就是引用类型的变量
var box=new String();
box.name='abc';
alert(box.name); //abc var box=new Boolean(123);
box.name='ca';
alert(box.name); //ca
alert(box); //true 因为在声明的时候赋了值,不为空故为true
var age=box;
alert(age.name); //ca */ /* 复制变量的值
//值类型的数据是拷贝的一个副本,是在栈内存空间开辟一块新的内存,
//复制完成后两个变量各不相干,改变一个变量的值不会改变另外一个变量的值
var box='abcd';
var age=box;
alert(box); //abcd
alert(age); //abcd
age=123;
alert(box); //abcd
alert(age); //123 //引用类型的变量复制时,也是复制的栈空间的值,也就是把变量的地址块的引用复制了一份
//但是两个变量还是指向的堆内存中同一块内存区域,故一个变量的值改变,另外一个变量值同样改变
var box2={};
box2.name='abcd';
var age2=box2;
alert(box2.name); //abcd
alert(age2.name); //abcd
age2.name=123;
alert(box2.name); //123
alert(age2.name); //123
*/ /* 参数传递 JS 中没有引用传递,只有值传递
//值类型变量,变量传入到函数后,无论函数如何改变值,在函数外面还是不变的,
//和拷贝差多不,同样是在栈内存中重新开辟一块区域
function func(box){
box=10;
} var box=20;
alert(box); //20
func(box);
alert(box); //20 //引用类型的变量 传递的同样是栈内存中的值,也就是对象的地址,这种变量传递过去后如果在
//函数中改了对象的某个属性的值,那么外面的源对象的值同样会改变,因为指向的是同一块堆内存空间
function func(num){
num.name=123;
} var box={};
box.name='abcd';
alert(box.name); //abcd
func(box);
alert(box.name); //123 值在函数func中被改变了 */ /* 类型检测 instanceof
//通过instanceof来确定变量是否是某一具体的类型,比如 Array RegExp 等类型
var box=[2,3,4,5];
alert(box instanceof Array); //true
alert(box instanceof Object); //true
alert(box instanceof RegExp); //false
var age=/123/;
alert(age instanceof RegExp); //true // 检测字面量形式申请的基本类型的时候返回的是false,但是采用new方式声明的变量返回的是true
var box='abcd';
alert(box instanceof String); //false
box=new String('abc');
alert(box instanceof String); //true var box=1234;
alert(box instanceof Number); //false
box=new Number(124);
alert(box instanceof Number); //true */ /* 块级作用域
function func(){ for(var i=0;i<10;i++){
var box=123;
}
//for循环中定义的变量可以再外面访问,也就所for中定义的变量再它所在的作用域中是全局的
alert(i); //10
alert(box); //123
} func(); */ /* 函数中的关键字var
function func(){
sum=100;
var age=100;
}
func();
alert(sum); //100 因为函数中声明变量的时候没有添加关键字,被认为是全局的,故可以访问
alert(age); //error
*/