变量分类(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之间的联系和区别
- 联系:都是用于定义变量
- 区别
var
和let
保存的数据可以更改,const
保存的数据”不可被更改”let
和const
具有块级作用域、TDZ
、”不存在声明提升”,同一块级作用域变量不能重复定义。var只有全局作用域或函数作用域、存在声明提升。
- 一般使用
const
来定义变量,如果要改变数据类型的话用let,优先级const >let >var
内存空间
数据(一切皆数据)
什么是数据
存储在内存中代表特定信息,本质上是010101…
数据的特点
可传递、可运算
内存中所有操作的目标:数据
内存空间划分
内存
概念
内存条通电之后产生的可存储数据的空间(临时的)
内存的产生和死亡
内存条(电路板)==>通电==>产生内存空间==>存储数据==>处理数据==>断电==>内存空间和数据都消失
一个内存的两个数据
内部存储的数据
地址值
javaScript中的内存
- 在物理角度下:JavaScript内存就一块区域,我们称为”堆(heap)”.
- 从逻辑角度下:
- 栈(stack):存放基本类型的数据以及引用地址,全局变量/局部变量
- 堆:存放所有的对象数据
- 数组
- 函数
- 自定义对象
- dom元素
- 常量池:存放常量(const 变量)
变量的空间分配
基本数据类型
- 当定义一个基本类型的数据时,JavaScript会在栈里分配一块内存空间(大小是固定的)用于保存数据,将该区域的内存地址赋给变量。即变量保存的是内存地址。当我们访问变量时,JavaScript会通过变量保存的内存地址找到对应的数据并使用。
- 如果遇到
var a=1;var c=a
这种情况时,实际上创建变量c
时,JavaScript会分配新的内存空间,把变量a
的值复制到空间里,并把新的内存地址赋给变量c
。即变量a
和变量c
的内存地址是不一样的,互不干扰。 - 每声明一个变量,就会在栈中开辟一个内存空间
- 在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值,最后这些变量都是 相互独立,互不影响的。
- 如果遇到
引用数据类型
- 如果一个变量的数据是对象,那么JavaScript会在堆里分配一块可动态增长的内存区域保存对象数据,然后再栈里分配一块区域保存对象数据对应堆空间的内存地址,最后将栈里对应空间的内存地址赋给变量。
内存、变量、数据三者之间的关系
- 内存用来存储数据的空间
- 变量是内存的标识
执行上下文(Execution Context)
- 全局执行上下文
- 在执行全局代码前,将
window
确定为全局执行上下文 - 对全局数据进行预处理
- var 定义的全局变量==>
undefined
添加为window
的属性 function
声明的全局函数==>赋值(fun),添加为window
的方法this
==>赋值(window
)
- var 定义的全局变量==>
- 开始执行全局代码
- 在执行全局代码前,将
- 函数级执行上下文(独立的区域,执行完之后销毁)
- 在调用函数,准备执行函数之前,创建对应的函数执行上下文对象(不是真实存在的,存在于栈中)
- 对局部数据进行预处理
- 形参变量–>赋值(实参)–>添加为执行上下文的属性
arguments
->赋值(实参列表),添加为执行上下文的属性var
定义的局部变量–>undefined
,添加为执行上下文的属性function
声明的函数==>赋值(fun),添加为执行上下文的方法this
–>赋值(调用函数的对象)
- 开始执行函数体代码
- 块级执行上下文,ES6认为,由
let
或const
加一个大括号所组成的区域。
执行上下文栈
- 在全局代码执行前,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