一、变量提升(变量预解析)
这是我们首先要了解的,也是必须要清楚的一个概念,那到底什么是变量提升呢?
JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的 时候分为两步:预解析和代码执行。
l 预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中 进行提前声明或者定义。
l 代码执行: 从上到下执行JS语句。
JS代码运行的时候确实是一行一行执行的,但是在实行前会有一个预解析的过程,相当于把所有的代码先全部扫描一遍,把带有 var 和 function 声明的变量先存在内存中,也可以说提到整体的代码前面去。
学习预解析能够让我们知道为什么在变量声明之前 访问变量的值是 undefined,为什么在函数声明之前就可以调用函数。
我们来看一段简单的代码
console.log(num); // 结果是多少?var num = 10; // ?
答案很简单是undefined
为什么?
1变量提升
变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。
所以我们来看一下这段代码的解析过程
其实相当于Var num;Log(num);num = 10;
2函数提升
函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
fn();function fn() {console.log(\'123\');}
答案:123
3相关例题
接着我们看几个相关案例题
案例一
结果是几?
var num = 10;function fn(){console.log(num);var num = 20;console.log(num);}fn();
undefined
20
案例二
结果是几?
var a = 18;f1();function f1() {var b = 9;console.log(a);console.log(b);var a = \'123\';}
undefined
9
案例三
结果是几?
f1();console.log(c);console.log(b);console.log(a);function f1() {var a = b = c = 9;console.log(a);console.log(b);console.log(c);}
99999报错
这里提一下:var a = b = c = 9; 这句话的本质上是这样的Var a=9,b=9,c=9注意 只有a是用了var的 ,所以a是局部变量,b c是全局变量
如果要集体申明应该是var a=9,b=9,c=9。但是其实现在实际开发中var用的很少。
二、Let
ES6中新增的用于声明变量的关键字。
特点:
A、不会进行预解析,结果会报错
B、与forEach()中的变量类似,每次执行都会定义一个互相之间不影响的新变量
C、不能重复定义变量名称,一个变量名称只能定义一次
D、定义的变量如果在中,则只能在中被执行调用,其他位置都不可以。所以在不同中,定义的变量名称是可以重复之用的
E、在循环中最好只用let定义
- let声明的变量只在所处于的块级有效
注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
if (true) {let a = 10;}console.log(a) // a is not defined
- 不存在变量提升
console.log(a); // a is not defined 注意这个地方是会报错的,而不是undefinedlet a = 20;
- 暂时性死区
Let会使当前的作用域形成块级作用域,也就是说当if里面使用了let后,括号里面的tmp和外面申明的tmp完全没有关系了,这也叫暂时性死区
var tmp = 123;if (true) {tmp = \'abc\'; // tmp is not definedlet tmp;}
let经典面试题
var arr = [];for (var i = 0; i < 2; i++) {arr[i] = function () {console.log(i);}}arr[0]();arr[1]();
答案:2 2
此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。打印i是会向上查找i的申明,但是只查到了循环体中的全局申明,所以i是全局变量当下面数组调用函数时,for循环早就遍历完了,此时的i已经是2了
let经典面试题
let arr = [];for (let i = 0; i < 2; i++) {arr[i] = function () {console.log(i);}}arr[0]();arr[1]();
此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的, 函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值
每个块级作用域都是相互独立的,不受相互影响的
三、Const
作用:声明常量,常量就是值(内存地址)不能变化的量。
特点:
A、在js中,const定义的变量成为常量,不能被重复赋值,数据已经定义,不能修改
B、const也是定义在中,不能在外调用
C、const定义的是对象、数组、函数、引用数据类型。其中只要引用数据类型的地址没变化,就可以改变引用数据类型中的单元存储的数据
D、const不会进行预解析,结果会报错
- 具有块级作用域
if (true) {const a = 10;}console.log(a) // a is not defined
- 声明常量时必须赋值
const PI; // Missing initializer in const declaration
- 常量赋值后,值不能修改。
声明了一个常量,代表这个识别名称的参照(reference)是唯读的(read-only),并不代表这个参照赋到的值是不可改变的(immutable),"const"只是针对存取层级的限制(access limitations),并不是不可改变性(immutability)。
上面这段在讲什么呢?实际上就是简单数据类型例如字符、数值常量赋值后不能更改,但是复杂数据类型,例如数组、对象,情况有所出入。
当对整个数组或者对象进行重新赋值时,是不被允许的,而修改里面的属性时是可以的,例如案例代码所示。
本质原理就是常量值对应的内存地址并不能变化,重新赋值等于修改内存地址,而修改属性则没有关系。
const PI = 3.14;PI = 100; // Assignment to constant variable.const ary = [100, 200];ary[0] = \'a\';ary[1] = \'b\';console.log(ary); // [\'a\', \'b\'];ary = [\'a\', \'b\']; // Assignment to constant variable.
额外
1声明常量时建议使用const还是let
这个问题网上的争议很多,有人说使用const有人说使用let,我仅我个人的理解来说我一下我的认识。
我喜欢还是用const (这是一个非常个人化主观化的观点,你说你喜欢用let当然可以)
-
const可以用在对象、数组与函数上,常量一声明时就要赋值,犯错的机会会减少很多。
-
JS引擎也可以作优化。虽然JS语言上并没有感受差异,但实际上引擎里有区分出常量与变量,而最重要的是因为JS语言是弱(动态)类型的脚本语言,常量一声明就赋值,代表它立即决定好是什么类型与值,自然效能比变量好得多。也可以理解说因为const声明的常量是固定的,内存不需要实时监测值的变化,所以比其他变量的内存占有率更少
2函数方法中的const
我不知道有没有人和我一样,有一段时间一直有个困惑,就是在函数中,或者方法中,用const申明变量,不是说const是不能再被赋值的吗?函数会调用很多次,为什么不报错?
其实后来一想很简单,因为函数每次执行完之后就会销毁了。。。。