# 引用类型

引用类型是一种用于将 数据功能 组织在一起的数据结构,也就是说它可以描述一类对象所具有的 属性方法。那么某个对象(引用类型的值)是引用类型的一个实例。
var person = new Object();新对像是使用 new 操作符后跟一个构造函数来创健的。

# 一、Object 类型

Object 是 ECMAScript 中使用最多的一个类型,创建 Object 实例的方式有两种,一种是之前提到的 new 操作符后跟 Object 构造函数,另一种是我们比较推荐使用的对象字面量表示法。
var person = new Object();等价于var person = {};

  • {}里可以定义属性,如:var person = { age: 24 }; 这是一个对象上下文,若属性名是数值,会自动转为 String;
  • 访问对象属性可使用点表示法也可使用方括号表示法,如:person["age"]person.age;方括号表示法用在属性名不规范的情况下,比如含有空格等字符,点表示法就需要属性名尽可能像标识符那样规范。

# 二、Array 类型

Array 是 ECMAScript 中除 Object 最常用的类型,与其他语言不同的是它的每一项可保存 任何类型 的数据(位置 0 存字符串,位置 1 存数值,位置 2 存对象等等),并且数组大小是 动态调整 的(创建时的长度可随着新增的元素而变长)。

# 2.1 创建数组

创建数组有两种:

第一种 自然是 new 操作符后跟 Array 构造函数(new 可省略)

var colors = new Array(3); // 入参是数值3,创建长度为3的数组,入参也可以不传
var names = new Array("Greg"); // 入参不是数值,创建了一个包含“Greg”元素的数组
1
2

第二种 是使用 数组字面量表示法var names = [];等价于var names = new Array();,如果有[1, 2, ][ , , ,],它可能在不同浏览器里有不同的效果,不推荐省略元素这种写法。

关于 数组长度 (length),length 值比原数组大时,新数组新增出来的位置会使用undefined值填充,而 length 值比原数组小时,新数组会移除多出来的位置,可以访问多出来的位置但值是 undefined

var colors = ["red", "blue", "green"];
colors[2] = "black"; // 修改数组第三项的值为"black"
colors[3] = "brown"; // 新增第四项"brown"
colors.length = 6; // 增长数组,现数组:["red", "blue", "black", "brown", undefined, undefined]
colors.length = 2; // 减短数组,现数组:["red", "blue"],如果此时访问colors[4]那返回的是undefined
colors[99] = "black"; // 增长数组,现数组:["red", "blue", undefined, ..., undefined, "black"],长度为99
1
2
3
4
5
6

# 2.2 isArray 检测数组

只有一个网页或一个全局执行环境是,我们使用instanceof就可以检测对象是不是数组。但是在多个网页或多个全局执行环境时,他们数组的构造函数版本不一样,导致判断不了,这时Array.isArray()方法可以确定它是不是数组,不管它是在哪个全局执行环境中创建的

# 2.3 toString/valueOf 等转换方法

所有对象都有toString()toLocaleString()valueOf(),数组对象也有:

  • valueOf():返回的还是数组本身;
  • toString():把数组每一项的 toString()结果以字符串形式拼接起来,用逗号分隔;
  • toLocaleString():把数组每一项的 toLocaleString()结果以字符串形式拼接起来,用逗号分隔;
  • 除上述 3 个方法,数组还有join()方法,它其实是数组的 toString()方法返回结果把逗号换成了 join()里的参数,没有参数则还是逗号,例如:join("||")就把,换成||
  • 如果数组的某项是 null 或 undefined 的,那么在上述 4 中方法结果中以空字符串表示

# 2.4 push/pop 尾部添加移除

ECMAScript 数组提供了一种类似于栈数据结构特点(后进先出)的方法:

var colors = ["red", "blue"];
colors.push("brown"); // 在数组末尾逐个添加push参数中的项,可以添加多项
var item = colors.pop(); // 移除数组末尾最后一项,并将结果返回给item,数组长度会减1
1
2
3

# 2.5 unshift/shift 首部添加移除

  1. ECMAScript 数组提供了一种类似于队列数据结构特点(先进先出)的方法:

    var colors = ["red", "blue"];
    colors.push("black");
    var item = colors.shift(); // 移除数组末尾第一项,并将结果返回给item,数组长度会减1
    
    1
    2
    3
  2. 其实还有一个与 shift 相反的方法unshift()

    var colors = ["red", "blue"];
    var count = colors.unshift("black", "green"); // 在数组的手段推入两项,返回现数组长度4,其实跟push类似但作用位置不同
    var item = colors.pop(); // 移除了最尾端的一项,那么数组最后就是["green", "black", "red"]
    
    1
    2
    3

# 2.6 reverse/sort 重排序

数组中用来重排序的方法有reverse()sort(),其中reverse()反转 数组项的顺序;
sort()按升序排列数组项,但它实际上先把每项调用toString()转换成字符串比较再排序的,我们也知道字符串的比较时对应位置字符编码值的比较(一位一位进行比较,只要一位要结果后面的几位就不用看了)。
所以sort()又有一种用法,把一个 比较函数 作为 sort()的参数,比较函数有 2 个入参,如果第一个参数位于第二参数之后就返回正数(大于并返回正数),而位于之前就返回负数(小于并返回负数),相等就返回 0,这样排序出来是 升序 的,降序的就是 大于并返回负数小于并返回负数、等于返回 0。

function compare(v1, v2) {
  if (v1 < v2) {
    return -1;
  } else if (v1 > v2) {
    return 1;
  } else {
    return 0;
  }
  // return v1 - v2; // 前提是v1 v2是数值
}
var values = [0, 10, 15, 1, 5];
values.sort(compare);
console.log(values); // [0, 1, 5, 10, 15]
1
2
3
4
5
6
7
8
9
10
11
12
13

如果数组里的类型是引用类型,要对其某个属性进行排序,例:

function comparisonFunction(propertyName) {
  return function (object1, object2) {
    var v1 = object1[propertyName];
    var v2 = object2[propertyName];
    if (v1 < v2) {
      return -1;
    } else if (v1 > v2) {
      return 1;
    } else {
      return 0;
    }
  };
}
var values = [{ age: 3 }, { age: 10 }, { age: 15 }, { age: 1 }, { age: 5 }];
values.sort(comparisonFunction("age"));
console.log(values); // [ { age: 1 }, { age: 3 }, { age: 5 }, { age: 10 }, { age: 15 } ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.7 concat/slice/splice 拼接裁剪

  1. concat()会先创建当前数组的一个 副本,然后将接收到的参数添加到这个副本的 末尾。接收到的参数可以是 数组和单个项的混合体,是数组的话会把数组 每项 添到末尾。例:

    var colors = ["red", "green", "blue"];
    var colors2 = colors.concat("yellow", ["black", "brown"]);
    console.log(colors); // ["red", "green", "blue"],原数组不变创建了新数组来添加新元素到末尾
    console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]
    
    1
    2
    3
    4
  2. slice()会先创建当前数组的一个 副本,然后会按照参数去对应位置 截取 数组。例:

    var colors = ["red", "green", "blue", "yellow", "purple"];
    var colors2 = colors.slice(1); // 可以是一个参数,截取指定位置到末尾的项
    var colors3 = colors.slice(1, 4); // 可以是两个参数,截取[1, 4)位置的项
    var colors4 = colors.slice(-2, -1); // 如果参数有负数,会在负数上加上数组长度,也就是[3, 4]
    console.log(colors); // ["red", "green", "blue", "yellow", "purple"],原数组不变
    console.log(colors2); // ["green", "blue", "yellow", "purple"]
    console.log(colors3); // ["green", "blue", "yellow"]
    console.log(colors4); // ["yellow"]
    
    1
    2
    3
    4
    5
    6
    7
    8
  3. splice()是操作 原数组本身,最后会返回删除的项组成的 数组;它有几个参数,第一个参数代表要操作的 位置,第二个参数代表从要操作的位置往后 删除几项,第三个以及后面的参数代表在删除位置上 新增的项。例:

    var colors = ["red", "green", "blue"];
    var removed = colors.splice(1, 1, "red", "purple");
    console.log(colors); // ["red", "red", "purple", "blue"] 在位置1上删除1个元素,并添加"red"和"purple")
    console.log(removed); // ["green"] 删除的元素组成的数组
    
    1
    2
    3
    4

# 2.8 indexOf/lastIndexOf 搜索项

  1. indexOf(item, start)在数组的 start 位置向右查找 item,查找到以后返回该位置 索引,没有找到的话就返回 -1,如果没有 start 参数代表是从位置 0 开始向右查找的。
  2. lastIndexOf(item, start)在数组的 start 位置向左查找 item,查找到以后返回该位置 索引,没有找到的话就返回 -1,如果没有 start 参数代表是从末尾开始向左查找的。

# 2.9 every/some/filter/map/forEach 迭代

ECMAScript5 为数组定义了 5 个迭代方法,每个迭代方法都有两个参数:数组每项将要运行的函数和运行该函数的作用域对象(可选参数)。其中“运行的函数”有 3 个参数:数组的某项、该项索引、数组对象本身。

  1. every()对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则最后返回 true。例:

    var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var everyResult = numer.every(function (item, index, array) {
      return item > 2;
    });
    console.log(everyResult); // false,要每一项都大于2,最后结果才是true
    
    1
    2
    3
    4
    5
  2. some()对数组中的每一项运行给定函数,如果该函数对任意一项返回 true,则最后返回 true。例:

    var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var someResult = numer.some(function (item, index, array) {
      return item > 2;
    });
    console.log(someResult); // true,只要有一项大于了2,最后就是true
    
    1
    2
    3
    4
    5
  3. filter()对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。例:

    var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var filterResult = numer.filter(function (item, index, array) {
      return item > 2;
    });
    console.log(filterResult); // [3, 4, 5, 4, 3],只保留大于2的项,并将这些项组成新数组返回
    
    1
    2
    3
    4
    5
  4. map()对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。例:

    var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var mapResult = numer.map(function (item, index, array) {
      return item * 2;
    });
    console.log(mapResult); // [2, 4, 6, 8, 10, 8, 6, 4, 2],对每一项都乘以2(可以是其他操作),并组成新数组返回
    
    1
    2
    3
    4
    5
  5. forEach()对数组中的每一项运行给定函数,没有返回值,淡出的遍历数组而去操作某个动作。例:

    var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
    var eachResult = numer.forEach(function (item, index, array) {
      // 执行某些操作
    });
    
    1
    2
    3
    4

# 2.10 reduce/reduceRight 归并

ECMAScript 为数组新增了两个归并方法,都有 2 个参数:数组每项将要运行的函数和作为归并基础的初始值。这个“运行的函数”有 4 个参数:前一个值,当前值,当前值的索引,数组对象。

  1. reduce()从数组的第一项开始遍历,然后构建一个最终返回值。构建树结构目录 (floating-toc.js)比较常用 reduce 归并方法,因为时候需要与前一项或者说归并值进行比较。

    var values = [1, 2, 3, 4, 5];
    var sum = values.reduce(function (pre, cur, index, array) {
      return pre + cur; // 这里构建的规则自己定义
    });
    console.log(sum); // 15
    
    1
    2
    3
    4
    5
  2. reduceRight()从数组的最后一项往前遍历,然后构建一个最终返回值。

    var values = [1, 2, 3, 4, 5];
    var sum = values.reduceRight(function (pre, cur, index, array) {
      return pre + cur; // 这里构建的规则自己定义
    });
    console.log(sum); // 15
    
    1
    2
    3
    4
    5

# 三、Date 类型

  • ECMAScript 中的 Date 类型是在早起 Java 中的 java.util.Date 类基础上构建的,Date 类型使用自 UTC(国际协调时间)1970 年 1 月 1 日午夜(零时)开始经过的毫秒数 来保存日期。
  • 创建一个日期对象,使用 new 操作符和 Date 构造函数:var now = new Date();新创建的对象 自动获得当前日期和时间,Date 构造函数可接受参数,参数一般是 毫秒数
  • 在 WEB 应用中,new Date(xxx)一般使用在时间显示组件中,这些组件是前端框架已经封装好了的,所以在业务代码中极少见到这种new Date(xxx),只需把毫秒数传给组件即可。
  • Date 类型的toLocaleString()会按照与浏览器设置的地区相适应的格式返回日期和时间。而toString()会返回带有时区信息的日期和时间,valueOf()会返回日期的毫秒表示。三种多用于调试。
  • 再介绍 Date 类型的日期/时间组件方法,注:UTC 日期指 0 时区(没有时区偏差)的日期值。
方法 说明
getTime() 返回表示日期的毫秒数;与 valueOf()方法返回的值相同
setTime(毫秒) 以毫秒数设置日期,会改变整个日期
getFullYear() 取得 4 位数的年份(如 2007 而非 07)
getUTCFullYear() 返回 UTC 日期的 4 位数年份
setFullYear(年) 设置日期的年份,传入的年份值必须是 4 位数字(如 2007 而非 07)
setUTCFullYear(年) 设置 UTC 日期的年份,传入的年份值必须是 4 位数字(如 2007 而非 07)
getMonth() 返回日期中的月份,其中 0 表示一月,11 表示十二月
getUTCMonth() 返回 UTC 日期中的月份,其中 0 表示一月,11 表示十二月
setMonth(月) 设置日期的月份,传入的月份值必须大于 0,超过 11 则增加年份
setUTCMonth(月) 设置 UTC 日期的月份,传入的月份值必须大于 0,超过 11 则增加年份
getDate() 返回日期月份中的天数(1 到 31)
getUTCDate() 返回 UTC 日期月份中的天数(1 到 31)
setDate(日) 设置日期月份中的天数,如果传入的值超过了该月中应有天数,则增加月份
setUTCDate(日) 设置 UTC 日期月份中的天数,如果传入的值超过了该月中应有天数,则增加月份
getDay() 返回日期中星期的星期几(其中 0 表示星期日,6 表示星期 6)
getUTCDay() 返回 UTC 日期中星期的星期几(其中 0 表示星期日,6 表示星期 6)
getHours() 返回日期中的小时数(0 到 23)
getUTCHours() 返回 UTC 日期中的小时数(0 到 23)
setHours(时) 设置日期中的小时数,传入的值超过了 23 则增加月份中的天数
setUTCHours(时) 设置 UTC 日期中的小时数,传入的值超过了 23 则增加月份中的天数
getMinutes() 返回日期中的分钟数(0 到 59)
getUTCMinutes() 返回 UTC 日期中的分钟数(0 到 59)
setMinutes(分) 设置日期中的分钟数,传入的值超过 59 则增加小时数
setUTCMinutes(分) 返回 UTC 日期中的分钟数,传入的值超过 59 则增加小时数
getseconds() 返回日期中的秒数(0 到 59)
getUTCseconds() 返回 UTC 日期中的秒数(0 到 59)
setseconds(秒) 设置日期中的秒数,传入的值超过 59 则增加分钟数
setUTCseconds(秒) 返回 UTC 日期中的秒数,传入的值超过 59 则增加分钟数
getMilliseconds() 返回日期中的毫秒数
getUTCMilliseconds() 返回 UTC 日期中的毫秒数
setMilliseconds(毫秒) 设置日期中的毫秒数
setUTCMilliseconds(毫秒) 设置 UTC 日期中的毫秒数
setTimezoneOffest() 返回本地时间与 UTC 时间相差的分钟数

# 四、RegExp 类型

ECMAScript 通过 RegExp 类型来支持正则表达式,var exp = /pattern/flags;模式(pattern)部分可以是任何简单或复杂的 表达式(可以包含 字符类、限定符、分组、向前查找以及反向引用)。而 flags 可以有对个,用以标明正则表达式的 行为

正则表达式的匹配模式支持 3 个标志:

  • g:表示全局(global)模式,在发现第一个匹配项时不会立即停止,会继续往后匹配所有。
  • i:表示不区分大小写(case-insensitive)模式,确定匹配项时忽略模式与字符串的大小写。
  • m:表示多行(multiline)模式,在到大一行文本末尾时,还会继续查找下一行。

元字符需要用\转义:([{\^$|)?*+.]}

创建正则表达式除了用字面量形式以外,还可以用 RegExp 构造函数,例:

var pattern1 = /[bc]at/i; // 匹配第一个“bat”或“cat”,不区分大小写
var pattern2 = new RegExp("[bc]at", "i"); // 与pattern1相同
1
2

这种形式的不同点:

  • 字面量形式里的元字符对应到构造函数的字符串里要 再转义一遍,例如字面量/\w\\hello\\123/对应构造函数字符串"\\w\\\\hello\\\\123"
  • 在 ECMAScript3 时,正则表达式字面量始终会共享同一个 RegExp 实例,而构造函数创建的每一个对象都是一个新 RegExp 实例,例如在 for 循环体中操作两种情形时的正则表达式,结果会大不一样,但在 ECMAScript5,这两种方式每次 都创建新的 RegExp 实例

# 4.1 RegExp 实例属性

RegExp 的每个实例都具有下列属性:

  • global:布尔值,表示是否设置了g标志;
  • ignoreCase:布尔值,表示是否设置了i标志;
  • lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从 0 算起;
  • multiline:布尔值,表示是否设置了m标志;
  • source:正则表达式的字符串表示,按照字面量形式返回(构造函数中的字符串去掉元字符的\符号)
var pattern = new RegExp("\\[bc\\]at", "i");
console.log(pattern.global); // false
console.log(pattern.ignoreCase); // true
console.log(pattern.lastIndex); // 0
console.log(pattern.multiline); // false
console.log(pattern.source); // "\[bc\]at"
1
2
3
4
5
6

# 4.2 RegExp 实例方法

  1. exec()专为捕获组设计,exec()接受一个参数(要应用的字符串),返回与捕获组相关的数组(没有匹配项则返回 null),这个返回值还有两个额外属性:inputindex

    var text = "cat, bat, sat, fat";
    var pattern = /.at/g;
    var matches = pattern.exec(text);
    console.log(matches.index); // 0,表示匹配项在字符串中的位置,而input就是text值
    console.log(matches[0]); // cat,第一个捕获组
    console.log(matches.lastIndex); // 3表示开始搜索下一个匹配项的字符位置
    matches = pattern.exec(text);
    console.log(matches.index); // 5,逗号后有一个空格是4,b才是5
    console.log(matches[0]); // bat
    console.log(matches.lastIndex); // 8
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    在设置全局标志的情况下,每次调用 exec()则都会在字符串中继续查找新匹配项,而没设置全局标志则会一直灰灰第一个匹配项不早醒的匹配项。

  2. test()判断目标字符串与某个模式是否匹配

    var text = "000-00-000";
    var pattern = /\d{3}-\d{2}-\d{4}/;
    console.log(pattern.test(text)); // true
    
    1
    2
    3
  3. RegExp 实例继承的 toLocaleString()和 toString()方法都会返回正则表达式的字面量(是构造函数创建的正则表达式也如此)。valueOf()方法返回正则表达式本身。

# 4.3 RegExp 构造函数属性

在其他语言被看作静态属性。适用于作用域中的所有正则表达式,并且基于所执行的最近一次正则标的是操作而变化。

长属性名 短属性名 说明
input $_ 最近一次要匹配的字符串
lastMatch $& 最近一次的匹配项
lastParen $+ 最近一次匹配的捕获组
leftContext $` input 字符串中 lastMatch 之前的文本
mutiline $* 布尔值,表示是否所有表达式都使用多行模式
rightContext $' input 字符串中 lastMatch 之后的文本
var text = "this has been a short summer";
var pattern = /(.)hort/g;
if (pattern.test(text)) {
  console.log(RegExp.$_); // "this has been a short summer"
  console.log(RegExp["$`"]); // "this has been a "
  console.log(RegExp["$'"]); // " summer"
  console.log(RegExp["$&"]); // "short"
  console.log(RegExp["$+"]); // "s"
  console.log(RegExp["$*"]); // false
}
1
2
3
4
5
6
7
8
9
10

除了上面这些属性还有多达 9 个用于存储捕获组的构造函数属性:$1$2$3 ... $9,例:

var text = "this has been a short summer";
var pattern = /(..)or(.)/g;
if (pattern.test(text)) {
  console.log(RegExp.$1); // "sh"
  console.log(RegExp.$2); // "t"
}
1
2
3
4
5
6

# 4.4 模式的局限性

缺少某些语言所支持的高级正则表达式特性:

  • 匹配字符串开始和结尾的\A\2锚(但支持匹配字符串的开始用^结尾用$
  • 向后查找(但完全支持向前查找)
  • 并集和交集类
  • 原子组
  • Unicide 支持(单个字符除外,如\uFFFF
  • 命名的捕获组
  • s(单行)和 x(无间隔)匹配模式
  • 条件匹配
  • 正则表达式注释

# 五、Function 类型

每个函数都是 Function 类型的实例,而且都与其他应用类型一样具有属性和方法。函数名实际上是一个指向函数对象的指针,不会与某个函数绑定。

函数有 3 种方式定义:

// 函数声明
function sum1(num1, num2) {
  return num1 + num2;
}
// 函数表达式
var sum2 = function (num1, num2) {
  return num1 + num2;
};
// 构造函数
var sum3 = new Function("num1", "num2", "return num1 + num2");
1
2
3
4
5
6
7
8
9
10

第一种函数声明和第二种函数表达式几乎相差无几,而第三种 Function 构造函数我们不推荐使用,因为最后一个参数是动态代码字符串。

# 5.1 函数声明/函数表达式/构造函数的区别

  1. 函数声明:js 整体一边编译一边执行,但在一个代码段里,是 先编译后执行。比如下面这段代码里,编译器先走,走到了function sum(num1, num2)时,编译器会去 变量对象 里查找sum,发现没有这个变量,就声明了一个名为sum并且类型是 Function 的变量(因为 function 是 声明的关键字,所以它的类型是 Function),并放在了 变量对象 里;编译完后 引擎开始执行代码,因为function sum(num1, num2)在编译器 已经提升了(代码段里先编译后执行) 所以会提前执行,给 sum 指引一个 Function 实例化对象,然后走到sum()去执行这个函数。也就是说不管sum()是在 函数声明前还是声明后执行 都不会报锗,这样的过程叫 函数声明提升

    sum(); // 执行代码,编译期会忽略这里
    function sum(num1, num2) {
      // 编译到这,声明提升了
      return num1 + num2;
    }
    /*****************等价于******************/
    function sum(num1, num2) {
      // 编译到这,声明提升了
      return num1 + num2;
    }
    sum();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  2. 函数表达式:编译器走到var sum时,只知道 sum 是个普通变量,不知道它在 执行期 会接收 Function 的实例化对象,所以它没有 函数声明提升(只是普通声明)。当引擎执行到sum = function (num1, num2)时,会把新创建的函数对象让 sum 指引着,然后继续去执行sum(),正常调用函数。如果sum()在函数表达式之前,那就会报错,因为 sum 没有函数声明提升,必须等 Function 实例赋值给 sum 后才能去调用这个函数,这也是函数表达式与函数声明的 唯一区别

    var sum = function (num1, num2) {
      return num1 + num2;
    };
    sum();
    /*****************下面会报错***************/
    sum();
    var sum = function (num1, num2) {
      return num1 + num2;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  3. Function 构造函数:与函数表达式有些类似,编译器走到var sum时,只知道 sum 是个普通变量,不知道它在 执行期 会接收 Function 的实例化对象;但不同的是,“函数表达式”在调用时会 先编译 生成 函数的执行环境,编译器认为 num1、num2 是变量,return 是个执行语句,而“Function 构造函数”虽说也生成函数的执行环境,编译器也认为 num1、num2 是变量,但是第三个参数是个字符串,在函数的 执行期 才把第三个参数这个字符串解析编译出来,再执行这个代码(函数表达式却是直接执行 return)。总的来说就是“Function 构造函数”在执行期比“函数表达式”多一步 动态代码字符串解析编译,极耗性能,这也是我们 不推荐使用“Function 构造函数”的原因,不说性能就字符串里写代码的稳定性就不好。

    var sum = new Function("num1", "num2", "return num1 + num2");
    sum();
    
    1
    2

# 5.2 没有重载

  • 重写 子类覆盖父类的方法(返回值和形参都不能改变,方法体改变了,也就是核心变了),在 js 里重写是可以的。
  • 重载:在同一个类里(执行环境),方法名相同而形参列表和方法体不一样的函数叫重载,这在 js 里是不存在的。
  • 因为 js 里使用 var 和 function 这样的关键字来声明 同名变量 时,都会以最后一个声明为准,它会覆盖之前所有 同名 的声明;js 里的同名函数的声明就是重载的写法,因为在同一个执行环境里,但由于同名的会被覆盖,那么 js 里没有函数重载这一说法。
function addSomeNumber(num) {
  return num + 100;
}
function addSomeNumber(num) {
  return num + 200;
}
var result = addSomeNumber(100); // addSomeNumber被定义了2次,后定义的会覆盖先定义的
1
2
3
4
5
6
7

# 5.3 作为值的函数

函数名是一个指针变量,那么函数对象可以作为一个值来使用,比如作为参数和返回值。

# 5.4 函数内部属性(arguments/this)

函数内部有两个特殊对象:argumentsthis,在之前的基础语法#函数里有介绍 arguments,在变量、作用域和内存问题#执行环境及作用域里介绍过 this。

arguments 有一个callee属性,是一个指向拥有当前 arguments 的函数对象的指针。

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * arguments.callee(num - 1); // 是 num * factorial(num - 1)的解耦形式
  }
}
1
2
3
4
5
6
7

函数内部还有一个caller属性(ECMAScript5 规范的),这个属性保存着 调用当前函数函数引用,如果实在全局作用域中调用的当前函数,caller 则为 null。可以结合 arguments 的 callee 指代当前函数来使用,例:

function outer() {
  inner();
}
function inner() {
  console.log(arguments.callee.caller); // outer函数的原码
}
outer();
1
2
3
4
5
6
7

注意:严格模式下,访问arguments.callee会导致错误,也不能为函数的 caller 属性赋值。

# 5.5 函数属性和方法(apply/call/bind)

  1. 函数都有两个属性,可在内部和外部使用:lengthprototype。通过函数名加上.来使用。

    • length属性表示函数 希望接收的命名参数的个数,并不是指实际参数个数
    • prototype属性是 保存 它们 所有实例方法 的真正存在。
  2. 函数都有两个非继承而来的方法:apply()call()。通过函数名加上.来使用。

    • 这两个方法是用来设置当前函数的this对象的值,改变函数所处的执行环境
    • apply()call()有多个入参时,第一个参数将作为 新执行环境;第二个参数 apply 是使用 形参的数组形式,而 call 是 形参本身的形式apply()call()只有一个参数时,自然是把这个参数设置为当前函数的 this,也就是少了传参。
    function add(c, d) {
      return this.a + this.b + c + d;
    }
    var s = { a: 1, b: 2 };
    console.log(add.apply(s, [3, 4])); // 10
    console.log(add.call(s, 5, 6)); // 14
    
    1
    2
    3
    4
    5
    6
    • ECMAScript5 定义了一个bind(),与apply()call()类似,除了把参数设置为当前函数的 this 值以外,bind()还会创建一个函数的实例,bind()的传参与 call 一样是 形参本身的形式
    window.color = "red";
    var o = { color: "blue" };
    function sayColor() {
      console.log(this.color);
    }
    var objectSayColor = sayColor.bind(o); // 不用立即执行,到后面想执行时使用实例对象执行
    objectSayColor(); // "blue"
    
    1
    2
    3
    4
    5
    6
    7
  3. 除了上述方法,函数还有几层的toLacaleString()toString()valueOf(),都是用来返回 函数源码 的,返回的格式因浏览器而异。

# 六、基本包装类型

ECMAScript 有 6 种数据类型,其中UndefinedNullBooleanNumberString是简单(基本)类型,剩下的Object是一种复杂数据类型。
在操作布尔值、数值和字符串时,后台会隐式创建一个对应的 基本包装类型 的对象,从而提供一些操作方法(实例上的方法),操作完会立即销毁这个对象,若要重新调用这个对象其实又隐式创建了 基本包装类型 的对象。
上述是操作布尔值、数值和字符串,即基本包装类型有三种:BooleanNumberString

var s1 = "some text"; // 这里隐式创建String包装类型,相当于var s1 = new String("some text");
s1.color = "red";
console.log(s1.color); // undefined,上一行代码执行完,s1对应基本包装类型的对象就销毁了,这里使用时是重现隐式创建了,所以color属性就没有了
1
2
3
var value = "25";
var number = Number(value); // 转型函数
console.log(typeof number); // "number"
var obj = new Number(value); // 构造函数
console.log(typeof obj); // "object"
1
2
3
4
5

上面这个例子也说明了三种数据基本类型和对应基本包装类型的区别了,实际上我们不建议显示地创建基本包装类型的对象,容易让人分不清是在处理基本类型还是引用类型(基本包装类型是引用类型)。

# 6.1 Boolean 包装类型

Boolean 包装类型 是布尔值对应的 引用类型,创建对象调用 Boolean 构造函数时传入 true 或 false,在使用这个对象时很容易造成误解,所以 不推荐显式构造 Boolean 包装类型

var falseOject = new Boolean(false); // 显式的使用了Boolean包装类型
var result = falseObject && true; // 此时falseObject是个对象,隐式的调用了Boolean(falseObject),结果为true
console.log(result); // true
1
2
3

跟其他应用类型一样,继承了 valueOf()、toString()、toLocaleString()方法,可以使用instanceof来区分布尔的基本类型和包装类型。

var falseOject = new Boolean(false);
var falseValue = false;
console.log(falseOject instanceof Boolean); // true
console.log(falseValue instanceof Boolean); // false
1
2
3
4

# 6.2 Number 包装类型

Number 包装类型是数值对应的引用类型,创建对象调用 Number 构造函数时传入相应的数值,与 Boolean 包装类型的对象一样在使用时容易造成误解不推荐显式使用。

同样的,Number 包装类型也继承有 valueOf()、toString()、toLocaleString()方法;它有还提供了一些将数值格式转为字符串的方法:

  • toFixed():按照指定的 小数位 返回数值的字符串表示;
  • toExponential():返回以指数表示法的数值的字符串形式;
  • toPrecision():按照指定位数以合适的形式(指数或小数)返回数值的字符串形式。
var num = 99;
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"
1
2
3
4

toFixed()toExponential()的入参都是指定小数位数,而toPrecision()的入参是指定的所有数字位数(整数位加上小数位,但不包括指数位)。

typeofinstanceof可区分 Number 的基本类型和包装类型。

# 6.3 String 包装类型

String 包装类型是字符串对应的引用类型,创建对象调用 String 构造函数时传入相应的字符串。

每个实例都有length属性,表示字符串包含字符的 个数,即使是 双字节字符串也算一个字符

继承有 valueOf()、toString()、toLocaleString()方法,都返回对象所表示的基本字符串的值。

# 6.3.1 charAt/charCodeAt 字符

  • charAt()返回给定位置的那个字符;
  • charCodeAt()返回给定位置的那个字符的字符编码;
  • ECMAScript5 还定义方括号加索引的方式来访问字符串中特定位置的字符,例:stringValue[0]
var stringValue = "hello world";
console.log(stringValue.charAt(1)); // "e"
console.log(stringValue.charCodeAt(1)); // 101  小写字母e的字符编码
1
2
3

# 6.3.2 concat/slice/substr/substring 拼接裁剪

  • concat()先创建当前字符串的一个副本,然后将接收到的一个或多个参数拼接到这个副本的末尾。其实字符串拼接更多的还是使用+操作符。

    var stringValue = "hello";
    var result = stringValue.concat(" world", "!");
    console.log(result); // "hello world!"  注意此时stringValue的值还是"hello"
    
    1
    2
    3
  • slice(start, end)是截取字符串;如果参数 end 不存在,则会从 start 位置截取至末尾,否则从[start, end)截取字符串;如果 start 和 end 中任意一个有负数,则会在负数基础上加上字符串的长度进行截取。

    var stringValue = "hello world";
    console.log(stringValue.slice(-3)); // -3+11=8,从位置8截取至末尾,"rld"
    console.log(stringValue.slice(3, -4)); // -4+11=7,从[3, 7)截取,"lo wo"
    
    1
    2
    3
  • substr(start, n)也是截取字符串;如果参数 end 不存在,则会从 start 位置截取至末尾,否则从 start 位置截取 n 长度的字符串;如果 start 为负数,那在负数基础上加上字符串的长度,但是如果 n 为负数意味着不截(截取 0 个)。

    var stringValue = "hello world";
    console.log(stringValue.substr(-3)); // -3+11=8,从位置8截取至末尾,"rld"
    console.log(stringValue.substr(3, -4)); // -4变为0,不截取,""
    
    1
    2
    3
  • substring(start, end)也是截取字符串;如果参数 end 不存在,则会从 start 位置截取至末尾,否则从[start, end)截取字符串;如果 start 和 end 中任意一个有负数,就将这个负数变为 0;如果都是非负数,并且 end 比 start 小,那实际上就是从[end, start)截取字符串。

    var stringValue = "hello world";
    console.log(stringValue.substring(-3)); // -3变为0,从位置0截取至末尾,"hello world"
    console.log(stringValue.substring(3, -4)); // -4变为0,0小于3,从[0, 3)截取,"hel"
    
    1
    2
    3

# 6.3.3 indexOf/lastIndexOf 搜索字符

  • indexOf(s, index)如果参数 index 不存在,会从 0 位置往 搜索字符 s,参数 index 存在就会从位置 idnex 往 搜索字符 s;搜索到的话会返回字符 s 的索引值,否则返回-1。
  • lastIndexOf(s, index)如果参数 index 不存在,会从 0 位置往 搜索字符 s,参数 index 存在就会从位置 idnex 往 搜索字符 s;搜索到的话会返回字符 s 的索引值,否则返回-1。
var stringValue = "hello world";
console.log(stringValue.indexOf("o", 6)); // 7
console.log(stringValue.lastIndexOf("o", 6)); // 4
1
2
3

# 6.3.4 trim 删除空格

  • ECMAScript5 为字符串定义了一个trim()方法,它会先创建一个字符串副本,删除这个副本 前置 以及 后缀 的所有空格。
  • 有些浏览器还支持非标准的trimLeft()trimRight()方法,单纯只删除 前置或后缀 的空格

# 6.3.5 toLowerCase/toUpperCase 大小写转换

  • toLowerCase()将字符串全转为小写;
  • toUpperCase()将字符串全转为大写;
  • 当不知道代码在哪种语言(哪个地区)环境中运行,可以采用toLocaleLowerCase()toLocaleUpperCase()

# 6.3.6 match/search 模式匹配

  • match()与 RegExp 对象调用exec()效果一样,也只是入参和使用对象对调了。

    var text = "cat, bat, sat, fat";
    var pattern = /.at/;
    var matches = text.match(pattern); // 与pattern.exec(text)效果一样
    console.log(matches.index); // 0
    console.log(matches[0]); // "cat"
    console.log(matches, lastIndex); // 0
    
    1
    2
    3
    4
    5
    6
  • search()只有一个入参,该入参是正则表达式;它始终会从字符串开头向后查找正则表达式所匹配到的 第一个匹配项的索引,没查到会返回-1。

    var text = "cat, bat, sat, fat";
    var pos = text.search(/at/);
    console.log(pos); // 1
    
    1
    2
    3

# 6.3.7 replace/split 替换和分割

replace()接受两个参数,第一个参数是 RegExp 对象或字符串,第二个参数是 字符串或函数,返回替换后的新字符串,但不影响原字符串(事先创建了字符串 副本)。

  1. 当第一个参数是字符串时,只替换匹配到的 第一项;当第一个参数是正则表达式并且指定 全局标志(g) 时,才会替换 所有 能匹配到的项。例:

    var text = "cat, bat, sat, fat";
    var result = text.replace("at", "ond");
    console.log(result); // "cond, bat, sat, fat"
    result = text.replace(/at/g, "ond");
    console.log(result); // "cond, bond, sond, fond"
    
    1
    2
    3
    4
    5
  2. 当第一个参数是正则表达式,第二个参数是字符串时,这个字符串里可使用一些 特殊字符序列,将正则表达式匹配到的值,插入到第二个参数字符串的特殊字符序列所在的位置。

    字符序列 替换文本
    $$ $
    $& 匹配整个模式的子字符串;与 RegExp.lastMatch 的值相同
    $' 匹配的子字符串之前的子字符串;与 RegExp.leftContext 的值相同
    $` 匹配的子字符串之后的子字符串;与 RegExp.rightContext 的值相同
    $n 匹配第 n 个捕获组的子字符串,其中 n 等于 0 到 9
    $nn 匹配第 nn 个捕获组的子字符串,其中 nn 等于 01 到 99
    var text = "cat, bat, sat, fat";
    var result = text.replace(/(.at)/g, "word[$1]"); // 要是/(.at)/g,不能是/.at/g
    console.log(result); // "word[cat], word[bat], word[sat], word[fat]"
    
    1
    2
    3
  3. 当第二个参数是一个函数时,在只有一个匹配项情况下,会向这个函数传递 3 个参数:模式的匹配项、模式匹配项在字符串中的位置、元素字符串。在正则表达式中定义多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配项,...,但最后两个参数仍然分别是模式匹配项在字符串中的位置和原始字符串。这个函数应该返回一个字符串,表示应该被替换的匹配项。

    function htmlEscape(text) {
      return text.replace(/[<>"&]/g, function (match, pos, originalText) {
        switch (match) {
          case "<":
            return "&lt;";
          case ">":
            return "&gt;";
          case "&":
            return "&amp;";
          case '"':
            return "&quot;";
        }
      });
    }
    // &lt;p class=&quot;greeting&quot;&gt;Hello world!&lt;/p&gt;
    console.log(htmlEscape('<p class="greeting">Hello world!</p>'));
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

split()基于指定 分隔符 将一个字符串分割成多个 子字符串,并把结果存放在一个 数组 里。分隔符可以是字符串也可以是一个 RegExp 对象,当然也可以接受可选的第二参数,来限定数组的大小。

var colorText = "red, blue, green, yellow";
var colors1 = colorText.split(", "); // ["red", "blue", "green", "yellow"]
var colors2 = colorText.split(", ", 2); // ["red", "blue"]
var colors3 = colorText.split(/[^\,]+/); // ["", ",", ",", ",", ""]
1
2
3
4

# 6.3.8 localeCompare 比较

localeCompare()由于比较两个字符串,返回值情况如下:

  • 如果参数字符串在字母表中排在调用字符串之前(一位位进行比较),则返回一个正数(大多数是 1);
  • 如果参数字符串等于调用字符串,则返回 0;
  • 如果参数字符串在字母表中排在调用字符串之后,则返回一个负数(大多数是-1)。
var stringValue = "yellow";
console.log(stringValue.localeCompare("brick")); // 1
console.log(stringValue.localeCompare("yellow")); // 0
console.log(stringValue.localeCompare("zoo")); // -1
1
2
3
4

# 6.3.9 fromCharCode 字符转换

fromCharCode()是 String 构造函数本身的一个静态方法,接收一个或多个字符编码,将它们转换成一个字符串,可以看做charCodeAt()的反向操作。

console.log(String.fromCharCode(104, 101, 108, 108, 111)); // "hello"
1

# 七、单体内置对象

内置对象:由 ECMAScript 提供 不依赖宿主环境 的对象,在代码执行前就已经存在。ObjectArrayObject是之前介绍过的内置对象,这一节主要讲两个 单体内置对象GlobalMath

# 7.1 Global 对象

ECMAScript 中的 Global 对象像一个兜底儿对象,即 不属于任何其他对象的属性和方法最终都是它的属性和方法 。比如在全局环境中定义的属性和函数都是 Global 对象的属性,诸如isNaN()isFinite()parseInt()parseFloat()都是 Global 对象的方法。

# 7.1.1 URI 编码

  • Global 对象的encodeURI()encodeURIComponent()这两个方法可以对 URI 进行特殊的 UTF-8 编码,使得浏览器能够接受和理解。

  • encodeURIComponent()会对 所有非字母、非数字的字符 进行编码;encodeURI()只对非字母、非数字字符中类似于空格这样 无效字符 进行编码,而对 URI 中特殊字符比如 冒号、正斜杠、问号和井字号 就不会编码;encodeURIComponent()要用得更多一些。

    var uri = "http://www.wrox.com/illegal value.htm#start";
    console.log(encodeURI(uri)); // "http://www.wrox.com/illegal%20value.htm#start";
    console.log(encodeURIComponent(uri)); // "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start";
    
    1
    2
    3
  • decodeURI()decodeURIComponent()是对应encodeURI()encodeURIComponent()的反向解码方法。

# 7.1.2 eval()方法

eval()在之前就提到过,它在执行期可能会改变编译期的作用域,从而导致性能问题,它还有一个问题是作为动态代码生成的一个方法很可能造成代码注入等安全问题。

# 7.1.3 Global 对象的属性

属性 说明 属性 说明
Undefined 特殊值 Undefined Date 构造函数 Date
NaN 特殊值 NaN RegExp 构造函数 RegExp
Infinity 特殊值 Infinity Error 构造函数 Error
Object 构造函数 Object EvalError 构造函数 EvalError
Array 构造函数 Array RangeError 构造函数 RangeError
Function 构造函数 Function ReferenceError 构造函数 ReferenceError
Boolean 构造函数 Boolean SyntaxError 构造函数 SyntaxError
String 构造函数 String TypeError 构造函数 TypeError
Number 构造函数 Number URIError 构造函数 URIError

# 7.1.3 window 对象

Web 浏览器是将 Global 对象这个全局对象作为 window 对象的一部分加以实现的,全局执行环境中的变量对象可以认为是 window 对象,this 在默认绑定形式下绑定的是 window 对象。

# 7.2 Math 对象

用来保存数学公式和信息,Math 提供的方法要比自己设计的计算功能执行起来要快。

# 7.2.1 Math 对象属性

属性 说明
Math.E 自然对数的底数,即常量 e 的值
Math.LN10 10 的自然对数
Math.LN2 2 的自然对数
Math.LOG2E 以 2 为底 e 的对数
Math.LOG10E 以 10 为底 e 的对数
Math.PI π 的值
Math.SQRT1_2 1/2 的平方根
Math.SQRT2 2 的平方根

# 7.2.2 min/max 最大最小值

  • Math.min()Math.max()都可以接受任意多个数值参数,用于确定这组数值中 最大和最小值

    var max = Math.max(3, 54, 32, 16);
    console.log(max); // 54
    var min = Math.min(3, 54, 32, 16);
    console.log(min); // 3
    
    1
    2
    3
    4
  • Math.min()Math.max()可以配合apply()来求一个数组的最大和最小值,也就不用再一个个传给Math.min()Math.max()了。

    var values = [1, 2, 3, 4, 5, 6, 7, 8];
    var max = Math.max.apply(Math, values); // max方法是Math里的,那么apply的第一个参数自然是Math
    console.log(max); // 8
    
    1
    2
    3

# 7.2.3 ceil/round/floor 取整

  • Math.ceil()向上取整
  • Math.round()四舍五入
  • Math.floor()向下取整

# 7.2.4 random 取随机数

Math.random()返回大于等于 0 小于 1 的一个随机数,也就是返回[0, 1)区间的一个整数

// 函数的作用:返回[lower, upper]区间里的整数
function selectFrom(lower, upper) {
  var choices = upper - lower + 1; // 加1的原因是结果可以等于upper,需要“加一”并配合“向下取整”
  return Math.floor(Math.random() * choices + lower); // 加lower自然是因为最后的区间从lower开始的
}
var num = selectFrom(2, 10); // 随机返回[2, 10]区间里的整数
1
2
3
4
5
6

# 7.2.5 其他方法

方法 说明 方法 说明
Math.abs(num) 返回 num 的绝对值 Math.asin(x) 返回 x 的反正弦值
Math.exp(num) 返回 Math.E 的 num 次幂 Math.atan(x) 返回 x 的反正切值
Math.log(num) 返回 num 的自然对数 Math.atan2(y,x) 返回 y/x 的反正切值
Math.pow(num, power) 返回 num 的 power 次幂 Math.cos(x) 返回 x 的余弦值
Math.sqrt(num) 返回 num 的平方根 Math.sin(x) 返回 x 的正弦值
Math.acos(x) 返回 x 的反余弦值 Math.tan(x) 返回 x 的正切值