JavaScript概述
撰写时间:2024-02-11
修订时间:2024-04-10
概述
从TIOBE的排行榜来看,从2004年开始,JavaScript一直排在所有编程语言的前10名。2004至2014年排在第9名,并于2014年被评为当年之最受欢迎的语言(language of the year)。2019年上升至第8名,2024年上升至第6名。
目前,排在前5名的编程语言分别为:Python, C, C++, Java, 以及C#。
随着更多Web标准的出现,JavaScript的重要性将愈发突显。
C语言高效而强大,但它一直缺乏一个简单而易用的图形库。而有Canvas支持的JavaScript则无此忧。随着ArrayBuffer的加入,不仅WebGL从此可在网页上实现,更被WebAssembly充分利用而实现了桌面应用程序到Web应用的完美移植。而2021年开始的WebGPU技术已让我们感到了不可思议。Web AI?它已经悄悄到来了。
世界变化速度开始比过山车还要更猛了。而所有的这一切,都有JavaScript在背后默默地强劲支撑。更不用说,JavaScript是Web网络的原生土著语言。
过去,JavaScript被认为只不过是一种脚本语言。但现在,越来越多的人认为,JavaScript是一种具备操控网页元素的脚本能力的编程语言。
过去,JavaScript被认为在学习时还在睡觉;而现在,JavaScript被认为在睡觉时仍在学习。
经过较长时间的发展、演化及规范化,JavaScript有许多实用的、独具一格的、令人着迷的编程语言特性。下面开始有趣的JavaScript特性之旅。
数据类型
变量没有固定的数据类型
有固定的数据类型,但声明变量时,变量没有固定的数据类型。
数据类型自动转换
当数值类型与字符串类型相加时,得到一个字符串的结果。
字符串的构建
可使用Unicode来创建字符串:
可通过本站制作的HTML Unicode工具来获取更多的Unicode符号。
还可转换Unicode的码位,得到相应的十进制数字,然后可在HTML中使用,如:在HTML中显示⚽
:
要构建一个字符串,不管是否多行,还是需要引用其他变量,都无比轻松:
数值的自由表达与转换
字面符
对于十进制的27
,我们可以在字面符中以不同的进制来指定:
二进制。以0b
或0B
为前缀:
八进制:以0o
或0O
为前缀:
十六进制。以0x
或0X
为前缀:
对于数据类型为BigInt的数值,则在上面各种进制的表达式之后加上n
。
对于较大的数字,可以加入下划线_
来分隔,以增加可读性:
转化为不同进制的字符串
自由的对象创建方式
创建一个对象无比的简单。
对象在创建后,可以自由地添加或删除属性。
最终结果:
遍历对象属性名值:
指针或引用
JavaScript也存在指针的操作。当变量引用Object类型时,即为引用关系。
变量a与b均指向同一个对象,当a改变时,b的值也同时改变。
数组
数组的创建
创建数组很直观。
数组是栈
数组是以栈的形式来组织的。
栈的特点是先进后出(FILO,first in, last out)。上例中,数值1
为栈底,数值2
为栈顶。Array类的push方法进栈,pop方法出栈。
可调用shift方法将栈底元素,也即第0个数组元素移出栈。
数组的合并
concat方法可将多个数组或元素合并进一个数组中。
使用展开操作符...,也可以字面符的方式来合并数组元素。
上面如果不使用展开操作符...操作符,则数组元素为数组。
高效的代码
无需借助第三方变量,我们可以一行代码就实现两个变量交换数值。
先将a与b置于一个数组,再通过数组解包,直接分别赋值于b与a,从而高效地进行数值交换。
函数
自调用函数
上面的代码,结合了JavaScript模块、lambda表达式及自调用函数的特点,使相关代码得以在页面内容加载完毕后自动运行。相当于:
内嵌函数
内嵌函数是JavaScript的一大特色。
如果某函数只在一个地方使用,而又不想与其他局部变量相互影响,则可像上面一样创建一个内嵌函数。它的好处是,对外部不可见,而又与内部的其他变量相互隔离。
因为是内部使用,甚至可以无需函数名而改写为自调用匿名函数:
这种方式,除具备上面两个特点外,还有另外一个特点:它可访问闭包范围外的变量诸如m。利用这种灵活性,我们可节省多少的函数参数传导?
函数指针
JavaScript支持函数指针。
invoker的参数是一个函数指针,在其方法体中,直接调用了作为参数所传进来的函数。
回调函数
更进一步,编制非常实用的回调机制。其基本形式如下:
回调函数的本质,是由一个数据提供者dataProvider,负责调用作为参数所传进的函数dataConsumer,并且将自己所持有的数据作为函数参数传递给后者。意为,我负责提供数据,并负责回调你以传送相应的数据。
Array的forEach方法就是基于上面的原理而编写的回调函数:
forEach方法就类似于上面的dataProvider函数,以参数element及index回调了一个匿名函数。然后,在该匿名函数中,我们就可随意使用所传入的参数。
上面的回调函数是单程的,即dataConsumer函数处置完数据后就结束了。
更进一步,我们可以利用回调函数的返回值,实现数据提供者也数据使用者之间的双程沟通。
dataProvider函数先初始化一个config变量,并通过回调机制传给dataCustomize函数由其负责具体设置该对象的值。之后,dataCustomize函数返回设置之后的值,dataProvider接收此返回值后,又进一步根据此值来设置相应的字体大小。这就实现了双程沟通。
这种设计机制有许多好处。dataProvider函数负责config的生命周期,但必须考虑将设置数值的自由还给客户端。而客户端dataCustomize函数只需根据实际需求,专注于设置相应的数值就行了,无需考虑过多的细节。双方合作得都很愉快。
Array类提供了许多这样的双程回调函数。
filter方法用于筛选出数组中符合特定条件的元素。而筛选条件由回调匿名函数来定义。而对于filter所传进的任一元素,匿名回调函数将条件设定为:如果其值小于0,则筛选出来。
filter方法根据各个元素的返回值重新生成了一个新的数组并赋值于变量newArr。
这就是回调机制的强大之处,filter方法将重新生成新数组的算法隐藏起来了,但又可让客户端自由地指定各种各样的筛选条件,从而实现了固定算法与灵活配置的完美结合。
除了上面的filter方法之外,Array类的some, every, map, reduce等方法均属于这种情况。
Lambda表达式
Lambda表达式是匿名函数的一种快捷表现方式:
相当于:
Lambda表达式在函数参数为函数时经常用到:
如果Lambda表达式的参数只有1个,可省略参数列表的括号()
:
如果Lambda表达式的函数体内,只有1条返回语句,则可省略大括号{}
以及return
关键字:
而如果需要将参数直接传至其他方法或函数处理,则可更加简练,直接编写该方法或函数的名称即可:
相当于:
结果输出:
注意,因为console的log方法的参数为可变长参数,因此forEach的回调函数的3个参数全部都自动传送过去了。
如果我们只需使用其中的某个参数,可编写自定义函数:
自定义函数show只接受1个参数,因此回调函数的后两个参数被自动忽略了。
因此,当lambda表达式浓缩为只有方法或函数名称时,需同时考虑回调函数及所调用方法、函数的参数数量、顺序,否则可能出现不易觉察的bug。
构造器
在ECMA 6的class出来之前,这是JavaScript最典型的创建类的对象的方式。Rect称为构造函数,也称为对象rect的构造器。我们可以用下面代码查看该构造器:
作为对比,也可像下面一样创建实例:
注意,作为创建对象的用例,上面在为rect声明方法时,不能使用lambda的形式。
在一些图形应用中,我很喜欢使用下面便捷的伪构造器代码:
Rect虽不是构造器,却返回一个通过字面符构造器来创建的对象实例。
自动运行代码
Function(code)()
效果等同与eval(code)
,但比后者更安全、高效。具体参见函数类一节。
解包
参见在WebGL入门教程
中的JavaScript解包。