注意
- 变量是从里面往外面找,块级作用域嵌套时是没有包含关系的,也就是说这个代码块不包含它体内的另一个函数内的内容,类似于“我的附庸的附庸不是我的附庸”,函数1所包含的函数2的内容也不属于函数1的代码块。
- 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声明的变量不能再被重新声明
let不存在变量提升
暂存死区
- ES6规定,如果块级作用域中存在let或者const声明的变量,那么这个变量一开始就会形成封闭的作用域。
- 也就是说,即使向上的作用域中存在同名变量也是拿不到的。
var不存在暂存死区,所以第一个console可以向上去上面的作用域找同名变量,拿过来打印。(其实有没有大括号意义不大)
let存在暂存死区,也就是说向上的作用域中存在同名变量也拿不到,所以第一个console拿不到上面的“我是美猴王”。let又不存在变量提升,所以第一个console中的monkey也没有被声明,只能报错。
补充:appendChild()、createElement()
HTML DOM Document 对象、appendChild() 方法、createElement()方法、createTextNode()都可以参考笔记”JS中修改/创建HTML DOM元素的方法”
使用let实现面试常见小例子
代码解析:
- 先在HTML文档中创建一个按钮节点btn
- 再改变节点文字
- 然后给节点btn设置一个点击事件
- 最后将节点btn添加到body中
原因:
执行事件时,alert()首先需要i,但是当前作用域找不到,它就会到上一级找,上一级通过自调用时有传进来的i,这个i是随着函数的调用而产生,随着函数调用结束而释放的,不是作用在一开始的var i身上的,所以每一个函数都对应不同的数字。
变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。所以每一次循环中btn的innerText所指向的i其实都是同一个,循环到最后时前面的i全部被最后的结果覆盖了。而局部变量的话就是每循环一次产生一个新的i,所以不会被覆盖。
原因:
执行事件时,alert()首先需要i,但是当前作用域找不到,它就会到上一级找,上一级直接就是var i了,所有的代码都作用在它身上,所以他会是11。
可以发现,使用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 | <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属性。
es6之前声明常量的方法
思路:自定义一个Object的带有参数obj(需要成为常量的变量名)的方法freezePolyfill,需要声明常量时就使用Object.freezePolyfill(变量名)
实现步骤:
- 遍历属性和方法
- 修改遍历到的属性的描述
- Object.seal()
解决方法:可以用递归来判断一下属性是不是方法,再去套上面的代码。(直接使用迭代的方式也可以)