// let 与 var
/* 一 */
{
let a =12;
var b =10;
}
//console.log(a) // a is not defined
//console.log(b) // b = 10
// let 声明的变量只再其所在的代码块中有效的
/* 二 */
// for 循环的计数器就很适合使用let 命令
for (let i =0; i <10; i++) {
// console.log(i) // 0,1,2,3,4,5,6,7,8,9
}
// console.log(i) // i is not defined -->原因:使用在for 循环里面使用let 定义的计数器 i ,只能在for循环体内有效,在循环体外面就会报错。
var a = [];
for(var i =0; i <10; i++){
a[i] =function () {
console.log(i)
}
}
a[6] ();// 10,
// 变量 i 是 var 声明的,在全局范围内都有效的,所以全局只有一个变量 i 。
// 每一个循环,变量 i 的值都会发生改变,而循环内,被赋给数组 a 的函数内部的 console.log(i)中的 i 指向全局的 i.
// 也就是说数组 a 的成员中的 i 指向的都是 同一个 i,导致运行时输出的是最后一轮的 i 的值,也就是10.
// 也可以理解为,全局变量i的值在循环里是不断变化的,相当于全局变量 i不断的被声明和覆盖并赋值给 数组 a,
// 如果使用 let ,声明的变量仅在块级作用域内有效,最后将输出 6
var a = [];
for(let i =0; i <10; i++){
a[i] =function () {
console.log(i);
}
}
a[6] (); // 6
// 上面的代码中,变量 i 是let声明的,当前的 i 只在本轮循环有效。所以每一次循环的 i,其实都是一个新的变量,于是最后输出的是 6 .
// 大家可能会问,如果每一次循环的变量 i都是重新声明的,那么它怎么知道上一轮循环的值,从而计算出本轮循环的值呢?
// 这是因为JavaScript引擎内部会记住上一轮循环的值,初始化本来的变量 i 时,就在上一轮循环的基础上进行计算。
// 另外 for 循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for(let i =0; i <3; i++){
let i ='abc';
console.log(i)
}
// 输出3次 abc
// 这表明函数内部的变量 i 与循环变量 i 不在同一个作用域,而有各自单促的作用域。
/* 三 */ //不存在变量提升
// var 命令发生 “变量提升” 现象,即变量可以在声明之前使用,值为 undefined。这种现象多多少少是有些奇怪的。
// 按照一般的逻辑,变量应该在声明语句之后才可以使用的
// 为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则便会报错的
// var 的情况
console.log(foo)// undefined
var foo =2
// 类似于 以下
// var foo;// 变量被声明了。但是没有赋值,输出没有赋值的变量就是undefined;
// console.log(foo)
// foo = 2;// 在此处变量赋值
// let 的情况
// console.log(bar); // 报错
// let bar = 2;
/*
在以上代码中,变量foo用var 命令声明会发生变量提升,即脚本开始运行时,变量foo便已经存在,但是没有值,所有会输出undefined 。
变量bar 使用 let命令声明则不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
*/
/* 四 暂时性死区 */
var tmp =123;
if (true) {
tmp ='abc'; // ReferenceError 报错了
// let tmp;
}
// 只要块级作用域内 存在 let 命令,它所声明的变量就“绑定(binding)”这个区域,不再受外部的影响。
// 上面的代码中存在全局变量 tmp, 但是块级作用域内let 又声明了一个局部变量tmp,导致后者绑定这个块级作用域.
// 所以在let声明变量前,对tmp赋值会报错的。
// ES6明确规定,如果区块中存在 let 和 const 命令,则这个区块对这些命令声明的变量,从一开始就形成封闭作用域。只要在声明之前就使用这些变量。就会报错。
// 总之,在代码块内,使用let命令声明变量之前。该变量都是不可用的。这在语法上称为“暂时性死区(temporal dead zone简称 TDZ)”.
if (true) {
// TDZ开始
// tmp = 'abc' // ReferenceError
// console.log(tmp); // // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined;
tmp =123
console.log(tmp)//123
}
/*
* 上面的代码中,在 let 命令声明变量 tmp 之前,都属于 变量 tmp的 “死区”
* “暂时性死区” 也意味着 typeof 不再是一个百分之百安全的操作。
*
* */
typeof x; // ReferenceError
// let x;
/*
* 上面的代码中,变量x使用 let 命令声明,所以在声明之前都属于 x 的“死区”,只要用到该变量就会报错。
* 因此,typeof 运行时就会抛出一个ReferenceError.
* */
// 如果一个变量根本没有被声明,使用typeof 反而不会报错。
console.log(typeof undeclared_variable)// undefined
/*
* 上面的代码中 变量undeclared_variable是不存在的变量。结果返回:undefined,
* 所以在没有 let 之前,typeof 运算符是百分百安全的,永远不会报错。现在这一点不成立了
* 这样的设计是为了让大家养成良好的编程习惯。变量一定要在声明之后使用。否则就会报错。
* */
// 有写 “死区” 比较隐蔽,不太容易发现的,如下:
// function bar (x = y, y = 2) {
// return [x, y];
// }
//bar(); // 报错了
/*
* 上面的代码中,调用bar函数之所以报错(某些实现可能不报错),
* 是因为参数 x 的默认值等于另一个参数 y ,而此时 y 还没有声明,属于 “死区”。
* 如果 y 的默认值是 x,就不会报错。因为此时 x 已声明。
* */
function bar(x =2, y = x) {
return [x, y];
}
console.log(bar()); //[2, 2]
// 下面的代码也会报错,与 var 的行为不同
var x = x; // 不会报错
// let y = y ; // 报错
/*
* 以上代码报错也是因为暂时性死区,使用let声明变量时,只要变量在还没有声明前使用的,就会报错。
* 以上示例就属于这种情况,在变量 x 的声明语句还没有执行完成前就尝试获取 x 的值,导致出现“x未定义”的错误。
* */
/* 五,不允许重复声明*/
// let 不允许在相同作用域内重复声明同一个变量
// 报错一
function a() {
let a =10;
var a =1;
}
// a()
// 报错二
function a() {
let a =10;
let a =1;
}
// a()
// 不能在函数内部重新声明参数
function func (arg) {
let arg;
}
// func()
// 不报错
function func (arg) {
{
let arg;
}
}
func();
/*六 块级作用域*/
// ES5 只有全局作用域和函数作用域,没有块级作用域,这个导致很多场景不合理。
// 第一种场景,内层变量可能会覆盖外层变量。
var tmp =new Date()
function f() {
// var tmp; // 内层的变量tmp声明提前了。导致内层的var tmp; 覆盖了外层的 tmp = new Date()
console.log(tmp); // undefined
if (false) {
var tmp ='Hello World'
}
}
console.log(f()); // undefined
// 第二种场景,用来计数的循环变量泄露为全局变量
var s ='hello';
for(var i =0; i < s.length; i++) {
console.log(s[i]); // h e l l o
}
console.log(i); // 5
/*
* 上面的代码中,变量 i 只用来控制循环,但是循环结束后,它并没有消失,而是泄露成了全局变量。
* */
// ES6 的 块级作用域
// let 实际上为JavaScript新增了块级作用域
function f1 () {
// 外层
let n =5;
if (true) {
// 内层
let n =10;
}
// 外层代码块不受内层代码块的影响。
console.log(n); //5; // 如果用 var 定义的话,输出的就是 10了。
}
f1();
// ES6 允许块级作用域的任意嵌套 (外层作用域是无法读取内层作用域的变量的。)
{{{{{let insane ="Hello World"}}}}}
// 外层作用域是无法读取内层作用域的变量的
{{{{
{let insane ="Hello World"}
// console.log(insane); // 报错的
}}}}
// 内层作用域可以定义外层作用域的同名变量的
{{{{
let insane ="Hello World"
{let insane ="Hello World"}
}}}}
// ES5 规定,函数只能在顶级作用域或者函数作用域之中声明的,不能在块级作用域中声明。
// 但是浏览器没有遵守这个规定,为了兼容以前的代码,还是支持在块级作用域之中声明函数。因此以下的情况都能运行,不会报错。
// 情况一
if (true) {
function f () {
}
}
// 情况二
try {
function f() {}
}catch (e) {
}
// ES6 引入了块级作用域,明确允许在块级之中声明函数。
// ES6 规定,在块级作用域之中,函数声明语句的行为类似于 let, 块级作用域之外不可引用。
function f () {console.log('I am outside!');}
(function () {
if (false) {
// 重复声明一次函数 f
function f() {console.log('I am inside!');}
}
// f();
}())
// 在ES6 中会报错
// 应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式得形式。而不是函数声明语句
// 函数声明语句
{
let a ='secret'
function f() {
return a
}
}
// 函数表达式
{
let a ='secret';
let f =function () {
return a;
}
}
// do表达式
// 本质上,块级作用域是一个语句,将多个操作封装再一起,没有返回值。
// 现在有个提案,使得块级作用域可以变为表达式,即可以返回值,办法就是再块级作用域之前加上 do.使他变为 do表达式。
// let x = do {
// let t = f();
// t * t + 1;
// }
// console.log(x)
const 命令
/* const 命令 */
// 基本用法
// const 声明一个只读的常量,一旦声明,常量的值就不能改变。
const PI =3.1415;
console.log(PI)
// PI = 3; // 改变常量的值会报错。
// const foo;
// const声明的常量不得改变值。这意味着,const一旦声明常量,就必须立即初始化,不能留到以后赋值。
// 顶层对象在浏览器环境中指的是 window 对象。在node 环境中指的是global对象。
// 在ES5中,顶层对象的属性与全局变量是等价的。
// 在ES6 中规定 let,const,class命令声明的全局变量不属于顶层对象的属性。
var a =1
console.log(window.a)// 1
console.log(this.a)// 1
console.log(self.a)
// 在浏览器中顶层对象是 window .但是 self 也指向顶层对象。但是 Node中没有 self .
// 在 Node 中,顶层对象是 global .但是其他环境都不支持。
// Es6
let b =1;
console.log(window.b)// undefined
// 上面的代码中,全局变量 a 是由 var命令声明,所以它是顶层对象的属性。
// 全局变量 b 是由 let 声明的。所以它不是顶层对象的属性。返回 undefined.
/*
* 在全局环境中,this会返回顶层对象。但是在Node 模块和 ES6 模块中,this 返回的是当前模块。
* 对于函数中的this.如果函数不是作为一个对象的方法运行,而是单纯作为函数运行,this.会指向顶层对象。但是在严格模式下,this.会返回undefined
*
*
* */
网友评论