复习

for与var

  1. for (var i = 0; i < 10; i++) {
  2. i = 'a';
  3. console.log(i);//a
  4. }

等价于

  1. var i = 0
  2. for (; i < 10; ) {
  3. i = 'a';
  4. console.log(i);//a
  5. i++;
  6. }
  1. for (var i = 0; i < 10; i++) {
  2. var i = 'a';
  3. console.log(i);//a
  4. }

也可以变成如下代码,for表达式中的var i与for()中的var i都被提升到了全局

  1. var i = 0
  2. for (; i < 10; ) {
  3. i = 'a';
  4. console.log(i);//a
  5. i++;
  6. }

for与let

  1. for (let i = 0; i < 10; i++) {
  2. /*调用i并给i赋值为'a',调用的i就是let i=0产生的,是一个i*/
  3. i = 'a';
  4. console.log(i);
  5. }
  1. for (let i = 0; i < 10; i++) {
  2. var i = 'a';
  3. //SyntaxError: Identifier 'i' has already been declared
  4. console.log(i);
  5. }

相当于,两个块级作用域

  1. {
  2. let i;
  3. {
  4. var i;
  5. }
  6. }

变量提升,可以变形成

  1. {
  2. var i;
  3. let i;
  4. {
  5. }
  6. }
  1. for (let i = 0; i < 10; i++) {
  2. let i = 'a';
  3. console.log(i);//10个a
  4. }

相当于

  1. {
  2. let i = 0;
  3. {
  4. let i = 'a';
  5. console.log(i);//a
  6. }
  7. console.log(i);//0
  8. }

类似于上面这段代码运行10次,写10次

因为let不允许在同一个块级作用域重复声明的,会报错
想让它重新let一个i,只能为它开辟一个新的块级作用域,在里面再声明i

转义后的es5代码

  1. {
  2. var i = 0;
  3. {
  4. var _i = 'a';
  5. console.log(_i); //a
  6. }
  7. console.log(i); //0
  8. }

for与数组1

  1. var arr = [];
  2. for (var i = 0; i < 10; i++) {
  3. arr[i] = function () {
  4. console.log(i)
  5. }
  6. }
  7. for (var i = 0; i < 10; i++) {
  8. arr[i]();//0-9
  9. /*i把之前的i覆盖了,因为都是i*/
  10. }

相当于

  1. var arr = [];
  2. var i = 0
  3. for (; i < 10; i++) {
  4. arr[i] = function () {
  5. console.log(i)
  6. }
  7. }
  8. var i = 0
  9. for (; i < 10; i++) {
  10. arr[i]();//0-9
  11. /*i把之前的i覆盖了,因为都是i*/
  12. }

for与数组2

  1. var arr = [];
  2. for (var i = 0; i < 10; i++) {
  3. arr[i] = function () {
  4. console.log(i)
  5. }
  6. }
  7. for (var k = 0; k < 10; k++) {
  8. arr[k]();//10个10
  9. }

见立即执行函数知识点
没有运行func声明语句拿着i

for与数组3:let

  1. var arr = [];
  2. /*把var改成let就变成了0-9,之前是10个10,如上面代码*/
  3. for (let i = 0; i < 10; i++) {
  4. arr[i] = function () {
  5. console.log(i)
  6. }
  7. }
  8. for (var k = 0; k < 10; k++) {
  9. arr[k]();//0-9
  10. }

转成es5代码

  1. var arr = [];
  2. /*把var改成let就变成了0-9,之前是10个10,如上面代码*/
  3. var _loop = function _loop(i) {
  4. arr[i] = function () {
  5. console.log(i);
  6. };
  7. };
  8. for (var i = 0; i < 10; i++) {
  9. _loop(i);
  10. }
  11. for (var k = 0; k < 10; k++) {
  12. arr[k](); //0-9
  13. }

存在全局的arr中,这是一个闭包
arr在全局中定义,通过_loop方法赋值,这形成了闭包,执行的_loop函数名次执行的AO中的i,被数组函数引用、调用,不会被销毁。
_loop每次运行的AO,存在arr数组方法声明产生的scope中,被全局的arr调用,不会被销毁。每次_loop运行时,AO中i的值都是不一样的,是新的AO。
方法声明里面的scope,有_loop每次运行的AO

  1. var arr = [];
  2. /*把var改成let就变成了0-9,之前是10个10,如上面代码*/
  3. for (let i = 0; i < 10; i++) {
  4. arr[i] = function () {
  5. console.log(i)
  6. }
  7. }
  8. /*把k改成i也是0-9,因为arr[i]方法中调用的是闭包AO中i,不是for中的i*/
  9. for (var i = 0; i < 10; i++) {
  10. arr[i]();//0-9
  11. }

for与数组4:let

现在再来看这道题

  1. var arr = [];
  2. /*把var改成let就变成了0-9,之前是10个10,如上面代码*/
  3. for (let i = 0; i < 10; i++) {
  4. arr[i] = function () {
  5. console.log(i)
  6. }
  7. }
  8. for (var k = 0; k < 10; k++) {
  9. arr[k]();//0-9
  10. }

不能把let提取到外面如下,结果不一样了

  1. var arr = [];
  2. /*把var改成let就变成了0-9,之前是10个10,如上面代码*/
  3. let i = 0;
  4. for (; i < 10; ) {
  5. arr[i] = function () {
  6. console.log(i)
  7. }
  8. i=i+1;
  9. }
  10. for (var k = 0; k < 10; k++) {
  11. arr[k]();//10个10
  12. }

等价于,不算循环

  1. {
  2. let i=0;
  3. /**/
  4. {
  5. arr[i] = function() {
  6. console.log(i)
  7. }
  8. }
  9. }

类似于

  1. var arr = [];
  2. /*把var改成let就变成了0-9,之前是10个10,如上面代码*/
  3. /*for (let i = 0; i < 10; i++) {
  4. arr[i] = function () {
  5. console.log(i)
  6. }
  7. }*/
  8. for (; 1;) {
  9. {
  10. let i = 0;
  11. {
  12. arr[i] = function () {
  13. console.log(i)
  14. }
  15. }
  16. }
  17. {
  18. let i = 1;
  19. arr[i] = function () {
  20. console.log(i)
  21. }
  22. }
  23. {
  24. let i = 2;
  25. arr[i] = function () {
  26. console.log(i)
  27. }
  28. }
  29. break;
  30. }
  31. /*把k改成i也是0-9,因为arr[i]方法中调用的是闭包AO中i,不是for中的i*/
  32. for (var i = 0; i < 3; i++) {
  33. arr[i]();//0,1,2
  34. }

let声明,相当于开了块级作用域再运行代码
for循环10次,执行了10次let声明语句,相当于开了10个块级作用域,每个块级作用域都有缓存记录,记录到AO中,类似于每个块级作用域都存到AO中,每个块级作用域中i的值都不一样,相当于一个闭包
块级作用域会缓存变量,类似于闭包

  1. for (let i = 0; i < 3; i++) {
  2. }

肯定不等价于

  1. for (; 1;) {
  2. let i = 0;
  3. let i = 1;
  4. let i = 2;
  5. break;
  6. }

等价于

  1. for (; 1;) {
  2. {let i = 0;}
  3. {let i = 1;}
  4. {let i = 2;}
  5. break;
  6. }

函数名与let

let与函数名重复也会报错

与函数名重名,也会报错

  1. let a=1;
  2. function a(){
  3. }
  4. console.log(a);//Uncaught SyntaxError: Identifier 'a' has already been declared

函数的提升后与let重名是可以的?全局的let影响了function提升到全局

全局的let影响了function提升到全局
这样可以运行,编辑器也没有错误提示

  1. let a = 1;
  2. {
  3. a();//10
  4. function a() {
  5. console.log(10)
  6. }
  7. }
  8. console.log(a);//1

这样在编辑器内会报错,但在编辑器内会有提示错误,但是是可以运行的,用nodejs可以运行
image.png
浏览器里面也可以运行成功
image.png

这个代码可以运行说明,它不能变成如下代码,function a不会提升到与let同级

  1. let a = 1;
  2. function a() {//SyntaxError: Identifier 'a' has already been declared
  3. console.log(10)
  4. }
  5. {
  6. a();
  7. }
  8. console.log(a);

函数声明提升仅限于当前的环境,当前{}

函数的提升

  1. {
  2. function a() {
  3. console.log(10)
  4. }
  5. a();//10
  6. }
  7. console.log(a);
  8. a();//10

image.png

函数的提升与let1

  1. let a=1;
  2. {
  3. function a() {
  4. console.log(10)
  5. }
  6. a();//10
  7. }
  8. console.log(a);//1
  9. a()

image.png
当外面有块级作用域,有let声明,{}里面的function不会声明提升

函数的提升与let2

全局的let影响了function提升到全局,如果全局有let相当于全局有块级作用域

  1. // a();//ReferenceError: Cannot access 'a' before initialization
  2. let a=11;
  3. {
  4. function a() {
  5. console.log(10)
  6. }
  7. a();//10
  8. }
  9. console.log(a);//11
  10. // a();//TypeError: a is not a function

建议用函数表达式的方式声明函数

不主张这么做,应该用
因为函数表达式可以与let结合,并且var会如果重复会报错

  1. let a = 1;
  2. {
  3. var test = function () {
  4. console.log(10)
  5. }
  6. }
  7. console.log(a);//1
  1. let a = 1;
  2. {
  3. /* var a = function () {
  4. //SyntaxError: Identifier 'a' has already been declared
  5. console.log(10)
  6. }*/
  7. let a=function () {
  8. console.log(10);
  9. }
  10. a()//10
  11. }
  12. console.log(a);//1

const

定义常量,定义时必须赋值

定义常量
1.const定义的变量,必须赋值,一旦定义必须赋值,值不能被更改。
常量恒为一个值,不变,如果不赋值,与常量冲突了。

  1. const a;//SyntaxError: Missing initializer in const declaration
  1. const a=12;
  2. console.log(a);//12

常量不能更改

  1. const a=12;
  2. a=10;//TypeError: Assignment to constant variable.
  3. console.log(a);//12

image.png

暂时性死区,与let一样

依然会存在暂时性死区的问题,const a只在块级作用域内有效,并且不会提升
2.有块级作用域,不能提升,有暂时性死区

  1. {
  2. const a = 12;
  3. }
  4. console.log(a);//ReferenceError: a is not defined
  1. {
  2. console.log(a);//ReferenceError: Cannot access 'a' before initialization
  3. const a = 12;
  4. }

不能重复声明

3.与let一样不能重复声明

  1. {
  2. const a = 12;
  3. let a=10;//SyntaxError: Identifier 'a' has already been declared
  4. }

const与引用值

  1. const obj={};
  2. obj.name='zhangsan';
  3. console.log(obj);

image.png

如果是原始值,存在栈内存中,栈内存地址不可变,如果重新赋值,其实改变了地址,因为见js第一节课。 原始值重新赋值改变了地址,与上次赋值的地址有变化。
引用值,指针固定,没有变化,但里面的内容,里面的数据结构没有办法保证。
const只能保障地址不会变化,但地址里面的内容保障不了。

但是我想让引用值也不能被更改,这时就需要对象冻结

可以修改const定义的数组

  1. const obj=[];
  2. obj[2]='zhangsan';
  3. console.log(obj);

image.png

冻结方法

对象原型上的冻结方法

  1. console.log(Object.prototype);

image.png

方法使用

  1. const obj=[];
  2. Object.freeze(obj);
  3. obj[2]='zhangsan';
  4. console.log(obj);

image.png

  1. const obj={};
  2. Object.freeze(obj);
  3. obj[2]='zhangsan';
  4. console.log(obj);

image.png

对象同理
但如果obj对象的属性也是个对象,仅仅冻结obj对象就没有用处了

冻结函数封装

  1. function myFreeze(obj) {
  2. Object.freeze(obj);
  3. for (var key in obj) {
  4. if (typeof (obj[key]) === 'object' && obj[key] !== null) {
  5. Object.freeze(obj[key]);
  6. }
  7. }
  8. }
  9. const person = {
  10. son: {
  11. lisi: '18',
  12. zhangsan: '19'
  13. },
  14. car: ['benze', 'mazda', 'BMW']
  15. }
  16. myFreeze(person);
  17. person.son.wangu=20;
  18. person.son.lisi=30;
  19. person.car[3]='toyaya';
  20. console.log(person);

image.png

注释掉冻结函数的结果
image.png

一般不用这个方法

  1. const http1=require('http');

请求方法,请求都是一个new出来的对象,更改new出来的对象就不会改变之前的构造函数。
例子,在上面代码更改http1变量,http包里面的函数,的内容不会改变。
就从根源上解决了这个问题,可以更改对象,但不要影响引用的包

顶层对象

  1. var a=1;
  2. b=1;
  3. console.log(a);//1
  4. console.log(window.a);//1
  5. console.log(b);//1
  6. console.log(window.b);//1

浏览器可以运行

nodejs中不可以运行,报错
ReferenceError: window is not defined

b、a挂载到window上面的,顶层对象的属性与全局变量是一样的,都是挂载到winodw上面

造成问题:
如果变量不写var,写个b=1,实际上我想写a=1,只不过我因为马虎写错了,但js并不会报错,会把b=1挂到window上面。a,b简单,如果变量名长了很容易写错了。

function、var允许变量声明,声明的变量是全局变量
let、const、class不允许通过声明变量方式,声明一个全局变量的,它们定义的不属于顶层对象属性,
这里面顶层对象是window
let、const、class是es6为了解决上面的问题,上面的现状提出的问题

  1. let a=1;
  2. console.log(window.a);//undefined
  3. console.log(a);//1
  4. var b=1;
  5. console.log(window.b);//1
  6. console.log(b);//1

let声明变量,在浏览器中,不会挂载到window上面

不同的浏览器顶层对象不一样,浏览器是window
node是global

  1. console.log(global);

image.pngimage.png