let命令与const命令

注意

  1. 变量是从里面往外面找,块级作用域嵌套时是没有包含关系的,也就是说这个代码块不包含它体内的另一个函数内的内容,类似于“我的附庸的附庸不是我的附庸”,函数1所包含的函数2的内容也不属于函数1的代码块。
  2. const优于let,在 let 和 const 建议优先使用const,尤其在全局环境,不应设置变量,只因设置常量。

const优于let的原因

  • const可以提醒阅读程序的人这个变量不应该改变。
  • const比较符合函数式编程思想,运算不改变值,只是新建值,防止无意间修改变量值所导致的错误,而且这样也有利于将来分布式运算。
  • JavaScript编译器会对const进行优化。
  • 多使用const,有利于提高程序的运行效率。
  • let和const的本质区别,其实是编译器内部的处理不同。

块级作用域

  • 注意:花括号包含块级作用域,声明一个一个对象但声明一个一个对并不是块级作用域。

var

例子
结果:打印出0 1 2和3
思路分析


let

let用于声明变量,用法类似于var,但是所声明的变量只在let命令所在的代码块内有效

使用let或者const声明的变量不能再被重新声明

var可以重复声明,不报错且可以对同一个值进行修改
let不能重复声明,会报错,显示已经被声明过(1)
let不能重复声明,会报错,显示已经被声明过(2)


let不存在变量提升

var存在变量提升,会将变量的声明提升,赋值则留在原地,打印出“undefined”
let不存在变量提升,所以变量声明不会提升,报错


暂存死区

  • ES6规定,如果块级作用域中存在let或者const声明的变量,那么这个变量一开始就会形成封闭的作用域。
  • 也就是说,即使向上的作用域中存在同名变量也是拿不到的

var不存在暂存死区
var不存在暂存死区,所以第一个console可以向上去上面的作用域找同名变量,拿过来打印。(其实有没有大括号意义不大)

let存在暂存死区,又不存在变量提升,所以第一个console报错
let存在暂存死区,也就是说向上的作用域中存在同名变量也拿不到,所以第一个console拿不到上面的“我是美猴王”。let又不存在变量提升,所以第一个console中的monkey也没有被声明,只能报错。


补充:appendChild()、createElement()

HTML DOM Document 对象、appendChild() 方法、createElement()方法、createTextNode()都可以参考笔记”JS中修改/创建HTML DOM元素的方法”

使用let实现面试常见小例子

代码解析

  1. 先在HTML文档中创建一个按钮节点btn
  2. 再改变节点文字
  3. 然后给节点btn设置一个点击事件
  4. 最后将节点btn添加到body中

使用var生成十个按钮每个按点击的时候弹出1-10
原因
执行事件时,alert()首先需要i,但是当前作用域找不到,它就会到上一级找,上一级通过自调用时有传进来的i,这个i是随着函数的调用而产生,随着函数调用结束而释放的,不是作用在一开始的var i身上的,所以每一个函数都对应不同的数字。

变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。所以每一次循环中btn的innerText所指向的i其实都是同一个,循环到最后时前面的i全部被最后的结果覆盖了。而局部变量的话就是每循环一次产生一个新的i,所以不会被覆盖。

做这个修改则点击每个按钮弹出的都是11
原因
执行事件时,alert()首先需要i,但是当前作用域找不到,它就会到上一级找,上一级直接就是var i了,所有的代码都作用在它身上,所以他会是11。

使用let生成十个按钮每个按点击的时候弹出1-10
可以发现,使用let以后就不需要额外放一个自调用函数把他们框起来了。
变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量。


const

与let特性十分相似:不能重复声明、不存在提升、只在当前(块级)作用域内有效

使用const来声明常量(不可改变的量)

使用var或者let声明的是变量

1
const a=1;

常量必须在声明的时候赋值,否则报错:Missing initializer in const declaration


常量声明后不能被修改

常量声明后不能被修改


常量为引用类型的时候可以修改该引用类型

常量为引用类型:对象、数组、函数

常量声明一个对象

常量声明一个对象

  • 修改xiaoming.age可以,修改xiaoming却会报错,因为const只能保证我们声明指向的地址不变,不能保证声明的值不变。
  • xiaoming指向的地址和两个属性指向的地址是一样的,常量要求xiaoming指向的地址不变。修改属性值以后属性依旧指向原本的地址,所以不会报错。但是修改xiaoming会使得指向地址变化,因此报错
  • 也就是说,常量为对象时,不能直接修改这个对象,但是可以修改这个对象的属性,因为此时该常量是引用类型。
1
2
3
4
5
6
7
8
<script>
const xiaoming = {
age: 14,
name: '小名'
};
xiaoming.sax = '男';
console.log(xiaoming.sax); //男
</script>

说明常量对象如果不处理是可以增加属性的。


常量声明一个数组

常量声明一个数组

  • 可以往常量数组中放入值,此时该常量数组指向的地址不变,不会报错。
  • 30试图让常量数组指向另一个地址,报错。

常量为引用类型时需要冻结来防止被修改

使用Object.freeze(常量名)
常量对象的例子
注意:使用Object.freeze()以后也不能再给常量增加属性了(比如xiaoming.dd=11;

常量数组的例子


es6之前怎么声明常量

补充:Object.defineProperty

Object.defineProperty除了可以增加一个属性以外还可以对已有的属性进行设置writable:false使该属性不可修改。

补充:Object.seal(变量名);

Object.seal(变量名);可以使该变量不能再增加属性。两者结合可以使变量成为常量。

补充:对象名.hasOwnProperty(属性名)

对象名.hasOwnProperty(属性名)可以帮助我们判断这个属性是自身的还是继承过来的。
例子
其中,obj2是通过obj1创建的,遍历打印obj2可以发现除了obj2自身的属性c、d之外还会打印出继承来的a、b属性。
增加hasOwnProperty进行判断以后输出的就是obj2自身的属性c、d

es6之前声明常量的方法

思路:自定义一个Object的带有参数obj(需要成为常量的变量名)的方法freezePolyfill,需要声明常量时就使用Object.freezePolyfill(变量名)

实现步骤

  1. 遍历属性和方法
  2. 修改遍历到的属性的描述
  3. Object.seal()

例子

问题
解决方法:可以用递归来判断一下属性是不是方法,再去套上面的代码。(直接使用迭代的方式也可以)