变量原理


变量分类(ES5)

全局变量

  • 定义:定义在函数外的变量,每个都是全局变量。

  • 特点

    • 页面上任何地方都可以使用

局部变量

  • 定义在函数内部的变量。
  • 特点:
    • 只能在函数内进行使用,函数外不能使用

  • 能够使用局部变量就用局部变量,可以极大的提高内存空间的利用率

let和const命令(ES6)

let命令

基本用法

  • ES6新增了let命令,用来声明变量,用法类似于var 但所声明的变量,只在let所在的代码块中起作用;
{
    let  a=10;
    var b=1;
}
console.log(a);//ReferenceError: a is not defined.
console.log(b);//1

在上述代码中,同样是在代码块中声明了变量a和b,但在代码快外输出,用let定义的报错,用var 定义的将结果输出,表明let声明的变量只在它所在的代码块中才有效

for循环中的使用

for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined

上面的代码,用let定义的i只在for循环体内有效,在循环体外就会报错

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
//所有数组a的成员里面的i,指向的都是同一个i
var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6
JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

  • for循环设置循环变量的那部分是父作用域,循环体内部是子作用域

    for (let i = 0; i < 3; i++) {
      let i = 'abc';
      console.log(i);
    }
    // abc
    // abc
    // abc
    //输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 let 重复声明同一个变量)
    

不存在变量提升

  • 不能在定义之前使用,它声明的变量一定要在声明后使用,否则报错
//var 定义
console.log(cout);//输出 undefined
var cout=2;

//let定义
console.log(cout);//报错  ReferenceError
let cout=2;

暂时性死区

  • 只要在块级作用域内使用let命令,它所声明的变量就‘绑定’ 这个区域,不再受外部的影响
var tmp = 123;
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的,这种在语法上,称为暂时性死区(TDZ);

// 不报错
var x = x;
// 报错
let x = x; //未声明就使用
// ReferenceError: x is not defined

暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

不允许重复声明

  • let不允许在相同作用域内,重复声明同一变量
// 报错
function func() {
  let a = 10;
  var a = 1;
}
// 报错
function func() {
  let a = 10;
  let a = 1;
}
// 报错
function func(a) {
  let a = 10;
}

function func(arg) {
    {//不同的块作用域
      let arg;
    }
  }
  func(10) // 不报错

块级作用域

  • 一对{} 包含的区域
  • ES6 允许块级作用域的任意嵌套。
  • 内层作用域可以定义外层作用域的同名变量
{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 报错
}}}};  //第四层的作用域无法读取到第五层作用域的内部变量

{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World'}
}}}};

const命令

基本用法

  • const声明一个只读的常量。一旦声明,常量的值就不能改变。

  • const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

    const foo;
    // SyntaxError: Missing initializer in const declaration
    
  • const的作用域与let命令相同:只在声明所在的块级作用域内有效

  • const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用

  • const声明的常量,也与let一样不可重复声明。

本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动.

  • 对于基本数据类型,值就保存在变量指向的那个内存地址,因此等同于常量
  • 对于引用数据类型,变量指向的内存地址,是不可变的,对于它的值是不能控制的

var let const之间的联系和区别

  • 联系:都是用于定义变量
  • 区别
    • varlet保存的数据可以更改,const保存的数据”不可被更改”
    • letconst具有块级作用域、TDZ、”不存在声明提升”,同一块级作用域变量不能重复定义。var只有全局作用域或函数作用域、存在声明提升。
  • 一般使用const来定义变量,如果要改变数据类型的话用let,优先级 const >let >var

内存空间

数据(一切皆数据)

  1. 什么是数据

    存储在内存中代表特定信息,本质上是010101…

  2. 数据的特点

    可传递、可运算

  3. 内存中所有操作的目标:数据

内存空间划分

内存

  • 概念

    内存条通电之后产生的可存储数据的空间(临时的)

  • 内存的产生和死亡

    内存条(电路板)==>通电==>产生内存空间==>存储数据==>处理数据==>断电==>内存空间和数据都消失

  • 一个内存的两个数据

    内部存储的数据

    地址值

javaScript中的内存

  • 在物理角度下:JavaScript内存就一块区域,我们称为”堆(heap)”.
  • 从逻辑角度下:
    1. 栈(stack):存放基本类型的数据以及引用地址,全局变量/局部变量
    2. 堆:存放所有的对象数据
      1. 数组
      2. 函数
      3. 自定义对象
      4. dom元素
    3. 常量池:存放常量(const 变量)

变量的空间分配

基本数据类型

  • 当定义一个基本类型的数据时,JavaScript会在栈里分配一块内存空间(大小是固定的)用于保存数据,将该区域的内存地址赋给变量。即变量保存的是内存地址。当我们访问变量时,JavaScript会通过变量保存的内存地址找到对应的数据并使用。
    1. 如果遇到var a=1;var c=a这种情况时,实际上创建变量c时,JavaScript会分配新的内存空间,把变量a的值复制到空间里,并把新的内存地址赋给变量c。即变量a和变量c的内存地址是不一样的,互不干扰。
    2. 每声明一个变量,就会在栈中开辟一个内存空间
    3. 在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值,最后这些变量都是 相互独立,互不影响的

引用数据类型

  • 如果一个变量的数据是对象,那么JavaScript会在堆里分配一块可动态增长的内存区域保存对象数据,然后再栈里分配一块区域保存对象数据对应堆空间的内存地址,最后将栈里对应空间的内存地址赋给变量。

内存空间

内存、变量、数据三者之间的关系

  • 内存用来存储数据的空间
  • 变量是内存的标识

执行上下文(Execution Context)

  • 全局执行上下文
    • 在执行全局代码前,将window确定为全局执行上下文
    • 对全局数据进行预处理
      • var 定义的全局变量==>undefined添加为window的属性
      • function声明的全局函数==>赋值(fun),添加为window的方法
      • this==>赋值(window
    • 开始执行全局代码
  • 函数级执行上下文(独立的区域,执行完之后销毁)
    • 在调用函数,准备执行函数之前,创建对应的函数执行上下文对象(不是真实存在的,存在于栈中)
    • 对局部数据进行预处理
      • 形参变量–>赋值(实参)–>添加为执行上下文的属性
      • arguments->赋值(实参列表),添加为执行上下文的属性
      • var 定义的局部变量–>undefined ,添加为执行上下文的属性
      • function声明的函数==>赋值(fun),添加为执行上下文的方法
      • this –>赋值(调用函数的对象)
    • 开始执行函数体代码
  • 块级执行上下文,ES6认为,由letconst加一个大括号所组成的区域。

执行上下文栈

  • 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  • 在全局执行上下文window确定后,将其添加到栈中(压栈)
  • 在函数执行上下文创建后,将其添加到栈中(压栈)
  • 在当前函数执行完之后,将栈顶的对象移除(出栈)
  • 当所有的代码执行完后,栈中只剩下window
console.log('gb':+i);
var i=1;
foo(1);
function foo(i){
    if(i==4){
        retrun;
    }
    console.log('fb'+i);
    foo(i+1);
    console.log('fe'+i);
}
console.log('ge'+i);
//输出
/*undefined
fb 1
fb 2
fb 3
fe 3
fe 2
fe 1
ge 1

文章作者: 时光路人
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 时光路人 !
评论
  目录