JavaScript学习笔记(一)

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 图 1

这里的标签是 HTML 中的内容,关于 JavaScript 和 HTML 的关系,将在下一关介绍。

第 2 种:控制台模式

通过 console 输出到控制台,在 script 标签下,调用 console.log() 函数,括号内填入输出的内容,以浏览器打开下面的代码后,按 F12 键即可在浏览器的控制台中实现输出。

<!DOCTYPE html>
<html>
    <body>    
        <script>
            console.log("Hello,World!");   
        </script>   
    </body>
</html>

在浏览器的控制台可以看到如图2所示的内容:

图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 图 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,这时studentCstudentA指向内存中的同一个地址块,视为同一个对象,所以两者相等。

  • 不等 对应于上面的等号,不等号也有两种:!=!==!===互为相反,==成立,!=一定不成立。 !=====互为相反,严格相等成立,则严格不相等不成立。

  • 其它 大于,小于,大于等于,小于等于的比较规则如下: 比较的两个对象都是数字,按照数学中数字的比较方法。 数字和字符串比较,字符串转为数字后再比较。 字符串和字符串比较,从第一个字符开始,逐个比较,发现不相等立即返回。字符按照ASCII编码值的大小比较,一般只要记住:数字<大写字母<小写字母,字母a小于字母zA小于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
逻辑运算符

在介绍逻辑运算符之前,我们必须明确逻辑运算符的操作数只能是布尔型,其他类型都会被转换为布尔型:除了0nullundefined""外,其他的值转换为布尔值都是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
4in测试属性是否存在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中的一切都是对象,这是该语言的一个很大的特点。像字符串、数组等已经定义的对象叫做内置对象。用户自己也可以定义对象,叫做自定义对象。本实训讲的对象特指自定义对象,自定义对象指数据和函数(又叫方法)的集合。数据指变量名和变量的值构成的组合。如下图所示:

img

对象的创建

下面介绍五种创建对象的方法,其中通过对象字面量和使用构造函数创建对象最常用。

对象字面量

这是最常用的创建对象的方法,通过新建一个键值对的集合(对象字面量)创建对象,如下:

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]],该属性的值就是上面提到的原型对象。如下图所示:

img

JavaScript中每个对象都有一个属性[[Prototype]],指向它的原型对象,该原型对象又具有一个自己的[[Prototype]],层层向上直到一个对象的原型为null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。如下图所示:

img

这种方法是对使用构造函数创建对象的改进,使用构造函数创建一个对象时,会把构造函数中的方法(上面的构造函数只有属性的键值对,没有方法)都创建一遍,浪费内存,使用原型不存在这个问题。

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;//删除失败,非自有属性
属性的检测与枚举
属性的检测

属性的检测指检查对象是否有某个属性或者方法,需要使用运算符inin的左侧是属性或者方法名,右侧是检查对象,对象有该属性或者方法则返回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,但是数组ab不变。

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是要排序的数组;
  • ab是两个参数,返回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()返回切割出的子数组,不修改原来的数组。

它有两个整数参数aba表示切割的起点,该点属于子数组;b可选,表示切割的终点,该点不属于子数组。

ab都可以为负数,如-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 类型

JavaScriptfor 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传给参数a2传给参数b3传给参数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传入形参bundefined传入形参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()

子字符串指一个字符串中连续的一部分。

img

上图中有两个字符串aaabcabc,将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位置(不含)之间的字符串,被截取的字符串不变,返回截取后获得的子字符串。比如a0b3,则截取0、1、2三个位置的字符。

b为可选参数,不填则表示截取到字符串的尾部ab为负数时表示从右往左排,即-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)功能相同,参数的意义也相同:ab都是表示位置的数字。只是参数的处理有些不同:

  • ab为负数时,自动转换为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匹配的就是字符串aa123匹配的就是数字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]表示上面的字符类,中间的-表示AD之间的所有字符。

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}中的ab都是数字,表示前面的字符至少出现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用来匹配单词的边界,那么什么是单词的边界?

img

图片中蓝色的线指示了单词的边界,实际上就是英文字母和非英文字母(如空格符)之间的界限。

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
修饰符

修饰符用来描述一些整体的匹配规则,有igm三种。

修饰符需要放在//符号之后,如果通过新建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匹配的有1A2B3C,这其中与子表达式$1匹配的分别是123,所以$1会相继被替换为123,然后再用123去分别替换1A2B3C

附 :正则表达式习题
  • 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?11+4asd0+3asdfa;
var pattern1  = /(\?\+){2,}/;
var pattern2 = /[0-9](\?|\+)[0-9]/;    
  • 创建正则表达式pattern1,匹配这样的字符串:三个数字加一个非数字字符再加三个数字,字符两侧的数字相同,如123A123234,234
  • 创建正则表达式pattern2,匹配这样的字符串:大写字母+数字+大写字母+数字+大写字母+数字,三个数字相同,如A2B2C2F9D9K9
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 goodjs's history符合要求,而js2jsjs不符合要求;

var pattern = /^js\b/;

pattern能够匹配一段英文中所有的单词shell,不区分大小写,该段英文在一行中;

var pattern = /\b(shell)\b/ig;
  • 一段成绩单是类似这样的字符串:Sivan:99,Kathy:100,Eray:8,,即:人名+冒号+成绩+逗号,成绩范围是0100,人名是英文;
  • 使用replace()方法删掉其中的分数,保留其他所有字符串,比如上面的最终结果应该是Sivan:,Kathy:,Eray:,
    var pattern = /\b([a-zA-Z]+:)([0-9]+)/g;
    var abc = a.replace(pattern,"$1");

关于

本篇笔记整理自移动云启-前端精品课 JavaScript学习手册前十章
来源地址:
移动云启-前端精品课

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

如果皮卡会coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值