Java程序员对JavaScript并不陌生,大家刚开始学习Java的时候,肯定会学习servlet和JSP的知识,因此对于前端的部分HTML,CSS和JavaScript的知识肯定都有涉猎,虽然不如专业的前端工程师那么专业,但是也都能使用EasyUI或者Bootstrap等一些简单的模板框架写一些模板化的功能页面,所以学Java的并不会对JavaScript陌生。
JS(JavaScript)的语法和使用其实都很简单,比如大家熟悉的弹出框alert,函数function,大部分人也会对成熟度很高的JQuery插件和Bootstrap非常熟悉。作为一个后台工程师,这些其实差不多足够了。但是前端的发展是非常快的,前后端分离也变得非常流行,如果我们想更了解一些前端的内容,那必须从前端的基础开始,补充基础语法和函数知识,甚至一些高级的框架知识(VUE)等内容,这样能更好的完成前后端分离的开发,甚至向全栈工程师方向发展。下面我们从基础开始,一点点完善和学习前端知识。
前端工程师大都喜欢Visual Studio Code(vscode)工具开发前端项目,非常非常好用。本人比较喜欢webStrom这个工具,与Java程序员常用的idea是一个系列的,并且我们常用的工具idea完全包含了webStorm的功能,因此我后面会使用idea来做前端代码的开发和演示。并且由于idea自带的插件NodeJS:
我们可以很方便的运行JS代码,就像运行Java的main方法一样,非常方便!
下面我们来从常用的基础开始回顾前端的一些东西。我们来新建一个静态的前端项目:
接下来选择项目路径:
然后点击完成,即可创建一个空项目。
众所周知,前端代码最终是要运行在html页面中的,因此我们来创建一个空页面,写一些简单的内容:
页面名字起名为demo,内容如下:
这是个非常简单的html页面,用右键打开:
上图中右键用默认浏览器打开,可以看到效果:
JavaScript代码可以直接嵌在网页的任何地方,不过通常我们都把JavaScript代码放到中:
从新打开页面,可以看到效果:
由包含的代码就是JavaScript代码,它将直接被浏览器执行,有些时候你会看到
从新打开页面运行,即可看到效果。并没有什么不一样!
除了常见的alert代码,更常用的调试操作是 console.log("hello JS");
这行代码本身不会影响页面的效果展示,同时也会把内容打印在浏览器的控制台中:
控制台效果如下:
我这里使用的是谷歌浏览器,按F12快捷键即可看到控制台,其它浏览器可以自行搜索如何看到控制台。
除了将JS代码写在html页面中,还有一种方法是把JavaScript代码放到一个单独的.js文件,然后在HTML中通过引入这个文件,下面新建一个.js文件:
文件命名为test,然后把代码挪到test.js中:
然后调整
从新打开页面运行可在控制台看到同样的效果,不再展示。
这种简单的代码每次都在浏览器里面看确实有点麻烦,idea开发工具提供了NodeJS插件,可以让我们像运行Java 的mian方法一样看到JS的运行效果,右键在JS文件中点击:
可以看到上图中圈中的Run代码的一个操作,点击即可看到效果:
可以看到控制台打印出来了Hello JS。这是idea工具的方便之处。但是类似alert这种弹出框,就不能这样运行了,必须在页面中看效果,如果强行运行会报错:
JS有很多简单的语法,由于非常简单,不仅前端常用,Java程序员也经常使用,这里就不再演示了,感兴趣的可以自行尝试。
JS语法还有很多非常简单的,但是很多Java人员很少使用,或者很少注意,不太熟悉的,下面我们来一一补充一下。
数字类型是一个非常简单的类型,定义一个数字也很简单: var a = 123;
定义一个其它不确定类型的,但是肯定是非数字类型的方式也有明确的方法: var notNum = NaN;
NaN表示Not a Number,当无法计算结果时用NaN表示(0 / 0=NaN),注意,判断不能用等号,用 isNaN 来判断是否是非数字类型:
Infinity
表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity(2 / 0=Infinity),判断无限大可以用等号:
JavaScript允许对任意数据类型做比较:
第一种是 ==
比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
第二种是 ===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。
由于JavaScript这个设计缺陷,不要使用 ==
比较,注意:始终坚持使用 ===
进行比较。
另一个例外是NaN这个特殊的Number与所有其他值都不相等,包括它自己:
NaN === NaN; // false
唯一能判断NaN的方法是通过isNaN()函数:
isNaN(NaN); // true
注意:无限大可以使用 ===
比较:
最后来看浮点数的比较, 正常来说,三分之一等于一减去三分之二: 1/3 === 1 - 2/3;
但是这个比较结果是false:
这不是JavaScript的设计缺陷。浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
如果if的条件判断语句结果不是true或false怎么办?例如:
var s = ‘123’;
if (s.length) { // 条件计算结果为3
//
}
JavaScript把null、undefined、0、NaN和空字符串’'视为false,其他值一概视为true,因此上述代码条件判断的结果是true。
JavaScript在设计之初,为了方便初学者学习,并不强制要求用var申明变量:
这个设计错误带来了严重的后果:如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量,
为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。
什么是ECMA?
因为网景开发了JavaScript,一年后微软又模仿JavaScript开发了JScript,为了让JavaScript成为全球标准,几个公司联合ECMA(European
Computer Manufacturers
Association)组织定制了JavaScript语言的标准,被称为ECMAScript标准。
所以简单说来就是,ECMAScript是一种语言标准,而JavaScript是网景公司对ECMAScript标准的一种实现。
那为什么不直接把JavaScript定为标准呢?因为JavaScript是网景的注册商标。
不过大多数时候,我们还是用JavaScript这个词。如果你遇到ECMAScript这个词,简单把它替换为JavaScript就行了。
启用strict模式的方法是在JavaScript代码的第一行写上:
‘use strict’;
这是一个字符串,不支持strict模式的浏览器会把它当做一个字符串语句执行,支持strict模式的浏览器将开启strict模式运行JavaScript。示例如下:
可以看到,strict模式下,没有使用var声明变量,直接就报错了,这种模式能让我们的代码更严谨,也能避免很多意外的问题。
好多时候,Java程序员会在JS中使用数组,但是数组的很多基础操作很多并不熟悉,下面补充一下基本知识。先来看数组的创建:
第一种方式: var arrOne = new Array(1,2,3);
// 创建了数组[1, 2, 3]
第二种方式 var arrTwo = [1,2,3];
// 创建了数组[1, 2, 3]
注意:强烈建议第二种方式!
Java中数组的长度大小是不可变的,JS中数组的长度大小是可变的,但是强烈不建议改变数组长度,因为变化的情况很多,不好把握。
如果直接把数组的长度直接变大,会直接在数组末尾增加三个空位置:
如果把数组的长度直接变小,就会直接丢失元素:
数组的常规操作就是用索引修改元素的值:
正常来说,上图中的数组索引只有0,1,2。但是如果在赋值的时候,直接超出了索引的范围,也会引起数组长度的变化:
所以,Java程序员如果想有把握的,在安全范围内使用数组,索引最好不要越界。当然,JS语法非常非常熟练的例外。
数组可以通过元素的值使用 indexOf() 来获取索引的值:
如果元素不存在,就返回 -1 :
数组可以使用 slice() 进行截取操作,和截取字符串挺类似,正常操作如下:
还可以从某个位置截取到末尾:
如果不传索引参数,就会截取全部数组,其实也是相当于复制了一遍数组,和原来的数组没啥关系了,比较会返回 false:
数组可以当成队列操作,使用 push() 可以向Array的末尾添加若干元素,使用pop()则把Array的最后一个元素删除掉:
如果是空数组,再删除,不会执行任何操作,也不会报错:
pop()操作会有返回值,会返回被删除的元素,如果是空数组继续pop不会报错,而是返回undefined:
数组不仅可以在末尾进行操作,还可以在开头进行操作,如果要往Array的头部添加若干元素,使用unshift()方法,shift()方法则把Array的第一个元素删掉,操作效果其实与push和pop类似:
sort()可以对当前Array进行排序,它会直接修改当前Array的元素位置,直接调用时,按照默认顺序排序:
sort操作也有返回值,返回的就是新数组:
reverse()操作会把整个Array的元素给调个个,也就是反转:
splice()方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素,比如从第index=2开始删除三个元素,再增加两个新的元素,同时 splice 操作的返回值就是被删除的元素组成的数组:
splice 操作还可以只删除,不新增:
splice 操作也可以只新增,不删除:
concat()方法把当前的Array和另一个Array连接起来,并返回一个新的Array(请注意,concat()方法并没有修改当前Array,而是返回了一个新的Array):
实际上,concat()方法可以接收任意个元素和Array,并且自动把Array拆开,然后全部添加到新的Array里:
join()方法是一个非常实用的方法,它把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串(如果Array的元素不是字符串,将自动转换为字符串后再连接):
以上是我认为Java程序员不常用的JS中数组的操作,有的使用起来还是非常方便的;
很多时候,我们习惯使用下面的函数定义方式:
但其实函数还有另外一种写法,就是赋值给一个变量:
这里把一个function赋值给了一个变量sum,同事函数的结尾加了一个分号,这时候sum就相当于函数的名字一样使用。这种写法在定义更复杂的插件时候,会很有用,这里首先了解一下。
常规的for循环很多人可能会模仿Java中的写法:
但是这里在for内部定义了 var i=0;
并不太好,这样其实把变量i定义成了全局变量,即使后面跳出了for循环,也会看到i最后的值:
要想变量i只在for的代码块中有效,可以使用 let 进行定义:
但是let是ES6(也就是 ECMAScript 6)中定义的一个关键字,如果报错,要查看ide或者浏览器是否支持!如果不支持,还是继续使用var吧。
再来看一下for … in循环,for循环的一个变体是for … in循环,它可以把一个对象的所有属性依次循环出来(注意是属性名字不是属性值):
当然获取值也简单:
当然JavaScript的对象不只是对象本身,也会有继承的父对象(关于继承,面型对象,后面会详细了解),要过滤掉对象继承的属性,用hasOwnProperty()来实现:
由于Array也是对象,而它的每个元素的索引被视为对象的属性,因此,for … in循环可以直接循环出Array的索引:
请注意,for … in对Array的循环得到的是String类型而不是Number。
JavaScript的对象其实非常类似于Json数据格式:
获取对象的属性很简单:
JavaScript访问对象属性是通过 .
操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用 ''
括起来:
属性名middle-school不是一个有效的变量,就需要用 ''
括起来。访问这个属性也无法使用 .
操作符,必须用[‘xxx’]来访问。至于其它普通属性,两种方式都可以:
因此我们在定义变量属性的时候,尽量使用标准的名字,这样就可以使用简单的方式获取属性值。
如果获取一个不存在的属性值,就会返回 undefined :
由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:
当然删除语句也可以写成 delete man['nickName'];
效果相同。
已经删除的或者根本没有的属性再执行删除操作也不会报错。
如果我们要检测 man 是否拥有某一属性,可以用in操作符:
不过要小心,如果in判断一个属性存在,这个属性不一定是该对象的,它可能是继承得到的:
因为toString定义在object对象中,而所有对象最终都会在原型链上指向object,所以对象 man 也拥有toString属性。
要判断一个属性是否是对象 man 自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:
JavaScript的对象有个小问题,就是对象的键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。
为了解决这个问题,最新的ES6规范引入了新的数据类型Map。
Map是一组键值对的结构,和Java中的Map有点类似,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,如果用Array实现,需要两个Array:
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
初始化Map需要一个二维数组,或者直接初始化一个空Map也可以,下面是一个空Map和对map操作的几个方法:
从上面的代码注释中可以看到增删改查的几个操作。
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值覆盖掉。关于JS中map的操作,其实和Java很类似,这里可以很容易理解,不再演示。
Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
可以看到,第一个Set中重复元素在Set中自动被过滤,注意数字3和字符串’3’是不同的元素。
通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
通过delete(key)方法可以删除元素:
遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。
具有iterable类型的集合可以通过新的for … of循环来遍历。用for … of循环遍历集合,用法如下(注意,这里是for…of,不是for…in):
你可能会有疑问,for … of循环和for … in循环有何区别?
for … in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。所以数组使用for…in,打印出来的,其实是下标:0,1,2…
数组按照我们通俗的理解,就是下标和元素的一一对应,现在把下标理解为属性,但是对象的属性是灵活的,可以添加和删除,当我们手动给Array对象添加了额外的属性后,for … in循环将带来意想不到的意外效果:
for … in循环将把name包括在内,但Array的length属性却不包括在内,没有发生改变,这就让数组变得很怪异。
for … of循环则完全修复了这些问题,它只循环集合本身的元素:
这就是为什么要引入新的for … of循环。
然而,还有更好的方式,就是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。以Array为例:
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
Map的回调函数参数依次为value、key和map本身:
如果对某些参数不感兴趣,觉得没必要这么多参数都写上,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Array的element:
对数组,Map和Set的循环,建议还是使用forEach !
打印一个多行的字符串很简单 使用 \n
即可:
字符串换行不仅可以使用\n换行,还可以使用飘号(在 tab 键上面):
获取字符串某个指定位置的字符,使用类似Array的下标操作,索引号从0开始:
需要特别注意的是,字符串是不可变的,如果对字符串的某个索引赋值,如果是最新的ES6,可能会报错误,但是,对字符串来说也没有任何效果:
如果是老的ES5,可以没有错误也没有效果!