Javascript之变量,做用域及内存管理

JavaScript变量及类型检测

javascript数据类型分为基本数据类型,引用数据类型和Symbol。javascript

基本数据类型

String,Number,Boolean,undefined,nulljava

引用数据类型

Object
这里的Object包括带编号的有序集合Array,包含key/value的无序集合和另外一种特殊对象Function。算法

要点:基本数据类型的值是不可变的,引用数据类型值是可变的
基本数据类型的复制

基本数据类型变量复制是分配新的地址,新值是被复制变量的一个副本,变量之间是独立的 ,互不影响。数组

var num1 = 5; 
var num2 = num1;

image.png

引用数据类型的复制

引用数据类型复制时候也会分配新的地址,不一样的是新地址中存储的是引用数据在堆内存中的指针。复制操做结束后,二者都指向同一个引用地址。所以,修改其中一个会影响另一个。浏览器

var obj1 = new Object(); 
var obj2 = obj1; 
obj1.name = "Nicholas"; 
alert(obj2.name); //"Nicholas"

image.png

类型检测

一、typeof闭包

typeof是操做符不是函数。使用typeof会返回以下值:ide

  • "undefined"———该值没有定义
  • "boolean" ———布尔值
  • "string"----字符串
  • "function"----函数
  • "number"----数字
  • "object"----对象或者null

image.png

注意:在使用typeof检测数组和null都会返回object,所以typeof并不能精确判断某一数据类型。

二、instanceof
若是某个变量是指定引用类型的实例,则instanceof会返回true。固然数组的判断可使用Array.isArray()。函数

注意:instanceof也并不是彻底可靠,1.因为变量的原型并非一层不变的,一旦原型被修改,就可能返回false。2.没法判断多全局对象,好比在多窗口之间进行原型判断,多窗口意味着多全局对象,拥有不一样的内置构造函数,好比[] instanceof window.frames[0].Array会返回false

三、constructor
“javascript中一切都是对象”,全部对象都会在其原型上继承一个constructor属性指向其构造函数。
image.pngspa

注意:null和undefined没有constructor。一样,对象的constructor也是能够改变的,好比:
var a=[];
a.constructor=new Number();
console.log(a.constructor);//Number

四、Object.prototype.toString操作系统

Object.prototype.toString会返回一个表示该对象的字符串
Object.prototype.toString.call()

image.png

JavaScript执行环境与做用域

JavaScript执行环境决定了变量或函数是否有权访问,每一个执行环境都有一个变量对象(variable object),执行环境中全部变量和函数都保存在该对象中,虽然咱们没法直接访问该对象,JavaScript解析器执行时会使用到它。某个执行环境全部代码执行完毕后随之销毁。

全局执行环境

全局执行环境是最外围的一个执行环境,在浏览器中,全局执行环境被认为是window对象,全部变量和函数都是window下的属性或者方法。

局部执行环境

每一个函数都有本身的局部执行环境,当执行流进入函数时候,函数的执行环境就会被推入到执行栈中,函数执行完毕后就会被弹出执行栈。函数的参数也是局部变量,其优先级高于外部变量。

函数做用域和变量声明提高

每一个函数内部变量只能在函数体内访问,函数外是无权访问的,包括函数的参数。
函数内定义的变量在整个函数体内均可访问,即便变量声明在变量使用以后,这就是变量声明提高。最典型的例子以下:

var a=1;
function test(){
    console.log(a);//1
    a=2;
    console.log(a);//2
}
test();

换一下

var a=1;
function test(){
    console.log(a);//undefined
    var a=2;
    console.log(a);//2
}
test();
for(var i=0;i<10;i++){
  console.log(i);//输出1~9
}
console.log(i);//输出10
ES6以前JavaScript没有块级做用域

理解这句话能够看以下例子:

function test(o){
  if(typeof o==='object'){
    var i="test";
    for(var k=0;k<10;k++){
      console.log(k);
    }
    console.log(k)
  }
  console.log(i);
}

test();//undefined
test({});//输出0~9,10,test

可见,即便加了if条件判断或者循环,变量i和k均可以在大括号代码块外访问。

做用域链

每段JavaScript代码或者函数都有一个与之关联的做用域链(scope chain),这个做用域链是一个对象列表或者链表,这组对象定义了这段代码“做用域”中的变量。
当JavaScript须要查找变量a的时候,它会从链中的第一个对象开始查找,若是这个对象包含变量a属性,则会直接使用该对象中的a属性,若是不存在,则继续向上查找第二个对象,若是第二个对象也没有,则继续查找下一个,以此类推。若是整个做用域链上都没有a属性,则会抛出异常。
注意:在JavaScript顶层代码中,做用域链由一个全局对象组成,在函数体内,做用域链有2个,一个是定义函数参数和函数局部变量的对象,一个是全局对象,若是函数内找不到某个变量,会继续在全局对象中查找。

JavaScript内存管理

声明变量就得在内存中给它分配存储地址,基本数据类型存储在栈中,引用数据类型存储在堆中

内存的生命周期
  1. 分配你所须要的内存
  2. 使用分配到的内存(读、写)
  3. 不须要时将其释放\归还

image

内存的分配

let myNumber = 23

JavaScript在执行上面代码时候流程以下:

  1. 为myNumber定义惟一标识符(identifier);
  2. 在内存中分配一个地址(运行时候分配);
  3. 将值23存储到分配的地址中。

image

基本数据类型的复制是分配新的地址,是被复制对象的副本,所以会互不干扰;
引用数据类型的复制是堆地址指针的复制,复制后它们指向同一地址,所以修改其中一个会影响另一个。

image.png

image.png

内存的使用
在JavaScript中使用分配的内存意味着在其中读写,这能够经过读取或写入变量或对象属性的值,或者将参数传递给函数来实现。

内存释放
这里最困难的地方是肯定什么时候再也不须要分配的内存,它一般要求开发人员肯定程序中哪些地方再也不须要内存的并释放它。

垃圾收集

引用计数

这是最简单的垃圾收集器算法。若是没有引用指向这个对象的时候,这个对象就被认为是“能够做为垃圾收集”。

var o = { 
 a: {
   b:2
 }
}; 
// 两个对象被建立,一个做为另外一个的属性被引用,另外一个被分配给变量o
// 很显然,没有一个能够被垃圾收集


var o2 = o; // o2变量是第二个对“这个对象”的引用
o = 1;      // 如今,“这个对象”的原始引用o被o2替换了

var oa = o2.a; // 引用“这个对象”的a属性
// 如今,“这个对象”有两个引用了,一个是o2,一个是oa

o2 = "yo"; // 最初的对象如今已是零引用了
       // 他能够被垃圾回收了
       // 然而它的属性a的对象还在被oa引用,因此还不能回收

oa = null; // a属性的那个对象如今也是零引用了
       // 它能够被垃圾回收了

循环引用的问题
当遇到循环的时候就会有一个限制。在下面的实例之中,建立两个对象,而且互相引用,所以就会产生一个循环。当函数调用结束以后它们会走出做用域以外,所以它们就没什么用而且能够被释放。可是,基于引用计数的算法认为这两个对象都会被至少引用一次,因此它俩都不会被垃圾收集器收集。

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();
标记清除

JavaScript 中最经常使用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,由于只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。这个算法由如下步骤组成:

  1. 这个垃圾收集器构建一个“roots”列表。Root是全局变量,被代码中的引用所保存。在 JavaScript中,“window”就是这样的做为root的全局变量的例子。
  2. 全部的root都会被监测而且被标志成活跃的(好比不是垃圾)。全部的子代也会递归地被监测。全部可以由root访问的一切都不会被认为是垃圾。
  3. 全部再也不被标志成活跃的内存块都被认为是垃圾。这个收集器如今就能够释放这些内存并将它们返还给操做系统。

image

四种常见的JavaScript泄露

1. 全局变量
一个未声明变量的引用会在全局对象内部产生一个新的变量。在浏览器的状况,这个全局变量就会是window。
function foo(arg) {
   bar = "some text";
}
等同于:
function foo(arg) { window.bar = "some text"; }
2.被遗忘的计时器和回调
setInterval 在 JavaScript 中是常常被使用的。大多数提供观察者和其余模式的回调函数库都会在调用本身的实例变得没法访问以后对其任何引用也设置为不可访问。 可是在setInterval的状况下,这样的代码很常见
var serverData = loadData();
setInterval(function() {
   var renderer = document.getElementById('renderer');
   if(renderer) {
       renderer.innerHTML = JSON.stringify(serverData);
   }
}, 5000); //每5000ms执行一次

renderer所表明的对象在将来可能被移除,让部分interval 处理器中代码变得再也不被须要。然而,这个处理器不可以被收集由于interval依然活跃的(这个interval须要被中止从而表面这种状况)。若是这个interval处理器不可以被收集,那么它的依赖也不可以被收集。这意味这存储大量数据的severData也不可以被收集。

3. 闭包
闭包的特性是内部函数可以访问外部函数的做用域。
var sayName = function(){
  var name = 'jozo';
  return function(){
    alert(name);
  }
};
var say = sayName(); 
say();

sayName返回了一个匿名函数,该函数又引用了sayName的局部变量name,sayName 调用后变量name应该被回收,可是因为say继续引用,致使没法回收。

小结: 一、JavaScript基本数据类型:string,number,boolean,null,undefined,引用类型,包括Object,Array,function,ES6新增的symbol。 二、判断数据类型的方法有typeof,instanceof,constructor,Object.prototype.toString。 三、JavaScript分为全局做用域和局部做用域,做用域链向上层层查找。 四、基本数据类型占据固定大小空间,所以存储在栈内存中,引用类型占据空间不肯定,存储在堆内存中。复制基本数据类型会分配新地址,新旧互不影响,引用类型复制是复制指针,新旧变量会互相影响。 五、JavaScript垃圾回收方法包括引用计数和标记清除。 六、JavaScript常见的内存泄漏包括全局变量,循环引用,计时器,闭包。
相关文章
相关标签/搜索