- 1. 变量的两种类型
- 1.1. 原始值类型
- 1.2. 引用值类型:
- 1.3. 原始类型和引用类型创建
- 1.4. 注意:
- 1.5. 数据类型转换
- 1.6. 变量的查询,会逐层向上查询,直到找到一个符合的变量结束
- 2. 栈内存和堆内存
- 3. 栈和堆的区别
- 4. 内存中存储对象的生命周期:
- 5. 内存的变化
- 6. 垃圾回收机制
- 7. 内存优化
1. 变量的两种类型
ECMAScript定义了变量的两种类型:原始值类型
和引用值类型
。区别两种类型的直接特征是:存储位置。
- 如果某种变量是直接存储在栈(stack)中的简单数据段,即为原始值类型。他们的值直接存储在变量访问的位置
- 如果是存储在堆(heap)中的对象,则为引用值类型。
1.1. 原始值类型
ECMAScript定义了六种原始值类型:Undefined, Null, Boolean, Number,String, Symbol
- Undefined类型只有一个字面常量——undefined
- Null类型也只有一个字面常量——null。当函数或者方法返回的是对象,当这个对象找不到的时候,返回的通常就是null
- Boolean有两个字面常量——true/false
- Number类型可以表示32位整数,也可以表示64位浮点数,还可以表示八进制(以0o开头)和十六进制(以0x开头)整数。最终都会返回10进制的运算结果。注意:浮点数字面量运算之前,真正存储的是字符串
- String类型没有固定长度,可以为空。JS中的String使用以16位整数表示的Unicode字符
1.2. 引用值类型:
Object,Array, 函数
1.3. 原始类型和引用类型创建
var box;
alert(box);
alert(typeof box);
// box 是Undefined类型,值是undefined,类型返回的字符串是undefined
var box = true;
alert(box);
alert(typeof box);
// box 是Boolean类型,值是true,类型返回的字符串是boolean
var box = 100;
alert(box);
alert(typeof box);
// box 是Number类型,值是100,类型返回的字符串是number
var box ="abc";
alert(box);
alert(typeof box);
// box 是String类型,值是abc,类型返回的字符串是string
// 这是一个空的对象,对象被创建了,但是里边没东西
// 空对象表示没有创建,是null
var box = {};
alert(box);
alert(typeof box);
// box 是Object类型,值是[object Object],类型返回的字符串是object
var box = new Object();
alert(box);
alert(typeof box);
// box 的类型是Object,值是[object Object],类型返回的字符串是object,这是对象的另一种创建方法
var box = null;
alert(box);
alert(typeof box);
// box 是Null类型,值是null,类型返回的字符串是object,这是因为Null是派生自Object
var box = function(){}
alert(box);
alert(typeof box);
// box 是Function函数,值是var box = function(){},类型返回的字符串是function
1.4. 注意:
- 给变量赋值为undefined是没必要的,因为会自动的转换为undefined
var box = undefined; // 没必要
alert(box);
alert(age); //错误:age is not defined
alert(typeof box); //undefined
alert(typeof age); //undefined
因为这两个的类型都是undefined,这里要在声明变量的同时对他进行初始化
1.5. 数据类型转换
var a = 12, b = "2";
console.log(a+b); // 122
console.log(a-b); // 10
如果将一个类型转化为string类型,就
+""
如果将一个类型转化为number类型,就-0
ECMAScript中的变量是松散型的,不强制类型。所谓松散类型,就是可以用来保存任何类型的数据,每个变量仅仅是一个用来保存值的占位符而已
1.6. 变量的查询,会逐层向上查询,直到找到一个符合的变量结束
var box = '123';
function color(){
return box;
}
alert(color()); //123,因为在函数体内没有搜索到box,这时候向上搜索到全局变量box
var box = '123';
function color(){
var box = 'red';
return box;
}
alert(color()); //red,在返回处的局部作用域中已存在box=red,所以直接返回
var box = "red";
function color(){
var box;
return box;
}
alert(color()); //undefined,这说明只要存在同名的变量,就不再向上搜索
注意 : 在变量查询中,访问局部变量比全局变量更快(消耗的资源更少),因为不需要向上搜索作用域链,所以我们尽量少定义全局变量
2. 栈内存和堆内存
JavaScript中的内存分为栈内存
和堆内存
。一般来说,栈内存中存放的是存储对象的地址,堆内存中存放的是存储对象的具体内容。
- 原始类型的值而言,其地址和具体内容都存放在栈内存中;
- 引用类型的值,其地址在栈内存,其具体内容存放在堆内存中。
一般将构造简单的原始类型的值放在栈内存中,将构造复杂的引用类型的值放在堆中而不影响栈的效率。
3. 栈和堆的区别
- 栈空间小,但是可以被解释器直接检索,响应速度快。
- 堆空间大,但不能被程序栈直接检索,响应速度稍慢。
一般而言,栈中存放的变量(原始值类型)都具有占据空间小,大小固定的特点。
为啥string类型大小不固定,却放在栈中?
String虽然不具备大小固定的要求,但JS中明确规定了String是不可变的
,。鉴于如此稳定而又不会被频繁使用,所以String放入栈中存储。
而在其他语言中,String是可以在适当的条件下修改的,所以放到堆存储中。
堆中存放的变量(引用值类型)都具有占据空间大,大小不固定的特点,因此,如果也存储在栈中,将会影响程序运行的性能。引用值类型还在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用指针时,首先会检索其在栈中的地址,取得地址后从堆中获得实体
let str = 'hello'; // str:'hello' 存放在栈中
let obj = {
value : 'hello'
}; // obj存放在栈中,{value : 'hello'}存放在堆中,通过栈中的变量名obj(访问地址)访问
4. 内存中存储对象的生命周期:
- 当对象将被需要的时候为其分配内存
- 使用已分配的内存进行读写等操作
- 当对象不再被需要的时候,释放存储这个对象的内存
5. 内存的变化
原始类型中
var str_a = 'a'; // 为str_a分配内存: str_a:'a'
var str_b = str_a; // 原始类型,直接访问值,所以,为str_b新分配内存: str_b:'a'
console.log(str_a); // a
console.log(str_b); // a
str_b = 'b'; // 栈内存中:str_b:'b', str_b的值为'b',str_a的值仍然为a
console.log(str_a); // a
console.log(str_b); // b
引用类型中
var obj_a = {v : 'a'}; // 为obj_a分配栈内存访问地址: obj_a, 堆内存中存储对象值 : {v : 'a'}
var obj_b = obj_a; // 为obj_b分配栈内存访问地址: obj_b, 引用堆内存的值:{v : 'a'}
console.log(obj_a.v); // a
console.log(obj_b.v); // a
obj_b.v = 'b'; // 通过obj_b访问修改堆内存中的变量,这时候堆内存中对象值为:{v : 'b'}。由于obj_a和obj_b引用的是堆内存中的同一个对象值,所以他俩的值都是{v : 'b'}
console.log(obj_a.v); // b
console.log(obj_b.v); // b
// 整体改变对象
obj_b = {v : 'c'}; // 因为改变的是整个对象,这里会在堆内存中创建一个新的对象值:{v :'c'},现在的obj_b引用的是这个对象,所以打印出的obj_a = {v : 'b'},而打印的obj_b = {v : 'c'}。这里两者在内存中引用的是不同的对象了
console.log(obj_a.v); // b
console.log(obj_b.v); // c
一个例子
// 连续赋值与求值顺序,js的赋值运算顺序永远是从右向左的,但是由于'.' 是优先级最高的运算符
var a = {n : 1};
var b = a; // 先执行这里:将b = {n : 1},a,b同时指向{n : 1}
a.x = a = {n : 2}; // 这里 '.' 运算符是优先级最高的运算符,这里就先执行了a.x。a指向的对象新增了属性x,x的值是undefined。而由于此时a,b同时指向那个对象,这时a,b都指向同一个对象,所以,b.x也存在,且值为undefined.
// 然后从右向左赋值,执行 a={n:2}, a的指向发生改变,变成了新对象{n:2},此时b还是指向原来的
// 接着执行a.x = a,理解为原来对象的属性x指向了后来的对象
console.log(a); // {n:2}
console.log(a.x); // undefined
console.log(b); // {n:1, x:{n:2}}
console.log(b.x); // {n:2}
6. 垃圾回收机制
JavaScript具有自动进行垃圾回收的机制。
原理是:找出不再使用的变量,然后释放掉其占用的内存,垃圾回收器会按照固定的时间间隔周期性的执行。这里不再使用的变量只能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在堆或者栈中分配相应的空间,直至函数结束。一旦函数结束,局部变量没有必要存在了,就可以释放它们占用的内存。
7. 内存优化
对于全局变量而言,JavaScript不确定他在后面还能不能被用到,所以从他声明开始就一直存在于内存中,直至手动释放或者关闭页面/浏览器,这就导致了某些不必要的内存消耗。我们就可以进行优化了
- 立即执行函数
- 对于某些需要一直存在的变量,我们可以将他挂载在window下
(function(window){
// 代码
})(window)
- 闭包容易产生问题,我们就可以采用回调函数来代替闭包来访问内部变量。回调函数的好处是:执行完成后会自动释放其中的变量