现在先来做两道练习题

for(var i=0;i<10;i++){var a='a'let b = 'b'
}
console.log(a)
console.log(b)
for(var i=0;i<3;i++){setTimeout(function(){console.log(i)},1000)
}
function(){console.log(a)var a =1;function a(){}console.log(a)
}

理解js作用域

作用域我自己的理解是变量在某个的范围内可访问,那这个范围就是这个变量的作用域。
在ES5中,js只有两种形式的作用域:全局作用域函数作用域

  • 全局作用域:变量在程序中任意地方都可以访问到
  • 函数作用域:变量在函数内部可以访问到,在函数外部无法访问
for(var i=0;i<10;i++){var a='a'let b = 'b'
}
console.log(a) //'a'
console.log(b) //'b' is not defined

上述代码中,变量a为全局变量

function test(){var a = 'a'
}
test()
console.log(a) // 函数外部无法直接访问函数内部变量,报错

上述代码中,变量a为局部变量,控制台打印报错信息a is not defined

function test(){a = 'a'
}
test()
console.log(a) //'a'

函数内部未使用var关键字定义变量,此时a为全局变量

小结

  • ES5中, js的作用域分为全局作用域和函数作用域
  • 函数内部可以访问函数外部的全局变量,函数外部却无法直接访问函数内部的局部变量
  • 未使用var关键字定义的变量是全局变量

现在我们知道函数内部可以访问函数外部的全局变量,函数外部却无法直接访问函数内部的局部变量。但有的时候我们需要读取函数内部的局部变量,该怎么办呢?


闭包

举个例子:

function f1(){var a = 1;function f2(){return a;}return f2;
}
console.log(f1()()) //1

上述代码就创建了一个闭包,我们可以在f1函数外部访问到f1函数内部的值。
我们看到上述代码有两个特点:

  • f1函数嵌套了f2函数
  • f1函数返回了f2函数

闭包的用处:

1.很多js流行框架都是使用匿名自执行函数来避免变量污染
;(function(){//todo
})()
2.缓存:闭包可以让变量的值始终保存在内存中,因此在使用时也要注意不要滥用闭包
function f1(){var n=999;nAdd = function(){n+=1}function f2(){alert(n)}return f2;
}
var result = f1(); //注意只有f1的返回值被外部引用,才不会被回收
result(); // 999
nAdd();
result(); //1000
3.封装
var person = function(){var name = ‘default’return {getName:function(){ return name},setName:function(newName){ name=newName}}
}()

既然ES5中有定义变量的方法,那为什么ES6中又定义了let,const关键字呢?


js中的变量提升

var定义变量存在变量提升:只提升声明语句,不提升赋值语句

var foo = {n:1};
(function(foo){console.log(foo.n);foo.n = 3;var foo = {n:2};console.log(foo.n);
})(foo)
console.log(foo.n); 

执行上述代码,我们可以看到控制台中按顺序依次打印:1,2,3。这是因为Javascript先编译后执行。编译阶段,先声明变量,所以引擎会将上面的代码理解为以下格式

var foo = {n:1};
(function(foo){var foo;console.log(foo.n)foo.n = 3;foo = {n:2};console.log(foo.n)
})(foo);
console.log(foo.n)

说明:

  1. 函数内部定义变量foo时,因为当前作用域中已经存在名为foo的变量,所以编译器忽略当前声明,继续进行编译,因此第一次打印的内容为外部变量foo的属性n值:1
  2. foo.n=3 改变的是外部变量foo,foo={n:2}将foo指向了内部变量,并重新赋值为{n:2},所以第二次打印的内容为内部重新赋值的变量foo的属性n值:2
  3. 第三次打印内容是外部变量foo.n,因为函数内容已经更改了外部变量foo,所以打印结果为:3

js中先提升函数,后提升变量。

思考以下代码:

(function(){console.log(a)var a =1;function a(){}console.log(a)
})()

执行上述代码,我们可以看到控制台中按顺序依次打印:a(){},1。按照刚才的理解,js引擎将上面的代码会理解为下面的格式

(function(){var a;console.log(a)a = 1;function a(){}console.log(a)
})()

那打印的结果应该为 undefined , f(){},这是因为我们忽略了一点,js先提升函数,后提升变量。所以正确的格式为

(function(){function a(){}var a;console.log(a)a = 1;console.log(a)
})()

说明:
1.定义变量a时,因为已经存在命名为a的函数,所以第一次打印结果为a(){}
2.a=1,将变量a重新赋值,所以第二次打印结果为1

小结

  • ES5中,使用var定义变量,变量的作用域有两种:全局作用域、函数作用域
  • var定义变量存在变量提升,此外,先提升函数,后提升变量

但是开发过程中,变量提升往往会对开发造成困扰,幸好ES6中引入了let语法。


let

块级作用域

我们刚才提到,ES5中,js只用两种作用域:全局作用域与函数作用域。在ES6中,let关键字会隐式地创建一个块级作用域(通常是{}内部),变量只能在这个作用域中被访问。例如题目一中

for(var i=0;i<10;i++){var a='a'let b = ‘b'
}
console.log(a)
console.log(b)

我们在循环的内部,使用let创建了变量b,在循环外部访问时报错,b is not defined.就是这个原因。
块级作用域的引入大大改善了代码中由于全局变量而引发的错误,比如文章开头提出的第二题:

for(var i=0;i<3;i++){setTimeout(function(){console.log(i)},1000)
}

上述代码由于变量i是用var声明的,所以全局范围有效 ,当循环体执行完时,i=2,所以定时器中console.log(i)中的i是指向全局变量i的,所以打印结果为2,2,2
如果我们将代码改为

for(let i=0;i<3;i++){setTimeout(function(){console.log(i)},1000)
}

上述代码中,变量i使用let定义,所以只在本轮for循环中有效,所以打印结果为0,1,2。

let不存在变量提升,其所声明的变量一定要在声明语句之后使用。

例如:

console.log(bar);
let bar = 2;

打印结果报错:bar is not defined

此外,let 声明的变量不能重复声明,例如

let foo = {n:1};
(function(foo){console.log(foo.n);foo.n = 3;let foo = {n:2};console.log(foo.n);
})(foo)
console.log(foo.n);

函数内部定义变量foo时,因为当前作用域中已经存在命名为foo的变量,所以报错:’foo’ has already been declared.

const

ES6中新增了let关键字的同时,也新增了const关键字。
let与const有很多共同点

  • 都支持块级作用域
  • 都不支持变量提升
  • 都不支持重复声明

此外,我们知道var声明全局变量时,变量是挂在window上的。而let,const声明变量,却不是。这样子便避免了window对象的变量污染问题。

当然,const与let也有区别。const与let的区别在于:

  • let声明变量时无需赋值,const声明变量时必须赋值
  • let声明变量,变量可重新赋值,const声明变量,完成初始化后,值不得更改 (基本类型)

刚刚提到const声明变量后,如果值类型是基本类型,则不得更改,如果是引用类型呢?

如图所示,可修改。如果我用const定义变量,值为对象,但是想让对象的属性无法修改应该怎么做呢?


对象属性的保护方法

Object.defineProperty

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
更多关于Object.defineProperty方法的信息,推荐阅读 MDN:Object.defineProperty

function setConst(obj) {Object.keys(obj).forEach(function (t,i) {Object.defineProperty(obj,t,{value:obj[t],writable:false, // 是否可重新赋值enumerable:false, //是否可枚举configurable:false // 是否可删除,其他设置属性,是否可修改})})}

如图所示,虽然实现了需求,对象属性无法修改,无法删除。可是控制台无提示,不是很人性化。幸好,ES6中提供了Proxy方法

Proxy对象代理

function setConst(obj) {return new Proxy(obj,{set:function (obj,prop,value) {throw new TypeError('Assignment to constant variable')},deleteProperty(target, key) {throw Error(`Error! ${key} cannot be deleted.`);}})}

转载于:https://www.cnblogs.com/yxqd/p/10365072.html

let const var 比较说明相关推荐

  1. js中const,var,let区别

    1.const定义的变量不可以修改,而且必须初始化. 1 const b = 2;//正确 2 // const b;//错误,必须初始化 3 console.log('函数外const定义b:' + ...

  2. js中const,var,let区别与用法

    原文链接:https://blog.csdn.net/qq_36784628/article/details/80966826 js中三种定义变量的方式const, var, let的区别. 1. c ...

  3. javaScript中const,var,let区别与用法详解

    业务场景:今天想从正则表达式数组对象取出几个参数的值,发现好多人都用的const声明的变量,这里一起总结一下吧. 上一篇-->前端使用正则表达式获取地址栏URL参数的值并将需要的参数值展示在页面 ...

  4. 【js】js中const,var,let区别

    在node.js使用例子中,第一次看到const的声明,查询了一下,可以看得出来: http://www.cnblogs.com/ksl666/p/5944718.html 参考 主要内容是:js中三 ...

  5. let , const , var , 的区别

    1.let : 变量不能重复声明 比如说 : let name = 'hygg'; let name = 'xiaohai'; 这样声明变量是会报错的 但是var 可以重复声明变量 var name  ...

  6. let const var 区别详解

    let和var区别 一.let不存在变量提升 首先先要了解什么叫变量提升?就是变量声明提前(!注意 不是赋值),var具有这个属性,下面有个小例子: console.log(a) var a='a' ...

  7. let const var 总结

    文章目录 写在前面 1.var关键字 1.1 没有块级作用域的概念,有全局作用域.函数作用域的概念 1.2 存在变量提升 1.3 全局作用域用var声明的变量会挂载到window对象上 1.4 同一作 ...

  8. js中let const var的区别

    1.var声明的变量会存在变量提升,而let 和 const的变量不会存在变量提升 也就是var声明的变量会被提升到他所在的作用域顶端去 // var: console.log(a) // 打印为 ' ...

  9. ES6基础(var let const 箭头函数)-学习笔记

    文章目录 ES6基础(var let const 箭头函数)- 学习笔记 定义:var let const 箭头函数 数据结构 set map ES6基础(var let const 箭头函数)- 学 ...

最新文章

  1. echarts geo地图示例_用Python,炫酷地图轻松绘制,一起来学习吧
  2. Pause/Resume Instance 操作详解 - 每天5分钟玩转 OpenStack(34)
  3. 【CyberSecurityLearning 34】Linux脚本编写(Shell脚本)
  4. WebDriver介绍
  5. 该如何高效实用Kotlin?看这一篇就够了!
  6. 简单 局部 整体光照模型计算机图形学,计算机图形学北大光照模型.ppt
  7. opencv java_opencv的Java开发环境配置(IntelliJ idea)
  8. PHP生产一个验证码图片,PHP使用GD库生成验证码图片,实现图片验证
  9. 我的docker随笔28:基于容器的升级方案实验
  10. PCL 显示一只小白兔和Eigen矩阵
  11. 协方差矩阵、相关矩阵的详细说明
  12. Charles使用教程(Mac)
  13. c语言必背代码成绩判断,c语言初学必背代码
  14. yabailv 运放_压摆率——限制了运放的速度
  15. 【K70例程】003读取LM75A温度传感器(I2C)
  16. 笔耕不辍 | Redis入门
  17. 用路由器实现不同vlan之间的通信
  18. Transformer Architectures and Pre-training Strategies for Fast and Accurate Multi-sentence Scoring
  19. VS2010下设置win32/win64,编译出不同平台程序版本
  20. 在vue中使用Google Recaptcha验证

热门文章

  1. android alpha不起作用,API 28(P)的Android设计支持库不起作用
  2. 【转载】自然语言推理介绍
  3. 手机 html5评测,三款主流手机浏览器HTML5性能横向评测
  4. python 类的封装、继承、重写方法
  5. python -opencv 使用滑动条 cv2.createTrackbar,cv2.getTrackbarPos(), cv2.setTrackbarPos
  6. Github代码版本控制可视化教程—Git Gui的使用
  7. 三十七、页面置换算法
  8. Debian,Ubuntu下安装zsh和oh-my-zsh
  9. 886n虚拟服务器,教程:普联TL-WR886N V2-V3如何设置虚拟服务器
  10. c语言int t格式,如何在C中打印int64_t类型