const关键字:终于拥有真正的常量声明语句
本文首发于个人网站:const关键字:终于拥有真正的常量声明语句
你好,今天大叔想和你唠扯唠扯 ES6 新增的关键字 —— const。在说 const 关键字之前,大叔先和你唠唠大叔自己对 const 的感受 —— JavaScript 尼玛终于可以声明真正的常量啦!大叔为啥会发出这样滴感叹?实在是“天下苦秦久矣”呀~
话说 long long ago,当初大叔从 Java 技术栈转向前端技术栈,首先要搞定的就是 JavaScript。虽然都说 JavaScript 和 Java 语言有很多的地方是相似滴,但你知道直到大叔发现这货不能声明常量时候的感受吗?!那真是欲哭无泪啊~ 一个堂堂滴编程语言,居然尼玛不能声明常量?!也好意思说自己是个编程语言?!
声明常量
好吧~ 大叔不吐槽了,还是说正事儿吧。其实,在 ES5 里面也不是不能声明常量,就是有那么一捏捏的别扭。你知道 Object 有个叫做 defineProperty() 的方法吧?它是用来给某个对象定义属性的。在 ES5 里面就可以通过这个方法间接地来声明常量。
比方说,你现在想在全局作用域里面声明一个常量,其实就可以看作是给顶级对象添加一个属性嘛。带入个场景比较好整明白,假设是一个 HTML 页面里面声明一个全局常量,就可以像下面这样整:
Object.defineProperty(window, 'a', {
  value: 'a',     // 设置该属性的值
  writable: false // 设置该属性的值不能写
})
这段代码的意思就是给 window 对象增加一个名叫 a 的属性。writeable 的作用就是这个属性能不能写,值为 false 就是只能读不能写的意思。所以,这时的属性 a 就只能访问不能修改。不信咱可以试试:
console.log(a)
这时打印的结果比较简单,就是 a。接下来咱们再修改一下试试看是啥结果:
a = 'b'
console.log(a)
打印的结果还是 a,并没有把 a 的值改成 b。
其实,这样的用法已经很接近常量的用法了。但是,可但是,但可是~ 你不觉得别扭吗?!为啥?大叔给你屡屡:
- 尼玛我现在要声明的是一个常量,你现在用的是定义属性的语法。
- 这个示例还好,因为是声明全局常量,可以加到 window这种顶级对象上。要是在一个函数作用域里面咋办,我找不准上下文对象咋办?!
- 即使就用这个办法能解决这个问题,也确实不能改值。但是,啥提示没有,你不难受吗?!
- 常量就是常量,属性就是属性。至少从概念上就别尼玛混淆在一起,你说是不是?!
所以,在 ES6 的新特性里面,大叔终于看到希望 —— const。这货才是真正用来定义常量滴!说到常量,大叔得先给你科普一下到底怎么样才算是常量。
所谓常量,简单来说就是不能变化的值。其实,不仅不能值变化,还不能重复赋值,也不能重复声明,这才算是对滴。
那接下来,大叔就给你看看 const 这货能不能做到这一点:
const a = 'a'
console.log(a)
a = 'b'
console.log(a)
这段代码不难看出就是使用 const 关键字声明了一个常量,两处打印的结果分别为:
- 第一处打印的结果是 a
- 第二处打印的结果是报错,报错的内容是:TypeError: Assignment to constant variable.,大概的意思就是你把常量当变量赋值了。
看到了吧?!这才是常量。不仅不能改变值,而且会进行报错,告诉你改变值这种行为是不对滴。
声明常量的注意事项
当然了,ES6 新增了这样的语法,附带的也有一些需要你注意的事儿 —— 就是在声明一个常量的时候,是必须把常量进行初始化的。不能像声明变量一样,声明和初始化可以分成两个步骤完成。比方说像下面这样婶儿做:
const a
a = 'a'
这段代码运行之后的结果就是给你报个大错,报错的内容是:SyntaxError: Missing initializer in const declaration,大概的意思就是说你在声明常量的时候没给人家初始化。
你看看,整得多明白!钉儿是钉儿,卯儿是卯儿的。
块级作用域
再有就是,const 不仅提供一个真正可以声明变量的方式,还提供了块级作用域。啥?你还不知道呢?!那你就去看看大叔写的《let关键字:ES6新增的var关键字加强版》这篇文章里面关于块级作用域的内容吧。
在这儿呢,大叔想再次重申一下块级作用域的好处 —— 就是会把之前暴露在全局作用域的一些变量全部限制在一个具体的块级作用域里面。比方说像下面这样婶儿的代码:
if (true) {
  const a = 'a'
}
console.log(a)
这段代码运行后打印的结果是报错,报错的内容是:ReferenceError: f is not defined。也就是说,你在一个块级作用域里面声明一个常量,在这个块级作用域之外是没办法访问到这个常量的。
暂时性死区(TDZ)
既然唠到 const 声明的常量是具有块级作用域的,那就不能不说一下暂时性死区的问题了。啥意思呢?就是说使用 const 声明的常量也同样存在暂时性死区的,不信你来看段代码:
if (true) {
  console.log(a)
  const a = 'a'
}
这段代码运行后的结果是报错,报错的内容还是暂时性死区的错误:ReferenceError: Cannot access 'a' before initialization。
存在了暂时性死区了,就说明 const 声明常量的时候也就不存在啥声明提前的事儿了。这两件事儿吧,其实说的是一个意思,你可得记明白了。
声明常量对象或者数组
唠到这儿吧,其实基本上关于 const 都唠完了。这货除了可以真正声明一个常量之外,其实没啥可唠的啦。但是,但可是,可但是~ 嘿嘿~
大叔想问你的问题:如果咱们用 const 声明一个对象或者数组,那这个对象的属性或者数组里面的元素能不能修改呢?
寻思是寻思不明白的,咱们直接整代码吧,用事实来看看到是个啥情况。比方说咱们先声明一个这样婶儿的对象:
const obj = {
  name: "不想成熟的大叔",
  age: 37
}
大叔今年都 37 啦,但是年轻的心还是有滴。所以,大叔想把 age 这个属性的值改成 18,就像这样婶儿的:
obj.age = 18
console.log(obj)
结果~ 咱们运行代码之后得到的结果就是这样婶儿的:
{
	name: "不想成熟的大叔",
  age: 18
}
注意啊~ 注意啊~ age 属性的值被成功滴改成了 18!不对吧?const 声明的不是常量吗?常量不是不能改变值吗?这尼玛结果也不对啊?
灵魂三连问也没用,事实摆在眼吧前儿,咱就得认!但是,为啥会这样涅?别急,且听大叔给你慢慢道来~
想整明白这个事儿吧,咱们就得先唠扯唠扯 JavaScript 的存储结构了。JavaScript 的存储结构有俩儿,一个叫做“堆内存”,一个叫做“栈内存”。一般情况下吧,咱们定义的变量或者常量都是存储在堆内存里面的。但是吧,对象和数组算是 JavaScript 里面比较复杂的一种数据,所以实际上对象或者数组的存储形式是这样婶儿的:
知道了这个事儿,你大概就能整明白为啥上面的代码运行的结果是这样婶儿的了吧?!说白了,const 声明的对象的值确实不能改变,但是对象的值存储的是一个引用地址,而具体的属性其实是存储在这个引用地址里面,const 是没办法限制的。
整到这儿,你是不是想问那这样婶儿的问题要怎么解决?嗯~ 也能解决的。你还记得 Object 提供了一个方法叫做 freeze() 吗?这个方法是用来冻结某个对象的。冻结之后不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。所以,就可以利用这个方法来解决上面提到的问题:
const obj = {
  name: "不想成熟的大叔",
  age: 37
}
Object.freeze(obj)
obj.age = 18
console.log(obj)
这样处理之后,咱们再来看看打印后的结果吧:
{
	name: "不想成熟的大叔",
  age: 37
}
问题被完美滴解决了!等一下,真的是这样婶儿的吗?大叔想继续再操作一下下的,比方说像这样婶儿的:
const obj = {
  name: "不想成熟的大叔",
  age: 37,
  skill: {
    name: "coding",
    year: 15
  }
}
啥意思?就是说咱们在声明一个对象的时候,这个对象的属性也同样是一个对象或者数组,那现在的问题就是你利用 Object.freeze() 方法还能成功地冻结不?咱们就来试试吧:
Object.freeze(obj)
obj.skill.year = 20
console.log(obj)
咱们得到的结果实际上是这样婶儿的:
{
	name: "不想成熟的大叔",
  age: 37,
  skill: {
  	name: "coding",
    year: 20
  }
}
发现还是被修改了~ 这又是咋回事儿呢?这就说明 Object.freeze() 方法只能冻结当前对象的属性,但是如果某个属性的值还是一个对象或者数组的话,那就说明这个属性存储的还是一个引用地址,实际的数据是存储在这个引用地址中的。
想要解决这个问题其实也不算难,就是继续利用 Object.freeze() 方法来冻结这个值为对象或者数组的属性就行了。就像这样婶儿操作:
Object.freeze(obj.skill)
obj.skill.year = 20
console.log(obj)
这样咱们得到的结果就是:
{
	name: "不想成熟的大叔",
  age: 37,
  skill: {
  	name: "coding",
    year: 15
  }
}
也就是说,关于这个问题咱们想一劳永逸地解决掉,可以定义一个函数,把对象作为参数传递进去。然后,这个函数主要利用递归方式把对象中所有值为对象或者数组的属性分别进行冻结,穷尽为止就可以啦!
写在最后的话
好了,整到这儿,ES6 新增的 const 关键字所有大叔想和你唠扯的内容都唠扯完了,也希望能对你有所帮助。最后再说一句:我是不想成熟的大叔,为前端学习不再枯燥、困难和迷茫而努力。你觉得这样学习前端技术有趣吗?有什么感受、想法,和好的建议可以在下面给大叔留言哦~
const关键字:终于拥有真正的常量声明语句的更多相关文章
- C++ ——  类中static和const关键字声明变量的初始化方式总结
		在类中声明变量/常量时,经常会用到static.const关键字.对于该变/常量的初始化问题,网上有许多相关文章,但是大多不够完善,或者存在错误.经过实际验证,总结如下: (注明:测试编译平台为VS2 ... 
- C语言学习及应用笔记之三:C语言const关键字及其使用
		在C语言程序中,const关键字也是经常会用到的一个关键字,那么使用const关键字的目的是什么呢?事实上,在程序中使用const关键字的主要目的就是为了向使用者传递设计者的一些意图. 事实上,无论我 ... 
- JS006. 详解自执行函数原理与数据类型的快速转换 (声明语句、表达式、运算符剖析)
		今天的主角: Operator Description 一元正值符 " + "(MDN) 一元运算符, 如果操作数在之前不是number,试图将其转换为number. 圆括号运算符 ... 
- C/C++中const关键字的用法及其与宏常量的比较
		1.const关键字的性质 简单来说:const关键字修饰的变量具有常属性. 即它所修饰的变量不能被修改. 2.修饰局部变量 ; ; 这两种写法是等价的,都是表示变量的值不能被改变,需要注意的是,用c ... 
- PHP的final关键字、static关键字、const关键字
		在PHP5中新增加了final关键字,它可以加载类或类中方法前.但不能使用final标识成员属性,虽然final有常量的意思,但在php中定义常量是使用define()函数来完成的. final关键字 ... 
- JS let和const关键字
		ES2015 引入了两个重要的 JavaScript 新关键词:let 和 const. Let关键字 1.用于作用域:块作用域,循环作用域,函数作用域,全局作用域, 在 ES2015 之前,Java ... 
- const(每个对象中的常量), static const(类的编译时常量)
		1 每个对象中的常量 --- const数据成员 const限定,意味着“在该对象生命周期内,它是一个常量”. 关键字const 使被限定的量为常量 在该类的每个对象中,编译器都为其const数据成员 ... 
- Const关键字
		const const是一个C语言的关键字,它限定一个变量不允许被改变.使用const在一定程度上可以提高程序的安全性和可靠性.另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程 ... 
- C++中const关键字的使用总结
		C++中使用const关键字来修饰常量,下面从两个方面总结:变量和成员函数. 变量:const可以修饰普通变量.指针(数组)和结构体. 1.const修饰普通变量是最简单的情形.这样的用法多为在程序中 ... 
随机推荐
- vue第二十单元(vux的配置中模块modules的用法)
			第二十单元(vux的配置中模块modules的用法) #课程目标 1.什么是module? 2.怎么用module? 3.样板代码目录结构 #知识点 #1.modules 在Vue中State使用是单 ... 
- 1-解决java Scanner出现 java.util.NoSuchElementException
			起因:在函数中新建scanner对象,然后多次调用此方法出现上述异常 原因:Scanner(system.in)在Scanner中接受的是键盘 输入,当调用close()方法时 Scanner的关闭会 ... 
- C#中更改DataTable列名的三种方法
			解决办法 直接修改列名 dt.Columns["Name"].ColumnName = "ShortName"; sql查询时设置别名 select ID as ... 
- (三)文件的链接(ln)
			一.链接的分类及特点 当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link ... 
- vscode常用快捷键和插件
			常用快捷键 快捷键 用途 ctrl+· 打开终端 shift+alt+A 块注释 ctrl+/ 行注释 ctrl+shift+F 文件夹查找 ctrl+enter 下方插入一行 ctrl+shift+ ... 
- iOS崩溃治理--开篇
			去年我开始负责iOS崩溃治理的工作,从原来的万分之五崩溃率,一直到现在的万分之一左右的崩溃率,期间踩了很多坑,因此想和大家分享一下,希望能对大家有所帮助,也欢迎大家私信交流. 如果你打算开始治理崩溃的 ... 
- JavaWeb基础总结:Filter专题
			Java Servlet Filter Filter 被称为过滤器,其主要作用是对 Servlet 容器调用 Servlet 的过程进行拦截,从而在 Servlet 进行响应处理的前后实现一些特殊功能 ... 
- 关闭layer
			function closeBox() { var index = parent.layer.getFrameIndex(window.name); //获取当前窗体索引 parent.layer.c ... 
- 推荐系统中的nlp知识
			都是转自其他博客,好好学习! 概述: https://blog.csdn.net/starzhou/article/details/73930117 tf-idf https://blog.csdn. ... 
- JAVA_JNI字段描述符“([Ljava/lang/String;)V”(Android)
			JNI字段描述符"([Ljava/lang/String;)V "([Ljava/lang/String;)V" 它是一种对函数返回值和参数的编码.这种编码叫做JNI字段 ... 
