{
let a = 10;
var b = 1;
}
console.log(b)//1
console.log(a)//报错 a is not defined
for循环的计数器就很适合let:
//以下i只在for循环体内有效,在循环体外就会报错
for(let i=0;i<10;i++){
//执行代码
}
console.log(i);//报错i is not defined
下面的代码中,变量i
是var
声明的,所以i
是一个全局变量在全局范围内都有效,所以全局只有一个变量i
,每一次循环i
的值都会发生改变,被赋给数组a的函数内部的console.log(i)
中的i指向全局的i
,因此所有数组a的成员中的i指向的都是同一个i
,导致运行时输出的是最后一轮的i
值10
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
}
}
a[6]();//10
当我们使用let
再来观察一下,变量i
是let
声明,当前的i
只在本轮循环中有效,所以每一次循环都是一个新的变量,于是最后输出6。
var a = [];
for (let i = 0; i < 10; i++) {
console.log(i);
a[i] = function () {
console.log(i);
}
}
a[6]();//6
思考:如果每一轮循环的变量i都是重新声明的,那么它怎么知道上一轮循环的值从而进行计算出本轮循环的值呢?
原因:其实因为JavaScript引擎内部都会记住上一轮循环的值,初始化本轮的变量i时就在上一轮的基础上进行计算。
设置循环变量的那一部分是一个父作用域,而循环体内部是一个单独的子作用域,下面的代码中最终结果是打印三次‘abc’
for(let i=0;i<3;i++){
let i ="abc";
console.log(i);
}
这表明了函数内部的变量i与循环变量i不在同一个作用域,而是各自有各自单独的作用域。
var
命令会发生“变量提升”的现象,即变量可以在声明之前使用,值为undefined
。这种现象多少有些奇怪,按照一般的逻辑,变量应该在声明语句之后才可以使用,为了纠正这种现象,let
命令改变了语法行为,他所声明的变量一定要在声明后使用,否则报错。
//var 的情况
console.log(foo);//undefined
var foo = "18"
//let 的情况
console.log(bar);//报错 ReferenceError
let bar = "12"
暂时性死区本质就是:只要进入作用域,所要使用的变量就已经存在了,但是不可以获取,必须等到声明变量的哪一行代码出现。
只要块级作用域中存在let命令,它所声明的变量就“绑定了”这个区域,不在收到外部影响。
例1:下面的代码中,存在全局变量tmp,但是块级作用域中let又声明了一个全局变量tmp,导致后者绑定这个块级作用域,所以在let声明变量之前,对tmp赋值就会报错
var tmp = 123;
if (true) {
tmp = "abc"; //报错ReferenceError
let tmp;
}
例2:下面代码汇总也是因为暂时性死区,只要在变量还没有声明前使用,就会报错
var x = x;//不报错
let y = y;//报错ReferenceError
let不允许在相同的作用域内重复声明同一个变量
function a() {
let a = 5;
var a = 10;//报错SyntaxError
}
function a() {
let a = 5;
let a = 10;//报错SyntaxError
}
因此不能再函数内部重新声明参数
function b(args) {
let args; //报错 SyntaxError: Identifier 'args' has already been declared
}
function c(args) {
{
let args;//不报错
}
}
ES5中只有全局作用域,没有块级作用域,这导致了很多场景不合理
第一种场景:内层变量可能会覆盖外层变量
例:以下代码的原义是指:if代码块外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升导致内层的tmp变量覆盖了外层的tmp变量。
var tmp = new Date();
function foo() {
console.log(tmp); //undefined
if (false) {
var tmp = "hello world";
}
}
foo();
第二种场景:用来计数的循环变量泄露为全局变量
例:下面的代码中,变量i只用来控制循环,但是循环结束后,它没有消失,而是泄露了全局变量
var s = "hello";
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); //5
let实际上为JavaScript新增了块级作用域。
例:下面的函数中有两个代码块,都声明了变量n,运行后输出5,这表示外层代码块不受内层代码块的影响,如果使用var
定义变量n,最后输出的值就是10
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); //5
}
例:
{{{{{let insane = "hello world"}}}}}//这个代码使用了一个5层的块级作用域
{{{{
{let insane = "hello world"}
console.log(insane);//insane is not defined 外层作用域无法读取内层作用域的变量
}}}}
//内层作用域可以定义外层作用域的同名变量
{{{{
{let insane = "hello world"}
let insane = "hello world"
}}}}
块级作用域的出现,实际上让前面获得广泛应用的立即执行匿名函数(IIFE)不再必要了
(function () {
var tmp = ...;
}())
//块级作用域写法
{
let tmp = ...;
}
函数能不能在块级作用域中声明?这是一个让人困惑的问题。
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域中声明。下面两种函数声明在ES5中都是非法的,但是浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持了在块级作用域中声明函数,因此下面两种情况实际上都能运行,并不会报错。
//情况一:
if (true) {
function f() {}
}
//情况二:
try {
function f() {}
} catch (e) {
//...
}
ES6引入了块级作用域,明确允许在块级作用域之中声明函数,ES6规定,在块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可以引用
function f() {
console.log("hello world");
}
(function () {
if (false) {
//重复声明一次函数f
function f() {
console.log('I am inside!');
}
}
f();//报错 Uncaught TypeError: f is not a function
}())
const可以声明一个只读的常量
例:下面的代码表明改变常量的值就会报错
const PI = 3.1415;
console.log(PI);
PI = 3; //报错TypeError: Assignment to constant variable
例:
const a;//只声明不赋值就会报错SyntaxError: Missing initializer in const declaratio
if(true){
const MAX = 10;
}
console.log(MAX);//MAX is not defined
if(true){
console.log(MAX);//ReferenceError: Cannot access 'MAX' before initialization
const MAX = 5;
}
var message = "hello";
let age = 18;
//下面两行都会报错
const message = "good";
const age = 40;
const
实际上保证的并不是变量的值不改动,而是变量指向的内存地址不得改动。对于简单类型的数据而言,值就保存在变量指向的内存地址中,因此等同于常量。但是对于引用类型的数据(对象、数组),变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的这是不能控制的,因此,将一个对象声明为常量时需要非常小心。
const foo = {};
// 为foo对象添加一个属性,可以成功
foo.pname = "老八";
console.log(foo.pname);//老八
//如果将foo指向另一个对象,就报错
foo = {};//TypeError: Assignment to constant variable.
const a = [];
a.push("666");//可执行
a.length = 3;//可执行
a = ['1']//报错
例:以下代码,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式下还会报错
const foo = Object.freeze({})
//严格模式下,下面一行会报错,在普通模式下不起作用
foo.pname = "老狗"
console.log(foo.pname); //03-const命令.html:71 undefined
除了将对象本身冻结之外,对象的属性也应该冻结:下面是一个将对象彻底冻结的函数
function constantize(obj) {
Object.freeze(obj);
Object.keys(obj).forEach((key, i) => {
if (typeof obj[key] === 'object') {
constantize(obj[key])
}
})
}
var p1 = {
name:"张三",
age:18,
say:function(){
console.log('你好');
}
}
p1.name = "李四";
constantize(p1);//冻结p1对象
p1.name = "Rose";
console.log(p1);//{name: "李四", age: 18, say: ƒ}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。