JavaScript基础篇(一)
JavaScript实现
一个完整的javascript实现应该由三个部分组成:
- 核心(ECMAScript)
- 文档对象模型(DOM)
- 浏览器对象模型(BOM)
ECMAScript
由 ECMA-262 定义的 ECMAScript 与 Web 浏览器没有依赖关系。实际上,这门语言本身并不包含输 入和输出定义。Web 浏览器只是 ECMAScript 实现可能的宿主环境之一。
ECMA-262 标准规定了这门语言有 下列组成部分:
- 语法
- 类型
- 语句
- 关键字
- 保留字
- 操作符
- 对象
ECMAScript 就是对实现该标准规定的各个方面内容的语言的描述。
什么是ECMAScript 兼容?
- 支持 ECMA-262 描述的所有“类型、值、对象、属性、函数以及程序句法和语义”
- 支持 Unicode 字符标准
- 添加 ECMA-262 没有描述的“更多类型、值、对象、属性和函数”。ECMA-262 所说的这些新增 特性,主要是指该标准中没有规定的新对象和对象的新属性。
- 支持 ECMA-262 没有定义的“程序和正则表达式语法”。(也就是说,可以修改和扩展内置的正 则表达式语法。)
文档对象模型(DOM)
文档对象模型(DOM,Document Object Model)是针对 XML 但经过扩展用于 HTML 的应用程序编 程接口(API,Application Programming Interface)。
DOM 把整个页面映射为一个多层节点结构。HTML 或 XML 页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据。
举个例子:
1 | <html> |
DOM分层节点图:
通过 DOM 创建的这个表示文档的树形图,开发人员获得了控制页面内容和结构的主动权。借助 DOM 提供的 API,开发人员可以轻松自如地删除、添加、替换或修改任何节点。
浏览器对象模型(BOM)
从根本上讲,BOM 只处理浏览器窗口和框架;但人们习惯上也把所有针对浏览器的 JavaScript 扩展 算作 BOM 的一部分。下面就是一些这样的扩展:
- 弹出新浏览器窗口的功能
- 移动、缩放和关闭浏览器窗口的功能
- 提供浏览器详细信息的 navigator 对象
- 提供浏览器所加载页面的详细信息的 location 对象
- 提供用户显示器分辨率详细信息的 screen 对象
- 对 cookies 的支持
- 像 XMLHttpRequest 和 IE 的 ActiveXObject 这样的自定义对象
由于没有 BOM 标准可以遵循,因此每个浏览器都有自己的实现,但是现在有了 HTML5,BOM 实现的细节有望朝着兼容性越来越高的方向发展。
<script>元素
在HTML中插入javascript就要使用到<script>
元素,H5中script的新特性:
使用<script>
元素的方式有两种:
- 直接页面嵌入
- 包含外部文件
直接嵌入:
1 | <script type="text/javascript"> |
在使用
<script>
嵌入 JavaScript 代码时,记住不要在代码中的任何地方出现”</script>”字符串,除非是代码结束,如果要打印这个内容,请记得使用转义符。
引用.js
外部文件:
1 | <script type="text/javascript" src="example.js"></script> |
按照惯例,外部 JavaScript 文件带有.js 扩展名。但这个扩展名不是必需的,因为 浏览器不会检查包含 JavaScript 的文件的扩展名。这样一来,使用 JSP、PHP 或其他 服务器端语言动态生成 JavaScript 代码也就成为了可能。但是,服务器通常还是需要 看扩展名决定为响应应用哪种 MIME 类型。如果不使用.js 扩展名,请确保服务器能 返回正确的 MIME 类型。
需要注意的是,带有 src
属性的标签之间再 包含额外的 JavaScript 代码。如果包含了嵌入的代码,则只会下载并执行外部脚本文件,嵌入的代码 会被忽略。
当然,除了指定外部文件之外,还可以使用CDN,src
属性的值还可以是完整的URL。
标签的位置
按照传统的做法,所有<script>
元素都应该放在页面的<head>
元素中,例如:
1 |
|
如果把js文件放在<head>
中,则意味着必须等到全部的js文件被下载完成之后才能开始呈现页面内容,无疑会增加页面延迟。为了解决这个问题,一般而言会把js文件放在</body>
之前,例如:
1 |
|
延迟脚本
HTML 4.01 为<script>
标签定义了 defer 属性,表明脚本在执行时不会影响页 面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行:
1 |
|
虽然我们把<script>
元素放在了文档的<head>
元素中,但其中包含的js将延迟到浏览器遇到</html>
标签后再执行。
defer 属性只适用于外部脚本文件。这一点在 HTML5 中已经明确规定,因此支持 HTML5 的实现会忽略给嵌入脚本设置的 defer 属性。IE4~IE7 还支持对嵌入脚本的 defer 属性,但 IE8 及之后版本则完全支持 HTML5 规定的行为。
把延迟脚本放在页面底部仍然是最佳选择
异步脚本
HTML5 为<script>
元素定义了 async 属性。这个属性与 defer 属性类似,都用于改变处理脚本的行为,async 只适用于外部脚本文件,并告诉浏览器立即下载文件。
1 |
|
标记为 async 的脚本并不保证按照指定它们的先后顺序执行,第二个脚本文件可能会在第一个脚本文件之前执行,确保两者之间互不依赖非常重要
在 XHTML 文档中,要把 async 属性设置为 async="async"
基本概念
语法
区分大小写
ECMAScript 中的一切(变量、函数名和操作符)都区分大小写。这也就意味着,变量名 test 和变量名 Test 分别表示两个不同的变量,而函数名不能使用 typeof,因为它是一个关键字,但 typeOf 则完全可以是一个有效的函数名。
标识符
标识符,就是指变量、函数、属性的名字,或者函数的参数,规则如下:
- 第一个字符必须是一个字母、下划线(_)或一个美元符号($)
- 其他字符可以是字母、下划线、美元符号或数字
一般来说标识符采用驼峰大小写格式,也就是第一个字母小写,剩下的每个单词的首字母大写,比如:firstSecond、myCar、doSomethingImportant
不能把关键字、保留字、true、false 和 null 用作标识符
注释
使用 C 语言风格的注释,包括单行注释和块级注释
1 | //这是单行注释 |
严格模式
ECMAScript 5 引入了严格模式(strict mode)的概念。严格模式是为 JavaScript 定义了一种不同的解析与执行模型。在严格模式下,ECMAScript 3 中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误。要在整个脚本中启用严格模式,可以在顶部添加如下代码:
1 | ; |
它是一个编译指示(pragma),用于告诉支持的 JavaScript 引擎切换到严格模式。在函数内部的上方包含这条编译指示,也可以指定函数在严格模式下执行:
1 | function doSomething(){ |
语句
语句以一个分号结尾;
如果省略分号,则由解析器确定语句的结尾:
1 | var sum = a + b |
可以使用 C 语言风格的语法:
1 | if (test){ |
下面这两种也是有效的,但是建议用花括号括起来:
1 | if (test) |
关键字和保留字
关键字可用于表示控制语句的开始或结束,或者用于执行特定操作等。
break | do | instanceof | typeof | case |
---|---|---|---|---|
else | new | var | catch | finally |
return | void | continue | for | switch |
while | debugger | function | this | with |
default | if | throw | delete | in |
try |
不能用作标识符的保留字,即就是以后可能会被用作关键字,以下是 ECMA-262 第 3 版定义的全部保留字:
abstract | enum | int | short | boolean |
---|---|---|---|---|
export | interface | static | byte | extends |
long | super | char | final | native |
synchronized | class | float | package | throws |
const | goto | private | transient | debugger |
implements | protected | volatile | double | import |
public | let | yield |
变量
ECMAScript 的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值的占位符而已。定义变量时要使用 var 操作符,后跟变量名(即一个标识符),如下所示:
1 | var message; |
这行代码定义了一个名为 message 的变量,该变量可以用来保存任何值(像这样未经过初始化的变量,会保存一个特殊的值undefined
ECMAScript 也支持直接初始化变量,因此在定义变量的同时就可以设置变量的值,如下所示:
1 | var message = "hi"; |
变量 message 中保存了一个字符串值”hi”。像这样初始化变量并不会把它标记为字符串类型,初始化的过程就是给变量赋一个值那么简单。因此,可以在修改变量值的同时修改值的类型,如下所示:
1 | var message = "hi"; |
用 var 操作符定义的变量将成为定义该变量的作用域中的局部变量。也就是说,如果在函数中使用 var 定义一个变量,那么这个变量在函数退出后就会无效:
1 | function test(){ |
创建一个全局变量:
1 | function test(){ |
可以使用一条语句定义多个变量,只要像下面这样把每个变量(初始化或不初始化均可)用逗号分隔开即可:
1 | var message = "hi", |
当然不换行也是可以的。
数据类型
ECMAScript 中有 5 种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number 和 String。还有 1种复杂数据类型——Object,Object 本质上是由一组无序的名值对组成的。
typeof操作符
typeof 就是负责检测给定变量的数据类型的操作符。对一个值使用 typeof 操作符可能返回下列某个字符串:
- “undefined”——如果这个值未定义
- “boolean”——如果这个值是布尔值
- “string”——如果这个值是字符串
- “number”——如果这个值是数值
- “object”——如果这个值是对象或 null
- “function”——如果这个值是函数
1 | var message = "some string"; |
上面就是例子。
typeof 是一个操作符而不是函数,因此例子中的圆括号尽管可以使用,但不是必需的
Undefined类型
Undefined 类型只有一个值,即特殊的 undefined。在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined
1 | var message; |
另一个例子:
1 | var message; // 这个变量声明之后默认取得了 undefined 值 |
需要注意的是对未初始化的变量执行 typeof 操作符会返回 undefined 值,而对未声明的变量执行 typeof 操作符同样也会返回 undefined 值:
1 | var message; // 这个变量声明之后默认取得了 undefined 值 |
Null类型
Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null。null 值表示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回”object”的原因。
如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null 而不是其他值。这样一来,只要直接检查 null 值就可以知道相应的变量是否已经保存了一个对象的引用,如下面的例子所示:
1 | if (car != null){ |
实际上,undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true:
1 | alert(null == undefined); //true |
Boolean类型
Boolean 类型是 ECMAScript 中使用得最多的一种类型,该类型只有两个字面值:true 和 false。需要注意的是,Boolean 类型的字面值 true 和 false 是区分大小写的。
虽然 Boolean 类型的字面值只有两个,但 ECMAScript 中所有类型的值都有与这两个 Boolean 值等价的值。要将一个值转换为其对应的 Boolean 值,可以调用转型函数 Boolean(),如下例所示:
1 | var message = "Hello world!"; |
在这个例子中,字符串 message 被转换成了一个 Boolean 值,该值被保存在 messageAsBoolean变量中。可以对任何数据类型的值调用 Boolean()函数,而且总会返回一个 Boolean 值。至于返回的这个值是 true 还是 false,取决于要转换值的数据类型及其实际值。
转换规则对理解流控制语句(如 if 语句)自动执行相应的 Boolean 转换非常重要,请看下面的代码:
1 | var message = "Hello world!"; |
运行这个示例,就会显示一个警告框,因为字符串 message 被自动转换成了对应的 Boolean 值 (true)。
Number类型
数值可以用十进制、八进制(以 8 为基数)或十六进制(以 16 为基数)来表示,例如:
1 | var intNum = 55; // 整数 |
八进制字面量在严格模式下是无效的,会导致支持的 JavaScript 引擎抛出错误,在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值。
浮点数
该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。例如:
1 | var floatNum1 = 1.1; |
对于那些极大或极小的数值,可以用 e 表示法(即科学计数法)表示的浮点数值表示。用 e 表示法表示的数值等于 e 前面的数值乘以 10 的指数次幂。例如:
1 | var floatNum = 3.125e7; // 等于 31250000 |
浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。
非常需要注意的是,有些特定的浮点数由于运算会存在舍入误差,得到的结果并不准确,例如0.1 加 0.2的结果不是 0.3,而是 0.30000000000000004。所以对浮点数的运算和比较要特别注意。
数值范围
由于内存的限制,js 并不能保存世界上所有的数值。js能够表示的最小数值保存在 Number.MIN_VALUE 中,这个值是 5e-324;能够表示的最大数值保存在Number.MAX_VALUE 中,这个值是 1.7976931348623157e+308。
如果某次计算的结果得到了一个超出 JavaScript 数值范围的值,那么这个数值将被自动转换成特殊的 Infinity 值。具体来说,如果这个数值是负数,则会被转换成-Infinity(负无穷),如果这个数值是正数,则会被转换成 Infinity(正无穷)。
要想确定一个数值是不是有穷的(换句话说,是不是位于最小和最大的数值之间),可以使用 isFinite()函数:
1 | var result = Number.MIN_VALUE + Number.MAX_VALUE; |
打印
Number.NEGATIVE_INFINITY
和Number.POSITIVE_INFINITY
也可以得到负和正 Infinity 的值。
NaN
NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况,例如,在其他编程语言中,任何数值除以 0都会导致错误,从而停止代码执行。但在 js中,任何数值除以 0会返回 NaN,因此不会影响其他代码的执行。
NaN有两个特点:
- 0 除以 0 返回 NaN,正数除以 0 返回 Infinity,负数除以 0 返回-Infinity
- NaN 与任何值都不相等,包括 NaN 本身
针对 NaN 的这两个特点,js定义了 isNaN()
函数,这个函数接受一个参数,该参数可以是任何类型,而函数会帮我们确定这个参数是否不是数值
1 | alert(isNaN(NaN)); //true |
注意,不是数值的情况才会返回true
isNaN()
确实也适用于对象。在基于对象调用 isNaN()
函数时,会首先调用对象的 valueOf()
方法,然后确定该方法返回的值是否可以转换为数值。如果不能,则基于这个返回值再调用 toString()
方法,再测试返回值。
数值转换
有 3 个函数可以把非数值转换为数值:
- Number()
- parseInt()
- parseFloat()
第一个函数即转型函数 Number()
可以用于任何数据类型,而另两个函数则专门用于把字符串转换成数值。
Number()
函数的转换规则如下:
- 如果是 Boolean 值,true 和 false 将分别被转换为 1 和 0
- 如果是数字值,只是简单的传入和返回
- 如果是 null 值,返回 0
- 如果是 undefined,返回 NaN
- 如果是对象,则调用对象的
valueOf()
方法,然后依照前面的规则转换返回的值。如果转换的结果是 NaN,则调用对象的toString()
方法,然后再次依照前面的规则转换返回的字符串值。
如果是字符串,遵循下列规则:
- 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即”1”会变成 1,”123”会变成 123,而”011”会变成 11(注意:前导的零被忽略了)
- 如果字符串中包含有效的浮点格式,如”1.1”,则将其转换为对应的浮点数值(同样,也会忽略前导零)
- 如果字符串中包含有效的十六进制格式,例如”0xf”,则将其转换为相同大小的十进制整数值
- 如果字符串是空的(不包含任何字符),则将其转换为 0
- 如果字符串中包含除上述格式之外的字符,则将其转换为 NaN
例如:
1 | var num1 = Number("Hello world!"); //NaN |
由于 Number()
函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常用的是parseInt()
函数。parseInt()
函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()
就会返回 NaN;也就是说,用 parseInt()
转换空字符串会返回 NaN(Number()
对空字符返回 0)。如果第一个字符是数字字符,parseInt()
会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。例如,”1234blue”会被转换为 1234,因为”blue”会被完全忽略。类似地,”22.5”会被转换为 22,因为小数点并不是有效的数字字符。
如果字符串中的第一个字符是数字字符,parseInt()也能够识别出各种整数格式(即十进制、八进制和十六进制数)。也就是说,如果字符串以”0x”开头且后跟数字字符,就会将其当作一个十六进制整数;如果字符串以”0”开头且后跟数字字符,则会将其当作一个八进制数来解析:
1 | var num1 = parseInt("1234blue"); // 1234 |
但是还有一种情况:
1 | var num = parseInt("070"); |
为了解决这个问题,可以给函数提供第二个参数,转换时使用的基数(即多少进制):
1 | var num = parseInt("0xAF", 16); //175 |
指定不同进制就会得到不同结果:
1 | var num1 = parseInt("10", 2); //2 (按二进制解析) |
parseFloat()
也是从第一个字符(位置 0)开始解析每个字符。而且也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。也就是说,字符串中的第一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。
除了第一个小数点有效之外,parseFloat()
与 parseInt()
的第二个区别在于它始终都会忽略前导的零。
由于 parseFloat()
只解析十进制值,因此它没有用第二个参数指定基数的用法。最后还要注意一点:如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后都是零),parseFloat()
会返回整数:
1 | var num1 = parseFloat("1234blue"); //1234 (整数) |
String类型
String 类型用于表示由零或多个 16 位 Unicode 字符组成的字符序列,即字符串。字符串可以由双引号"
或单引号'
表示:
1 | var firstName = "hahaha"; |
PHP 中的双引号和单引号会影响对字符串的解释方式不同
字符字面量
String 数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,或者具有其他用途的字符:
例如:
1 | var text = "This is the letter sigma: \u03a3."; |
这个例子中的变量 text 有 28 个字符,其中 6 个字符长的转义序列表示 1 个字符。任何字符串的长度都可以通过访问其 length 属性取得,例如:
1 | alert(text.length); // 输出 28 |
字符串的特点
ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,例如:
1 | var lang = "Java"; |
转换为字符串
数值、布尔值、对象和字符串值都有 toString()
方法,但 null 和 undefined 值没有这个方法。
多数情况下,调用 toString()
方法不必传递参数。但是,在调用数值的 toString()
方法时,可以传递一个参数:输出数值的基数。默认情况下,toString()
方法以十进制格式返回数值的字符串表示。而通过传递基数,toString()
可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值,例如:
1 | var num = 10; |
在不知道要转换的值是不是 null 或 undefined 的情况下,还可以使用转型函数 String()
,这个函数能够将任何类型的值转换为字符串。String()函数遵循下列转换规则:
- 如果值有
toString()
方法,则调用该方法(没有参数)并返回相应的结果 - 如果值是 null,则返回”null”
- 如果值是 undefined,则返回”undefined”
例如:
1 | var value1 = 10; |
Object类型
JavaScript中的对象其实就是一组数据和功能的集合,对象可以通过执行 new 操作符后跟要创建的对象类型的名称来创建,而创建 Object 类型的实例并为其添加属性和(或)方法,就可以创建自定义对象。
1 | var o = new Object(); |
Object 的每个实例都具有下列属性和方法:
- constructor:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor)就是
Object()
- hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定,例如:o.hasOwnProperty(“name”)
- isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型
- propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句来枚举。与
hasOwnProperty()
方法一样,作为参数的属性名必须以字符串形式指定 - toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应
- toString():返回对象的字符串表示
- valueOf():返回对象的字符串、数值或布尔值表示。通常与
toString()
方法的返回值相同