JavaScript学习笔记(一)
写在前面
本篇笔记的内容整理自移动云启的前端精品课。最后的关于部分可跳转。考虑到网站内容相对分散,故整合起来,以供自己后续复习。其中包含了JS数据类型,基础语法,函数,字符串常用方法以及正则表达式。
文章目录
一 JS简介
JavaScript 是动态网页开发最常见的语言,在 2017 年 6 月版的全球编程语言排名 TIOBE 榜单中高居第 7,仅次于 Java、C 等应用范围极其广泛的语言。 JavaScript 又称为 JS,诞生于 1995 年的 NetSpace 公司,最初应用于著名的网景浏览器,经过多年的发展,JS 目前的应用领域有所扩大,包括网站前端、后台、移动开发、插件开发等。 JS 之所以如此流行,与其突出的跨平台性特点密不可分,不受制于操作系统,被绝大多数浏览器支持,使得 JS 在动态网页开发中几乎居于垄断地位。
搭建 JavaScript 的运行环境
JavaScript 运行环境的搭建很简单,下载并安装任意一种浏览器就可以。 以下是运行一个 JavaScript 源代码的全过程:
- 新建一个文本编辑器,打开后输入 JavaScript 源代码,关闭时以 .js 结尾;
- 在 HTML 中调用刚刚建立的 JS 文件;
- 以浏览器方式打开 HTML 文件。 用 Windows 系统自带的文本编辑器不是很方便,推荐使用 Sublime,这是一个免费的集成开发环境,具有代码自动补全等很多优点。
JavaScript 输出
JavaScript 的输出有三种方式,如下:
第 1 种:弹出对话框模式
通过 alert() 函数弹出对话框,这是最常用的方式。在 script 标签下,调用 window.alert() 函数,括号内填入输出的内容。将下面的代码以文本编辑器编辑后改为 .html 后缀,然后用浏览器打开后即可弹出对话框。
<!DOCTYPE html>
<html>
<body>
<script>
window.alert("Hello,World!");
</script>
</body>
</html>
以浏览器方式打开刚刚建立的 HTMl 文件,在浏览器上面可以看到,如图1所示:
图 1
这里的标签是 HTML 中的内容,关于 JavaScript 和 HTML 的关系,将在下一关介绍。
第 2 种:控制台模式
通过 console 输出到控制台,在 script 标签下,调用 console.log() 函数,括号内填入输出的内容,以浏览器打开下面的代码后,按 F12 键即可在浏览器的控制台中实现输出。
<!DOCTYPE html>
<html>
<body>
<script>
console.log("Hello,World!");
</script>
</body>
</html>
在浏览器的控制台可以看到如图2所示的内容:
图 2
除此之外,还有 console.error()、console.warn() 等方式实现输出,分别表示输出错误,输出警告。
第 3 种:页面输出模式
通过 document.write() 实现输出到页面。在 document.write() 函数的括号中填入输出的内容即可。例子如下:
<!DOCTYPE html>
<html>
<body>
<script>
document.write("Hello,World!");
</script>
</body>
</html>
浏览器的页面输出如图3所示:
图 3
第4种 提示框
提示框prompt,可以在浏览器正上方弹出一个提示框。用户是可以输入数据。
我们可以获取到用户输入进来的数据,利用这些用户输入进来的数据,进行编程。
<script type="text/javascript">
//提示框
var age = prompt("帅哥,你今年多大了呀");
console.log(age);
console.log(typeof age);
</script>
- 用户在提示框中输入的进来的数据,我们可以用变量进行存储。
- 用户输入进来的数据都是字符串类型的数据。
此外,还有其他几种比较少用的输出方式,这里不做介绍,可参考相关文档。
使用内置 script 标签对的方式嵌入 JS 代码
将 JavaScript 嵌入到 HTML 中有两种方法,对于内容较少的 JavaScript 代码,常采用将 JavaScript 代码直接写在 HTML 中的做法。 在<script></script>
之间写入 JavaScript 代码,然后将标签连同代码放入到<head></head>
或者<body></body>
之间,常见的是放在<head></head>
之间,因为这样 JavaScript 和 HTML 的主体内容相对分开,便于阅读。如下是一个实例:
<!DOCTYPE html>
<html>
<head>
<script>
console.log("在HTML中嵌入JavaScript,这里是JS部分");
</script>
</head>
<body>
<!--这里是HTML的主体部分-->
</body>
</html>
引入外置代码源文件的方式在 HTML 中嵌入 JavaScript
对于较长的 JavaScript 源代码,使用内置代码的方式会使得 HTML 代码过长,不符合程序开发模块化的要义。这时可采用引入外部代码文件的方式,即将所有的 JavaScript 代码放在一个扩展名为 .js 的文档中,然后将 script 标签的 src 属性值设置为待引入的 JavaScript 文件的路径名,再将<script></script>
放在<head></head>
或者<body></body>
之间。如下是一个实例: 我们将 JavaScript 代码放在 myjs.js 中,HTML 代码放在 hello.html 中,两者在同一个目录下,其内容分别是: myjs.js 如下:
console.log("在HTML中嵌入JavaScript,这里是JS部分");
注意:上面的代码中千万不要再加
<script></script>
标签,这些标签是 HTML 语言的内容。hello.html
如下:
<!DOCTYPE html>
<html>
<head>
<script src="myjs.js">
</script>
</head>
<body>
<!--这里是HTML的主体部分-->
</body>
</html>
注意1:这里 src 属性的值 JavaScript 文件的路径名,而不是文件名!这里因为是在同一个目录下,所以路径名同文件名。
注意2:一般来说,只有最简单的脚本才嵌入到 HTML 中。更复杂的脚本存放在单独的文件中。使用独立文件的好处是浏览器会下载它,然后将它保存到浏览器的 缓存 中。之后,其他页面想要相同的脚本就会从缓存中获取,而不是下载它。所以文件实际上只会下载一次。这可以节省流量,并使得页面(加载)更快。
注意3:一个单独的 <script> 标签不能同时有 src 特性和内部包裹的代码。这将不会工作。
二 JS数据类型
JavaScript 是一种动态类型的语言。关于编程语言,动态类型和静态类型有何区别呢?
传统的编程语言例如 c、c++、Java 都是静态类型的语言。所谓静态,是指在编程的时候,对于所有的变量,需要指定其数据类型,如 Java 中的数据类型有 int、float、char,分别表示整数,浮点数和字符。但是也有一些编程语言,如 JavaScript、Python 等,是动态类型的语言。这类语言在编程的时候并不需要指定变量的数据类型,而是在运行的时候根据变量的具体值确定其数据类型。
尽管在 JavaScript 编程的时候,变量的数据类型看起来不再重要,但这只是一种假象。因此本实训要带大家一起深入了解一下 JavaScript 的数据类型,让大家有自己的见解,真正理解动态类型、快速具备动态类型的应用能力。
JavaScript 中的变量
不同于 C 或 Java,JavaScript 是一种动态类型的语言,即申明的时候不指定变量的数据类型,而在运行的时候根据变量的具体值动态的判断变量的数据类型。 JavaScript 的变量名以字母或者$
或者_
开头;变量名只能含有字母、数字、下划线和美元符号;大小写敏感;不能使用保留的关键字,如 html、var、function 等,这一条一定要记住,使用关键字造成的错误往往很难排除。
JavaScript 中的变量的申明、初始化和赋值
- 变量的申明 在 JavaScript 中,申明变量以关键字 var 开头,空一格后再接变量的名字;当然,可以一次申明多个变量,这时 var 只需要出现一次,多个变量名之间用英文的逗号隔开即可。如:
var myvar1; //申明变量"myvar1"var myvar2,myvar3,myvar4; //一次申明三个变量
- 变量的初始化和赋值 既然变量是用来记录数据的,如何给变量赋值呢?简单来说,和数学中一样,用一个等号连接变量名和变量的值即可,对于数字的赋值,直接用等号连接数字和变量,对于字符串的赋值,需要将字符串包含在英文双引号之中。 变量的第一次赋值称之为初始化。 你可能已经想到了,申明和初始化能一起进行吗?答案是肯定的,下面我们给出具体的例子:
var numberVar; //申明
numberVar = 1; //赋值为数字
var stringVar; //申明
stringVar = "I am a String"; //赋值为字符串
var myNumber = 2; //申明的同时赋值为数字
var myString = "我是字符串"; //申明的同时赋值
var number1 = 1,number2 = 2; //一次申明、赋值多个变量
注意:一个变量经过多次赋值,它的值为最后一次赋值的值。
JavaScript 中的变量的作用域
变量的作用范围,或者说作用域,是指变量保持有效的范围,JavaScript 中的变量广义上来说分为局部变量和全局变量。
- 全局变量 在函数外部申明的变量称为全局变量,全局变量的作用自申明的地方起,到整个 JavaScript 文件的末尾(包括这其中的所有函数的内部)。下面是一个例子:
var wholeVar = 12; //申明并初始化一个全局变量
function() {
var localVar = 1; //局部变量
console.log(wholeVar+localVar); //输出13
}
console.log(wholeVar); //输出12
- 局部变量 局部变量是指申明在函数内部的变量,其作用域仅是本函数内部,在函数外不可用。 如果局部变量和全局变量的名字相同,那么在函数内部全局变量会被局部变量覆盖。
var myVar = 1; //全局变量
function scope() {
var myVar = 2; //局部变量,覆盖了上面的值
console.log(myVar); //输出2
}
- 申明提前 JavaScript 局部变量有一个很重要的概念,叫申明提前,我们先来看一个例子。
var wholeVar = 1; //全局变量
function myTest() {
console.log(wholeVar);
var wholeVar = 2;
console.log(wholeVar);
}
关于第三行的输出,你的第一反应一定是1吧,正确答案是 undefined。这是因为在函数内部,变量不论在何处申明,都应该看成是在最开始申明(赋值不会看成是在最开始赋值,这就是不输出2的原因),这就是“申明提前”,所以,以上代码等价于:
var wholeVar = 1;
function myTest() {
var wholeVar;//申明提前了,覆盖了全局变量
console.log(wholeVar); //上面只申明,没赋值
wholeVar = 2;
console.log(wholeVar);
}
注意:这个地方不太好理解,所以我们在编程的时候局部变量的名字最好不要和全局变量冲突。
JavaScript 数据类型转换
字符串转整数
如果你想把一个字符串转换成整数,有两种方式。
- 使用 parseInt() 方法,参数为字符串,结果为该字符串转换而来的整数; 转化规则是:如果字符串的首字符不是一个数字,转换失败,返回 NaN;否则,转换到字符串中第一个不是数字的字符止,即,遇到字母、小数点下划线等字符立即停止转换。需要注意的是,16 进制的符号 0x 不会让转换停止。 parseInt() 还可以有第二个参数,表示待转换字符串的进制。下面给一些例子:
parseInt("12");
parseInt("12.2");
parseInt("C",16);
parseInt("12a2");
parseInt("0XC"); //以上均返回数字12
parseInt("a12"); //失败
- 使用 Number() 进行强制类型转换; 使用 Number() 转换一个字符串,这个字符串必须是只含有数字的字符串,即数字的字符串形式。与上面的 parseInt() 方法对比可知,Number() 实现的是更加严格的字符串转换为数字操作。因为对于 12a3 这种含有字母等非数字字符的字符串,Number() 会报错。下面是一些例子。
Number("12"); //返回12
Number("12a2"); //返回NaN
Number(""); //返回0
字符串转小数
与整数相同,字符串转小数也有两种方式:parseFloat() 和 Number()。
parseFloat() 方法只转换到字符串中第一个不是数字的字符为止,当然这个字符不包括第一个小数点。这里有些不好理解,下面用例子解释。
parseFloat("12"); //返回12
parseFloat("12.2a"); //返回12.2
parseFloat("12.2.2"); //返回12.2,第二个小数点会让转换停止
parseFloat(null); //返回0
数字转字符串
toString() 实现一般的数字转字符串,String() 则是强制类型转换。
toString() 括号内有一个可选的参数,指以几进制的形式转换该字符串,如数字12调用 toString(16) 得到的结果就是 C,即12的16进制表示方式。
String() 可以转换 null 和 undefined,而 toString() 不可以。
var myNum = 15;console.log(myNum.toString());//输出"15"
console.log(myNum.toString(16)); //输出"F"
console.log(String(myNum)); //输出"15"
布尔型与其他类型的相互转换
布尔型的值只有两个 true 和 false 。转换规则如下:
- 布尔型转为字符串直接就是字符串 true 或者 false;
- 布尔型中的 true 转换为数字 1,布尔型中的 false 转换为数字 0;
- 数字 0、null、undefined、空字符串转换为布尔型的 false,其他所有都是转换为 true。
下面例子中的 Boolean() 方法实现其他的类型转布尔型。
var myBool = ture;
myBool.toString(); //返回"true"
Number(true); //返回1
Boolean("js"); //返回true
Boolean(""); //返回false
注意,上面讲的空字符串是""
,而不是空格字符串" "
,这两个不同,后者双引号之间有一个英文字符的大小的空位,他们转为布尔型的结果不同:
Boolean(""); //返回false
Boolean(" "); //返回true
隐式转换
JavaScript 是一种弱类型语言,不同类型的变量在运算符的作用下会发生类型转换。这个是编译环境下直接进行的,所以叫隐式类型转换。下面是一些转换规则:
+
运算的两个操作数是数字和字符串,数字会被转换为字符串;+
运算的两个操作数是数字和布尔型,布尔型会被转换为数字;+
运算的两个操作数是字符串和布尔型,布尔型会被转换为字符串;- 减、乘、除、取余运算会把其他类型转换为数字;
- if 括号中单独的一个变量会被转换为布尔型。
三 JS运算符
算术
算法运算符
JavaScript
中的算术运算符除了数学中常见的加减乘除外,还有递增、递减和取余等。
JavaScript
中的加号除了具有数学中将两个数字相加的作用外,还能对字符串做连接操作。
对两个数字相加和数学中一样,字符串相加就是拼接字符串的意思,比如Java+Script
的结果是字符串JavaScript
。
当字符串和数字相加时需要进行类型转换,数字会先转为字符串,然后再做字符串的拼接。
var resultNumber = 1+1;//结果是2
var resultString1 = "1"+"1";//结果是“11”
var resultString2 = 1+"2";//结果是“12”
减法、乘法、除法以及取余运算符只能用于数字之间的计算,不能做字符串操作。
-
和数学中减号用法相同;
*
和数学中乘号用法相同;
/
表示除以,结果是浮点数,不做四舍五入;
%
表示取余数,a%b
返回a
除以b
得到的余数,结果与a
的符号相同。
var floatNumber = 3/2;//结果是1.5
var intNumber1 = 3%2;//结果是1
var intNumber2 = -3%2; //结果是-1
比较与逻辑
比较运算符
JavaScript
中的比较运算符有==
,===
,>
,<
,!=
,>=
等。
==
叫做相等,===
叫做严格相等。双等号和三等号的区别是:三等号要求数据类型和值都相等,双等号只需要值相等即可,相等包含严格相等。
从比较过程来看,严格相等先比较两个对象的数据类型是否相等,不相等则结束比较,返回false
,相等在数据类型不同时,尝试进行数据类型转换,例如,在字符串和数字的比较中,字符串会被转为数字再比较;布尔值true
转为数字1
,布尔值false
转为数字0
。
如果数据类型相同,相等和严格相等都会直接比较值,值相等返回true
。
需要特别注意的是,以上仅仅适用于非对象类型。对于对象类型,相等或者严格相等比较的都是对象的引用,而不是具体的值,就是说,一个对象和其他任何对象都是不相等的,即使两者属性、值都相等。下面给出一些例子:
var stringVariable = "2";
var number1 = 2;
var number2 = 2;
console.log(stringVariable == number1);//true
console.log(stringVariable === number1);//false
console.log(number1 === number2);//true
var studentA = {name:"Bob",age:22}
var studentB = {name:"Bob",age:22}
console.log(studentA == studentB);//false,因为不是同一个对象
var studentC = studentA;
console.log(studentA == studentC);//true,因为是同一个对象
将studentA
赋值给studentC
,这时studentC
和studentA
指向内存中的同一个地址块,视为同一个对象,所以两者相等。
-
不等 对应于上面的等号,不等号也有两种:
!=
和!==
。!=
和==
互为相反,==
成立,!=
一定不成立。!==
和===
互为相反,严格相等成立,则严格不相等不成立。 -
其它 大于,小于,大于等于,小于等于的比较规则如下: 比较的两个对象都是数字,按照数学中数字的比较方法。 数字和字符串比较,字符串转为数字后再比较。 字符串和字符串比较,从第一个字符开始,逐个比较,发现不相等立即返回。字符按照
ASCII
编码值的大小比较,一般只要记住:数字<大写字母<小写字母,字母a
小于字母z
,A
小于Z
, 比较过程中,当一个字符串结束另外一个字符串还有,还没有比较出大小,则长的字符串较大。
var number1 = 1;//定义变量number1
var number2 = 2;//定义变量number2
var string1 = "3";//string1
var lowerLetter = "a";//定义变量lowerLetter
var upperLetter = "A";//定义变量upperLetter
var string1 = "aa";//定义变量string1
var String2 = "ab";//定义变量String2
console.log(number1<number2);//输出true
console.log(number2<string1);//输出true
console.log(upperLetter<lowerLetter);//输出true
console.log(lowerLetter<string1);//输出false
console.log(string1<string2);//输出true
逻辑运算符
在介绍逻辑运算符之前,我们必须明确逻辑运算符的操作数只能是布尔型,其他类型都会被转换为布尔型:除了0
,null
,undefined
,""
外,其他的值转换为布尔值都是true
。
- 逻辑与:逻辑与有两个操作数,中间用
&&
连接,只有两个操作数都是true
结果才是true
; 其中一个操作数不是布尔型,当左操作数为真值时,返回右操作数。当左操作数为假值时,返回左操作数。 - 逻辑或:逻辑或同样有两个操作数,用
||
连接,至少有一个操作数为true
时结果为true
; 其中一个操作数不是布尔型,当左操作数为真值时,返回左操作数。当左操作数为假值时,返回右操作数。 - 逻辑非:逻辑非只有一个操作数,
!
后连接操作数或表达式,意思是将操作数取反; 如果操作数不是布尔型,编译器首先将其他类型转换为布尔型,然后返回true
或者false
。
console.log(true&&false);//false
console.log(true||false);//true
console.log(!false);//true
var number1 = 1;
var number2 = 0;
var string = "a";
console.log(number1&&string);//输出字符串"a"
console.log(number1||string);//输出数字1
console.log(!number1);//false
条件与赋值
条件运算符
条件运算符由?
和:
构成,三个操作数分别在?
的左边、右边以及:
的右边,第一个操作数如果是真值,整个表达式返回第二个操作数的值;第一个操作数如果是假值,返回第三个操作数的值。
一般我们说到JavaScript
中的三元运算符,指的就是条件运算符,因为它有三个操作数。条件运算符通常用来简化表达式。
var result1 = a>b?a:b;//result1赋值为a和b中大的那一个
var result2 = (a==5)?(a+1):(a-1);//a为5,返回a+1,否则返回a-1
赋值运算符
赋值运算符就是=
号,其实这个在前面出现过很多次了。赋值运算符的左边是一个变量或者对象的属性,右边是这个变量的值,意思是设置左边变量的值为右边的具体值。
除了基本的等号外,赋值运算符还可以和算术运算符结合。例如a+=1
表示a=a+1
,其中就把相加和赋值结合了起来。同理,相减、相乘、相除、取余、位运算都可以和赋值结合起来。如下:
var b = 1;b += 1;//等价于b = b+1
b *= 1;//等价于b = b*1
b /= 1;//等价于b = b/1
b %= 1;//等价于b = b%1
b &= 1;//等价于b = b&1
优先级与结合性
运算符的优先级
JavaScript
中运算符的优先级和数学中运算符的优先级意思相同,优先级高的运算符比优先级低的运算符先计算,例如数学中1+2*3
算式,先计算2*3
,结果再与1
相加,所以乘法的优先级高于加法,在JavaScript
中,不同的优先级也是这个作用。
下面是常见的运算符的优先级:
分类 | 运算符 | 含义 | 结合性 |
---|---|---|---|
1 | ++ | 前后增量 | R |
1 | – | 前后减量 | R |
1 | ! | 逻辑非 | R |
2 | * / % | 乘 除 求余 | L |
3 | + - | 加减 | L |
4 | < <= > >= | 比较数字顺序 | L |
4 | in | 测试属性是否存在 | L |
5 | == | 判断相等 | L |
5 | != | 判断不等 | L |
6 | & | 按位与 | L |
7 | && | 逻辑与 | L |
8 | ?: | 条件运算符 | R |
9 | = | 赋值 | R |
9 | += -= *= /= %= | 运算且赋值 | R |
10 | , | 忽略第一个操作数 | L |
从上到下优先级逐渐降低。第一栏数字相同的运算符优先级相同,对于这个表格,不需要全部记住,但是常见的需要记住,比如加减乘除的优先级高于比较运算符,赋值运算符的优先级几乎是最低的,下面给出例子帮助理解:
var a = 1;
var b = 2;
var c = ++a+b;
因为++
的优先级高于+
,所以上面的第三个句子等价于:
var c = (++a)+b;
运算符的结合性
运算符的优先级是针对不同优先级的运算符来说的,对于同一级的运算符,运算顺序取决于运算符的结合性,比如加法和减法的优先级相同,而加法和减法都是从左向右结合,所以a+b-c
式中,会先计算a+b
,即按照阅读的顺序计算。
也有很多运算符是从右到左结合的,比如取反、逻辑非。
上面图中的第三栏就是结合性,R
表示从右向左结合,L
表示从左到右结合,从左向右的占多数,这和我们在数学中的习惯相同。
var d = a*b/c;//先计算乘法后计算除法
var aa = 2;
var bb = 3;
var cc = aa *= bb;//先计算aa*=bb,再把结果赋值给cc,为6
四 JS 对象
JavaScript
是一种基于对象(Object-based)
的语言,在JavaScript
中,对象的创建和Java
不同,既有Java
使用的构造函数方式,也有其他方法。
对象的定义
JavaScript
中的一切都是对象,这是该语言的一个很大的特点。像字符串、数组等已经定义的对象叫做内置对象。用户自己也可以定义对象,叫做自定义对象。本实训讲的对象特指自定义对象,自定义对象指数据和函数(又叫方法)的集合。数据指变量名和变量的值构成的组合。如下图所示:
对象的创建
下面介绍五种创建对象的方法,其中通过对象字面量和使用构造函数创建对象最常用。
对象字面量
这是最常用的创建对象的方法,通过新建一个键值对的集合(对象字面量)创建对象,如下:
var song = {
name:"Liekkas",
time:180,
"song language":English,
singer:
{
singerName:"Sofia Jannok",
singerAge:30
}
};
键值对中的键指的是属性的名字,若其中含有空格,名字需要用双引号包含在内。值指的是属性的值,可以是基本类型:如字符串,数字,布尔型,也可以是一个对象。键值对之间用逗号隔开,最后一个键值对后面没有逗号,所有的键值对在一个大括号中。
通过关键字new创建对象
通过new
关键字创建对象也是一个常用的方法。如下:
var Store = new Object();//创建对象的一个实例
Store.name = "lofo Market";
Store.location = "NO.13 Five Avenue";
Store.salesVolume = 1000000;
通过上面的代码,我们就能创建一个名为Store
的对象。
通过工厂方法创建对象
工厂方法就是通过函数创建对象,函数封装了创建对象的过程。
这是一种通过函数创建对象的方法,函数封装了对象的创建过程,创建新对象时只需要调用该函数即可。这种方法适合于一次创建多个对象。
//对象的创建函数
function createStoreObject(name,location,salesVolume) {
var store = new Object();
store.name = name;
store.locaion = location;
store.salesVolume = salesVolume;
store.display = function() {
console.log(this.name);
};
return store;
}
//利用该函数创建一个对象
var store1 = createStoreObject("panda express","No.1,People Street",200000);
这样就创建了一个名为store1
的对象,注意这个对象除了属性之外还有一个方法display
。要创建更多的类似store1
的对象,直接调用该函数即可。
使用构造函数创建对象
上面虽然也是通过函数创建对象,但不是构造函数,只是普通函数。构造函数名必须以大写字母开头,函数体内没有返回语句。
//构造函数
function Store(name,location,salesVolume) {
this.name = name;
this.locaion = location;
this.salesVolume = salesVolume;
}
//创建对象的实例
var myStore = new Store("KeyExp","No.1,L.Street",540000);
上面的代码首先是Store
对象的构造函数,然后用该构造函数创建了Store
对象的一个实例myStore
。
使用原型(prototype)创建对象
当我们创建一个函数时,函数就会自动拥有一个prototype
属性,这个属性的值是一个对象,这个对象被称为该函数的原型对象。也可以叫做原型。
当用new
关键字加函数的模式创建一个对象时,这个对象就会有一个默认的不可见的属性[[Prototype]]
,该属性的值就是上面提到的原型对象。如下图所示:
JavaScript
中每个对象都有一个属性[[Prototype]]
,指向它的原型对象,该原型对象又具有一个自己的[[Prototype]]
,层层向上直到一个对象的原型为null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节。如下图所示:
这种方法是对使用构造函数创建对象的改进,使用构造函数创建一个对象时,会把构造函数中的方法(上面的构造函数只有属性的键值对,没有方法)都创建一遍,浪费内存,使用原型不存在这个问题。
function Store() {};
Store.prototype.name = "SF Express";
Store.prototype.locaion = "Hong Kong";
Store.prototype.salesVolume = 1200000000;
//创建对象
var myStore = new Store();
//创建一个新的对象
var hisStore = new Store();
hisStore.name = "STO Express";//覆盖了原来的name属性
这种方法的好处是,创建一个新的对象时,可以更改部分属性的值。
属性的增删查改
在Java
中,当实体类建立以后,类的属性只能获取与修改,不能增加与删除。但是因为JavaScript
是动态类型的语言,JavaScript
中对象的属性具有增删改查所有的操作。
属性的获取
方式一
属性的获取有两种方式,一种是使用.
符号,符号左侧是对象的名字,符号右侧是属性的名字,如下:
var student = {name:"Alice",gender:"girl"};
console.log(student.name);//输出Alice
这种情况下属性名必须是静态的字符串,即不能是通过计算或者字符串的拼接形成的字符串。
方式二
另外一种是使用[""]
符号,符号的左边是对象的名字,双引号中间是属性的名字,这种情况下属性名可以是一个表达式,只要表达式的值是一个字符串即可。如下:
var student = {name:"Alice",gender:"girl"};
console.log(student["name"]);//输出Alice
有两种情况必须使用第二种方式:
- 属性名含有空格字符,如
student["first name"]
,这时不能用student.first name
代替,编译器无法解释后者; - 属性名动态生成,比如用
for
循环获取前端连续id
的值,这种id
名之间一般有特定关系。如下面的例子:
for(int i = 0;i < 5;i ++) {
console.log(student["id"+i]);
}
属性的修改与新增
属性的修改指修改已有属性的值,这个直接用赋值符号即可。
属性的新增与修改在形式上完全相同,区别仅在于编译器会根据属性的名字判断是否有该属性,有则修改,没有则新增。
var student = { name:"Kim", age:21};
student.age = 20;//修改属性,覆盖了原来的值21
student.gender = "female";//新增属性gender
删除属性
JavaScript
中的属性还可以删除,这在其他的面向对象语言如Java
或者C++
中是无法想象的,删除通过delete
运算符实现。删除成功返回布尔型true
,删除失败也是返回true
,所以在删除之前需要判断一个属性是否存在,这个内容将在下一关讲解。
需要注意的是,对象只能删除自己特有的属性,而不能删除继承自原型对象的属性。同时,对象在删除属性时,要防止删除被其他对象继承的属性,因为这样会导致程序出错。
var Store = new Object();
Store.name = "lofo Market";
Store.location = "NO.13 Five Avenue";
console.log(delete Store.name);//删除成功,输出true
console.log(Store.name);//已删除,返回undefined
delete Store.prototype;//删除失败,非自有属性
属性的检测与枚举
属性的检测
属性的检测指检查对象是否有某个属性或者方法,需要使用运算符in
,in
的左侧是属性或者方法名,右侧是检查对象,对象有该属性或者方法则返回true
,否则返回false
,如下:
var school = {
name:"SJTU",
location:"ShangHai",
studentNum:40000,
display:function() {
console.log(this.name);
}
};
//检测属性
console.log("name" in school);//输出true
console.log("sales" in school);//输出false
//检测方法
console.log("display" in school);//输出true
console.log("print" in school);//输出false
这里的属性名是字符串,必须用双引号包含在内。
还可以用hasOwnProperty()
检测对象是否具有某个自有属性或方法。括号内的参数是属性或者方法的名字。
所谓自有属性或者方法,是指对象自己定义的属性或者方法,而不是从原型链上继承来的。关于原型链,请参考本实训第一关。
var school = {
name:"SJTU",
location:"ShangHai",
studentNum:40000,
display:function() {
console.log(this.name);
}};
console.log(school.hasOwnProperty("studentNum"));//true
console.log(school.hasOwnProperty("hasOwnProperty"));//false
因为hasOwnProperty
方法继承自object
对象,不是自有方法,所以返回false
。
属性的枚举
定义:属性的枚举指按顺序逐个的列出属性的名字。如下面的例子:
var person = { name:"Ye", gender:"Gril", age:23, salary:23000, height:1.78}
根据前面的知识,我们知道对象person
有五个属性,所谓枚举,就是依次列出这五个属性的名字,即:name、gender、age、salary、height
,至于它们排列的顺序,在不同的浏览器中的结果不同,这里不讨论。
在继续下面的知识点之前,首先要知道一个概念:可枚举性(enumerable
),这是对象的属性的一个性质,用户自己定义的属性默认为可枚举,系统内置的对象的属性默认为不可枚举。
枚举属性有三种方法:
-
for...in...
循环; 可以枚举所有可枚举的属性,包括继承的属性。如下:
//首先定义一个school对象,它从原型链上继承的属性都是不可枚举的,而下面自定义的四个属性或者方法都是可枚举的 var school = { name:"SJTU", location:"ShangHai", studentNum:40000, display:function() { console.log(this.name); }}; //枚举school的属性 //下面的圆括号中的att表示对象的属性,school表示对象 for(var att in school) { //依次输出name,location,studentNum,display console.log(att); }
圆括号里面的表达式中, att 表示对象的属性, school 表示该对象,这个循环将依次遍历对象的所有可枚举属性,每次输出一个属性的值。
-
Object.getOwnPropertyNames()
; 括号中有一个参数,是要枚举的对象。该表达式将返回对象的所有自有属性的名字,不区分是否可枚举,结果以字符串数组的形式呈现,如下://定义一个school对象 var school = { name:"SJTU", location:"ShangHai", studentNum:40000, display:function() { console.log(this.name); }}; //为school对象增加一个不可枚举的属性 rangeObject.defineProperty(school, "range", { value: 4,//设置range属性的值 enumerable: false//设置range属性为不可枚举 }); //输出["name","location","studentNum","display","range"] console.log(Object.getOwnPropertyNames(school));
如果用上面的
for...in...
循环,range
属性是不能够枚举到的。 -
Object.keys()
; 括号中有一个参数,是要枚举的对象。该表达式返回可枚举的自有属性,以字符串数组的形式。所以这里对属性的要求更加严格,既要求是自有属性,又要求可枚举。var school = { name:"SJTU", location:"ShangHai", studentNum:40000, display:function() { console.log(this.name); }}; //为school对象增加一个不可枚举的属性 rangeObject.defineProperty(school, "range", { value: 4,//设置range属性的值 enumerable: false//设置range属性为不可枚举 }); //输出["name","location","studentNum","display"] console.log(Object.keys(school));
总结一下上面三个方法对属性是否自有,是否可枚举的要求:
方法名 | 是否要求可枚举 | 是否要求自有 |
---|---|---|
for…in… | 是 | 否 |
Object.getOwnPropertyNames() | 否 | 是 |
Object.keys() | 是 | 是 |
五 JS数组
数组基本操作
数组的创建
创建数组有两种方法,一是使用数组字面量,简单来说就是在[]
之中列出数组的所有元素:
var numberArray = [1,2,3,4,5];//数字数组
var stringArray = ["java","script","edu","coder"];//字符串数组
var mixArray = [1,2,"java",true,6.6];//混合数组
如上,各元素之间用,
隔开。JavaScript
中数组的元素可以是不同的数据类型,如上面的第三个数组。
创建数组的第二种方法是新建一个Array
对象的实例,如:
var myArray = new Array();//创建一个初始为空的数组
var lengthMixArray = new Array(6);//创建一个长为6的数组
这种情况下可以设置数组的长度(即数组中元素的个数),也可以不设置。
数组元素的读取和写入
数组元素的读取和写入在形式上相似,都是用赋值符号连接的两个表达式。
读取时,存放读入值的变量在左边,数组元素在右边:
var readArray = [1,3,"js",true,2.22];
var read = readArray[0];//读取第一个元素到变量read中
写入时,数组元素在左边,待写值在右边:
var writeArray = [1,3,"js",true,2.22];
writeArray[0] = 2;//在第一个元素的位置写入2
console.log(writeArray[0]);//原来的1已经被覆盖,输出2
数组长度
数组长度指数组中元素的个数,等于最大索引值加1
,数组的length
属性即数组的长度。
var arrayLength = [1,"js",true];
console.log(arrayLength.length);//输出3
数组的长度也可以写入,当写入的值小于数组的实际长度时,数组会被删除一部分。大于实际长度时,数组会在尾部添加一些空的区域。
arrayLength.length = 2;
console.log(arrayLength);//输出[1,"js"]
数组元素的增加
在JavaScript
中,为数组增加元素可以在数组头部(索引最小处)或者尾部进行,可以使用数组的方法或者直接使用运算符。
在尾部添加元素
最直观的方法是直接给当前尾部元素的后一个位置赋值。
var numberArray = [12,23,34,45];
numberArray[numberArray.length] = 56;
console.log(numberArray);//输出[12,23,34,45,56]
第二种方法是使用push()
函数,往数组的末尾添加一个或多个元素,参数是要添加的元素,返回数组长度。
//利用push()方法在数组尾部添加元素
var numberArray = [12,23,34,45];
var newLength = numberArray.push(56);
console.log(newLength);//输出5
console.log(numberArray);//输出[12,23,34,45,56]
在头部添加元素
unshift()
方法在数组的头部添加元素,并返回数组新的长度,其余元素自动向索引大的方向移动。
var sArray = ["ja","va","script"];
var newLength = sArray.unshift("he","llo");
console.log(newLength)//输出5
console.log(sArray);//输出["he","llo","ja","va","script"];
数组元素的删除
删除也能在数组头部(索引值小)或者尾部进行。
在尾部删除元素
直接修改数组长度为更小的值。
var array = [1,2,true,"hello"];
array.length = 3;//索引最大的元素被删除
console.log(array);//输出[1,2,true]
第二种方法是使用delete
运算符。delete
运算符后接要删除的元素,但是删除后,会有一个空占位符,所以数据的长度保持不变。如:
var dArray = [11,22,33,44,55];
delete dArray[4];//删除索引最大的元素
console.log(dArray);//输出[11,22,33,44]
console.log(dArray.length); //长度为5
第三种方法是使用pop()
,一次删除一个,并返回被删除的元素。
//利用pop()方法在数组尾部删除元素
var numberArray = [3,4,5,6,7];
var deletedNumber = numberArray.pop();
console.log(deletedNumber);//输出被删除的元素7
console.log(numberArray);//删除后的数组为[3,4,5,6]
在头部删除元素
有unshift()
,自然有shift()
,shift()
的作用是删除数组头部一个元素并返回该元素,然后所有元素往索引值小的方向移动一位。
初学者很容易混淆这两个方法,建议记住shift
单词的意思是:删除,去掉。
var dArray = [11,22,33,44,55];
console.log(dArray.shift());//输出11,11被从数组中删除
console.log(dArray);//输出[22,33,44,55]
数组的遍历
数组的遍历指按顺序访问你数组的每一个元素。有两种方法:
- 使用
for
循环
//依次在浏览器的控制台输出one,two,three,four
var stringArray = ["one","two","three","four"];
for(var i=0,sLength=stringArray.length;i<sLength;i++) {
console.log(stringArray[i]);
}
- 使用
forEach()
方法forEach()
方法的参数是一个无名字的函数,函数有三个参数,第一个参数是当前的数组元素,第二个参数是当前的索引,第三个参数是数组对象的索引。与for
循环的区别是无法用break
中断循环。
var numArr = [10,11,12,13,14];
numArr.forEach(function(mem,i,arr) {
mem *= 10;
arr[i] = mem;
});
console.log(numArr);//输出[100,110,120,130,140]
多维数组的实现
多维数组实际上就是数组的数组,指数组的每一个元素也是一个数组,这里仅讨论二维数组。
JavaScript
中二维数组的列的长度不唯一,第一列可以有4
个元素,第二列可以有5
个元素,等等。
- 二维数组的创建 创建已知的二维数组:
var multiArr = [[1,2,3,4],[5,6,7],[8,9]];
创建仅知道长度的二维数组
//创建一个4行6列的二维数组
var muArr = new Array(4);
for(var i = 0;i <4;i++) {
muArr[i] = new Array(6);
}
- 二维数组的读写 二维数组的读写用
数组名[][]
的方式,第一个中括号内为行数,从0
计数,第二个中括号内为列数,也从0
计数。 以上面的数组multiArr
为例:
var multiArr = [[1,2,3,4],[5,6,7],[8,9]];
console.log(multiArr[1][1]);//读元素,输出6multiArr[0][0] = 0;//写元素
数组常用方法
查找元素的位置
根据值查找元素的位置,有两个方法:indexOf()
和lastIndexOf()
,前者从索引小处往大搜索,后者相反。都返回第一次遇到该元素时的索引。
两者都有两个参数,第一个参数为要查找的元素,第二个参数可选,为搜索的起点索引。如:
var search = ["a","b","a","b","c","d","a","a","b","a"];
console.log(search.indexOf("a"));//输出0
console.log(search.lastIndexOf("a"));//输出9
console.log(search.indexOf("a",2));//输出2,从索引为2处开始搜索
第二个参数可以是负数,-1
表示倒数第一个元素,-2
表示倒数第二个元素,依次类推。如:
var search = ["a","b","a","b"];
console.log(search.indexOf("a",-3));//输出2
console.log(search.lastIndexOf("a",-3));//输出0
数组的合并
concat()
实现数组合并,其形式是数组a.concat(数组b)
,合并之后返回新数组,新数组为数组a
后面连接数组b
,但是数组a
和b
不变。
var a = [1,2,3];
var b = [4,5,6];
var c = a.concat(b);//合并后返回新数组
console.log(c);//输出[1,2,3,4,5,6]
数组倒置
reverse()
实现数组倒置,无参数,返回倒置后的数组,同时调用该方法的数组也会被倒置。称为就地逆置。
var a = [1,2,3,4];
var b = a.reverse();
console.log(a);//输出[4,3,2,1]
console.log(b);//输出[4,3,2,1]
元素合并
join()
将数组的所有元素连接起来组成字符串,参数为元素之间的分隔符,默认逗号。
var sArray = ["June","July","August"];
console.log(sArray.join());//输出June,July,August
console.log(sArray.join("+"));//输出June+July+August
元素排序
sort()
实现数据元素排序,不带该参数表示元素按照ASCII
表从小到大排序(参考JavaScript
学习手册三)。如:
var stringArray = ["a","ab","b","aa"];
stringArray.sort();
console.log(stringArray);//输出["a","aa","ab","b"]
需要注意的是数字的排序,例子如下:
var arr = [1,2,10,5,12];arr.sort();console.log(arr);//输出[1,10,12,2,5];
带参数的格式如下:
arr.sort(function(a,b){ return a-b; //升序排列})
或者:
arr.sort(function(a,b){ return b-a; //降序排列})
说明:
arr
是要排序的数组;a
,b
是两个参数,返回a-b
,升序排列,返回b-a
,降序排列。
对于数字的排序,sort()
带参数和不带参数是不一样的,例子如下:
var arr = [1,2,10,5,12];
arr.sort();
console.log(arr);//输出[1,10,12,2,5]
arr.sort(function(a,b){
return a-b;
});
console.log(arr);//输出[1,2,5,10,12]
提取子数组
slice()
返回切割出的子数组,不修改原来的数组。
它有两个整数参数a
和b
,a
表示切割的起点,该点属于子数组;b
可选,表示切割的终点,该点不属于子数组。
a
和b
都可以为负数,如-1
表示倒数第一个位置,依次类推。
var arr = ["a","b","c","d","e"];
console.log(arr.slice(0,3));//["a","b","c"]
console.log(arr.slice(0,-2));//["a","b","c"]
console.log(arr.slice(4));//["e"]
console.log(arr.slice(-4));//["b","c","d","e"]
六 JS条件语句
这个太简单了,又和其他语言一样,就放个小标题吧
if - else
switch
七 JS循环语句
下面基础的三个也跳过吧。
while类型
do while类型
for类型
for in 类型
JavaScript
的for in
循环主要用于枚举对象的可枚举属性名
对象类型是键值对的集合,键指的是属性的名字,值指的是属性的值。
for in
除了枚举对象自己拥有的可枚举属性外,还会枚举继承的可枚举属性。
var orange = {
color:"orange",
weight:200,
location:"GanZhou",
date:"October"
};
for(var att in orange) {
console.log(att);//依次输出color,weight,location,date
}
八 JS函数
定义函数
函数语句定义函数
(这个是常规操作啦,举个例子)
function sumArray(arr) {
var sum = 0;
for(var i = 0,aLength = arr.length;i < aLength;i++) {
sum += arr[i];
}
return sum;
}
表达式定义函数
用表达式的方式定义函数,就是用赋值表达式把函数赋值给一个变量,这其实就是把函数看成一个变量。这个时候函数可以有名字,也可以没有名字,没有名字的函数叫做匿名函数。
- 带名字的;
var funct = function getMax(a,b) {
return a>b?a:b;
};//注意这后面的分号不能少,因为我们定义的是一个变量!
和前一关不同的是,只能在函数定义语句之后调用该函数,且调用的时候只能用变量名funct
,不能用函数名getMax
,如:
var funct = function getMax(a,b) {
return a>b?a:b;
};
console.log(funct(1,2));//输出2
- 匿名函数; 所谓匿名函数就是关键字
function
之后直接是参数列表:
var funct = function(a,b) {
return a>b?a:b;
};
这个函数没有名字,它被赋值给了变量funct
,所以叫匿名函数。同样,也只能在这一语句之后调用该函数。
var funct = function(a,b) {
return a>b?a:b;
};
console.log(funct(1,2));//输出2
函数的调用
//函数的定义:求三个数的最大值
function max(a,b,c) {
if(a > b) {
if(a > c)
return a;
else
return c;
}
else {
if(b > c)
return b;
else
return c;
}}
//调用该函数
var result = max(1,2,3);//result为3
调用函数的时候,需要传入和形参相同个数的的具体值,上面的函数有3
个参数,所以下面调用的时候传入3
个具体的值,1
传给参数a
,2
传给参数b
,3
传给参数c
。函数的返回值通过赋值符号=
传给了变量result
。如果函数体内没有return
关键字,将返回undefined
。
再来看一下对象里定义的函数的调用:
var ob = {
id:1,
getMax:function(a,b) {
return a>b?a:b;
}
};
var result = ob.getMax(2,1);//result值为2
var result1 = ob["getMax"](2,1);//result1的值也是2
与上面的区别是,这里要定位到函数,需要使用对象名.函数名
或者对象名["函数名"]
,其它相同。
函数的参数
未定义的实参
函数的基本功能是对函数内的参数进行操作,其中,函数定义时的参数被称为形式参数,函数被调用时传入的参数被称为实际参数。
在大部分的编程语言里面,都会对调用函数时传入的实参个数和类型进行检查,而JavaScript
既不检查实参的类型,也不检查实参的个数。 JavaScript
中的实参会按照顺序从左到右依次匹配上形参,例如:
function myFunction(a,b,c) {
console.log(a);
console.log(b);
console.log(c);
}
myFunction(1,2,3);
实参1
传入形参a
,实参2
传入形参b
,实参3
传入形参c
。 当实参个数少于形参时,靠右的形参会被传入值undefined
。如:
function myFunction(a,b,c) {
console.log(a);
console.log(b);
console.log(c);
}
myFunction(1,2);
实参1
传入形参a
,实参2
传入形参b
,undefined
传入形参c
。 如果只想给右侧的参数传入数据,可以给前几个实参传入undefined
。如:
function myFunction(a,b,c){
console.log(a);
console.log(b);
console.log(c);
}
myFunction(undefined,1,2);
上面这两种做法不够严谨,最佳实践是给可能被传入undefined
值的形参设定一个默认值。如:
function getSum(a,b,c) {
if(c === undefined)
c = 0;
console.log(a+b+c);
}
myFunction(1,2);
实参对象
JavaScript
一切都是对象,实参也是一个对象,有一个专门的名字arguments
,这个对象可以看成一个数组(类数组,不是真的数组),实参从左到右分别是arguments[0]、arguments[1]...
,arguments.length
表示实参的个数。
实参对象一个最重要的应用是可变长参数列表,想象一下求一组数的和,如果这组数不在一个数组里面,使用函数来求则无法定义函数体,因为不知道形参的个数。这个时候就可以用arguments
来解决问题。如:
//求参数的和
function getSum() {
var aLength = arguments.length;
var sum = 0;
for(var i = 0;i < aLength;i++) {
sum += arguments[i];
}
return sum;
}
console.log(getSum(1,2,3,4,5))//输出15
这里的形参直接省略,使用arguments[i]
表示。
对象作为参数
复杂的函数通常多达十几个参数,尽管JavaScript
不做参数个数和类型的检查,但是调用时实参的顺序不能乱。开发人员需要检查每一个实参和形参的对应关系,这样效率很低。一种很好的解决方案是使用对象作为参数,函数会根据对象的属性名操作参数。
function myFunction(obj) {
console.log(obj.name);
obj.number++;
return obj.number;
}
myObj = {name:"myObj",number:34};myFunction(myObj);//输出myObj
console.log(myObj.number);//输出35
这种情况下开发人员不需要记住或查阅形式参数的顺序。
JavaScript
中一切都是对象,这句话同样适用于函数。函数对象可以作为函数的参数。
函数对象作为另一个函数的参数
一个函数(为方便行文,称为a
函数)可以作为另外一个函数(称为b
函数)的参数,b
函数最终可以返回一个具体的值。
从原理上来说,b
函数在自己的函数体内调用了a
函数,所以需要把a
函数的名字作为实际参数传递给b
函数。如下:
//求最大值
function getMax(a,b) {
return a>b?a:b;
}
//求最小值
function getMin(a,b) {
return a<b?a:b;
}
//下面这个函数以函数作为参数,并最终返回一个值
function getM(func,num1,num2) {
return func(num1,num2);
}
getM(getMax,1,2);//返回2
getM(getMin,1,2);//返回1
九 JS字符串
indexOf()
子字符串指一个字符串中连续的一部分。
上图中有两个字符串aaabc
和abc
,将abc
从左往右在aaabc
中查找,第一次出现的位置是在aaabc
的索引2
处。
indexOf()
就是返回某个字符串在字符串中首次出现的位置。如果搜索到尾部还没有找到,返回-1
。
indexOf(a,b)
中参数a
是字符串;b
是开始查找的位置,即从调用者(一个字符串)的第几个位置开始查找,可选。
lastIndexOf()
从名字上就可以知道,这个函数与indexOf()
功能很相似,区别是搜素方向是从后往前搜索。
lastIndexOf()
也有两个参数,含义同indexOf()
。
var short = "ab";
var long = "aabccccaab";
var mix = "cdef";
long.lastIndexOf(short);//返回8
long.lastIndexOf(short,4);//返回1
long.lastIndexOf(mix);//返回-1
charAt()
charAt()
作用是返回调用者指定位置的字符,位置从0
计数:
var str = "abcdefg";
console.log(str.charAt(0));//输出a
console.log(str.charAt(str.length-1));//输出g
slice()
slice(a,b)
的作用是截取a
位置(含)到b
位置(不含)之间的字符串,被截取的字符串不变,返回截取后获得的子字符串。比如a
是0
,b
是3
,则截取0、1、2
三个位置的字符。
b
为可选参数,不填则表示截取到字符串的尾部。a
或b
为负数时表示从右往左排,即-1
表示最右侧的位置,依次类推。
var str = "0123456789";
console.log(str.slice(0,4));//输出0123
console.log(str.slice(3));//输出3456789
console.log(str.slice(-3));//输出789
console.log(str.slice(-5,-2));//输出567
substring()
substring(a,b)
与slice(a,b)
功能相同,参数的意义也相同:a
和b
都是表示位置的数字。只是参数的处理有些不同:
a
或b
为负数时,自动转换为0
;a
大于b
时,编译器会自动对调两者。
var str = "0123456789";
console.log(str.substring(0,4));//输出0123
console.log(str.substring(-1,4));//输出0123
console.log(str.substring(4,0));//输出0123
substr()
与上面的两个函数不同,substr(a,b)
指定开始位置和要截取的长度。
a
表示开始位置(包含);b
表示截取的长度,可选。不填则截取到字符串结尾处;
a
为负数时表示从右往左排,即-1
表示最右侧的位置,依次类推。
var myStr = "0123456789";
console.log(myStr.substr(3));//输出3456789
console.log(myStr.substr(3,2));//输出34
console.log(myStr.substr(-2));//输出89
toLowerCase()
toLowerCase()
把字符串中的所有大写英文字母转为小写,返回转换后的字符串,但是操作该函数的字符串不变。
var upperStr = "aBCd";
var lowerStr = upperStr.toLowerCase();
console.log(upperStr);//输出aBCd
console.log(lowerStr);//输出abcd
toUpperCase()
与上面的函数相反,toUpperCase()
把字符串中的所有小写英文字母转为大写,返回转换后的字符串,但是操作该函数的字符串不变。
var str = "asdf";
var strin = str.toUpperCase();
console.log(str);//输出asdf
console.log(strin);//输出ASDF
split()
还记得数组对象的方法join()
吗?
字符串的方法split()
与join()
正好相反,split()
以指定的字符分割字符串,从而得到一个子字符串数组。
比如字符串a,b,c,d,e,f
以逗号为分隔符,就可以得到数组["a","b","c","d","e","f"]
。
split(a,b)
中的参数a
是分割符,它的含义是:原来的字符串以该分隔符为边界,分为若干个子字符串(不含该分隔符)。b
表示返回的数组的最大长度,不填表示返回所有子字符串组成的数组。
如果要实现分割每一个字符,需要用空字符串""
作为分隔符。
var str = "012304560789";
var arr1 = str.split("");//返回["0","1","2","3","4","5","6","7","8","9]
var arr1 = str.split("0");//返回["123","456","789"];
var arr2 = str.split("0",2);//返回["123","456"];
后面的学习中我们将看到,a
也可以是一个正则表达式,表示以该正则表达式匹配到的字符串为分隔符。
十 JS正则表达式
正则表达式的定义
正则表达式:能够定义一类具有相同特征的字符串的式子。
在JavaScript
中,正则表达式有两种定义方法:
创建RegExp对象的实例
创建RegExp
对象的一个实例:
var myFirstPattern = new RegExp("s$");
上面等号右侧定义了一个正则表达式,并赋值给变量myFirstPattern
。
使用//符号定义
更简单的方法是直接用//
符号创建正则表达式:
var easyPattern = /s$/;
这里的easyPattern
和上面的myFirstPattern
含义一模一样。
匹配
上面的正则表达式表示的是这样一类字符串:以字母s
结尾的字符串,比如字符串apples
就符合这个要求,我们把符合要求叫做匹配。
需要注意的是,字符串和正则表达式匹配,指的是字符串的一部分(子串)和正则表达式匹配即可,比如/s/
匹配的是字母s
,一个单词如果含有字母s
,那么就和这个正则表达式匹配。
通过方法test()
可以检测字符串和正则表达式是否匹配,括号内的参数是待匹配的字符串,如果匹配返回true
,否则返回false
,如下:
var pattern = /s/;
pattern.test("s");//匹配,返回true
pattern.test("asa");//匹配,返回true
pattern.test("aa");//不匹配,返回false
使用正则表达式的关键是正确编写表达式,一个设计良好的正则表达式可以表示一类非常复杂的字符串。为了实现这种复杂的表示,正则表达式提供了字符串字面量、字符类、重复、选择、分组、引用、指定匹配位置、修饰符等功能,这些将在本实训的剩余部分逐条讲述。
字符串字面量
正则表达式里面的数字和英文字母都是按照本来的含义匹配的,即aa
匹配的就是字符串aa
。123
匹配的就是数字123
。这些数字和字母称为字符串字面量。
var pattern = /aa/;
pattern.test("aa");//true
pattern.test("aabb");//true
pattern.test("abb")//false
一些特殊的符号,比如换行符、换页符,需要用斜杠加字母表示。比如\n
匹配的是换行符,下面列出一些常用的特殊符号:
字符 | 匹配 |
---|---|
\n | 换行符 |
\f | 换页符 |
\t | 制表符 |
\v | 垂直制表符 |
\r | 回车符 |
前一关阐述了字符串字面量的使用,一种更常见的需求是匹配一类字符:比如选择题的答案只能是ABCD
中的任何一个。正则表达式用字符类来实现,字符类被放在中括号之内。[ABCD]
表示一个字符类,它和A、B、C、D
中的任何一个都匹配。
还可以用[A-D]
表示上面的字符类,中间的-
表示A
到D
之间的所有字符。
var lowerCharPattern = /[a-z]/;//匹配任意小写字母
var upperCharPattern = /[A-Z]/;//匹配任意大写字母
var numberPattern = /[0-9]/;//匹配任意数字
var mixPattern = /[a-zA-Z0-9]/;//匹配大小写字母,数字
在中括号内最前面加上^
符号表示反向匹配:匹配和中括号内的正则表达式不匹配的所有字符,比如:
var notNumberPattern = /[^0-9]/;
notNumberPattern.test("123");//false
notNumberPattern.test("ahc");//true
[^0-9]
匹配的是任意非数字的字符。其中^
符号一定要在中括号内排在最前面。
字符类第二种表示
字符类还有一种较为简单的表示方法,比如\d
和[0-9]
的含义相同:表示任意的数字。下面用表格列出:
字符 | 匹配 | 等价于 |
---|---|---|
\w | 大小写字符或数字 | [a-zA-Z0-9] |
\W | 非字母,非数字 | [^a-zA-Z0-9] |
\d | 数字 | [0-9] |
\D | 非数字 | [^0-9] |
上面的内容都是单独的一个字面量或者字符类,对于稍微复杂一点的,比如一个数字后面紧跟着一个小写字母,该怎么表示呢?来看一个例子:
//表示数字后面紧跟着一个小写字母
var pattern = /[0-9][a-z]/;
pattern.test("1a");//true
pattern.test("11a");//true
pattern.test("a1");//false
从上面可以看出,表示数字的正则表达式[0-9]
和表示小写字母的正则表达式[a-z]
直接连接起来,即表示数字后面紧跟小写字母。其他的字符串字面量或者字符类也可以用这种方式。
重复
重复表示指定的字符或者字符串(本关可以简单理解为前面紧邻的字符)可以连续出现多次。比如匹配含有100
个字母a
的字符串,在这个字符串中,a
连续出现100
次,用正则表达式表示为:
var pattern = /a{100}/;
//匹配100个连续的字母a组成的字符串
有多种表示重复的方法:
{a,b}
中的a
和b
都是数字,表示前面的字符至少出现a
次,最多出现b
次;
var pattern = /at{1,2}/;//表示a后面最少一个t,最多两个t
pattern.test("at");//true
pattern.test("att");//true
pattern.test("am");//false
-
{a,}
表示前面的字符至少出现a
次,最多不限制; -
{a}
表示前面的字符出现a
次; -
?
,表示前面的字符出现一次或者不出现,等价于{0,1}
; -
+
,表示前面的字符至少出现一次,等价于{1,}
; -
*
,表示前面的字符至少出现0
次,等价于{0,}
;
需要特别注意的是,至少出现0
次,就是说这个字符可以不出现,比如正则表达式[0-9]{0,}
和字符串hello
是匹配的,这里特别容易出错。
总结一下重复匹配的用法,如下:
表达式 | 匹配 | 等价于 |
---|---|---|
{a,b} | 至少出现a次,最多出现b次 | |
{a,} | 至少出现a次 | |
{a} | 出现a次 | {a,a} |
+ | 最少出现一次 | {1,} |
? | 出现一次或者不出现 | {0,1} |
* | 至少出现0次 | {0,} |
转义
对于?
、+
等表示特殊含义的字符,如何实现字面量匹配呢?就是说如何匹配他们本来的含义呢?
在JavaScript
中,使用\
实现特殊符号的普通化,又叫做转义:
var pattern1 = new RegExp("\?");//匹配一个问号
var pattern2 = /\+{4}/;//匹配四个加号
选择
选择使用符号|
来实现,比如0|1
表示匹配0
或者1
,\d|a
表示匹配数字或者字母a
。
所谓匹配指的是匹配字符串的某一个子串。但是涉及到选择的时候情况就会有些复杂,比如正则表达式[0-9]|[a-z]
匹配的是字符串1ABCa
中的子串1
还是a
?
实际上JavaScript
会先挑选左边的子正则表达式[0-9]
进行匹配,匹配成功后立即结束,所以匹配上的子串是1
。
var pattern1 = /[ABCD]|[abcd]/;//匹配ABCD中任意一个或者abcd中任意一个pattern1.test("A");//true
pattern1.test("Ab");//true,且匹配的是A
var pattern2 = /\d|\+/;//匹配数字或者+号
pattern2.test("1");//true
pattern2.test("+");//true
选择符号作用范围
|
作用到整个式子中的,即整个的正则表达式会被选择符号一分为二,一个字符串和该表达式匹配,要么匹配左侧的整个子正则表达式,要么匹配右侧的整个子正则表达式,如:
var pattern = /0|1ABC/;
pattern.test("0");//true,和左侧的整个子正则表达式0匹配
pattern.test("1");//false,和右侧的整个子正则表达式1ABC不匹配
如果想要限制|
符号的作用范围,需要将目标作用范围用圆括号包含在内,如:
var pattern = /(0|1)ABC/;//选择符号仅仅作用在0和1上,而不是像上面的例子一样作用在整个正则表达式中pattern.test("0");//false,注意这里和上面的例子不同
pattern.test("0ABC");//truepattern.test("1ABC");//true
子表达式
我们把一个完整的正则表达式的一部分叫做子正则表达式,或者子表达式。
分组
前面讲过的正则表达式的写法中,重复只能作用在紧邻符号的前面***一个***字符上,比如:
var pattern = /hello{3}/;
pattern
表达的意思是字母o
必须重复三遍,而不是单词hello
必须重复三遍。
分组的符号是()
,括号中是单独的项构成的子表达式,将这些子表达式看成一个整体进行操作,比如:
var pattern = /(hello){2}/;//匹配字符串hellohello
pattern.test("shellohellos");//返回true
pattern.test("helloo");//返回false
编号
什么时候需要用到引用?
比如需要匹配这样一类字符串:以数字开头,中间是若干个字母,以数字结尾,并且开头的数字和结尾的数字相同,这个时候用前面所有介绍过的方法都不可行,无法确保开头的数字和结尾的数字相同。
所谓引用,就是在后面使用和前面完全相同的子表达式。我们把所有的带圆括号的子表达式编个号,从1
开始。比如:
var pattern = /(A|B)(\d{5})not([0-9])/;//匹配字母A或者B后面紧跟5个数字,后面再紧跟字符串not,后面再紧跟1个数字
其中(A|B)
编号为1
,(\d{5})
编号为2
,([0-9])
编号为3
,中间的not
不在圆括号内,不参与编号。
一个小问题:如果子表达式里面嵌套另外一层子表达式,引用时如何编号?简单来说,以子表达式前面的左括号的个数为准:
var pattern = /(play(ed|ing)){1,}/;
ed|ing
前面有两个左括号,所以编号为2
。
引用
后面可以用\1
引用编号为1
的子表达式,依次类推,比如:
var pattern = /(A|B)(\d{5})not([0-9])\1\2/;
pattern
在最后引用了第一个和第二个子表达式。
注意:* 这里的引用是对与子表达式匹配的字符串的引用*,而不是简单的对子表达式的引用。例如:
var pattern = /([0-9])AA\1/;
pattern
不等价于正则表达式([0-9])AA[0-9]
,而是指字符串AA
后面的数字必须和前面的相同,即形如1AA1
这样的字符串。
再看一个例子:
var pattern = /([a-z])([a-z])([a-z])\3\2\1/;
pattern.test("abccba");//返回true
pattern.test("123eduude456");//返回true
pattern.test("abcdefg");//返回false
在上面的正则表达式pattern
里面,我们先引用第三个子表达式,表示此处的字符串必须和第三个子表达式相同,然后引用第二个子表达式,最后引用的是第一个子表达式,所以pattern
匹配一个回文串,即顺序读取和倒序读取结果相同的字符串。
指定匹配位置
^
用来匹配字符串的开头,比如:
var startPattern = /^[0-9]/;//匹配以数字开头的字符串console.log(startPattern.test("1aa"));//trueconsole.log(startPattern.test("a11"));//false
注意,^
符号在中括号的外面!不要与[^0-9]
混淆,后者匹配非数字字符。
$
用来匹配字符串的结尾,比如:
var endPattern = /ing$/;//匹配以ing结尾的字符串console.log(endPattern.test("playing"));//trueconsole.log(endPattern.test("going first"));//false
\b
用来匹配单词的边界,那么什么是单词的边界?
图片中蓝色的线指示了单词的边界,实际上就是英文字母和非英文字母(如空格符)之间的界限。
var boundaryPattern = /\bOK\b/;//匹配单词OKconsole.log(boundaryPattern.test("OK"));//trueconsole.log(boundaryPattern.test("rewa OK de"));//trueconsole.log(boundaryPattern.test("sa,OK"));//trueconsole.log(boundaryPattern.test("OKNot"));//false
简单来说,\b
表示英文字母和非英文字母之间的界限,这个界限不占用字符的位置。
\B
用来匹配非单词的边界,与上面的\b
相反。
var pattern = /\Bed/;//ed左侧不能是单词的边界console.log(pattern.test("played"));//trueconsole.log(pattern.test("edu"));//false
修饰符
修饰符用来描述一些整体的匹配规则,有i
、g
、m
三种。
修饰符需要放在//
符号之后,如果通过新建RegExp
对象定义正则表达式,则修饰符作为第二个参数。比如:
var pattern1 = /^java/m;var pattern2 = new RegExp("^java","m");
不区分大小写
i
表示整个的匹配过程中不考虑单词的大小写。如:
var pattern = /^edU/i;console.log(pattern.test("edu"));//输出true
console.log(pattern.test("Edu"));//输出true
console.log(pattern.test("EDUCoder"));//输出true
全局匹配
g
表示执行全局匹配,即找出所有满足匹配的子字符串。比如,已知match()
函数返回由匹配结果组成的数组,如果没有匹配到返回null
。
不用g
修饰时:
var pattern = /[a-z]/;//匹配小写字母
console.log("a1b2c3".match(pattern));//输出["a", index: 0, input: "a1b2c3"]
显然,只匹配到了第一个小写字母a
。
用g
修饰时:
var pattern = /[a-z]/g;//全局匹配小写字母
console.log("a1b2c3".match(pattern));//输出["a", "b", "c"]
三个小写字母都被匹配到了,这就是所谓的全局匹配。
多行模式
有的时候,需要匹配的字符串很长,分为很多行(即中间有换行符号)。
m
在多行模式中执行匹配,即:符号^
不仅匹配整个字符串的开头,还匹配每一行的开头,&
不仅匹配整个字符串的结尾,还匹配每一行的结尾。
var pattern = /[0-9]$/m;//多行匹配以数字结尾的字符串
var string = "1\nhello";//字符串在两行,中间的\n是换行符
console.log(pattern.test(string));//输出true
如果没有m
修饰,将会输出false
,因为这种情况下$
不会和换行符\n
匹配。
正则表达式的使用
search(a)方法
参数:a
为正则表达式。 功能:返回字符串中与该正则表达式匹配的第一个子串的起始位置,无匹配返回-1
。
var pattern = /[0-9]/;
var string = "a3b2c1";
console.log(string.search(pattern));//输出1
split(a)方法
参数:a
是字符串或者正则表达式; 功能:以a
为分隔符分隔原来的字符串; 返回值:分割后形成的子字符串数组。
console.log("a1b2c3d".split(/[0-9]/));//以数字为分隔符,输出["a", "b", "c", "d"]
replace(a,b)方法
参数:a
是正则表达式,b
是字符串; 功能:用b
替换掉第一个和a
匹配的子串,如果a
中有修饰符g
,替换掉所有子串; 返回值:被替换后的字符串。
var pattern1 = /[0-9]/;
var pattern2 = /[0-9]/g;
var string = "a1b2c3";
console.log(string.replace(pattern1,"A"));//部分替换,输出aAb2c3
console.log(string.replace(pattern2,"A"));//全部数字被替换,输出aAbAcA
复杂的情况:b
还可以是子表达式$1
、$2
等,$1
等会先被替换为与它匹配的子串。比如:
var pattern = /([0-9])[A-Z]/g;
var string = "1A,2B,3C,1";
console.log(string.replace(pattern,"$1"));//输出1,2,3,1
上面的$1
指的是与子表达式[0-9]
匹配的子字符串,比如第一个匹配1A
中$1
指的是1
,第二个匹配2B
中$1
指的是2
,依次类推。
string
中与pattern
匹配的有1A
、2B
、3C
,这其中与子表达式$1
匹配的分别是1
、2
和3
,所以$1
会相继被替换为1
、2
和3
,然后再用1
、2
和3
去分别替换1A
、2B
、3C
。
附 :正则表达式习题
pattern1
匹配这样一类字符串:?
至少出现一次;pattern2
匹配这样一类字符串:+
只出现三次;pattern4
匹配这样一类字符串:\
出现一次或者不出现;
// var pattern1 = new RegExp("\?+"); //为什么好像不行?
var pattern1 = /\?+/;
// var pattern2 = new RegExp("\+{3}");
var pattern2 = /\+{3}/;
// var pattern4 = new RegExp("\\{0,1}");
var pattern4 = /\\{0,1}/;
- 创建正则表达式
pattern1
,匹配这样的字符串:?+
需要连续出现至少两次; - 创建正则表达式
pattern2
,匹配这样的字符串:数字+
问号+
数字,或者数字+
加号+
数字,比如1?1
、1+4
、asd0+3asdfa
;
var pattern1 = /(\?\+){2,}/;
var pattern2 = /[0-9](\?|\+)[0-9]/;
- 创建正则表达式
pattern1
,匹配这样的字符串:三个数字加一个非数字字符再加三个数字,字符两侧的数字相同,如123A123
、234,234
; - 创建正则表达式
pattern2
,匹配这样的字符串:大写字母+
数字+
大写字母+
数字+
大写字母+
数字,三个数字相同,如A2B2C2
、F9D9K9
;
var pattern1 = /([0-9]{3})(\D)\1/;
var pattern2 = /[A-Z]([0-9])[A-Z]\1[A-Z]\1/;
pattern
匹配以单词js
开头的一段文本:这段文本以js
开头,且这个js
是一个单词,比如js is good
、js's history
符合要求,而js2
、jsjs
不符合要求;
var pattern = /^js\b/;
pattern
能够匹配一段英文中所有的单词shell
,不区分大小写,该段英文在一行中;
var pattern = /\b(shell)\b/ig;
- 一段成绩单是类似这样的字符串:
Sivan:99,Kathy:100,Eray:8,
,即:人名+
冒号+
成绩+
逗号,成绩范围是0
到100
,人名是英文; - 使用
replace()
方法删掉其中的分数,保留其他所有字符串,比如上面的最终结果应该是Sivan:,Kathy:,Eray:,
;
var pattern = /\b([a-zA-Z]+:)([0-9]+)/g;
var abc = a.replace(pattern,"$1");
关于
本篇笔记整理自移动云启-前端精品课 JavaScript学习手册前十章
来源地址:
移动云启-前端精品课