2.png 0
8i98

家事,国事,天下事,事事关心

2022-03-13

js笔记05-标准库

2022-05-17 0 54 web前端社区

2.png 0 8i98

js标准库

对象是 JavaScript 语言最主要的数据类型,三种原始类型的值——数值、字符串、布尔值——在一定条件下,也会自动转为对象,也就是原始类型的“包装对象”(wrapper)。

所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的NumberStringBoolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。

Number 对象

Number对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用。

作为构造函数时,它用于生成值为数值的对象。

  1. var n = new Number(1);
  2. typeof n // "object"

上面代码中,Number对象作为构造函数使用,返回一个值为1的对象。

作为工具函数时,它可以将任何类型的值转为数值。

Number()

使用Number函数,可以将任意类型的值转化成数值。

下面分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象。

(1)原始类型值

原始类型值的转换规则如下。

  1. // 数值:转换后还是原来的值
  2. Number(324) // 324
  3. // 字符串:如果可以被解析为数值,则转换为相应的数值
  4. Number('324') // 324
  5. // 字符串:如果不可以被解析为数值,返回 NaN
  6. Number('324abc') // NaN
  7. // 空字符串转为0
  8. Number('') // 0
  9. // 布尔值:true 转成 1,false 转成 0
  10. Number(true) // 1
  11. Number(false) // 0
  12. // undefined:转成 NaN
  13. Number(undefined) // NaN
  14. // null:转成0
  15. Number(null) // 0

Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN

  1. parseInt('42 cats') // 42
  2. Number('42 cats') // NaN

上面代码中,parseInt逐个解析字符,而Number函数整体转换字符串的类型。

另外,parseIntNumber函数都会自动过滤一个字符串前导和后缀的空格。

  1. parseInt('\t\v\r12.34\n') // 12
  2. Number('\t\v\r12.34\n') // 12.34
  1. Number(true) // 1

上面代码将布尔值true转为数值1

实例方法

Number对象有4个实例方法,都跟将数值转换成指定格式有关。

Number.prototype.toString()

Number对象部署了自己的toString方法,用来将一个数值转为字符串形式。

  1. (10).toString() // "10"

toString方法可以接受一个参数,表示输出的进制。如果省略这个参数,默认将数值先转为十进制,再输出字符串;否则,就根据参数指定的进制,将一个数字转化成某个进制的字符串。

  1. (10).toString(2) // "1010"
  2. (10).toString(8) // "12"
  3. (10).toString(16) // "a"

上面代码中,10一定要放在括号里,这样表明后面的点表示调用对象属性。如果不加括号,这个点会被 JavaScript 引擎解释成小数点,从而报错。

  1. 10.toString(2)
  2. // SyntaxError: Unexpected token ILLEGAL

只要能够让 JavaScript 引擎不混淆小数点和对象的点运算符,各种写法都能用。除了为10加上括号,还可以在10后面加两个点,JavaScript 会把第一个点理解成小数点(即10.0),把第二个点理解成调用对象属性,从而得到正确结果。

  1. 10..toString(2)
  2. // "1010"
  3. // 其他方法还包括
  4. 10 .toString(2) // "1010"
  5. 10.0.toString(2) // "1010"

toString方法只能将十进制的数,转为其他进制的字符串。如果要将其他进制的数,转回十进制,需要使用parseInt方法。

Number.prototype.toFixed()

toFixed()方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串。

  1. (10).toFixed(2) // "10.00"
  2. 10.005.toFixed(2) // "10.01"

上面代码中,1010.005先转成2位小数,然后转成字符串。其中10必须放在括号里,否则后面的点会被处理成小数点。

toFixed()方法的参数为小数位数,有效范围为0到100,超出这个范围将抛出 RangeError 错误。

由于浮点数的原因,小数5的四舍五入是不确定的,使用的时候必须小心。

  1. (10.055).toFixed(2) // 10.05
  2. (10.005).toFixed(2) // 10.01

Number.prototype.toExponential()

toExponential方法用于将一个数转为科学计数法形式。

  1. (10).toExponential() // "1e+1"
  2. (10).toExponential(1) // "1.0e+1"
  3. (10).toExponential(2) // "1.00e+1"
  4. (1234).toExponential() // "1.234e+3"
  5. (1234).toExponential(1) // "1.2e+3"
  6. (1234).toExponential(2) // "1.23e+3"

toExponential方法的参数是小数点后有效数字的位数,范围为0到100,超出这个范围,会抛出一个 RangeError 错误。

Number.prototype.toPrecision()

Number.prototype.toPrecision()方法用于将一个数转为指定位数的有效数字。

  1. (12.34).toPrecision(1) // "1e+1"
  2. (12.34).toPrecision(2) // "12"
  3. (12.34).toPrecision(3) // "12.3"
  4. (12.34).toPrecision(4) // "12.34"
  5. (12.34).toPrecision(5) // "12.340"

该方法的参数为有效数字的位数,范围是1到100,超出这个范围会抛出 RangeError 错误。

该方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关。

  1. (12.35).toPrecision(3) // "12.3"
  2. (12.25).toPrecision(3) // "12.3"
  3. (12.15).toPrecision(3) // "12.2"
  4. (12.45).toPrecision(3) // "12.4"

自定义方法

与其他对象一样,Number.prototype对象上面可以自定义方法,被Number的实例继承。

  1. Number.prototype.add = function (x) {
  2. return this + x;
  3. };
  4. 8['add'](2) // 10

上面代码为Number对象实例定义了一个add方法。在数值上调用某个方法,数值会自动转为Number的实例对象,所以就可以调用add方法了。由于add方法返回的还是数值,所以可以链式运算。

  1. Number.prototype.subtract = function (x) {
  2. return this - x;
  3. };
  4. (8).add(2).subtract(4)
  5. // 6

String 对象

String对象是 JavaScript 原生提供的三个包装对象之一,用来生成字符串对象。

  1. var s1 = 'abc';
  2. var s2 = new String('abc');
  3. typeof s1 // "string"
  4. typeof s2 // "object"
  5. s2.valueOf() // "abc"

上面代码中,变量s1是字符串,s2是对象。由于s2是字符串对象,s2.valueOf方法返回的就是它所对应的原始字符串。

字符串对象是一个类似数组的对象(很像数组,但不是数组)。

  1. new String('abc')
  2. // String {0: "a", 1: "b", 2: "c", length: 3}
  3. (new String('abc'))[1] // "b"

上面代码中,字符串abc对应的字符串对象,有数值键(012)和length属性,所以可以像数组那样取值。

除了用作构造函数,String对象还可以当作工具方法使用,将任意类型的值转为字符串。

  1. String(true) // "true"
  2. String(5) // "5"

上面代码将布尔值true和数值5,分别转换为字符串。

静态方法

JavaScript String 对象用于处理字符串,其中提供了大量操作字符串的方法,以及一些属性。

创建 String 对象的语法格式如下:
var val = new String(value);
var val = String(value);

其中参数 value 为要创建的字符串或字符串对象。

JavaScript 中,字符串和字符串对象之间能够自由转换,因此不论是创建字符串对象还是直接声明字符串类型的变量,都可以直接使用字符串对象中提供的方法和属性。
String 对象中的属性
下表中列举了 String 对象中提供的属性及其描述信息:

属性 描述
constructor 获取创建此对象的 String() 函数的引用
length 获取字符串的长度
prototype 通过该属性您可以向对象中添加属性和方法
示例代码如下:

  1. var str = new String('JavaScript');
  2. String.prototype.name = null;
  3. str.name = "Hello World!";
  4. document.write(str.constructor + "<br>"); // 输出:function String() { [native code] }
  5. document.write(str.length + "<br>"); // 输出:10
  6. document.write(str.name); // 输出:Hello World!
  7. String 对象中的方法

下表中列举了 String 对象中提供的方法及其描述信息:

方法 描述

  1. anchor() 创建一个 HTML 锚点,即生成一个<a>标签,标签的 name 属性为 anchor() 方法中的参数
  2. big() 用大号字体显示字符串
  3. blink() 显示闪动的字符串
  4. bold() 使用粗体显示字符串
  5. charAt() 返回在指定位置的字符
  6. charCodeAt() 返回指定字符的 Unicode
  7. 编码
  8. concat() 拼接字符串
  9. fixed() 以打字机文本显示字符串
  10. fontcolor() 使用指定的颜色来显示字符串
  11. fontsize() 使用指定的尺寸来显示字符串
  12. fromCharCode() 将字符编码转换为一个字符串
  13. indexOf() 检索字符串,获取给定字符串在字符串对象中首次出现的位置
  14. italics() 使用斜体显示字符串
  15. lastIndexOf() 获取给定字符串在字符串对象中最后出现的位置
  16. link() 将字符串显示为链接
  17. localeCompare() 返回一个数字,并使用该数字来表示字符串对象是大于、小于还是等于给定字符串
  18. match() 根据正则表达式匹配字符串中的字符
  19. replace() 替换与正则表达式匹配的子字符串
  20. search() 获取与正则表达式相匹配字符串首次出现的位置
  21. slice() 截取字符串的片断,并将其返回
  22. small() 使用小字号来显示字符串
  23. split() 根据给定字符将字符串分割为字符串数组
  24. strike() 使用删除线来显示字符串
  25. sub() 把字符串显示为下标
  26. substr() 从指定索引位置截取指定长度的字符串
  27. substring() 截取字符串中两个指定的索引之间的字符
  28. sup() 把字符串显示为上标
  29. toLocaleLowerCase() 把字符串转换为小写
  30. toLocaleUpperCase() 把字符串转换为大写
  31. toLowerCase() 把字符串转换为小写
  32. toUpperCase() 把字符串转换为大写
  33. toString() 返回字符串
  34. valueOf() 返回某个字符串对象的原始值
  35. 示例代码如下:
  36. var str = new String('JavaScript教程');
  37. document.write(str.anchor("myanchor") + "<br>"); // 生成一段 HTML 代码:<a name="myanchor">JavaScript教程</a>
  38. document.write(str.big() + "<br>"); // 生成一段 HTML 代码:<big>JavaScript教程</big>
  39. document.write(str.blink() + "<br>"); // 生成一段 HTML 代码:<blink>JavaScript教程</blink>
  40. document.write(str.bold() + "<br>"); // 生成一段 HTML 代码:<b>JavaScript教程</b>
  41. document.write(str.charAt(10) + "<br>"); // 获取 str 中的第 11 个字符,输出:教
  42. document.write(str.charCodeAt(10) + "<br>"); // 获取 str 中第 11 个字符的 Unicode 编码,输出:25945
  43. document.write(str.concat(" String 对象") + "<br>"); // 将字符串“ String 对象”拼接到字符串 str 之后,输出:JavaScript教程 String 对象
  44. document.write(str.fixed() + "<br>"); // 生成一段 HTML 代码:<tt>JavaScript教程</tt>
  45. document.write(str.fontcolor("red") + "<br>"); // 生成一段 HTML 代码:<font color="red">JavaScript教程</font>
  46. document.write(str.fontsize(2) + "<br>"); // 生成一段 HTML 代码:<font size="2">JavaScript教程</font>
  47. document.write(String.fromCharCode(72,69,76,76,79) + "<br>"); // 将 Unicode 编码转换为具体的字符,输出:HELLO
  48. document.write(str.indexOf("Script") + "<br>"); // 获取字符串“Script”在 str 中首次出现的为,输出:4
  49. document.write(str.italics() + "<br>"); // 生成一段 HTML 代码:<i>JavaScript教程</i>
  50. document.write(str.lastIndexOf("a") + "<br>"); // 获取字符串“a”在 str 中最后一次出现的位置,输出 3
  51. document.write(str.link("http://c.biancheng.net/") + "<br>"); // 生成一段 HTML 代码:<a href="http://c.biancheng.net/">JavaScript教程</a>
  52. document.write(str.localeCompare("JavaScript") + "<br>"); // 比较字符串对象与给定字符串,返回:1
  53. document.write(str.match(/[abc]/g) + "<br>"); // 根据正则 /[abc]/g 检索 str,返回:a,a,c
  54. document.write(str.replace(/[abc]/g, "Y") + "<br>"); // 使用字符串“Y”替换正则 /[abc]/g 匹配的字符,返回:JYvYSYript教程
  55. document.write(str.search(/[Script]/g) + "<br>"); // 获取与正则匹配的字符串首次出现的位置,返回:4
  56. document.write(str.slice(6,11) + "<br>"); // 截取字符串(获取 str 中第 7 到第 11 个字符),返回:ript教
  57. document.write(str.small() + "<br>"); // 生成一段 HTML 代码:<small>JavaScript教程</small>
  58. document.write(str.split("a") + "<br>"); // 根据“a”将字符串 str 拆分为数组,返回:J,v,Script教程
  59. document.write(str.strike() + "<br>"); // 生成一段 HTML 代码:<strike>JavaScript教程</strike>
  60. document.write(str.sub() + "<br>"); // 生成一段 HTML 代码:<sub>JavaScript教程</sub>
  61. document.write(str.substr(3, 7) + "<br>"); // 从第 4 个字符开始,向后截取 7 个字符,返回:aScript
  62. document.write(str.substring(3, 7) + "<br>"); // 截取字符串(获取 str 中第 4 到第 7 个字符),返回:aScr
  63. document.write(str.sup() + "<br>"); // 生成一段 HTML 代码:<sup>JavaScript教程</sup>
  64. document.write(str.toLocaleLowerCase() + "<br>"); // 返回:javascript教程
  65. document.write(str.toLocaleUpperCase() + "<br>"); // 返回:JAVASCRIPT教程
  66. document.write(str.toLowerCase() + "<br>"); // 返回:javascript教程
  67. document.write(str.toUpperCase() + "<br>"); // 返回:JAVASCRIPT教程
  68. document.write(str.toString() + "<br>"); // 返回:JavaScript教程
  69. document.write(str.valueOf() + "<br>"); // 返回:JavaScript教程

Math 对象

Math是 JavaScript 的原生对象,提供各种数学功能。该对象不是构造函数,不能生成实例,所有的属性和方法都必须在Math对象上调用。

静态属性

Math对象的静态属性,提供以下一些数学常数。

  • Math.E:常数e
  • Math.LN2:2 的自然对数。
  • Math.LN10:10 的自然对数。
  • Math.LOG2E:以 2 为底的e的对数。
  • Math.LOG10E:以 10 为底的e的对数。
  • Math.PI:常数π
  • Math.SQRT1_2:0.5 的平方根。
  • Math.SQRT2:2 的平方根。
  1. Math.E // 2.718281828459045
  2. Math.LN2 // 0.6931471805599453
  3. Math.LN10 // 2.302585092994046
  4. Math.LOG2E // 1.4426950408889634
  5. Math.LOG10E // 0.4342944819032518
  6. Math.PI // 3.141592653589793
  7. Math.SQRT1_2 // 0.7071067811865476
  8. Math.SQRT2 // 1.4142135623730951

这些属性都是只读的,不能修改。

静态方法

Math对象提供以下一些静态方法。

  • Math.abs():绝对值
  • Math.ceil():向上取整
  • Math.floor():向下取整
  • Math.max():最大值
  • Math.min():最小值
  • Math.pow():幂运算
  • Math.sqrt():平方根
  • Math.log():自然对数
  • Math.exp()e的指数
  • Math.round():四舍五入
  • Math.random():随机数

Math.abs()

Math.abs方法返回参数值的绝对值。

  1. Math.abs(1) // 1
  2. Math.abs(-1) // 1

Math.max(),Math.min()

Math.max方法返回参数之中最大的那个值,Math.min返回最小的那个值。如果参数为空, Math.min返回Infinity, Math.max返回-Infinity

  1. Math.max(2, -1, 5) // 5
  2. Math.min(2, -1, 5) // -1
  3. Math.min() // Infinity
  4. Math.max() // -Infinity

Math.floor(),Math.ceil()

Math.floor方法返回小于或等于参数值的最大整数(地板值)。

  1. Math.floor(3.2) // 3
  2. Math.floor(-3.2) // -4

Math.ceil方法返回大于或等于参数值的最小整数(天花板值)。

  1. Math.ceil(3.2) // 4
  2. Math.ceil(-3.2) // -3

这两个方法可以结合起来,实现一个总是返回数值的整数部分的函数。

  1. function ToInteger(x) {
  2. x = Number(x);
  3. return x < 0 ? Math.ceil(x) : Math.floor(x);
  4. }
  5. ToInteger(3.2) // 3
  6. ToInteger(3.5) // 3
  7. ToInteger(3.8) // 3
  8. ToInteger(-3.2) // -3
  9. ToInteger(-3.5) // -3
  10. ToInteger(-3.8) // -3

上面代码中,不管正数或负数,ToInteger函数总是返回一个数值的整数部分。

Math.round()

Math.round方法用于四舍五入。

  1. Math.round(0.1) // 0
  2. Math.round(0.5) // 1
  3. Math.round(0.6) // 1
  4. // 等同于
  5. Math.floor(x + 0.5)

注意,它对负数的处理(主要是对0.5的处理)。

  1. Math.round(-1.1) // -1
  2. Math.round(-1.5) // -1
  3. Math.round(-1.6) // -2

Math.pow()

Math.pow方法返回以第一个参数为底数、第二个参数为指数的幂运算值。

  1. // 等同于 2 ** 2
  2. Math.pow(2, 2) // 4
  3. // 等同于 2 ** 3
  4. Math.pow(2, 3) // 8

下面是计算圆面积的方法。

  1. var radius = 20;
  2. var area = Math.PI * Math.pow(radius, 2);

Math.sqrt()

Math.sqrt方法返回参数值的平方根。如果参数是一个负值,则返回NaN

  1. Math.sqrt(4) // 2
  2. Math.sqrt(-4) // NaN

Math.log()

Math.log方法返回以e为底的自然对数值。

  1. Math.log(Math.E) // 1
  2. Math.log(10) // 2.302585092994046

如果要计算以10为底的对数,可以先用Math.log求出自然对数,然后除以Math.LN10;求以2为底的对数,可以除以Math.LN2

  1. Math.log(100)/Math.LN10 // 2
  2. Math.log(8)/Math.LN2 // 3

Math.exp()

Math.exp方法返回常数e的参数次方。

  1. Math.exp(1) // 2.718281828459045
  2. Math.exp(3) // 20.085536923187668

Math.random()

Math.random()返回0到1之间的一个伪随机数,可能等于0,但是一定小于1。

  1. Math.random() // 0.7151307314634323

任意范围的随机数生成函数如下。

  1. function getRandomArbitrary(min, max) {
  2. return Math.random() * (max - min) + min;
  3. }
  4. getRandomArbitrary(1.5, 6.5)
  5. // 2.4942810038223864

任意范围的随机整数生成函数如下。

  1. function getRandomInt(min, max) {
  2. return Math.floor(Math.random() * (max - min + 1)) + min;
  3. }
  4. getRandomInt(1, 6) // 5

返回随机字符的例子如下。

  1. function random_str(length) {
  2. var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  3. ALPHABET += 'abcdefghijklmnopqrstuvwxyz';
  4. ALPHABET += '0123456789-_';
  5. var str = '';
  6. for (var i = 0; i < length; ++i) {
  7. var rand = Math.floor(Math.random() * ALPHABET.length);
  8. str += ALPHABET.substring(rand, rand + 1);
  9. }
  10. return str;
  11. }
  12. random_str(6) // "NdQKOr"

上面代码中,random_str函数接受一个整数作为参数,返回变量ALPHABET内的随机字符所组成的指定长度的字符串。

三角函数方法

Math对象还提供一系列三角函数方法。

  • Math.sin():返回参数的正弦(参数为弧度值)
  • Math.cos():返回参数的余弦(参数为弧度值)
  • Math.tan():返回参数的正切(参数为弧度值)
  • Math.asin():返回参数的反正弦(返回值为弧度值)
  • Math.acos():返回参数的反余弦(返回值为弧度值)
  • Math.atan():返回参数的反正切(返回值为弧度值)
  1. Math.sin(0) // 0
  2. Math.cos(0) // 1
  3. Math.tan(0) // 0
  4. Math.sin(Math.PI / 2) // 1
  5. Math.asin(1) // 1.5707963267948966
  6. Math.acos(1) // 0
  7. Math.atan(1) // 0.7853981633974483

Date 对象

Date对象是 JavaScript 原生的时间库。它以国际标准时间(UTC)1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)。

普通函数的用法

Date对象可以作为普通函数直接调用,返回一个代表当前时间的字符串。

  1. Date()
  2. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

注意,即使带有参数,Date作为普通函数使用时,返回的还是当前时间。

  1. Date(2000, 1, 1)
  2. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

上面代码说明,无论有没有参数,直接调用Date总是返回当前时间。

构造函数的用法

Date还可以当作构造函数使用。对它使用new命令,会返回一个Date对象的实例。如果不加参数,实例代表的就是当前时间。

  1. var today = new Date();

Date实例有一个独特的地方。其他对象求值的时候,都是默认调用.valueOf()方法,但是Date实例求值的时候,默认调用的是toString()方法。这导致对Date实例求值,返回的是一个字符串,代表该实例对应的时间。

  1. var today = new Date();
  2. today
  3. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"
  4. // 等同于
  5. today.toString()
  6. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

上面代码中,todayDate的实例,直接求值等同于调用toString方法。

作为构造函数时,Date对象可以接受多种格式的参数,返回一个该参数对应的时间实例。

  1. // 参数为时间零点开始计算的毫秒数
  2. new Date(1378218728000)
  3. // Tue Sep 03 2013 22:32:08 GMT+0800 (CST)
  4. // 参数为日期字符串
  5. new Date('January 6, 2013');
  6. // Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
  7. // 参数为多个整数,
  8. // 代表年、月、日、小时、分钟、秒、毫秒
  9. new Date(2013, 0, 1, 0, 0, 0, 0)
  10. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)

关于Date构造函数的参数,有几点说明。

参数为年、月、日等多个整数时,年和月是不能省略的,其他参数都可以省略的。也就是说,这时至少需要两个参数,因为如果只使用“年”这一个参数,Date会将其解释为毫秒数。

  1. new Date(2013)
  2. // Thu Jan 01 1970 08:00:02 GMT+0800 (CST)

上面代码中,2013被解释为毫秒数,而不是年份。

  1. new Date(2013, 0)
  2. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
  3. new Date(2013, 0, 1)
  4. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
  5. new Date(2013, 0, 1, 0)
  6. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
  7. new Date(2013, 0, 1, 0, 0, 0, 0)
  8. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)

上面代码中,不管有几个参数,返回的都是2013年1月1日零点。

最后,各个参数的取值范围如下。

  • 年:使用四位数年份,比如2000。如果写成两位数或个位数,则加上1900,即10代表1910年。如果是负数,表示公元前。
  • 月:0表示一月,依次类推,11表示12月。
  • 日:131
  • 小时:023
  • 分钟:059
  • 秒:059
  • 毫秒:0999

注意,月份从0开始计算,但是,天数从1开始计算。另外,除了日期的默认值为1,小时、分钟、秒钟和毫秒的默认值都是0

这些参数如果超出了正常范围,会被自动折算。比如,如果月设为15,就折算为下一年的4月。

  1. new Date(2013, 15)
  2. // Tue Apr 01 2014 00:00:00 GMT+0800 (CST)
  3. new Date(2013, 0, 0)
  4. // Mon Dec 31 2012 00:00:00 GMT+0800 (CST)

上面代码的第二个例子,日期设为0,就代表上个月的最后一天。

参数还可以使用负数,表示扣去的时间。

  1. new Date(2013, -1)
  2. // Sat Dec 01 2012 00:00:00 GMT+0800 (CST)
  3. new Date(2013, 0, -1)
  4. // Sun Dec 30 2012 00:00:00 GMT+0800 (CST)

上面代码中,分别对月和日使用了负数,表示从基准日扣去相应的时间。

日期的运算

类型自动转换时,Date实例如果转为数值,则等于对应的毫秒数;如果转为字符串,则等于对应的日期字符串。所以,两个日期实例对象进行减法运算时,返回的是它们间隔的毫秒数;进行加法运算时,返回的是两个字符串连接而成的新字符串。

  1. var d1 = new Date(2000, 2, 1);
  2. var d2 = new Date(2000, 3, 1);
  3. d2 - d1
  4. // 2678400000
  5. d2 + d1
  6. // "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800 (CST)"

静态方法

Date.now()

Date.now方法返回当前时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数,相当于 Unix 时间戳乘以1000。

  1. Date.now() // 1364026285194

Date.parse()

Date.parse方法用来解析日期字符串,返回该时间距离时间零点(1970年1月1日 00:00:00)的毫秒数。

日期字符串应该符合 RFC 2822 和 ISO 8061 这两个标准,即YYYY-MM-DDTHH:mm:ss.sssZ格式,其中最后的Z表示时区。但是,其他格式也可以被解析,请看下面的例子。

  1. Date.parse('Aug 9, 1995')
  2. Date.parse('January 26, 2011 13:51:50')
  3. Date.parse('Mon, 25 Dec 1995 13:30:00 GMT')
  4. Date.parse('Mon, 25 Dec 1995 13:30:00 +0430')
  5. Date.parse('2011-10-10')
  6. Date.parse('2011-10-10T14:48:00')

上面的日期字符串都可以解析。

如果解析失败,返回NaN

  1. Date.parse('xxx') // NaN

Date.UTC()

Date.UTC方法接受年、月、日等变量作为参数,返回该时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数。

  1. // 格式
  2. Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
  3. // 用法
  4. Date.UTC(2011, 0, 1, 2, 3, 4, 567)
  5. // 1293847384567

该方法的参数用法与Date构造函数完全一致,比如月从0开始计算,日期从1开始计算。区别在于Date.UTC方法的参数,会被解释为 UTC 时间(世界标准时间),Date构造函数的参数会被解释为当前时区的时间。

get 类方法

Date对象提供了一系列get*方法,用来获取实例对象某个方面的值。

  • getTime():返回实例距离1970年1月1日00:00:00的毫秒数,等同于valueOf方法。
  • getDate():返回实例对象对应每个月的几号(从1开始)。
  • getDay():返回星期几,星期日为0,星期一为1,以此类推。
  • getFullYear():返回四位的年份。
  • getMonth():返回月份(0表示1月,11表示12月)。
  • getHours():返回小时(0-23)。
  • getMilliseconds():返回毫秒(0-999)。
  • getMinutes():返回分钟(0-59)。
  • getSeconds():返回秒(0-59)。
  • getTimezoneOffset():返回当前时间与 UTC 的时区差异,以分钟表示,返回结果考虑到了夏令时因素。

所有这些get*方法返回的都是整数,不同方法返回值的范围不一样。

  • 分钟和秒:0 到 59
  • 小时:0 到 23
  • 星期:0(星期天)到 6(星期六)
  • 日期:1 到 31
  • 月份:0(一月)到 11(十二月)
  1. var d = new Date('January 6, 2013');
  2. d.getDate() // 6
  3. d.getMonth() // 0
  4. d.getFullYear() // 2013
  5. d.getTimezoneOffset() // -480

上面代码中,最后一行返回-480,即 UTC 时间减去当前时间,单位是分钟。-480表示 UTC 比当前时间少480分钟,即当前时区比 UTC 早8个小时。

下面是一个例子,计算本年度还剩下多少天。

  1. function leftDays() {
  2. var today = new Date();
  3. var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);
  4. var msPerDay = 24 * 60 * 60 * 1000;
  5. return Math.round((endYear.getTime() - today.getTime()) / msPerDay);
  6. }

Date对象提供了一系列set*方法,用来设置实例对象的各个方面。

  • setDate(date):设置实例对象对应的每个月的几号(1-31),返回改变后毫秒时间戳。
  • setFullYear(year [, month, date]):设置四位年份。
  • setHours(hour [, min, sec, ms]):设置小时(0-23)。
  • setMilliseconds():设置毫秒(0-999)。
  • setMinutes(min [, sec, ms]):设置分钟(0-59)。
  • setMonth(month [, date]):设置月份(0-11)。
  • setSeconds(sec [, ms]):设置秒(0-59)。
  • setTime(milliseconds):设置毫秒时间戳。

这些方法基本是跟get*方法一一对应的,但是没有setDay方法,因为星期几是计算出来的,而不是设置的。另外,需要注意的是,凡是涉及到设置月份,都是从0开始算的,即0是1月,11是12月。

  1. var d = new Date ('January 6, 2013');
  2. d // Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
  3. d.setDate(9) // 1357660800000
  4. d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST)

set*方法的参数都会自动折算。以setDate()为例,如果参数超过当月的最大天数,则向下一个月顺延,如果参数是负数,表示从上个月的最后一天开始减去的天数。

  1. var d1 = new Date('January 6, 2013');
  2. d1.setDate(32) // 1359648000000
  3. d1 // Fri Feb 01 2013 00:00:00 GMT+0800 (CST)
  4. var d2 = new Date ('January 6, 2013');
  5. d2.setDate(-1) // 1356796800000
  6. d2 // Sun Dec 30 2012 00:00:00 GMT+0800 (CST)

上面代码中,d1.setDate(32)将日期设为1月份的32号,因为1月份只有31号,所以自动折算为2月1日。d2.setDate(-1)表示设为上个月的倒数第二天,即12月30日。

set类方法和get类方法,可以结合使用,得到相对时间。

  1. var d = new Date();
  2. // 将日期向后推1000天
  3. d.setDate(d.getDate() + 1000);
  4. // 将时间设为6小时后
  5. d.setHours(d.getHours() + 6);
  6. // 将年份设为去年
  7. d.setFullYear(d.getFullYear() - 1);

JSON 对象

JSON 格式

JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。

相比 XML 格式,JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。所以,JSON 迅速被接受,已经成为各大网站交换数据的标准格式,并被写入标准。

每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。总之,只能是一个值,不能是两个或更多的值。

JSON 对值的类型和格式有严格的规定。

  1. 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
  2. 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinityundefined)。
  3. 字符串必须使用双引号表示,不能使用单引号。
  4. 对象的键名必须放在双引号里面。
  5. 数组或对象最后一个成员的后面,不能加逗号。

以下都是合法的 JSON。

  1. ["one", "two", "three"]
  2. { "one": 1, "two": 2, "three": 3 }
  3. {"names": ["张三", "李四"] }
  4. [ { "name": "张三"}, {"name": "李四"} ]

以下都是不合法的 JSON。

  1. { name: "张三", 'age': 32 } // 属性名必须使用双引号
  2. [32, 64, 128, 0xFFF] // 不能使用十六进制值
  3. { "name": "张三", "age": undefined } // 不能使用 undefined
  4. { "name": "张三",
  5. "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
  6. "getName": function () {
  7. return this.name;
  8. }
  9. } // 属性值不能使用函数和日期对象

注意,null、空数组和空对象都是合法的 JSON 值。

JSON 对象

JSON对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。它有两个静态方法:JSON.stringify()JSON.parse()

JSON.stringify()

基本用法

JSON.stringify()方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以被JSON.parse()方法还原。

  1. JSON.stringify('abc') // ""abc""
  2. JSON.stringify(1) // "1"
  3. JSON.stringify(false) // "false"
  4. JSON.stringify([]) // "[]"
  5. JSON.stringify({}) // "{}"
  6. JSON.stringify([1, "false", false])
  7. // '[1,"false",false]'
  8. JSON.stringify({ name: "张三" })
  9. // '{"name":"张三"}'

上面代码将各种类型的值,转成 JSON 字符串。

注意,对于原始类型的字符串,转换结果会带双引号。

  1. JSON.stringify('foo') === "foo" // false
  2. JSON.stringify('foo') === "\"foo\"" // true

上面代码中,字符串foo,被转成了"\"foo\""。这是因为将来还原的时候,内层双引号可以让 JavaScript 引擎知道,这是一个字符串,而不是其他类型的值。

  1. JSON.stringify(false) // "false"
  2. JSON.stringify('false') // "\"false\""

上面代码中,如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串。

如果对象的属性是undefined、函数或 XML 对象,该属性会被JSON.stringify()过滤。

  1. var obj = {
  2. a: undefined,
  3. b: function () {}
  4. };
  5. JSON.stringify(obj) // "{}"

上面代码中,对象obja属性是undefined,而b属性是一个函数,结果都被JSON.stringify过滤。

如果数组的成员是undefined、函数或 XML 对象,则这些值被转成null

  1. var arr = [undefined, function () {}];
  2. JSON.stringify(arr) // "[null,null]"

上面代码中,数组arr的成员是undefined和函数,它们都被转成了null

正则对象会被转成空对象。

  1. JSON.stringify(/foo/) // "{}"

JSON.stringify()方法会忽略对象的不可遍历的属性。

  1. var obj = {};
  2. Object.defineProperties(obj, {
  3. 'foo': {
  4. value: 1,
  5. enumerable: true
  6. },
  7. 'bar': {
  8. value: 2,
  9. enumerable: false
  10. }
  11. });
  12. JSON.stringify(obj); // "{"foo":1}"

上面代码中,barobj对象的不可遍历属性,JSON.stringify方法会忽略这个属性。

第二个参数

JSON.stringify()方法还可以接受一个数组,作为第二个参数,指定参数对象的哪些属性需要转成字符串。

  1. var obj = {
  2. 'prop1': 'value1',
  3. 'prop2': 'value2',
  4. 'prop3': 'value3'
  5. };
  6. var selectedProperties = ['prop1', 'prop2'];
  7. JSON.stringify(obj, selectedProperties)
  8. // "{"prop1":"value1","prop2":"value2"}"

上面代码中,JSON.stringify()方法的第二个参数指定,只转prop1prop2两个属性。

这个类似白名单的数组,只对对象的属性有效,对数组无效。

  1. JSON.stringify(['a', 'b'], ['0'])
  2. // "["a","b"]"
  3. JSON.stringify({0: 'a', 1: 'b'}, ['0'])
  4. // "{"0":"a"}"

上面代码中,第二个参数指定 JSON 格式只转0号属性,实际上对数组是无效的,只对对象有效。

第二个参数还可以是一个函数,用来更改JSON.stringify()的返回值。

  1. function f(key, value) {
  2. if (typeof value === "number") {
  3. value = 2 * value;
  4. }
  5. return value;
  6. }
  7. JSON.stringify({ a: 1, b: 2 }, f)
  8. // '{"a": 2,"b": 4}'

上面代码中的f函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它乘以2,否则就原样返回。

注意,这个处理函数是递归处理所有的键。

  1. var obj = {a: {b: 1}};
  2. function f(key, value) {
  3. console.log("["+ key +"]:" + value);
  4. return value;
  5. }
  6. JSON.stringify(obj, f)
  7. // []:[object Object]
  8. // [a]:[object Object]
  9. // [b]:1
  10. // '{"a":{"b":1}}'

上面代码中,对象obj一共会被f函数处理三次,输出的最后那行是JSON.stringify()的默认输出。第一次键名为空,键值是整个对象obj;第二次键名为a,键值是{b: 1};第三次键名为b,键值为1。

递归处理中,每一次处理的对象,都是前一次返回的值。

  1. var obj = {a: 1};
  2. function f(key, value) {
  3. if (typeof value === 'object') {
  4. return {b: 2};
  5. }
  6. return value * 2;
  7. }
  8. JSON.stringify(obj, f)
  9. // "{"b": 4}"

上面代码中,f函数修改了对象obj,接着JSON.stringify()方法就递归处理修改后的对象obj

如果处理函数返回undefined或没有返回值,则该属性会被忽略。

  1. function f(key, value) {
  2. if (typeof(value) === "string") {
  3. return undefined;
  4. }
  5. return value;
  6. }
  7. JSON.stringify({ a: "abc", b: 123 }, f)
  8. // '{"b": 123}'

上面代码中,a属性经过处理后,返回undefined,于是该属性被忽略了。

第三个参数

JSON.stringify()还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。

默认返回的是单行字符串,对于大型的 JSON 对象,可读性非常差。第三个参数使得每个属性单独占据一行,并且将每个属性前面添加指定的前缀(不超过10个字符)。

  1. // 默认输出
  2. JSON.stringify({ p1: 1, p2: 2 })
  3. // JSON.stringify({ p1: 1, p2: 2 })
  4. // 分行输出
  5. JSON.stringify({ p1: 1, p2: 2 }, null, '\t')
  6. // {
  7. // "p1": 1,
  8. // "p2": 2
  9. // }

上面例子中,第三个属性\t在每个属性前面添加一个制表符,然后分行显示。

第三个属性如果是一个数字,则表示每个属性前面添加的空格(最多不超过10个)。

  1. JSON.stringify({ p1: 1, p2: 2 }, null, 2);
  2. /*
  3. "{
  4. "p1": 1,
  5. "p2": 2
  6. }"
  7. */

参数对象的 toJSON() 方法

如果参数对象有自定义的toJSON()方法,那么JSON.stringify()会使用这个方法的返回值作为参数,而忽略原对象的其他属性。

下面是一个普通的对象。

  1. var user = {
  2. firstName: '三',
  3. lastName: '张',
  4. get fullName(){
  5. return this.lastName + this.firstName;
  6. }
  7. };
  8. JSON.stringify(user)
  9. // "{"firstName":"三","lastName":"张","fullName":"张三"}"

现在,为这个对象加上toJSON()方法。

  1. var user = {
  2. firstName: '三',
  3. lastName: '张',
  4. get fullName(){
  5. return this.lastName + this.firstName;
  6. },
  7. toJSON: function () {
  8. return {
  9. name: this.lastName + this.firstName
  10. };
  11. }
  12. };
  13. JSON.stringify(user)
  14. // "{"name":"张三"}"

上面代码中,JSON.stringify()发现参数对象有toJSON()方法,就直接使用这个方法的返回值作为参数,而忽略原对象的其他参数。

Date对象就有一个自己的toJSON()方法。

  1. var date = new Date('2015-01-01');
  2. date.toJSON() // "2015-01-01T00:00:00.000Z"
  3. JSON.stringify(date) // ""2015-01-01T00:00:00.000Z""

上面代码中,JSON.stringify()发现处理的是Date对象实例,就会调用这个实例对象的toJSON()方法,将该方法的返回值作为参数。

toJSON()方法的一个应用是,将正则对象自动转为字符串。因为JSON.stringify()默认不能转换正则对象,但是设置了toJSON()方法以后,就可以转换正则对象了。

  1. var obj = {
  2. reg: /foo/
  3. };
  4. // 不设置 toJSON 方法时
  5. JSON.stringify(obj) // "{"reg":{}}"
  6. // 设置 toJSON 方法时
  7. RegExp.prototype.toJSON = RegExp.prototype.toString;
  8. JSON.stringify(/foo/) // ""/foo/""

上面代码在正则对象的原型上面部署了toJSON()方法,将其指向toString()方法,因此转换成 JSON 格式时,正则对象就先调用toJSON()方法转为字符串,然后再被JSON.stringify()方法处理。

JSON.parse()

JSON.parse()方法用于将 JSON 字符串转换成对应的值。

  1. JSON.parse('{}') // {}
  2. JSON.parse('true') // true
  3. JSON.parse('"foo"') // "foo"
  4. JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
  5. JSON.parse('null') // null
  6. var o = JSON.parse('{"name": "张三"}');
  7. o.name // 张三

如果传入的字符串不是有效的 JSON 格式,JSON.parse()方法将报错。

  1. JSON.parse("'String'") // illegal single quotes
  2. // SyntaxError: Unexpected token ILLEGAL

上面代码中,双引号字符串中是一个单引号字符串,因为单引号字符串不符合 JSON 格式,所以报错。

为了处理解析错误,可以将JSON.parse()方法放在try...catch代码块中。

  1. try {
  2. JSON.parse("'String'");
  3. } catch(e) {
  4. console.log('parsing error');
  5. }

JSON.parse()方法可以接受一个处理函数,作为第二个参数,用法与JSON.stringify()方法类似。

  1. function f(key, value) {
  2. if (key === 'a') {
  3. return value + 10;
  4. }
  5. return value;
  6. }
  7. JSON.parse('{"a": 1, "b": 2}', f)
  8. // {a: 11, b: 2}

上面代码中,JSON.parse()的第二个参数是一个函数,如果键名是a,该函数会将键值加上10。

定时器

JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()setInterval()这两个函数来完成。它们向任务队列添加定时任务。

setTimeout()

setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。

  1. var timerId = setTimeout(func|code, delay);

上面代码中,setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数。

  1. console.log(1);
  2. setTimeout('console.log(2)',1000);
  3. console.log(3);
  4. // 1
  5. // 3
  6. // 2

上面代码会先输出1和3,然后等待1000毫秒再输出2。注意,console.log(2)必须以字符串的形式,作为setTimeout的参数。

如果推迟执行的是函数,就直接将函数名,作为setTimeout的参数。

  1. function f() {
  2. console.log(2);
  3. }
  4. setTimeout(f, 1000);

setTimeout的第二个参数如果省略,则默认为0。

  1. setTimeout(f)
  2. // 等同于
  3. setTimeout(f, 0)

除了前两个参数,setTimeout还允许更多的参数。它们将依次传入推迟执行的函数(回调函数)。

  1. setTimeout(function (a,b) {
  2. console.log(a + b);
  3. }, 1000, 1, 1);

上面代码中,setTimeout共有4个参数。最后那两个参数,将在1000毫秒之后回调函数执行时,作为回调函数的参数。

还有一个需要注意的地方,如果回调函数是对象的方法,那么setTimeout使得方法内部的this关键字指向全局环境,而不是定义时所在的那个对象。

  1. var x = 1;
  2. var obj = {
  3. x: 2,
  4. y: function () {
  5. console.log(this.x);
  6. }
  7. };
  8. setTimeout(obj.y, 1000) // 1

上面代码输出的是1,而不是2。因为当obj.y在1000毫秒后运行时,this所指向的已经不是obj了,而是全局环境。

为了防止出现这个问题,一种解决方法是将obj.y放入一个函数。

  1. var x = 1;
  2. var obj = {
  3. x: 2,
  4. y: function () {
  5. console.log(this.x);
  6. }
  7. };
  8. setTimeout(function () {
  9. obj.y();
  10. }, 1000);
  11. // 2

上面代码中,obj.y放在一个匿名函数之中,这使得obj.yobj的作用域执行,而不是在全局作用域内执行,所以能够显示正确的值。

另一种解决方法是,使用bind方法,将obj.y这个方法绑定在obj上面。

  1. var x = 1;
  2. var obj = {
  3. x: 2,
  4. y: function () {
  5. console.log(this.x);
  6. }
  7. };
  8. setTimeout(obj.y.bind(obj), 1000)
  9. // 2

setInterval()

setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。

  1. var i = 1
  2. var timer = setInterval(function() {
  3. console.log(2);
  4. }, 1000)

上面代码中,每隔1000毫秒就输出一个2,会无限运行下去,直到关闭当前窗口。

setTimeout一样,除了前两个参数,setInterval方法还可以接受更多的参数,它们会传入回调函数。

下面是一个通过setInterval方法实现网页动画的例子。

  1. var div = document.getElementById('someDiv');
  2. var opacity = 1;
  3. var fader = setInterval(function() {
  4. opacity -= 0.1;
  5. if (opacity >= 0) {
  6. div.style.opacity = opacity;
  7. } else {
  8. clearInterval(fader);
  9. }
  10. }, 100);

上面代码每隔100毫秒,设置一次div元素的透明度,直至其完全透明为止。

setInterval的一个常见用途是实现轮询。下面是一个轮询 URL 的 Hash 值是否发生变化的例子。

  1. var hash = window.location.hash;
  2. var hashWatcher = setInterval(function() {
  3. if (window.location.hash != hash) {
  4. updatePage();
  5. }
  6. }, 1000);

setInterval指定的是“开始执行”之间的间隔,并不考虑每次任务执行本身所消耗的时间。因此实际上,两次执行之间的间隔会小于指定的时间。比如,setInterval指定每 100ms 执行一次,每次执行需要 5ms,那么第一次执行结束后95毫秒,第二次执行就会开始。如果某次执行耗时特别长,比如需要105毫秒,那么它结束后,下一次执行就会立即开始。

为了确保两次执行之间有固定的间隔,可以不用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间。

  1. var i = 1;
  2. var timer = setTimeout(function f() {
  3. // ...
  4. timer = setTimeout(f, 2000);
  5. }, 2000);

上面代码可以确保,下一次执行总是在本次执行结束之后的2000毫秒开始。

clearTimeout(),clearInterval()

setTimeoutsetInterval函数,都返回一个整数值,表示计数器编号。将该整数传入clearTimeoutclearInterval函数,就可以取消对应的定时器。

  1. var id1 = setTimeout(f, 1000);
  2. var id2 = setInterval(f, 1000);
  3. clearTimeout(id1);
  4. clearInterval(id2);

上面代码中,回调函数f不会再执行了,因为两个定时器都被取消了。

setTimeoutsetInterval返回的整数值是连续的,也就是说,第二个setTimeout方法返回的整数值,将比第一个的整数值大1。

  1. function f() {}
  2. setTimeout(f, 1000) // 10
  3. setTimeout(f, 1000) // 11
  4. setTimeout(f, 1000) // 12

上面代码中,连续调用三次setTimeout,返回值都比上一次大了1。

利用这一点,可以写一个函数,取消当前所有的setTimeout定时器。

  1. (function() {
  2. // 每轮事件循环检查一次
  3. var gid = setInterval(clearAllTimeouts, 0);
  4. function clearAllTimeouts() {
  5. var id = setTimeout(function() {}, 0);
  6. while (id > 0) {
  7. if (id !== gid) {
  8. clearTimeout(id);
  9. }
  10. id--;
  11. }
  12. }
  13. })();

上面代码中,先调用setTimeout,得到一个计算器编号,然后把编号比它小的计数器全部取消。

实例:debounce 函数

有时,我们不希望回调函数被频繁调用。比如,用户填入网页输入框的内容,希望通过 Ajax 方法传回服务器,jQuery 的写法如下。

  1. $('textarea').on('keydown', ajaxAction);

这样写有一个很大的缺点,就是如果用户连续击键,就会连续触发keydown事件,造成大量的 Ajax 通信。这是不必要的,而且很可能产生性能问题。正确的做法应该是,设置一个门槛值,表示两次 Ajax 通信的最小间隔时间。如果在间隔时间内,发生新的keydown事件,则不触发 Ajax 通信,并且重新开始计时。如果过了指定时间,没有发生新的keydown事件,再将数据发送出去。

这种做法叫做 debounce(防抖动)。假定两次 Ajax 通信的间隔不得小于2500毫秒,上面的代码可以改写成下面这样。

  1. $('textarea').on('keydown', debounce(ajaxAction, 2500));
  2. function debounce(fn, delay){
  3. var timer = null; // 声明计时器
  4. return function() {
  5. var context = this;
  6. var args = arguments;
  7. clearTimeout(timer);
  8. timer = setTimeout(function () {
  9. fn.apply(context, args);
  10. }, delay);
  11. };
  12. }

上面代码中,只要在2500毫秒之内,用户再次击键,就会取消上一次的定时器,然后再新建一个定时器。这样就保证了回调函数之间的调用间隔,至少是2500毫秒。

运行机制

setTimeoutsetInterval的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。

这意味着,setTimeoutsetInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeoutsetInterval指定的任务,一定会按照预定时间执行。

  1. setTimeout(someTask, 100);
  2. veryLongTask();

上面代码的setTimeout,指定100毫秒以后运行一个任务。但是,如果后面的veryLongTask函数(同步任务)运行时间非常长,过了100毫秒还无法结束,那么被推迟运行的someTask就只有等着,等到veryLongTask运行结束,才轮到它执行。

再看一个setInterval的例子。

  1. setInterval(function () {
  2. console.log(2);
  3. }, 1000);
  4. sleep(3000);
  5. function sleep(ms) {
  6. var start = Date.now();
  7. while ((Date.now() - start) < ms) {
  8. }
  9. }

上面代码中,setInterval要求每隔1000毫秒,就输出一个2。但是,紧接着的sleep语句需要3000毫秒才能完成,那么setInterval就必须推迟到3000毫秒之后才开始生效。注意,生效后setInterval不会产生累积效应,即不会一下子输出三个2,而是只会输出一个2。

setTimeout(f, 0)

含义

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f, 0),那么会立刻执行吗?

答案是不会。因为上一节说过,必须要等到当前脚本的同步任务,全部处理完以后,才会执行setTimeout指定的回调函数f。也就是说,setTimeout(f, 0)会在下一轮事件循环一开始就执行。

  1. setTimeout(function () {
  2. console.log(1);
  3. }, 0);
  4. console.log(2);
  5. // 2
  6. // 1

上面代码先输出2,再输出1。因为2是同步任务,在本轮事件循环执行,而1是下一轮事件循环执行。

总之,setTimeout(f, 0)这种写法的目的是,尽可能早地执行f,但是并不能保证立刻就执行f

实际上,setTimeout(f, 0)不会真的在0毫秒之后运行,不同的浏览器有不同的实现。以 Edge 浏览器为例,会等到4毫秒之后运行。如果电脑正在使用电池供电,会等到16毫秒之后运行;如果网页不在当前 Tab 页,会推迟到1000毫秒(1秒)之后运行。这样是为了节省系统资源。

应用

setTimeout(f, 0)有几个非常重要的用途。它的一大应用是,可以调整事件的发生顺序。比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,想让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)

  1. // HTML 代码如下
  2. // <input type="button" id="myButton" value="click">
  3. var input = document.getElementById('myButton');
  4. input.onclick = function A() {
  5. setTimeout(function B() {
  6. input.value +=' input';
  7. }, 0)
  8. };
  9. document.body.onclick = function C() {
  10. input.value += ' body'
  11. };

上面代码在点击按钮后,先触发回调函数A,然后触发函数C。函数A中,setTimeout将函数B推迟到下一轮事件循环执行,这样就起到了,先触发父元素的回调函数C的目的了。

另一个应用是,用户自定义的回调函数,通常在浏览器的默认动作之前触发。比如,用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。因此,下面的回调函数是达不到目的的。

  1. // HTML 代码如下
  2. // <input type="text" id="input-box">
  3. document.getElementById('input-box').onkeypress = function (event) {
  4. this.value = this.value.toUpperCase();
  5. }

上面代码想在用户每次输入文本后,立即将字符转为大写。但是实际上,它只能将本次输入前的字符转为大写,因为浏览器此时还没接收到新的文本,所以this.value取不到最新输入的那个字符。只有用setTimeout改写,上面的代码才能发挥作用。

  1. document.getElementById('input-box').onkeypress = function() {
  2. var self = this;
  3. setTimeout(function() {
  4. self.value = self.value.toUpperCase();
  5. }, 0);
  6. }
0

Copyright (C) 2021-2026 98社区 All Rights Reserved 蜀ICP备20012692号-3