[编程开发] 为什么javascript的语法那么烂?

[复制链接]
yufan163 发表于 2023-10-4 06:55:27|来自:北京 | 显示全部楼层 |阅读模式
接触过c.java.c#.都没有感觉很离谱.js的语法真心无语.求解释k
全部回复5 显示全部楼层
wowo0888 发表于 2023-10-4 06:56:15|来自:北京 | 显示全部楼层
初用觉得辣鸡,用了2年后还是觉得辣鸡……转眼用3年了,也还好,基本适应前端了,各种奇异报错基本能很快找到位置,js有些东西不设计也很正常,因为本来就走的简洁语言路线,js貌似的确没java那么庞大的知识量,最近也在学数据结构,js的教程特别少,js只提供了数组和对象,但貌似什么数据结构都能实现。
希望这篇文对您有帮助,写得不好的地方还请海涵。同时非常感谢参考文献中的大佬们的文章分享,深知自己很菜,得努力前行。也希望自己能深入下去,做更多实际工程,写更好的文章,共勉!

学习完对动画与算法(JavaScript实战详解-动画与算法(附学习目标+笔记))有一个基本的了解,在本章中将学习JavaScript语言的核心部分ECMAScript6.0中的最新语法和高级用法,是未来JavaScript发展的趋势和方向,有必要提前进行学习和掌握。
ECMAScript6.0入门

学习目标
(1)能够掌握ECMAScript6.0简介
(2)能够掌握新增语法
(3)能够掌握解构赋值
(4)能够掌握rest参数
(5)能够掌握箭头函数
(6)能够掌握Symbol类型
Ø ECMAScript6.0简介
1. ECMAScript与JavaScript关系
ECMAScript 是JavaScript 的的规格,JavaScript是ECMAScript的一种实现,通常情况下两个词可以互换。
ES6是ECMAScript 6.0的简称,JavaScript语言的下一代标准。其目标是让JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
JavaScript语言能够迅速发展的重要的原因之一是其既可以作为客户端语言,又可以作为服务端语言。目前很多流行的框架已经采用ECMAScript6.0语法进行编程,例如Angular2、React、KOA等框架。
2. ECMAScript6.0与ES2015的关系
有时也可以用ES2015来表示ECMAScript6.0。两种不同的表示方式,一种是通过版本来表示,另一种是通过年份来表示。ECMAScript6.0是一个泛指,包含ECMAScript2015、 ECMAScript2016、 ECMAScript2017等。
3. ECMAScript6.0的环境搭建
虽然 ECMAScript6.0的大部分功能已经被现代的浏览器所支持,但小部分功能还没有得到支持。可以利用一些工具把 ECMAScript6.0转化成 ECMAScript5.0的语法,让浏览器支持此功能,可以达到测试的目的。可以利用gulp.js加babel.js的方式。
gulp.js为一个前端自动化工具,主要实现对代码的打包、压缩、转换等操作。gulp.js工具基于nodeJS环境,因此需要先安装nodeJS(服务器端JS)和npm(包管理工具)。
可以到nodeJS官网进行下载并安装。安装成功后,打开命令窗口,并运行node -v和npm -v。如果能打印出版本号,即说明nodeJS和npm已经安装成功,如图所示。



安装node和npm后,即可以安装gulp.js工具。首先需要全局安装gulp,即可以在全局的环境下调用gulp的一些命令。安装命令如下:



接下来进入项目目录下,进行gulp工具的局部安装。安装命令如下:



当以上两条命名执行完毕后,如果可以通过gulp -v打印出版本信息的话,就说明安装已成功。如图所示。



gulp的使用需要配置gulpfile.js文件,将需要执行的任务输入到文件中,其中任务包括以下四种:
(1) task() 设置任务
(2) src() 查找原始文件
(3) pipe() 连接后续操作
(4) dest() 生成任务后的文件
然后通过npm包管理器去下载gulp-babel模块,用于ECMAScript6.0a转ECMAScript5.0的操作。命令如下:



下面配置一下gulpfile.js文件,当执行“gulp 任务名”时,就可以得到转化后的代码。gulpfile.js文件如下:



通过gulp babalTask命名,可以把es6.js文件进行转换,并把转换结果生成到dist文件夹下,如图所示。



Ø 新增语法
ECMAScript6.0对之前的JavaScript语法进行了一些功能补充与扩展,具体分为变量、字符串、数值和数组。
1. 变量
ECMAScript6.0提供两个新的变量语法,即let和const。let的使用与var相似,但let定义的变量具备块级作用域,下面列举与var定义变量的不同,具体示例代码如下:



let定义的变量具备块级作用域,只能在相应的块内起作用,利用let定义变量具备块级作用域特点可以实现作用域的保存操作,具体示例代码如下:



当单击任意<li>标签时,会弹出对应的索引值,即0、1、2…。
const用于定义常量,常量为不可修改的量,如果对其值进行修改会报错,一般用大写的方式来定义常量。具体示例代码如下:



2. 字符串
ECMAScript6.0中提供了字符串模板的写法,对字符串拼接进行简化。字符串模板写法需要使用反引号引入字符串,并且通过${}方式来添加变量。具体示例代码如下:



除了字符串模板以外,还提供一些新的字符串方法。具体如下:
(1) includes():判断是否包含相关的字符串,返回布尔值。具体示例代码如下:



(2) startsWith()和endsWith():判断起始和结束位置,返回布尔值。具体示代码如下:
1 <script>



(3) repeat():生成重复的字符串。具体示例代码如下:



3. 数值
ECMAScript6.0中提供一些新的数值操作方法。具体如下:
(1) Number.isFinite():判断是否是有限的数字,返回布尔值。具体示例代码如下:



示例中的Infinity表示无穷大,通过Number.isFinite()方法会返回false。在ECMAScript5.0中提供了isFinite()方法,它与Number.isFinite()方法区别在于inFinite()可以转换类型,具体示例代码如下:



(2) Number.isNaN():判断是否是数字类型,返回布尔值。具体示例代码如下:



Number.isNaN()方法与isNaN()方法相比,不会自动进行类型转换,而isNaN()方法会自动转换类型,因此Number.isNaN()方法更加严谨。
(3) Number.isInteger():判断是否是整数数值,返回布尔值。具体示例代码如下:



(4) Number.EPSILON:返回一个非常小的常量,用于解决JavaScript小数精度问题。具体示例代码如下:



在JavaScript中小数计算会存在精度问题,如0.1与0.2之和不等于0.3,对于需要数值判断的情况下,可能就会出现问题。利用Number.EPSILON常量可以解决小数精度的问题。具体示例代码如下:



4. 数组
ECMAScript6.0中提供了一些新的数组操作方法。
(1) Array.from():能将伪数组或可迭代对象转换成数组对象。具体示例代码如下:



通过Array.from()方法将arguments转换成真正的数组,可以调用数组相关的方法,如map()方法等。
(2) copyWithin():在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。具体示例代码如下:



其中,第一个参数为要覆盖的起始位置;第二个参数为截取的起始位置;第三个参数为截取的结束位置(不包括结束位置元素)。例中把截取到的数值4覆盖掉原来2位置上的数值3,得到数组[1, 2, 4, 4, 5]。
(3) find()和findIndex():查找数组中第一个满足条件的值和查找数组中第一个满足条件的索引值。具体示例代码如下:



(4) fill():使用给定值,填充一个数组。具体示例代码如下:



第一个参数为要填充的元素,第二个参数为填充的起始位置。
(5) includes ():表示某个数组是否包含给定的值,返回一个布尔值。具体示例代码如下:



Ø 解构赋值
解构赋值是ECMAScript6.0中提供的一种定义变量的模式,从数组和对象中提取相应的值进行操作。下面列出 ECMAScript5.0中定义变量的方法,具体示例代码如下:



在 ECMAScript6.0中可以通过解构赋值的方式来定义变量,具体示例代码如下:



解构赋值的方式为数组的方式,下面看下结构赋值的对象方式,具体示例代码如下:



结构赋值也可以嵌套使用,例如数组方式跟对象方法混合设置,具体示例代码如下:



结构赋值可以针对字符串类型,但是不支持数字类型和布尔值类型。具体示例代码如下:



只有具备iterator接口的类型才可以进行结构赋值,数字类型和布尔值类型不具备iterator接口。
Ø rest参数
rest参数也称不定参数或剩余参数,形式为“...变量名”,用于获取函数或解构中的多余参数。具体示例代码如下:



示例中,变量c会打印出[3,4,5],即后续的剩余参数,rest参数一定是出现在定义变量的最后,而不能出现在其他位置。
下面列出rest参数在函数中的使用,具体示例代码如下:



注意函数的length属性返回函数形参的个数,但不包括rest参数。
Ø 箭头函数
箭头函数用于对函数进行简化操作,主要涉及到的语法为箭头(=>)。下面列举了对比 ECMAScript5.0的写法,便于理解箭头函数的写法。



下面示例演示匿名函数或回调函数如何改写为箭头函数。具体示例代码如下:



箭头函数与普通函数除在写法上有区别外,功能上也有一些需要注意的方面,下面分别进行讲解。
1. this指向问题
箭头函数中的this不会受到任何影响,而且它还会指向外层的环境。具体示例代码如下:



可以发现,当单击页面时,会打印出this为document,此时,this并没有收到定时器的影响,因为定时器采用箭头函数的设置,不会对this造成影响。
2. 不能用在构造函数中
箭头函数作为构造函数使用时会提示报错。
3. 不能使用arguments
箭头函数中不存在arguments对象,调用时会提示报错。
4. 不能在Generator函数中使用
箭头函数中不能在Generator函数中使用,这里不作过多陈述,只需了解不可以使用箭头函数即可。
Ø Symbol类型
Symbol类型是 ECMAScript6.0中提供的一种新的数据类型,表示独一无二的值。这种类型主要用于解决对象属性冲突问题。



当函数内部对obj对象的myName属性进行设置时,会影响到其外面的myName属性。如果两个myName属性是由两个不同的开发人员设置的,此时就会产生冲突。
下面演示Symbol类型如何解决冲突的问题,具体示例代码如下:



通过Symbol对象定义的属性,只能在函数内部使用,而不能在外面被调用,可以防止对象属性的一个冲突。
ECMAScript6.0进阶

<a href="http://www.zhihu.com/zvideo/1557678515776106496" data-draft-node="block" data-draft-type="link-card">学习目标
(1)能够掌握新增面向对象
(2)能够掌握promise规范
(3)能够掌握for…of循环
(4)能够掌握set和map数据结构
(5)能够掌握遍历器与生成器
Ø 新增面向对象
在 ECMAScript5.0中通过构造函数来实现,与传统的面向对象语言(例如 C++ 、Java)具有很大差异,容易让初学者感到困惑。
ECMAScript6.0提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字定义类。可以把ECMAScript6.0中的类等价于 ECMAScript5.0中的构造函数,接下来用示例演示Class方式如何来创建对象。



constructor()方法为固定语法格式,表示构造器,一般用于设置对象中的属性。sum()方法为自定义方法,表示添加到原型下的方法。创建对象的方式与ECMAScript 5.0当中的实现一样。
在ECMAScript 6.0中专门提供了一个extends语法,用于对象的继承操作。在继承时,子类的this需要指向父类中的this。需要通过super()方法来获取父类的this,才可以继承中使用。具体示例代码如下:



注意,super()方法一定要写到constructor()构造器的最前面,否则会报错。
Ø promise规范
promise规范是异步编程的一种解决方案,比传统的解决方案回调函数更合理、更强大。它由社区最早提出和实现, ECMAScript6.0将其写进语言标准,提供promise对象。
首先了解传统的回调方式是如何解决异步问题,例如,实现一个延迟定时器,而且每隔一段时间执行一个任务。



示例中,过1s后执行任务1,再过2s后执行任务2,再过3s后执行任务3。通过层层回调的方式来实现这种异步的执行,通常把这种层层嵌套的函数称为回调金字塔。但这种方式不利于阅读、后期维护也不方便。promise规范就是用来将层层嵌套的函数改为按顺序抒写的模式。
接下来介绍promise对象的基本使用,在promise对象中有三种状态,具体如下:
(1)pending 等待状态;
(2)resolved 解决状态;
(3)rejected 未解决状态。
只有异步操作的结果可以决定当前处于哪一种状态,任何其他操作都无法改变当前状态。一旦状态确定,就不会再变,无论何时都可以得到这个结果。
promise对象的状态改变,只能是从pending状态到resolved状态和从pending状态到rejected状态。只要这两种情况发生,状态就会凝固,无法改变。当状态变成resolved时,会触发对应成功的回调函数。当状态变成rejected时,会触发对应失败的回调函数。
promise对象可以将异步操作以同步操作的流程表达出来,避免层层嵌套的回调函数。此外,promise对象提供统一的接口,使得控制异步操作更容易。具体示例代码如下:



创建promise对象通过new Promise()来实现,在回调函数中接收两个参数resolve和reject,表示解决状态和未解决状态。then()方法可以设置状态对应的触发回调操作,第一个参数为成功时触发的回调,第二个参数为失败时触发的回调。通过promise对象和then()方法,就可以实现顺序的写法,从而避免回调金字塔的产生。
Ø for…of循环
for...of循环是 ECMAScript6.0中提供的新的遍历数据的方法。它比 ECMAScript5.0中提供的for、for..in、forEach()等遍历方法都要简单。而且避免了for...in、forEach()的所有缺陷。
学习for…of循环前,先来介绍for..in和forEach()存在的缺陷,具体如下:
(1) 当用for...in去遍历数组时,key值为索引值,但得到的索引值类型为字符串类型,而不是数字类型。接下来通过案例来演示,具体案例详情参考14.2.3。
(2) for...in遍历是无序输出的,结果可能会有顺序问题。接下来通过案例来演示,具体案例详情参考14.2.3。
(3) for..in可以遍历到可扩展的属性或方法,但有时并不需要获取到这些可扩展的元素。接下来通过案例来演示,具体案例详情参考14.2.3。
(4) forEach()遍历不能使用break和continue进行跳出操作,否则会报错,也不能使用return跳出操作。for...of遍历可以避免这些问题,接下来通过案例来演示,具体案例详情参考。



Ø set和map数据结构
1. set数据结构
set数据结构通过new Set()方式来创建,set方式没有length属性,用size属性表示数据集合的长度。具体案例详情参考。



set数据结构可通过for...of遍历到每一项,接下来通过案例来演示,具体如例所示。



set方式提供了一些相关的方法,有助于对set数据结构进行操作。下面分别介绍这些方法。
(1)add()
add()方法表示往set数据中添加元素。具体示例代码如下:



(2)delete()
delete()方法表示从set数据中删除元素。具体示例代码如下:



(3) has()
has()方法表示判断set数据中是否存在该元素。具体示例代码如下:



(4)clear()
clear()方法表示清除set数据中的所有元素。具体示例代码如下:



set数据类型与数组类型最大的区别在于,可以去掉重复的元素。
利用set数据的去掉重复数据的特点可以实现数组去重的功能,具体示例代码如下:



2. map数据结构
map数据结构与set数据结构相似,同样具备size属性,与set数据不同的是map数据结构需要设置一组键值对。具体示例代码如下:



map方式提供了一些有助于对map数据结构进行操作的方法。下面分别介绍这些方法。
set和map数据结构
(1)set()和get()
set()方法和get()方法表示向map数据中添加元素和获取元素。具体示例代码如下:



(2)delete()
delete()方法表示从map数据中删除元素。具体示例代码如下:



(3)has()
has()方法表示判断map数据中是否存在该元素。具体示例代码如下:



(4)clear()
clear()方法表示清除map数据中的所有元素。具体示例代码如下:



map数据的主要作用是可以把DOM节点作为对象的属性。在ECMAScript 5.0中如果把DOM节点作为对象的属性,则map数据会被转换成字符串类型。而ECMAScript 6.0中则不会转换。具体示例代码如下:



Ø 遍历器与生成器
1. 遍历器
在 ECMAScript6.0语法中提供了一个内置的接口,即Iterator遍历器,它能为各种不同的数据结构提供统一的访问机制。当在数据结构中设置Iterator接口后,就可以使用for...of语法对数据进行遍历操作。
Iterator遍历器的原理是创建一个指针对象,遍历时通过调用指针对象的next()方法,移动到下一个数据。Iterator遍历器指针,如图所示。



在 ECMAScript6.0语法中,通过Symbol.iterator可以访问到指针对象。
通过Symbol.iterator访问指针对象。



使用遍历器对性能有一定的提升,因为针对大量数据操作时,只需把指针对象的位置移动即可。所以尽量在ECMAScript 6.0中采用for…of循环。
2. 生成器
在ES6中提供了一个称为Generator生成器的语法,它主要是提供了一种异步编程的解决方案,它比promise规范更加的灵活,更适合异步的编程。
Generator函数与普通函数写法上有些不同,需要在function语法后添加一个*号,而且在Generator函数内部提供了一个yield语法。



下面介绍yield语句的返回值,需要在next()方法中传递参数,传递的参数会作为上一次yield语法的返回结果。具体示例代码如下:



下面示例演示如何利用Generator函数来解决异步的问题,首先演示问题代码,具体如下:



由于定时器属于异步操作,因此result调用时,异步操作未结束,这时打印result结果会返回undefined。通过Generator函数,可以让异步变得简单,像写同步操作的方式来抒写异步操作。具体示例代码如下:



1s后会在控制台上打印出25,这种方式更符合编程的习惯。
JavaScript实战详解-ECMAScript6探索小结
了解ECMAScript 6.0的一些相关概念和基本语法,并能够运用ECMAScript 6.0中的一些高级用法。包括rest参数、箭头函数、class面向对象、Smybol类型、promise规范、set和map类型、遍历器、生成器等。
JCRen.net 发表于 2023-10-4 06:56:58|来自:北京 | 显示全部楼层
早期JavaScript很粗糙,据说只用了10天就完成了初始版本,当时的应用也主要是网页表单验证,语言的一些细节考虑得确实不够严谨,比如留存至今的 typeof null 为 object 的bug;当时可能没人想到这个语言之后会在今后应用地如此广泛,否则肯定会在设计之初多斟酌几番。
大致观摩JavaScript的语法,可能会发现它似乎混杂了很多不同类型编程语言的精华和糟粕。基本语法和C很像,但又引入了Java的部分数据类型,还能看到一些函数式编程语言的影响,另外还有今天看起来比较古怪的基于“原型链”的面向对象……JavaScript大概是当前使用率最高的若干编程语言中语法最杂乱的那个。JS后续的诸多版本又引入了各种新语法,但同时又顽强地保留了很高的向后兼容性,最终让JS变成了当代编程语言的“活化石”。
以上内容偏向于批评JavaScript的设计粗糙、不严谨,但我还是很敬佩ECMA委员会年复一年对JS进行优化、改进的。自ES6以来,JS的语法已经完善了很多,个人认为JS整体上是当前最好用几个脚本语言之一(在我心中和Python地位不分上下)。另外,由于JS先天不足,JS一直以来都没有形成类似Python那样的严格“哲学”理念,开发始终以实用性为导向,很少会为了语言的“艺术性”扯皮——这使得JS可能是当前最积极响应开发者呼求的编程语言,更新改进的效率非常高,我认为这是JS最大的优点。
sky94132003 发表于 2023-10-4 06:57:58|来自:北京 | 显示全部楼层
别问,问就是最烂的语言,没有之一!
wolfwithknife 发表于 2023-10-4 06:58:40|来自:北京 | 显示全部楼层
我也....  学过C/C++/Java/Python,感觉语法规则都很顺畅,容易理解,互相切换也没有问题。 直到最近学习JS,简直离谱,感觉太不严谨了!感觉很难适应
dudelee 发表于 2023-10-4 06:59:22|来自:北京 | 显示全部楼层
刚开始看也想吐槽这个,贴一些链接。其中第一个链接解释了原因:JS是开发者被公司逼迫只用了10天就设计出来的语言。
1. Javascript诞生记 - 阮一峰的网络日志
2. 详解JavaScript语法对{}处理的坑爹之处
3. JavaScript语句后应该加分号么? - hax的技术部落格 - ITeye技术网站
4. javascript - 为什么0 [0]在语法上有效? - 代码日志
之后也许会陆续补充

快速回帖

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则