一、什么是 JavaScript
1、JavaScript 实现
完整的 JavaScript 实现包含以下几个部分:
-- --
核心(ECMAScript) 文档对象模型(DOM) 浏览器对象模型(BOM)
2、DOM
文档对象模型(DOM,Document Object Model)是一个应用编程接口(API),用于在 HTML 中使 用扩展的 XML。DOM 将整个页面抽象为一组分层节点。HTML 或 XML 页面的每个组成部分都是一种节点,包含不同的数据。
-- --
如下 HTML:
<html> <head> <title>Sample Page</title> </head> <body> <p> Hello World!</p> </body> </html>
这些代码通过 DOM 可以表示为一组分层节点
DOM 通过创建表示文档的树,让开发者可以随心所欲地控制网页的内容和结构。使用 DOM API, 可以轻松地删除、添加、替换、修改节点。
3、BOM
浏览器对象模型(BOM) API,用于支持访问和操作浏览器的窗口。使用BOM,开发者可以操控浏览器显示页面之外的部分。
-- --
总体来说,BOM主要针对浏览器窗口和子窗口(frame),不过人们通常会把任何特定于浏览器的扩展都归在BOM的范畴内。比如,下面就是这样一些扩展:
- 弹出新浏览器窗口的能力;
- 移动、缩放和关闭浏览器窗口的能力;
- navigator对象,提供关于浏览器的详尽信息;
- location对象,提供浏览器加载页面的详尽信息;
- screen对象,提供关于用户屏幕分辨率的详尽信息;
- performance对象,提供浏览器内存占用、导航行为和时间统计的详尽信息;
- 对cookie的支持;
- 其他自定义对象,如XMLHttpRequest和IE的ActiveXObject。
4、总结
JavaScript是一门用来与网页交互的脚本语言,包含以下三个组成部分。
- ECMAScript:由ECMA-262定义并提供核心功能。
- 文档对象模型(DOM):提供与网页内容交互的方法和接口。
- 浏览器对象模型(BOM):提供与浏览器交互的方法和接口。
二、HTML中的 JavaScript
1、<script> 元素
将JavaScript插入HTML的主要方法是使用<script>元素。有下列属性:
- async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本文件有效。
- defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。
- src:可选。表示包含要执行的代码的外部文件。
- type:可选。代替language,表示代码块中脚本语言的内容类型(也称MIME类型)。
crossorigin:可选。配置相关请求的CORS(跨源资源共享)设置。默认不使用CORS。crossorigin= "anonymous"配置文件请求不必设置凭据标志。crossorigin="use-credentials"设置凭据标志,意味着出站请求会包含凭据。
integrity:可选。允许比对接收到的资源和指定的加密签名以验证子资源完整性(SRI,
Subresource Integrity)。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN,Content Delivery Network)不会提供恶意内容。
charset:可选。使用src属性指定的代码字符集。这个属性很少使用,因为大多数浏览器不在乎它的值。
-- --
注意:
- 在使用行内JavaScript代码时,要注意代码中不能出现字符串</script>。浏览器解析行内脚本的方式决定了它在看到字符串时,会将其当成结束的标签。想避免这个问题,只需要转义字符“\”即可。
- 使用了src属性的<script>元素不应该在<script>和</script>标签中再包含其他JavaScript代码。如果两者都提供的话,则浏览器只会下载并执行脚本文件,从而忽略行内代码。
2、行内代码与外部文件
虽然可以直接在 HTML 文件中嵌入 JavaScript 代码,但通常认为最佳实践是尽可能将 JavaScript 代码放在外部文件中。不过这个最佳实践并不是明确的强制性规则。推荐使用外部文件的理由如下。
-- --
- 可维护性。JavaScript 代码如果分散到很多 HTML 页面,会导致维护困难。而用一个目录保存 所有 JavaScript 文件,则更容易维护,这样开发者就可以独立于使用它们的 HTML 页面来编辑 代码。
- 缓存。浏览器会根据特定的设置缓存所有外部链接的 JavaScript 文件,这意味着如果两个页面都用到同一个文件,则该文件只需下载一次。这最终意味着页面加载更快。
- 适应未来。通过把 JavaScript 放到外部文件中,就不必考虑用 XHTML 或前面提到的注释黑科技。 包含外部 JavaScript 文件的语法在 HTML 和 XHTML 中是一样的。
3、<noscript> 元素
用于给不支持 JavaScript 的浏览器提供替代内容。
-- --
在下列两种 情况下,浏览器将显示包含在 <noscript> 中的内容:
- 浏览器不支持脚本;
- 浏览器对脚本的支持被关闭。
任何一个条件被满足,包含在<noscript>中的内容就会被渲染。否则,浏览器不会渲染<noscript>中的内容。
4、总结
JavaScript 是通过 <script> 元素插入到 HTML 页面中的。这个元素可用于把 JavaScript 代码嵌入到 HTML 页面中,跟其他标记混合在一起,也可用于引入保存在外部文件中的 JavaScript。
- 要包含外部 JavaScript 文件,必须将 src 属性设置为要包含文件的 URL。文件可以跟网页在同一台服务器上,也可以位于完全不同的域。
- 所有<script>元素会依照它们在网页中出现的次序被解释。在不使用 defer 和 async 属性的 情况下,包含在<script>元素中的代码必须严格按次序解释。
- 对不推迟执行的脚本,浏览器必须解释完位于<script>元素中的代码,然后才能继续渲染页面 的剩余部分。为此,通常应该把<script>元素放到页面末尾,介于主内容之后及</body>标签之前。
- 可以使用 defer 属性把脚本推迟到文档渲染完毕后再执行。推迟的脚本原则上按照它们被列出的次序执行。
- 可以使用 async 属性表示脚本不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异步脚本不能保证按照它们在页面中出现的次序执行。
- 通过使用<noscript>元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持并启用脚本,则<noscript>元素中的任何内容都不会被渲染。
三、语言基础
1、严格模式
ECMAScript 5 增加了严格模式(strict mode)的概念。严格模式是一种不同的 JavaScript 解析和执行模型,ECMAScript 3 的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。
-- --
要对 整个脚本启用严格模式,在脚本开头加上这一行:
"use strict";
虽然看起来像个没有赋值给任何变量的字符串,但它其实是一个预处理指令。任何支持的 JavaScript 引擎看到它都会切换到严格模式。选择这种语法形式的目的是不破坏 ECMAScript 3 语法。
-- --
也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:
function doSomething() { "use strict"; // 函数体 }
2、for 循环中的 var 和 let 声明
for (var i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 输出:5、5、5、5、5
之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值。
for (let i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 输出: 0、1、2、3、4
使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。 每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
3、数据类型
简单数据类型(也称为原始类型):
Undefined、Null、Boolean、Number、 String 和 Symbol。
Symbol(符号)是 ECMAScript 6 新增的。
-- --
复杂数据类型:
Object(对 象)。Object 是一种无序名值对的集合。
4、数值转换
有 3 个函数可以将非数值转换为数值:Number()、parseInt() 和 parseFloat()。
- Number()是转型函数,可用于任何数据类型。
- 后两个函数主要用于将字符串转换为数值。
Number()函数基于如下规则执行转换。
- 布尔值,true 转换为 1,false 转换为 0。
- 数值,直接返回。
- null,返回 0。
- undefined,返回 NaN。
- 字符串,应用以下规则。
- 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。 因此,Number("1")返回 1,Number("123")返回 123,Number("011")返回 11(忽略前面 的零)
- 如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)。
- 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整 数值。
- 如果是空字符串(不包含字符),则返回 0。
- 如果字符串包含除上述情况之外的其他字符,则返回 NaN。
- 对象,调用 valueOf()方法,并按照上述规则转换返回的值。如果转换结果是 NaN,则调用 toString()方法,再按照转换字符串的规则转换。
示例:
let num1 = Number("Hello world!"); // NaN let num2 = Number(""); // 0 let num3 = Number("000011"); // 11 let num4 = Number(true); // 1
通常在需要得到整数时可以优先使 用 parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。
- 字符串最前面的空格会被忽略,从第一个非空格字符开始转换。
- 如果第一个字符不是数值字符、加号或减号,parseInt()立即 返回 NaN。这意味着空字符串也会返回 NaN(这一点跟 Number()不一样,它返回 0)。
- 如果第一个字符 是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。
比如,"1234blue"会被转换为 1234,因为"blue"会被完全忽略。类似地,"22.5"会被转换为 22,因为小数 点不是有效的整数字符。
console.log(parseInt('1234blue')); // 1234 console.log(parseInt('22.5')); // 22
假设字符串中的第一个字符是数值字符,parseInt()函数也能识别不同的整数格式(十进制、八 进制、十六进制)。
换句话说,如果字符串以"0x"开头,就会被解释为十六进制整数。如果字符串以"0" 开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为八进制整数。
如下示例:
let num1 = parseInt("1234blue"); // 1234 let num2 = parseInt(""); // NaN let num3 = parseInt("0xA"); // 10,解释为十六进制整数 let num4 = parseInt(22.5); // 22 let num5 = parseInt("70"); // 70,解释为十进制值 let num6 = parseInt("0xf"); // 15,解释为十六进制整数
不同的数值格式很容易混淆,因此 parseInt()也接收第二个参数,用于指定底数(进制数)。
如下示例:
let num1 = parseInt("10", 2); // 2,按二进制解析 let num2 = parseInt("10", 8); // 8,按八进制解析 let num3 = parseInt("10", 10); // 10,按十进制解析 let num4 = parseInt("10", 16); // 16,按十六进制解析
parseFloat()函数的工作方式跟 parseInt()函数类似,都是从位置 0 开始检测每个字符。同样, 它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。
这意味着第一次出现的小数点是有 效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略 。
console.log(parseFloat('22.34.5')); // 22.34
parseFloat()函数的另一个不同之处在于
- 它始终忽略字符串开头的零。
- 这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。
- 十六进制数值始终会返回 0。因为 parseFloat()只解析十进制值,因此不能指定底数。
- 最后,如果字符串表示整数(没有小数点或者小 数点后面只有一个零),则 parseFloat()返回整数。
如下示例:
let num1 = parseFloat("1234blue"); // 1234,按整数解析 let num2 = parseFloat("0xA"); // 0 let num3 = parseFloat("22.5"); // 22.5 let num4 = parseFloat("22.34.5"); // 22.34 let num5 = parseFloat("0908.5"); // 908.5 let num6 = parseFloat("3.125e7"); // 31250000
5、String 类型
转换为字符串
几乎所有值都有的 toString() 方法。
let age = 11; let ageAsString = age.toString(); // 字符串"11" let found = true; let foundAsString = found.toString(); // 字符串"true"
toString()方法可见于数值、布尔值、对象和字符串值。(没错,字符串值也有 toString()方法, 该方法只是简单地返回自身的一个副本。)null 和 undefined 值没有 toString()方法。
toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示。
let num = 10; console.log(num.toString()); // "10" console.log(num.toString(2)); // "1010" console.log(num.toString(8)); // "12" console.log(num.toString(10)); // "10" console.log(num.toString(16)); // "a"
-- --
String()方法。
不确定一个值是不是 null 或 undefined,可以使用 String()转型函数,它始终会返回表示相应类型值的字符串。
String()函数遵循如下规则:
- 如果值有 toString()方法,则调用该方法(不传参数)并返回结果。
- 如果值是 null,返回"null"。
- 如果值是 undefined,返回"undefined"。
let value1 = 10; let value2 = true; let value3 = null; let value4; console.log(String(value1)); // "10" console.log(String(value2)); // "true" console.log(String(value3)); // "null" console.log(String(value4)); // "undefined"
因为 null 和 undefined 没有 toString()方法, 所以 String()方法就直接返回了这两个值的字面量文本。
6、Symbol 类型
Symbol(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
7、Object 类型
对象其实就是一组数据和功能的集合。对象通过 new 操作符后跟对象类型的名称来创建。开发者可以通过创建 Object 类型的实例来创建自己的对象,然后再给对象添加属性和方法
let o = new Object();
每个 Object 实例都有如下属性和方法。
- constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是 Object() 函数。
- hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty("name"))或符号。
- isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
- propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用 for-in 语句枚举。与 hasOwnProperty()一样,属性名必须是字符串。
- toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
- toString():返回对象的字符串表示。 valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString()的返回值相同。
8、操作符
8.1、一元操作符
1、递增/递减操作符
let age = 29; ++age; // 30 age++; // 30 --age; // 28 age--; // 28 let anotherAge = --age + 2; // 30
前缀递增和递减在语句中的优先级是相等的,因此会从左到右依次求值。比如:
let num1 = 2; let num2 = 20; let num3 = --num1 + num2; // 21 let num4 = num1 + num2; // 21
把递增操作符放到变量后面不会改变语句执行的结果,因为递增是唯一的操作。可是,在跟其他操作混合时,差异就会变明显,比如:
let num1 = 2; let num2 = 20; let num3 = num1-- + num2; // 22 let num4 = num1 + num2; // 21
这里的不同之处在于,计算 num3 时使用的是 num1 的原始值(2),而计算 num4 时使用的是 num1 递减后的值(1)。
-- --
递增和递减操作符遵循如下规则:
- 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
- 对于字符串,如果不是有效的数值形式,则将变量的值设置为 NaN 。变量类型从字符串变成数值。
- 对于布尔值,如果是 false,则转换为 0 再应用改变。变量类型从布尔值变成数值。
- 对于布尔值,如果是 true,则转换为 1 再应用改变。变量类型从布尔值变成数值。
- 对于浮点值,加 1 或减 1。
- 如果是对象,则调用其valueOf()方法取得可以操作的值。对得到的 值应用上述规则。
- 如果是 NaN,则调用 toString()并再次应用其他规则。变量类型从对象变成 数值。
let s1 = "2"; let s2 = "z"; let b = false; let f = 1.1; let o = { valueOf() { return -1; } }; s1++; // 值变成数值 3 s2++; // 值变成 NaN b++; // 值变成数值 1 f--; // 值变成 0.1
2、一元加和减
将一元加应用到非数值,则会执行与使用 Number()转型函数一样的类型转换:
- 布尔值 false 和 true 转换为 0 和 1
- 字符串根据特殊规则进行解析
- 对象会调用它们的 valueOf()和/或 toString() 方法以得到可以转换的值。
let s1 = "01"; let s2 = "1.1"; let s3 = "z"; let b = false; let f = 1.1; let o = { valueOf() { return -1; } }; // 加号 s1 = +s1; // 值变成数值 1 s2 = +s2; // 值变成数值 1.1 s3 = +s3; // 值变成 NaN b = +b; // 值变成数值 0 f = +f; // 不变,还是 1.1 o = +o; // 值变成数值-1 // 减号 s1 = -s1; // 值变成数值-1 s2 = -s2; // 值变成数值-1.1 s3 = -s3; // 值变成 NaN b = -b; // 值变成数值 0 f = -f; // 变成-1.1 o = -o; // 值变成数值 1
8.2、布尔操作符
布尔操作符一共有 3 个:逻辑非、逻辑与和逻辑或。
1、逻辑非
这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反。
规则如下:
- 如果操作数是对象,则返回 false。
- 如果操作数是空字符串,则返回 true。
- 如果操作数是非空字符串,则返回 false。
- 如果操作数是数值 0,则返回 true。
- 如果操作数是非 0 数值(包括 Infinity),则返回 false。
- 如果操作数是 null,则返回 true。
- 如果操作数是 NaN,则返回 true。
- 如果操作数是 undefined,则返回 true。
console.log(!false); // true console.log(!"blue"); // false console.log(!0); // true console.log(!NaN); // true console.log(!""); // true console.log(!12345); // false
同时使用两个叹号(!!),相当于调用了转型函数 Boolean()。无论操作数是什么类型,第一个叹号总会返回布尔值。第二个叹号对该布尔值取反, 从而给出变量真正对应的布尔值。
console.log(!!"blue"); // true console.log(!!0); // false console.log(!!NaN); // false console.log(!!""); // false console.log(!!12345); // true
2、逻辑与
逻辑与操作符由两个和号(&&)表示,应用到两个值,如下所示:
let result = true && false;
逻辑与操作符可用于任何类型的操作数,不限于布尔值。如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则。
- 如果第一个操作数是对象,则返回第二个操作数。
- 如果第二个操作数是对象,则只有第一个操作数求值为 true 才会返回该对象。
- 如果两个操作数都是对象,则返回第二个操作数。
- 如果有一个操作数是 null,则返回 null。
- 如果有一个操作数是 NaN,则返回 NaN。
- 如果有一个操作数是 undefined,则返回 undefined。
逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。对逻辑与操作符来说,如果第一个操作数是 false,那么无论第二个操作数是什么值,结果也不可能等于 true。
let found = true; let result = (found && someUndeclaredVariable); // 这里会出错 console.log(result); // 不会执行这一行 let found = false; let result = (found && someUndeclaredVariable); // 不会出错 console.log(result); // 会执行
3、逻辑或
逻辑或操作符由两个管道符(||)表示,比如:
let result = true || false;
与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循如 下规则。
- 如果第一个操作数是对象,则返回第一个操作数。
- 如果第一个操作数求值为 false,则返回第二个操作数。
- 如果两个操作数都是对象,则返回第一个操作数。
- 如果两个操作数都是 null,则返回 null。
- 如果两个操作数都是 NaN,则返回 NaN。
- 如果两个操作数都是 undefined,则返回 undefined。
同样与逻辑与类似,逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数求值为 true,第二个操作数就不会再被求值了。
let found = true; let result = (found || someUndeclaredVariable); // 不会出错 console.log(result); // 会执行 let found = false; let result = (found || someUndeclaredVariable); // 这里会出错 console.log(result); // 不会执行这一行
利用这个行为,可以避免给变量赋值 null 或 undefined。比如:
let myObject = preferredObject || backupObject;
在这个例子中,变量 myObject 会被赋予两个值中的一个。
其中,preferredObject 变量包含首 选的值,backupObject 变量包含备用的值。
如果 preferredObject 不是 null,则它的值就会赋给 myObject;
如果 preferredObject 是 null,则 backupObject 的值就会赋给 myObject。
8.3、乘性操作符
乘法操作符由一个星号(*)表示,可以用于计算两个数值的乘积。
let result = 34 * 56;
- 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果 ECMAScript 不能表示乘积,则返回 Infinity 或 -Infinity。
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 乘以 0,则返回 NaN。
- 如果是 Infinity 乘以非 0的有限数值,则根据第二个操作数的符号返回 Infinity 或-Infinity。
- 如果是 Infinity 乘以 Infinity,则返回 Infinity。
- 如果有不是数值的操作数,则先在后台用 Number()将其转换为数值,然后再应用上述规则。
8.4、除法操作符
除法操作符由一个斜杠(/)表示,用于计算第一个操作数除以第二个操作数的商。
let result = 66 / 11;
- 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正 值,符号不同的值相除得到负值。如果ECMAScript不能表示商,则返回Infinity或-Infinity。
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 除以 Infinity,则返回 NaN。
- 如果是 0 除以 0,则返回 NaN。
- 如果是非 0 的有限值除以 0,则根据第一个操作数的符号返回 Infinity 或-Infinity。
- 如果是 Infinity 除以任何数值,则根据第二个操作数的符号返回 Infinity 或-Infinity。
- 如果有不是数值的操作数,则先在后台用 Number()函数将其转换为数值,然后再应用上述规则。
8.5、取模操作符
取模(余数)操作符由一个百分比符号(%)表示。
let result = 26 % 5; // 等于 1
- 如果操作数是数值,则执行常规除法运算,返回余数。
- 如果被除数是无限值,除数是有限值,则返回 NaN。
- 如果被除数是有限值,除数是 0,则返回 NaN。
- 如果是 Infinity 除以 Infinity,则返回 NaN。
- 如果被除数是有限值,除数是无限值,则返回被除数。
- 如果被除数是 0,除数不是 0,则返回 0。
- 如果有不是数值的操作数,则先在后台用 Number()函数将其转换为数值,然后再应用上述规则。
8.6、指数操作符
ECMAScript 7 新增了指数操作符,Math.pow()现在有了自己的操作符**。
console.log(Math.pow(3, 2); // 9 console.log(3 ** 2); // 9 console.log(Math.pow(16, 0.5); // 4 console.log(16** 0.5);
不仅如此,指数操作符也有自己的指数赋值操作符**=,该操作符执行指数运算和结果的赋值操作
let squared = 3; squared **= 2; console.log(squared); // 9 let sqrt = 16; sqrt **= 0.5; console.log(sqrt); // 4
8.7、加性操作符
1、加法操作符
加法操作符(+)用于求两个数的和。
let result = 1 + 2;
如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:
- 如果有任一操作数是 NaN,则返回 NaN;
- 如果是 Infinity 加 Infinity,则返回 Infinity;
- 如果是-Infinity 加-Infinity,则返回-Infinity;
- 如果是 Infinity 加-Infinity,则返回 NaN;
- 如果是+0 加+0,则返回+0;
- 如果是-0 加+0,则返回+0;
- 如果是-0 加-0,则返回-0。
不过,如果有一个操作数是字符串,则要应用如下规则:
- 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
- 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
- 如果有任一操作数是对象、数值或布尔值,则调用它们的 toString()方法以获取字符串,然后再 应用前面的关于字符串的规则。
- 对于 undefined 和 null,则调用 String()函数,分别获取 "undefined"和"null"。
let result1 = 5 + 5; // 两个数值 console.log(result1); // 10 let result2 = 5 + "5"; // 一个数值和一个字符串 console.log(result2); // "55"
let num1 = 5; let num2 = 10; // 示例一 let message = "The sum of 5 and 10 is " + num1 + num2; console.log(message); // "The sum of 5 and 10 is 510" // 示例二 let message = "The sum of 5 and 10 is " + (num1 + num2); console.log(message); // "The sum of 5 and 10 is 15"
2、减法操作符
减法操作符(-)也是使用很频繁的一种操作符。
let result = 2 - 1;
规则如下:
- 如果两个操作数都是数值,则执行数学减法运算并返回结果。
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 减 Infinity,则返回 NaN。
- 如果是-Infinity 减-Infinity,则返回 NaN。
- 如果是 Infinity 减-Infinity,则返回 Infinity。
- 如果是-Infinity 减 Infinity,则返回-Infinity。
- 如果是+0 减+0,则返回+0。
- 如果是+0 减-0,则返回-0。
- 如果是-0 减-0,则返回+0。
- 如果有任一操作数是字符串、布尔值、null 或 undefined,则先在后台使用 Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是 NaN,则减法计算的结果是 NaN。
- 如果有任一操作数是对象,则调用其 valueOf()方法取得表示它的数值。如果该值是 NaN,则 减法计算的结果是 NaN。如果对象没有 valueOf()方法,则调用其 toString()方法,然后再将得到的字符串转换为数值
示例:
let result1 = 5 - true; // true 被转换为 1,所以结果是 4 let result2 = NaN - 1; // NaN let result3 = 5 - 3; // 2 let result4 = 5 - ""; // ""被转换为 0,所以结果是 5 let result5 = 5 - "2"; // "2"被转换为 2,所以结果是 3 let result6 = 5 - null; // null 被转换为 0,所以结果是 5
8.8、关系操作符
关系操作符执行比较两个值的操作,包括小于()、大于(>)、小于等于()和大于等于(>=), 用法跟数学课上学的一样。这几个操作符都返回布尔值,如下所示:
let result1 = 5 > 3; // true let result2 = 5 < 3; // false
在将它们应用到不同数据类型时也会发生类型转换和其他行为:
- 如果操作数都是数值,则执行数值比较。
- 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
- 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
- 如果有任一操作数是对象,则调用其 valueOf()方法,取得结果后再根据前面的规则执行比较。 如果没有 valueOf()操作符,则调用 toString()方法,取得结果后再根据前面的规则执行比较。
- 如果有任一操作数是布尔值,则将其转换为数值再执行比较。
字符串而言,关系操作符会比较字符串中对应字符的编码,而这些编码是数值。
let result = "Brick" < "alphabet"; // true 因为字母 B 的编码是 66,字母 a 的编码 是 97。 let result = "23" < "3"; // true 因为两个操作数都是字符串,所以会逐个比较它们的字符编码 (字符"2"的编码是 50,而字符"3"的编码是 51) let result = "23" < 3; // false 因为这次会将字符串"23"转换为数值 23,然后再跟 3 比较 let result = "a" < 3; // 因为"a"会转换为 NaN,所以结果是 false
8.9、相等操作符
1、等于和不等于
ECMAScript 中的等于操作符用两个等于号(==)表示,如果操作数相等,则会返回 true。不等于 操作符用叹号和等于号(!=)表示,如果两个操作数不相等,则会返回 true。这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。
如下规则:
- 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false 转换为 0,true 转换 为 1。
- 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否 相等。
- 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf()方法取得其原始值,再根据前面的规则进行比较。 在进行比较时,这两个操作符会遵循如下规则。
- null 和 undefined 相等。
- null 和 undefined 不能转换为其他类型的值再进行比较。
- 如果有任一操作数是 NaN,则相等操作符返回 false,不相等操作符返回 true。记住:即使两个操作数都是 NaN,相等操作符也返回 false,因为按照规则,NaN 不等于 NaN。
- 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象, 则相等操作符返回 true。否则,两者不相等。
2、全等和不全等
全等和不全等操作符与相等和不相等操作符类似,只不过它们在比较相等时不转换操作数。全等操作符由 3 个等于号(===)表示,只有两个操作数在不转换的前提下相等才返回 true。
let result1 = ("55" == 55); // true,转换后相等 let result2 = ("55" === 55); // false,不相等,因为数据类型不同 let result1 = ("55" != 55); // false,转换后相等 let result2 = ("55" !== 55); // true,不相等,因为数据类型不同
null === undefined 是 false,因为它们不是相同的数据类型。
8.10、条件操作符
variable = boolean_expression ? true_value : false_value;
同时也叫做三元运算符。
8.11、赋值操作符
简单赋值用等于号(=)表示,将右手边的值赋给左手边的变量。
let num = 10; num = num + 10; 以上代码的第二行可以通过复合赋值来完成: let num = 10; num += 10;
- 乘后赋值(*=)
- 除后赋值(/=)
- 取模后赋值(%=)
- 加后赋值(+=)
- 减后赋值(-=)
- 左移后赋值(<<=)
- 右移后赋值(>>=)
- 无符号右移后赋值(>>>=)
这些操作符仅仅是简写语法,使用它们不会提升性能。
8.12、逗号操作符
逗号操作符可以用来在一条语句中执行多个操作,如下所示:
let num1 = 1, num2 = 2, num3 = 3;
在一条语句中同时声明多个变量是逗号操作符最常用的场景。不过,也可以使用逗号操作符来辅助 赋值。在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值:
let num = (5, 1, 4, 8, 0); // num 的值为 0
9、语句
9.1、if 语句
if 语句是使用最频繁的语句之一,语法如下:
if (condition) statement1 else statement2
这里的条件(condition)可以是任何表达式,并且求值结果不一定是布尔值。
ECMAScript 会自动调用 Boolean()函数将这个表达式的值转换为布尔值。
如果条件求值为 true,则执行语句 statement1;
如果条件求值为 false,则执行语句 statement2。
这里的语句可能是一行代码,也可能是一个代码块(即包含在一对花括号中的多行代码)。
//示例一 if (i > 25) console.log("Greater than 25."); // 只有一行代码的语句 else { console.log("Less than or equal to 25."); // 一个语句块 } //示例二 if (condition1) statement1 else if (condition2) statement2 else statement3 //示例三 if (i > 25) { console.log("Greater than 25."); } else if (i < 0) { console.log("Less than 0."); } else { console.log("Between 0 and 25, inclusive."); }
9.2、do-while 语句
do-while 语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。换句话说,循环体内的代码至少执行一次。do-while 的语法如下:
do { statement } while (expression); 下面是一个例子: let i = 0; do { i += 2; } while (i < 10); 在这个例子中,只要 i 小于 10,循环就会重复执行。i 从 0 开始,每次循环递增 2。
9.3、while 语句
while 语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。
因此,while 循环体内的代码有可能不会执行。下面是 while 循环的语法:
while(expression) statement 这是一个例子: let i = 0; while (i < 10) { i += 2; } 在这个例子中,变量 i 从 0 开始,每次循环递增 2。只要 i 小于 10,循环就会继续。
9.4、for 语句
for 语句也是先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式,语法如下:
下面是一个用例: let count = 10; for (let i = 0; i < count; i++) { console.log(i); }
以上代码在循环开始前定义了变量 i 的初始值为 0。然后求值条件表达式,如果求值结果为 true (i < count),则执行循环体。因此循环体也可能不会被执行。如果循环体被执行了,则循环后表达式也会执行,以便递增变量 i。
for 循环跟下面的 while 循环是一样的:
let count = 10; let i = 0; while (i < count) { console.log(i); i++; }
无法通过 while 循环实现的逻辑,同样也无法使用 for 循环实现。因此 for 循环只是将循环相关的代码封装在了一起而已。
在 for 循环的初始化代码中,其实是可以不使用变量声明关键字的。
初始化、条件表达式和循环后表达式都不是必需的。
for (;;) { // 无穷循环 doSomething(); }
如果只包含条件表达式,那么 for 循环实际上就变成了 while 循环: let count = 10; let i = 0; for (; i < count; ) { console.log(i); i++; }
9.5、for-in 语句
for-in 语句是一种严格的迭代语句,用于枚举对象中的非符号键属性 。
for (const propName in window) { document.write(propName); }
这个例子使用 for-in 循环显示了 BOM 对象 window 的所有属性。
每次执行循环,都会给变量 propName 赋予一个 window 对象的属性作为值,直到 window 的所有属性都被枚举一遍。
与 for 循环 一样,这里控制语句中的 const 也不是必需的。但为了确保这个局部变量不被修改,推荐使用 const。
ECMAScript 中对象的属性是无序的,因此 for-in 语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异。
如果 for-in 循环要迭代的变量是 null 或 undefined,则不执行循环体。
9.6、for-of 语句
for-of 语句是一种严格的迭代语句,用于遍历可迭代对象的元素。
for (const el of [2,4,6,8]) { document.write(el); }
使用 for-of 语句显示了一个包含 4 个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完。与 for 循环一样,这里控制语句中的 const 也不是必需的。但为了确保 这个局部变量不被修改,推荐使用 const。
for-of 循环会按照可迭代对象的 next()方法产生值的顺序迭代元素。
如果尝试迭代的变量不支持迭代,则 for-of 语句会抛出错误。
9.7、标签语句
标签语句用于给语句加标签。
start: for (let i = 0; i < count; i++) { console.log(i); }
在这个例子中,start 是一个标签,可以在后面通过 break 或 continue 语句引用。标签语句的典型应用场景是嵌套循环。
9.8、break 和 continue 语句
break 和 continue 语句为执行循环代码提供了更严格的控制手段。
break 语句用于立即退出循环,强制执行循环后的下一条语句。
continue 语句也用于立即退出循环,但会再次从循环顶部开始执行。
1、break
let num = 0; for (let i = 1; i < 10; i++) { if (i % 5 == 0) { break } num++; } console.log(num); // 4
当 i 等于 5 时,break 语句会导致循环退出,该次循环不会执行递增 num 的代码。
2、continue
let num = 0; for (let i = 1; i < 10; i++) { if (i % 5 == 0) { continue; } num++; } console.log(num); // 8
当 i 等于 5 时,循环会在递增 num 之前退出,但会执行下一次迭代,此时 i 是 6。然后,循环会一直执行到自然结束,即 i 等于 10。最终 num 的值是 8 而不是 9,是因为 continue 语句导致它少递增了一次。
9.9、with 语句
with 语句的用途是将代码作用域设置为特定的对象。
let qs = location.search.substring(1); let hostName = location.hostname; let url = location.href; 上面代码中的每一行都用到了 location 对象。如果使用 with 语句,就可以少写一些代码: with(location) { let qs = search.substring(1); let hostName = hostname; let url = href; } with 语句用于连接 location 对象。这意味着在这个语句内部,每个变量首先会被认为是一个局部变量。 如果没有找到该局部变量,则会搜索 location 对象,看它是否有一个同名的属性。 如果有,则该变量会被求值为 location 对象的属性。
with 语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便 利 。
严格模式不允许使用 with 语句,否则会抛出错误。
9.10、switch 语句
switch 语句是与 if 语句紧密相关的一种流控制语句,从其他语言借鉴而来。
如下示例:
if语句写法: if (i == 25) { console.log("25"); } else if (i == 35) { console.log("35"); } else { console.log("Other"); } switch语句写法: switch (i) { case 25: console.log("25"); break; case 35: console.log("35"); break; default: console.log("Other"); }
这里的每个 case(条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。”break 关键字会导致代码执行跳出 switch 语句。如果没有 break,则代码会继续匹配下一个条件。default 关键字用于在任何条件都没有满足时指定默认执行的语句(相当于 else 语句)。
如果确实需要连续匹配几个条件,可以不写break 关键字。
注意 :
switch 语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值 10)。
10、函数
函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。 ECMAScript 中的函数使用 function 关键字声明,后跟一组参数,然后是函数体。
以下是函数的基本语法:
function sayHi(name, message) { console.log("Hello " + name + ", " + message); } sayHi("Nicholas", "how are you today?"); // 调用
任何函数在任何时间都可以使用 return 语句来返回函数的值,用法是后跟要返回的值。
function sum(num1, num2) { return num1 + num2; } const result = sum(5, 10); // 15
只要碰到 return 语句,函数就会立即停止执行并退出。因此,return 语句后面的代码不会被执行。
严格模式对函数也有一些限制:
- 函数不能以 eval 或 arguments 作为名称;
- 函数的参数不能叫 eval 或 arguments;
- 两个命名参数不能拥有同一个名称。
- 如果违反上述规则,则会导致语法错误,代码也不会执行
11、总结
ECMAScript 包含 =所有基本语法、操作符、数据类型和对象,能完成基本的计算任务,但没有提供获得输入和产生输出的 机制。理解 ECMAScript 及其复杂的细节是完全理解浏览器中 JavaScript 的关键。下面总结一下 ECMAScript 中的基本元素。
- ECMAScript 中的基本数据类型包括 Undefined、Null、Boolean、Number、String 和 Symbol。
- 与其他语言不同,ECMAScript 不区分整数和浮点值,只有 Number 一种数值数据类型。 Object 是一种复杂数据类型,它是这门语言中所有对象的基类。
- 严格模式为这门语言中某些容易出错的部分施加了限制。
- ECMAScript 提供了 C 语言和类 C 语言中常见的很多基本操作符,包括数学操作符、布尔操作符、 关系操作符、相等操作符和赋值操作符等。
- 这门语言中的流控制语句大多是从其他语言中借鉴而来的,比如 if 语句、for 语句和 switch 语句等。
ECMAScript 中的函数与其他语言中的函数不一样。
- 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。
- 不指定返回值的函数实际上会返回特殊值 undefined。