JavaScript引用类型

本文共计22085字,如果您一时半会读不完,建议收藏!码字不易,转发请注明出处,谢谢!

1. Object 类型

大多数类型都是Object类型的实例,创建Object实例的方法有两种,第一种是使用new操作符后跟Object这个构造函数。例如:

1
2
3
4
5
var person = new Object;
person.name = 'linlif';
person.age = 25;
...中间省略N个属性
person.xxx = xxx;

另外一种方法是对象字面量表示法。对象字面量是对象定义的简写方式,目的在于简化创建包含大量属性的对象的过程(上面的例子,person重复了很多次)。使用字面量后,对象定义简洁,而且给人一种封装数据的感觉。例如:

1
2
3
4
5
6
var person = {
name: 'linlif',
age: 25
...
xxx: xxx
}

属性名可以使用字符串(使用双引号将属性名包裹起来)。但不能在最后一个属性后面加逗号,否则会在IE7及更早版本和Opera中导致错误。

1
2
3
4
var person = {
"name": 'linlif',
"age": 25, // 这里最好不要加逗号,否则早起浏览器版本可能会报错!
}

对象字面量还有一个用途,就是用于像函数传递大量的可选参数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function showInfo(args){
var output = '';
if(typeof args.name === "string"){
output += "Name: " + args.name + '\n';
}
if(typeof args.age === "number"){
output += "Age: " + args.age + '\n';
}
console.log(output);
}
showInfo({
name: "linlif",
age: 25
});
// 输出结果:
// Name: linlif
// Age: 25
showInfo({
name: "linlif",
age: "100",
});
// 输出结果:
// Name: linlif

上面的例子,showInfo(args)接受一个参数args,这个参数可能带有nameage这两个属性,也可能没有或者类型不正确。通过这种字面量的传入方式,我们很容易对传入的参数作判断,保证函数正常运行。

访问对象属性,通常使用的是点表示法,这也是很多面向对象语言中通用的方法。但是,在JavaScript中,除了点表示法,我们还可以通过方括号语法来访问对象的属性。例如:

1
2
3
4
5
6
7
8
var person = {
name: "linlif",
250: false, 属性名是一个数字,无法使用点表示法
first Name: "lif" 属性名中间有一个空格,无法使用点表示法
}
console.log(person["250"]); // false
console.log(person["first Name"]); // lif

通常,除非必须使用方括号语法来访问属性,否则建议使用点表示法。

2. Array 类型

ECMAScript数组与其他类型的语言中的数组一样,都是有序列表。但是,ECMAScript数组每一项都可以保存任何类型的数据,例如:一个数组的第一项是数字,第二项可以是字符串,第三项可以是引用类型的数据。另外,ECMAScript数组的大小是动态调整的,即可以随着数据的添加,自动增长数据以容纳新数据。

与OBject类型类似,创建数组的基本方式也有两种方式。第一种是使用Array构造函数。例如:

1
2
3
var colors = new Array(3); // 创建length为3的数组
var colors = new Array("blue","red","black"); // 创建包含3项数据的数组。
var colors = Array(2); // 使用Array构造函数创建数组时,可以省略new关键字

创建数组的第二种方式是使用数组字面量表示法。即使用方括号[]将数组的数据项包裹起来,多个数组项之间以逗号隔开。例如:

1
2
3
4
var colors = ["blue","red","black"];
var person = []; // 创建一个空数组
var values = [1,2,]; // 不要这样做!这样做在高级浏览器会创建2项数据,IE8及更早版本会创建3项数据
var options = [,,]; // 不要这样做!这样做在高级浏览器会创建2项数据,IE8及更早版本会创建3项数据

ECMAScript中,数组的length属性不是只读的。通过设置这个属性可以从数组的末尾移除项或向数组中添加新项。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 移除数据项
var colors = ["blue","red","green"];
colors.length = 2; // 此时数组最后一项已经被移除
console.log(colors[2]); // undefined
// 增加数据项
var colors = ["blue","red","green"];
colors.length = 4;
console.log(colors[3]); // undefined 数组长度变长,但是最后一个位置没有赋值,所以是undefined
// 利用length属性,往数组末尾添加新项
var colors = ["blue","red","green"];
colors[colors.length] = "black"; // 在数组末尾添加一个数据,现在数组长度是4
colors[colors.length] = "brown"; // 在数组末尾添加一个数据,现在数组长度是5
// 在数组超出数组长度的位置添加数据
var colors = ["blue","red","green"];
colors[99] = "black";
console.log(colors.length); // 返回100,此时数组的长度变为100,而位置3-98 实际上不存在,所以他们的值是undefined

注意:数组最多可以包含4294 967 295个项,超出会发生异常,如果创建接近这个上限值的数组,可能导致运行时间超出的脚本错误。

2.1 检测数组(精确检测)

对于只有一个网页或者只有一个全局作用域的时候,使用instanceof操作符就可以检测数组类 型。例如:

1
2
3
if(value instanceof Array){
// do something...
}

如果网页中包含多个框架(像一个页面包含多个frame),实际上就存在两个及以上全局执行环境,从而存在两个以上不同版本的Array构造函数。如果你从一个框架箱另一个框架传入一个数组,那么传入的数组在第二个框架中原生创建的数组分别具有各自不同的构造函数。

为了解决这个问题,ECMAScript5新增加Array.isArray()方法,这个方法目的是最终确定某个值到底是不是数组,而不管它是在哪个环境中创建的。该方法的用法如下:

1
2
3
if(Array.isArray(value)){
// 满足这个条件的就是数组,此时可以对数组做某些操作了。
}

支持这个方法的浏览器有IE9+、FireFox 4+、Safari 5+、Opera 10.5+ 和Chrome。在未实现这个方法的浏览器中,怎么办?

大家都知道,任意值几乎都继承了Object原生的toString()方法,调用这个方法会返回一个[object NativeConstructorName] 格式的字符串。每个类在内部都有一个[[Class]]属性,这个属性中就指定了上述字符串的构造函数名。例如:

1
console.log(Object.prototype.toString().call([1,2,3])); // "[object Array]"

基于这个思路,我们就可以创建如下检测函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 判断value是否为数组
function isArray(value){
return Object.prototype.toString().call(value) == "[object Array]";
}
// 判断value是否为函数
function isFunction(value){
return Object.prototype.toString().call(value) == "[object Fucntion]";
}
// 判断value是否为正则表达式
function isRegExp(value){
return Object.prototype.toString().call(value) == "[object RegExp]";
}

注意:以上方法仅限原生的JavaScript对象,所以正确检测JavaScript对象非常重要!值得注意的是,Object.prototype.toString()本身也可能会被修改,所以这里假设该方法是未修改过的原生版本

2.2 转换方法:toString()、valueOf()等

所有非空对象都有toString()valueOf()toLocaleString()方法。

1
2
3
4
5
6
7
var colors = ["red","green","blue"];
console.log(colors.toString()); // “red,green,blue” 返回以逗号分隔的字符串
console.log(colors.valueOf()); // ["red", "green", "blue"] // 还是返回数组
console.log(colors); // ["red", "green", "blue"] // 返回数组
alert(colors); // “red,green,blue” alert()需要字符串作为参数,所以会调用toString(),返回值与toString()方法一致

调用数组的join()可以使用不同的分隔符来构件字符串,例如:

1
2
3
4
5
var colors = ["red","green","blue"];
console.log(colors.join(',')); // "red,green,blue"
console.log(colors.join('||')); // "red||green||blue"
console.log(colors.join()); // 不传值,默认以逗号分隔
console.log(colors.join(undefined)); // 高级浏览器以逗号为分隔符,IE7及以前,会用“undefined”为分隔符

2.3 栈方法:push()和pop()

栈是一种LIFO(last-in-first-out,后进先出)的数据结构,往数组中添加值可以使用push()方法,从数组中取值使用pop()方法。

push()方法可以接受任意数量的参数,把它们逐个添加到数组的末尾,并返回修改后的数组的长度。pop()方法从数组末尾移除最后一项,减少数组的length值,并返回移除的项。例如:

1
2
3
4
5
6
7
var colors = new Array();
var count = colors.push("red","green","blue"); // 往数组中推入3项数据
console.log(count); // 3 push()方法返回修改后数组的长度
var item = colors.pop();
console.log(colors.length); // 2 数组长度length已经减1
console.log(item); // blue, 返回被删除的值

2.4 队列方法:shift()和unshift()

队列是一种FIFO(First-in-first-out,先进先出)的数据结构,队列在列表的末端添加项,从列表的前端移除项。

shift()方法用于从数组前面移除第一项数据,减少length值,并返回移除的值。例如:

1
2
3
4
5
6
7
var colors = new Array();
var count = colors.push("red","green","blue"); // 往数组中推入3项数据
console.log(count); // 3 push()方法返回修改后数组的长度
var item = colors.shift();
console.log(colors.length); // 2 数组长度length已经减1
console.log(item); // red, 返回被删除的值

unshift()方法与shift()方法相反,调用它可以在数组前端添加任意个数据项并返回新数组的长度。例如:

1
2
3
var colors = new Array();
var count = colors.unshift("red","blue");
console.log(count); // 2 数组当前的长度

注意:IE7及更早版本的unshift()方法总是返回undefined而不是数组的新长度

2.5 重排序方法:reverse()和sort()

reverse()方法可以反转数组项的顺序,例如:

1
2
3
var values = [1,2,3];
values.reverse();
console.log(values); // [3,2,1]

reverse()方法只能讲数据项翻转,显然不能灵活。于是就有了sort()方法。sort()方法默认按升序排列数据项。该方法会调用每个数据项的toString()方法,然后比较得到的字符串的ASCII码,即使所有数据项都是数值,sort()方法比较的还是字符串。例如:

1
2
3
var values = [1,3,10,15,20];
values.sort();
console.log(values); // [1, 10, 15, 20, 3] 请仔细看,这里的数值并不是从小到大的顺序,原因上面已经说了。是按照字符串的ASCII码排序的

直接调用sort()方法,通常得不到我们想要的值。为了实现更加灵活的排序,sort()方法可以接收一个比较函数作为参数,以便实现个性化的排序。例如:

1
2
3
4
5
6
7
8
var values = [5,3,1,15,20];
// 升序排列
values.sort(function(a,b){return a-b;});
console.log(values); //  [1, 3, 5, 15, 20]
// 降序排列
values.sort(function(a,b){return b-a;});
console.log(values); // [20, 15, 5, 3, 1]

2.6 操作方法:concat()、slice()等

concat()方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建一个当前数组的副本,然后将接收到的参数添加到副本的末尾,最后返回一个新构建的数组。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 不传参数,返回当前数组的一个副本
var colors = ["red"];
var colors2 = colors.concat();
console.log(colors2); // ["red"] 返回了一个副本
// 证明确实返回的是副本
var colors = ["red"];
colors3 = [];
console.log(colors); // ["red"]
console.log(colors3); // [] // colors3被清空,但是colors没有变
// 传参数
var colors = ["red"];
var colors4 = colors.concat('blue','green');
console.log(colors4); // ["red", "blue", "green"]

slice()方法,基于当前数组的一个或多个项,创建一个新数组。slice()方法可以接受一个或两个参数。传入一个参数时,返回从指定位置开始到数组末尾的所有项;传入两个参数时,返回指定起始和结束位置的所有项。注意:slice()方法不会影响原始数组。例如:

1
2
3
4
5
6
7
8
9
10
11
var colors = ["red","blue","green"];
// 只传入一个参数
var colors2 = colors.slice(1);
// 传入两个参数
var colors3 = colors.slice(1,2);
console.log(colors); // ["red", "blue", "green"]
console.log(colors2); // ["blue", "green"]
console.log(colors3); // ["blue"]

注意:如果slice()方法参数中有一个负数,则用数组长度加上该负数来确定相应的位置。如果结束位置小于起始位置,则返回一个空数组

splice()方法,这个方法恐怕是最强大的数组方法了。用法很多,具体如下:

  • 删除:需要提供两个参数:起始位置、要删除的项数。例如:splice(0,1)删除第一项。
  • 插入:至少需要提供三个参数:起始位置、0(要删除的项数)、要插入的项。如果要插入多个项,可以在后面再传入任意多个参数。例如:splice(2,0,"red","green")会在位置2开始插入”red“和”green“.
  • 替换:需要提供三个参数:起始位置、要删除的项数、要插入的任意多个项。例如:splice(2,1,"red","green")会删除位置2后面的1项,并插入”red“和”green”。

2.7 位置方法:indexOf()和lastIndexOf()

ECMAScript5 为数组实例添加了两个位置方法:indexOf()lastIndexOf()这两个方法都接受两个参数:要查找的项和起点位置的索引(可选)。其中indexOf()从数组的开头开始向后查找,lastIndexOf()则从数组的末尾开始向前查找。

这两个方法都返回查找到的数据项在数组中的位置(索引),没有找到会返回-1。比较时,实用的是===(全等操作符),也就是说查找的项必须严格相等。例如:

1
2
3
4
5
var numbers = [1,2,3,4,5];
console.log(numbers.lastIndexOf(4)); // 3 从后向前查找”4“,找到的位置在第4位(索引为3)
console.log(numbers.indexOf(4)); // 3 从前到后查找”4“,找到”4“的位置在第4位(索引为3)
console.log(numbers.indexOf(4,3)); // 3 表示从索引为的位置开始向后查找”3“,找到”4“的位置在第4位(索引为3)
console.log(numbers.lastIndexOf(4,4)); // 3 表示从最后一个位置(索引为4)开始,从后向前查找”4“,找到”4“的位置在第4位(索引为3)

2.8 迭代方法:every()、forEach()等

ECMAScript5为数组定义了5个迭代方法,每个方法都接受两个参数:要在每一项上运行的函数和运行改函数的作用域对象(可选)—— 影响this的值。传入的函数有三个参数:数据项的值(value)、数据项的索引(index)和数组对象本身。

  • every():对数组中的每一项运行给定函数,如果该函数对每一个值都返回true,则返回true
  • filter():对数组中的每一项运行给定函数,返回执行该函数后会返回true的项的数组。
  • forEach():对数组中的每一项运行给定函数,没有返回值
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some():对数组中的每一项运行给定函数,如果该函数对任意一项返回true,则返回true

以上方法都不会修改原数组的值。具体用法,请看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// every()每个值度满足,才返回true
var numbers = [1,2,3,4,5,6];
var everyResult = numbers.every(function(value,index,array){
return (value > 0);
});
console.log(numbers); //  [1, 2, 3, 4, 5, 6]
console.log(everyResult); // true
// filter() 返回满足条件的项的数组
var numbers = [1,2,3,4,5,6];
var filterResult = numbers.filter(function(value,index,array){
return (value>3);
});
console.log(numbers); // [1, 2, 3, 4, 5, 6]
console.log(filterResult); // [4, 5, 6]
// forEach(),没有返回值
var numbers = [1,2,3,4,5,6];
var forEachResult = [];
numbers.forEach(function(value,index,array){
forEachResult.push(value * value);
})
console.log(numbers); //  [1, 2, 3, 4, 5, 6]
console.log(forEachResult); // [1, 4, 9, 16, 25, 36]
// map()方法,返回每次调用的结果组成的数组
var numbers = [1,2,3,4,5,6];
var mapResult = numbers.map(function(value,index,array){
return value*index;
});
console.log(numbers); // [1, 2, 3, 4, 5, 6]
console.log(mapResult); // [0, 2, 6, 12, 20, 30]
// some()方法,如果有某个项满足条件,则返回true,所有都不满足条件返回false
var numbers = [1,2,3,4,5,6];
var someResult = numbers.some(function(value,index,array){
return (value % 2 === 0);
});
console.log(numbers); // [1, 2, 3, 4, 5, 6]
console.log(someResult); // true

2.9 归并方法:reduce()和reduceRight()

ECMAScript 5 新增两个归并数组的方法:reduce()reduceRight()这两个方法都会迭代数组的所有项,然后返回一个最终值。reduce()从数组第一项开始遍历到最后,而reduceRight()则从数组最后一项开始,遍历到数组第一项。

这两个方法均接受两个参数:在每个项上调用的函数和作为归并基础的初始值(可选)。传给这两个方法的函数接受4个参数:前一个值、当前值、项的索引和数组对象。这个函数的返回的值会作为第一个参数自动传给下一项。例如:

这两个方法,都不会改变原有数组的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用reduce()方法,求数组的和
var numbers = [1,2,3,4];
var sum = numbers.reduce(function(prev,cur,index,array ){
return prev + cur;
});
console.log(numbers); // [1, 2, 3, 4]
console.log(sum); // 10
// 使用reduceRight()方法,求数组的积
var numbers = [1,2,3,4];
var product = numbers.reduceRight(function(prev,cur,index,array ){
return prev * cur;
});
console.log(numbers); // [1, 2, 3, 4]
console.log(product); // 24

在上述例子中,reduce()方法第一次回调时,prev是1,cur是2。而reduceRight()方法第一次回调时,prev是4,cur是3。其实,reduce()方法和reduceRight()方法的唯一区别就是遍历开始的方向不同,其他都一样。

3. Date 类型

Date类型使用来自UTC(Coordinated Universal Time,国际协调时间)1970年1月1日零时开始经过的毫秒数来保存日期。在使用这种数据存储格式下,Data类型保存的日期能够精确到从1970年1月1日之前或之后285 616年。

创建日期的方法:使用new操作符和Date构造函数即可。例如:

1
2
var now = new Date();
console.log(now)// Tue Apr 24 2018 10:51:39 GMT+0800 (CST)

3.1 Date.parse()方法

如果要创建特定日期和时间的日期对象,可以使用Date.parse()方法和Date.UTC()方法。它们的使用方法如下:

Date.parse()方法接受一个表示日期的字符串参数,然后尝试将这个字符串返回相应日期的毫秒数。ECMA-262没有定义Date.parse()应该支持哪种日期格式。因此这个方法的行为因实现而异,通常因地区而异。

1
2
3
4
5
6
7
// 为2018年5月25日创建一个日期对象
var someDate = new Date(Date.parse('May 25,2018'));
console.log(someDate); // Fri May 25 2018 00:00:00 GMT+0800 (CST)
// 直接给Date传递字符串格式的日期,Date会后台调用Date.parse(),所以也是可行的。
var someDate = new Date('May 25,2018');
console.log(someDate); // Fri May 25 2018 00:00:00 GMT+0800 (CST)

3.2 Date.UTC()方法

Date.UTC()方法接受至少两个必须参数,必须参数是:年份,基于0的月份(1月是0,2月是1,以此类推)。可选参数是:月中的哪一天(1-31)、小时数(0-23)、分钟、秒钟、毫秒数。如果只传前两个必须参数,则其他参数默认为0。例如:

1
2
3
4
5
6
7
// GMT时间2018年4月24日
var date = new Date(Date.UTC(2018,3,24));
console.log(date); // Tue Apr 24 2018 08:00:00 GMT+0800 (CST)
// GMT时间2018年4月24日上午19:07:50,这里需要注意时区。
var date2 = new Date(Date.UTC(2018,3,24,11,07,50));
console.log(date2); // Tue Apr 24 2018 19:07:50 GMT+0800 (CST)

3.3 Date.now()方法

ECMAScript 5 新增了Date.now()方法。返回表示调用这个方法时的日期和时间的毫秒数(时间戳)。支持Date.now()方法的浏览器有:IE9+、Firefox 3+、Safari 3+、Opera 10.5和Chrome。

1
2
var now = Date.now();
console.log(now); // 1524539696331

在不支持的浏览器中,使用+操作符把Date对象转成字符串,也可以达到同样的效果。

1
2
var now = +new Date();
console.log(now); // 1524539903806

3.4 其他格式化方法

Date类型重写了toLoacaleString()、toString()、valueOf()方法。Date类型的toLocaleString()方法,会返回与浏览器设置的地区相适应的格式的日期和时间。toString()方法通常返回带有时区信息的日期和时间,其中时间一般以军用时间(即0-23小时)表示。

事实上,这两个方法在不同浏览器中显示大相径庭,在实际项目中很少用。仅在调试代码时候可能会用到,所以大家不用管它们了。

valueOf()方法,不返回字符串,而是返回日期的毫秒表示。因此,可以使用这个方法来比较日期值。例如:

1
2
3
var date1 = new Date(2018,1);
var date2 = new Date(2017,1);
console.log(date1>date2); // true

除以上方法外,日期有专门的格式化为字符串的方法。这些方法如下:

  • toDateString():格式化为 星期几、月、日、年。
  • toTimeString():格式化为 时、分、秒、时区。
  • toLocaleDateString():格式化为 地区的星期几、月、日、年。
  • toLocaleTimeString():格式化为 地区的时、分、秒。
  • toUTCString():格式化为 完整格式的UTC日期
  • toLocaleString():与toString()类型,因浏览器而异,不推荐使用。

3.5 组件化方法:getTime()、getFullYear() 等

以下方法将获取Date类型的某部分的值,需要注意的是,UTC日期指的是在没有时区偏差下的日期值(将日期转成GMT时间)。

方法 说明
getTime() 返回表示日期的毫秒数,与valueOf()方法的返回值一致
setTime(毫秒数) 以毫米数设置日期,会改变整个日期
getFullYear() 取得4位数的年份
getUTCFullYear() 取得UTC日期的4位数年份
setUTCFullYear(年) 设置UTC日期中的年份,必须传入4位数字
getMonth() 返回日期中的月份,0表示1月,11表示12月
getUTCMonth() 返回UTC日期中的,0表示1月,11表示12月
setMonth(月) 设置日期的月份,传入的月份必须大于0,超过11则增加年份
setUTCMonth(月) 设置UTC日期的月份,传入的月份必须大于0,超过11则增加年份
getDate() 返回日期月份中的天数(1-31)
getUTCDate() 返回UTC日期月份中的天数(1-31)
setDate(日) 设置日期月份中的天数,如果传入的值超过该月天数,则增加月份。
setUTCDate(日) 设置UTC日期月份中的天数,如果传入的值超过该月天数,则增加月份。
getDay() 返回日期中的星期几(0表示星期日,6表示星期六)
getUTCDay() 返回UTC日期中的星期几(0表示星期日,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日期中的毫秒数

getTimezoneOffset() 返回本地时间与UTC时间相差的分钟数。例如:美国东部标准时间返回300。在某地进入夏令时的情况下,这个值会有所变化

4. RegExp 类型

ECMAScript通过RegExp类型来支持正则表达式。关于正则表达式,请参照我的另一篇文章 JavaScript正则表达式 这里不做过多赘述。

5. Function 类型

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

这里可能不容易理解,需要了解JavaScript原型及继承的相关知识后才会明白。所以看不懂暂时先不要管,我后面会总结原型和继承的文章,到时再反过来琢磨,肯定没问题的![这段话是广告]

5.1 函数定义

定义Function的两种方式:函数声明和函数表达式

1
2
3
4
5
6
7
8
9
// 函数声明
function sum(num1,num2){
return num1 + num2;
}
// 函数表达式
var sum = function(num1,num2){
return num1 + num2;
}

由于函数名仅仅是指向函数的指针,所以函数可能有多个名字。例如:

1
2
3
4
5
6
7
8
9
10
function sum(num1,num2){
return num1 + num2;
}
console.log(sum(1,1)); // 2
var anotherSum = sum;
console.log(anotherSum(1,1)); // 2
sum = null;
console.log(anotherSum(1,1)); // 2

5.2 没有重载

ECMAScript的函数的函数名是指针,所以没有函数重载的概念。因为后面定义的函数与前面函数同名,修改的是同一个变量(引用)。

1
2
3
4
5
6
7
function add(num){
return num + 100;
}
function add(num){
return num + 200;
}
console.log(add(100)); // 300

5.3 函数声明提升

前面我们提过,函数有两种定义方式:函数声明和函数表达式。这两种定义函数的方式区别如下:

1
2
3
4
5
6
7
8
9
10
11
// 使用函数声明的方式
console.log(sum(1,1)); // 先使用再定义也不会报错,因为函数在执行前会被提升到该语句前面。
function sum(num1,num2){
return num1 + num2;
}
// 使用函数表达式
console.log(sum(1,1)); // 报错!因为执行这个函数前,变量都不会保存对函数的引用,所以会报错。
var sum = function(num1,num2){
return num1 + num2;
}

5.4 作为值的函数

由于ECMAScript中,函数名本身就是变量,所以函数可以作为值来使用。可以将函数传递给另一个函数,也可以将函数作为另一个函数的返回值。例如:

1
2
3
4
5
6
7
8
9
10
function call(args, callback){
return callback(args)
}
function callback(args){
return 'Hello, ' + args + '!';
}
var result = call('linlif',callback);
console.log(result); // ”Hello, linlif!“

5.5 函数的内部属性

在函数内部,有两个特殊对象:argumentsthisarguments是一个类数组,保存着传入函数的所有参数。arguments有一个叫callee的属性,是一个指针,指向拥有这个arguments对象的函数。使用这个属性可以很容易实现递归。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 普通方式,实现阶乘函数
function factorial(num){
if(num<=1){
return 1;
}else {
return num * factorial(num - 1);
}
}
// 使用callee属性,实现阶乘函数
function factorial(num){
if(num<=1){
return 1;
}else {
return num * arguments.callee(num - 1)
}
}

使用callee属性的写法,无论函数名更改成什么名字,都可以保证完成正常的递归调用。

注意:在严格模式下,访问arguments.callee会导致错误!

5.6 函数的属性和方法

在ECMAScript中函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length和prototype。其中length属性表示函数希望接受的命名参数的个数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function one(name){
console.log(name);
}
function two(name,age){
console.log(name);
console.log(age);
}
function three(name,age,job){
console.log(name);
console.log(age);
console.log(job);
}
console.log(one.length); // 1
console.log(two.length); // 2
console.log(three.length); // 3

prototype属性是保存所有实例方法的地方,在创建引用类型以及实现继承时,prototype属性极为重要,在ECMAScript 5中,prototype属性是不可枚举的,因此使用for-in无法发现。关于prototype属性的介绍,还是在继承和原型链的文章中单独讲吧。敬请期待!

5.7 apply()和call()方法

每个函数都包含两个非继承而来的方法:apply()方法和call()方法。这两个方法的用途是改变函数的上下文,即函数体内的this对象的值。

apply()方法和call()方法的作用是相同的,唯一的区别是接受参数的方式不同。这两个方法都接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组(或枚举参数)。例如:

1
2
3
4
5
6
7
8
9
10
11
12
function sum(num1,num2){
return num1 + num2;
}
function callSum(num1,num2){
return sum.apply(this, arguments); // 传入参数数组arguments
}
function callSum2(num1,num2){
return sum.call(this, num1, num2); // 将参数枚举并传入
}
console.log(callSum(1,1)); // 2
console.log(callSum2(2,2)); // 4

其实,apply()方法和call()方法的真正用途是:扩充函数的作用域。来看下面的例子:

1
2
3
4
5
6
7
8
9
10
window.color = "red";
var obj = {color:"blue"};
function sayColor(){
console.log(this.color);
}
sayColor(); // red
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(obj); // blue 改变执行环境为obj对象,sayColor()里面的this指向obj,所以调用的是obj里面的color

5.8 bind()方法

ECMAScript 5还定义了一个bind()方法。这个方法会创建一个函数的实例,其this会被绑定到传给bind()函数的值。例如:

1
2
3
4
5
6
7
8
window.color = "red";
var obj = {color:"blue"};
function sayColor(){
console.log(this.color);
}
var objSayColor = sayColor.bind(obj); // 注意这里sayColor的后面没有括号。
objSayColor(); // blue

支持bind()方法的浏览器有:IE9+、FireFox 4+、Safari 5.1+、Opera 12+和Chrome

6. 基本包装类型

为了便于操作基本类型值,ECMAScript提供了3个特殊的引用类型:Boolean、Number和String()。实际上,每当读取一个基本类型的值的时候,后台会创建一个对应的基本包装类型,从而让我们能够调用一些方法来操作这些数据。例如:

1
2
var s1 = "some text";
var s2 = s1.substring(2);

喜欢思考的人可能会疑问:基本数据类型不是对象,为什么会有方法?其实,为了让我们能实现这种直观的操作,后台做了一系列的处理。当第二行代码读取s1的时候,访问过程处于一种读取模式,也就是要从内存中读取这个字符串的值。这个过程,后台自动完成了以下处理:

1、创建String类型的一个实例;
2、在实例上调用指定的方法;
3、销毁这个实例。

经过上面的处理,字符串类型就跟对象一样了。同样的,上述步骤也适用于Number类型、Boolean类型对应的值。

使用new操作符创建的引用类型实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于代码执行的一瞬间,然后立即被销毁。这意味着,不能在运行时为基本类型值添加属性和方法。

所以,引用类型与基本包装类型的主要区别是对象的生命期。

6.1 Boolean 类型

Boolean类型的实例重写了valueOf()方法,返回基本类型值true或false。重写了toString()方法,返回字符串”true“”false“

Boolean类型需要注意的点是:理解基本类型的布尔值和Boolean 对象之间的区别。通常他们之间的关系很微妙,所以我们建议永远不要使用Boolean对象。例如:

1
2
var result = new Boolean(false); //传入的是false
console.log(result && true); // true 返回的竟然是true,懵逼。。。

我们也不建议使用Boolean实例化Boolean类型,因为会导致类型判断问题。例如:

1
2
3
4
5
6
7
var booleanObj = new Boolean(false);
var booleanValue = false;
console.log(typeof booleanObj); // "object" 使用Boolean实例化的布尔值不是布尔类型。
console.log(typeof booleanValue); // "boolean"
console.log(booleanObj instanceof Boolean); // true
console.log(booleanObj instanceof Boolean); // true

6.2 Number类型

与Boolean类型一样,Number类型也重写了valueOf()toLocaleString()toString() 方法。重写后的valueOf()方法返回对象表示的基本类型的数值。另外两个方法返回字符串形式的数值。例如:

1
2
3
4
var num = 100;
console.log(num.valueOf()); // 100
console.log(num.toLocaleString()); //"100"
console.log(num.toString()); //"100"
6.2.1 toString()方法(进制转换)

Number类型的toString()方法可以传递一个表示基数的参数,告诉函数返回几进制的字符串形式。例如:

1
2
3
4
5
6
var 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)); // ”6“
6.2.2 toFixed()和toExponential()方法

除了继承的方法外,Number类型还提供了一些将数值格式化为字符串的方法。其中,toFixed()方法会按指定的小数位,返回数值的字符串表示。例如:

1
2
var num = 1.2567;
console.log(num.toFixed(2)); // "1.26"

注意:这个方法会对数值进行四舍五入。

另一个格式化的函数是:toExponential(),该方法返回一个科学表示法(也叫e表示法)表示的数值的字符串形式。例如:

1
2
var num = 100000;
console.log(num.toExponential()); // "1e+5"

toExponential()始终会使用科学计数法表示,如果想要使用最合适的方式表示一个数值,可以使用toPrecision()方法。toPrecision()可能会返回指数格式也可能不会,具体规则是看哪种格式最合适。该方法接受一个参数,这个参数告诉函数要保留的数字位数(不包括指数)。请看下面例子:

1
2
3
4
5
var num = 66;
console.log(num.toPrecision()); // 66
console.log(num.toPrecision(1)); // 7e+1
console.log(num.toPrecision(2)); // 66
console.log(num.toPrecision(3)); // 66.0

与Boolean对象类型,Number对象也会以后台方式为数值提供重要的功能,但是,我们仍然不建议直接实例化Number类型。原因是,在使用typeof和instanceof操作符测试基本类型和引用类型时,得到的结果完全不同。如下所示:

1
2
3
4
5
6
var numberObj = new Number(10);
var numberValue = 10;
console.log(typeof numberObj); // "object" 返回了”object“,会给类型判断造成干扰!
console.log(typeof numberValue); // "number"
console.log(numberObj instanceof Number); // true
console.log(numberValue instanceof Number); // false 基本类型的数值不是Number类型的实例

6.3 String 类型

String()类型是字符串的对象包装类型,可以使用String()构造函数创建字符串。例如:

1
2
3
var str = new String("some string")
console.log(str); // String {"some string"}
console.log("some string"); // "some string"

String类型的每个实例都有length属性。表示字符串包含多少个字符。例如:

1
2
3
4
5
var str = "hello, world!";
console.log(str.length); // 13 // 空格也算字符
var str2 = "hello, \u03a3";
console.log(str2.length); // 8 双字节字符也算算一个字符,例如”\u03a3“这个Unicode字符。
6.3.0 String()实例的方法
6.3.1 字符方法:charAt() 和 charCodeAt()

这两个方法都接受一个参数,即基于0的字符位置。chartAt()方法以单字符字符串(因为ECMAScript没有字符类型)的形式返回给定位置的那个字符。charCodeAt()方法返回的是给定位置的字符编码。具体看下面的例子:

1
2
3
4
var stringVal = "hello, world!";
console.log(stringVal.charAt(0)); // "h"
console.log(stringVal.charCodeAt(0)); // 104
console.log(stringVal[0]); // "h" ECMAScript 定义的访问个别字符的方法,仅支持IE8以上版本,IE7会返回undefined
6.3.2 字符串操作方法:concat()、slice()、substr()、substring()
  • concat():用于将一个或多个字符串拼接起来,返回拼接好的新字符串(不会更改原字符串)。接受任意多个字符串类型的参数。
  • slice():接受一或两个参数。第一个参数用于指定字符串的开始位置,第二个参数指定的是子字符串最后一个字符后面的位置。
  • substring():第一个参数用于指定字符串的开始位置,第二个参数指定的是子字符串最后一个字符后面的位置。
  • substr(): 接受一或两个参数。第一个参数用于指定字符串的开始位置,第二个参数指定的则是返回的字符个数

从上面的描述看,三个方法好像没多少区别?是的,主要区别只在于传入的参数为负数的情况:

  • slice() 会将传入的负值与字符串的长度相加;
  • substr() 方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0;
  • substring() 方法会把所有负值参数都转换为0。

一起看看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 只传一个参数,三个方法的结果相同
var stringVal = "hello world";
console.log(stringVal.slice(3)); // "lo world"
console.log(stringVal.substring(3)); // "lo world"
console.log(stringVal.substr(3)); // "lo world"
// 两个参数,前两个相同。substr()不同,因为它第二个参数指定的是要返回的字符个数。
var stringVal = "hello world";
console.log(stringVal.slice(3,7)); // "lo w"
console.log(stringVal.substring(3,7)); // "lo w"
console.log(stringVal.substr(3,7)); // "lo worl"
// 只传入一个参数(负数)时:slice()会将传入的负值与字符串的长度相加;substr()方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0;substring()方法会把所有负值参数都转换为0。
var stringVal = "hello world";
console.log(stringVal.slice(-3)); // "rld"
console.log(stringVal.substring(-3)); // "hello world"
console.log(stringVal.substr(-3)); // "rld"
// 两个参数(负数),与传入一个负数类似。
var stringVal = "hello world";
console.log(stringVal.slice(3,-4)); // "lo w"
console.log(stringVal.substring(3,-4)); // "hel"
console.log(stringVal.substr(3,-4)); // ""(空字符串)
6.3.3 字符串位置方法:indexOf()、lastIndexOf()

indexOf()lastIndexOf()都是从字符串中查找子字符串的方法。找到则返回字符串的位置,没有找到则返回-1。它们的区别是,indexOf()从字符串的开头向后搜索子字符串,而lastIndexOf()则是从字符串的末尾向前搜索子字符串。

1
2
3
var stringVal = "hello world";
console.log(stringVal.indexOf('o')); // 4
console.log(stringVal.lastIndexOf('o')); // 7

这两个字符串方法,还可以接收第二个参数,表示从字符串的哪个位置开始搜索。例如:

1
2
3
var stringVal = "hello world";
console.log(stringVal.indexOf('o',6)); // 7
console.log(stringVal.lastIndexOf('o',6)); // 4

这次得到的结果,刚好相反,因为indexOf()是从第6位开始向后找的,所以找到的”world“中的”o“,而lastIndexOf()是从第6位开始向前找的,所以找到的是”hello“中的”o“

利用第二个参数,可以循环indexOf()lastIndexOf()找到所有匹配的子字符串。例如:

1
2
3
4
5
6
7
8
9
var stringVal = "I am an engineer";
var positions = [];
var pos = stringVal.indexOf('e');
while(pos > -1){
positions.push(pos);
pos = stringVal.indexOf('e',pos+1);
}
console.log(positions); // [8, 13, 14]
6.3.4 trim()方法

ECMAScript 5 为所有字符串定义了trim()方法。这个方法会创建一个字符串的副本,删除前置及后置的所有空格,然后返回结果。例如:

1
2
3
4
var stringVal = " hello, world ";
var trimVal = stringVal.trim();
console.log(stringVal); //" hello, world "
console.log(trimVal); //"hello, world"

支持这个方法的浏览器有:IE9+、Firefox3.5+、Safari 5+、Opera 10.5+和Chrome

6.3.5 字符串大小写转换方法

ECMAScript中有四个方法用于字符串大小写转换:toLowerCase()、toLocaleLowerCase()、toUpperCase()、toLocaleUpperCase()。

其中toLowerCase()和toUpperCase()是经典方法,借鉴自java.lang.String中的同名方法。而剩下的两个带有”Locale“的是针对特定地区的实现。例如:土耳其语会为Unicode大小写转换应用特殊的规则,这时候就必须使用针对地区的方法保证实现正确的转换

6.3.6 字符串的模式匹配方法:match()

match()方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。

1
2
3
4
5
6
7
8
var text = "cat, cat, sat, fat";
var pattern = /.at/;
// 与执行pattern.exec(text)相同
var matchs = text.match(pattern);
console.log(matchs.index); // 0
console.log(matchs[0]); // cat
console.log(pattern.lastIndex); // 0

search()方法,接受一个参数:由字符串或RegExp对象指定的一个正则表达式。search()始终从字符串的开头向后查找,返回找到的第一个匹配项,如果没有找到则返回-1。例如:

1
2
3
var text = "cat,cat,fat";
var pos = text.search(/.at/g); // 只返回第一项,即使正则表达式中增加了”g“
console.log(pos); // 0

replace()方法:接受两个参数,第一个参数是一个RegExp对象或者一个字符串(这个字符串不会被转换成正则表达式),第二个参数可以是一个字符串或一个函数。如果第一个参数是一个字符串,那么只会替换第一个子字符串。要想替换所有的子字符串,唯一的方法就是提供一个正则表达式,而且要加上全局(g)标志。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 第一个参数是字符串,第二个参数也是字符串
var text = "cat,cat,fat";
var result = text.replace("at","ao");
console.log(text); // "cat,cat,fat"
console.log(result); // "cao,cat,fat"
// 第一个参数是正则表达式,第二个参数是字符串
result = text.replace(/at/g,"ao");
console.log(text); // "cat,cat,fat"
console.log(result); // "cao,cao,fao"
// 第一个参数是正则,第二个参数是特殊的字符序列
var text = "cat,bat,cat,fat";
var result = text.replace(/(.at)/g,"word($1)");
console.log(result); // "word(cat),word(bat),word(cat),word(fat)" 每个匹配项都被替换成”word“后跟一对圆括号,而圆括号里面的是被字符序列$1索替换的单词。
// 第一个参数是正则,第二个参数是函数。
function htmlEscape(text){ // html转义函数
return text.replace(/[<>"&]/g,function(match,pos,originalText){
switch(match){
case "<":
return "&lt;";
case ">":
return "&gt;";
case "&":
return "&amp;";
case "\"":
return "&quot;";
}
})
}
console.log(htmlEscape("<p class=\"greeting\">hello world!</p>")); // "&lt;p class=&quot;greeting&quot;&gt;hello world!&lt;/p&gt;"

split()方法,这个方法基于指定的分隔符将一个字符串分割成多个子字符串,并返回一个数组。分隔符可以是一个字符串,也可以是一个RegExp对象(这个方法不会将字符串看做正则表达式)。split()可以接收第二个参数(可选),用于指定数组的大小,以确保返回的数组不会超过既定大小。例如:

1
2
3
4
5
6
7
var stringVal = "red,blue,yellow,green";
var arr1 = stringVal.split(",");
var arr2 = stringVal.split(",",2);
var arr3 = stringVal.split(/[^\,]+/);
console.log(arr1); // ["red", "blue", "yellow", "green"]
console.log(arr2); // ["red", "blue"]
console.log(arr3); // ["", ",", ",", ",", ""]
6.3.7 字符串比较方法:localeCompare()

localeCompare()方法用于比较两个字符串,并返回-1、0或1,具体返回什么请看下面的规则。

  • 如果字符串在字母表中应该排在字符串参数之前,返回一个负数(大多数情况下是-1,具体视情况而定)。
  • 如果字符串等于字符串参数,返回0;
  • 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数(大多数情况下是1,具体视情况而定)

来看下面的例子:

1
2
3
4
var text = "red";
console.log(text.localeCompare("yellow")); // -1 "red"在字母表中应排在”yellow“之前,所以返回-1
console.log(text.localeCompare("red")); // 0 相等,所以返回0
console.log(text.localeCompare("black")); // 1 // "red"在字母表中应该排在”black“之后,所以返回一个正数
6.3.8 字符编码转换成字符串方法:fromCharCode()

这个方法的任务是接收一个或多个字符编码,然后将它们转换成一个字符串。从本质上来说,这个方法与实例方法charCodeAt()实现的是相反的操作。例如:

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

7. 单体内置对象:Global对象和Math对象

ECMAScript对内置对象的定义是:”由ECMAScript实现提供的、不依赖于宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在。“意思就是说,开发人员不比显式地实例化内置对象,因为它们已经实例化了。

前面提到的Object、Array和String都是内置对象,除此之外,ECMAScript还定义了两个单体内置对象:Global和Math。

7.1 Global对象

Global对象是一个最特别的对象,因为它根本不存在!任何不属于其他对象的属性和方法,最终都属于Global的属性和方法。例如:isNaN()idFinite()parseInt()以及parseFloat()。这些方法已经在前面的文章中介绍过了。今天我们来介绍剩下的一些方法。

7.1.1 URI编码方法

Global对象有encodeURI()encodeURIComponent()两个方法对URI(Uniform Resource Identifiers,通用资源标识码)进行编码,以便发送给浏览器。

有效的URI不能包含某些特殊字符,例如空格。而这两个方法就对URI进行编码,使用特殊的UTF-8编码替换所有无效的字符,使浏览器能够接受和理解。

encodeURI()主要用于整个URI(例如:http://www.baidu.com/s="hahah"),而encodeURIComponent()主要用于对URI中的某一段进行编码。

它们的区别是:encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和井号;而encodeURIComponent()则会对它发现的任何非标准字符进行编码。例如:

1
2
3
4
5
6
7
var uri = "https://linlif.github.io/2018/04/19/JavaScript数据类型#more";
// 使用encodeURI()对URI进行编码
console.log(encodeURI(uri)); // 返回 https://linlif.github.io/2018/04/19/JavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B#more
// 使用encodeURIComponent(uri)对URI进行编码
console.log(encodeURIComponent(uri)); // 返回 https%3A%2F%2Flinlif.github.io%2F2018%2F04%2F19%2FJavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%23more

由例子我们可以知道,encodeURI()仅对不属于URI的特殊字符进行编码,而encodeURIComponent()对所有非标准字符都进行了编码。所以,请根据实际需要使用,否则可能得不到想要的结果。

讲完编码,我们来看看解码:encodeURI()encodeURIComponent()对于的解码方法分别是decodeURI()decodeURIComponent()。关于它们的区别,请看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用decodeURI()解码encodeURI()的编码
var uri = "https://linlif.github.io/2018/04/19/JavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B#more";
console.log(decodeURI(uri));// 返回(解码成功) https://linlif.github.io/2018/04/19/JavaScript数据类型#more
// 使用decodeURIComponent()解码encodeURIComponent()的编码
var uri = "https%3A%2F%2Flinlif.github.io%2F2018%2F04%2F19%2FJavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%23more";
console.log(decodeURIComponent(uri));//返回(解码成功) https://linlif.github.io/2018/04/19/JavaScript数据类型#more
// 使用decodeURIComponent()解码encodeURI()的编码
var uri = "https://linlif.github.io/2018/04/19/JavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B#more";
console.log(decodeURIComponent(uri));//返回(解码成功) https://linlif.github.io/2018/04/19/JavaScript数据类型#more
// 使用decodeURI()解码encodeURIComponent()的编码
var uri = "https%3A%2F%2Flinlif.github.io%2F2018%2F04%2F19%2FJavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%23more";
console.log(decodeURI(uri)); //返回(解码失败) https%3A%2F%2Flinlif.github.io%2F2018%2F04%2F19%2FJavaScript数据类型%23more
7.1.2 eval()方法

eval()是整个ECMAScript语言中最强大的方法。eval()方法就像一个完整的ECMAScript解析器。它只接受一个参数,即要执行的JavaScript字符串。例如:

1
2
eval("alert('hello world!')");// 弹出"hello world"
eval("hello world!"); // 弹出”hello world“,相当于上面语句的简写

eval()方法会将传入的参数作为ECMAScript语句来解析,然后将解析的结果插入到eval()所在的位置。通过eval()解析的代码被认为是当前作用域的语句,意味着eval()可以引用包含在环境中定义的变量。例如:

1
2
3
4
5
var msg = "haha";
eval("alert(msg)"); // "haha"
eval("function sayHi(){alert('Hi');}");
sayHi(); // 弹出”Hi“ ,仅限非严格模式,严格模式下,外部访问不到eval()中创建的任何变量及函数。

值得一提的是,eval()方法由于存在安全性问题,已经不推荐使用!除非你能确保传入eval()的代码是安全的。

7.1.3 Global对象的所有属性

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

ECMAScript 5明确禁止给undefined、NaN和Infinity赋值,这样在非严格模式下也会报错。

虽热ECMAScript没有明确指出如何直接访问Global对象,但是Web浏览器都是将这个全局对象作为window对象的一部分加以实现的。因此,全局中声明的变量和函数,就成为了window对象的属性。例如:

1
2
3
4
5
6
7
8
var color = "red"
console.log(window.color); // "red"
// 另一种取得Global对象的方法
var global = function(){
return this;
}();
console.log(global);// 返回了window对象

8. Math对象

看到这里的童鞋都是好样的,下面我们来看最后一个知识点!

8.1 Math对象的属性

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平方根(即2的平方根的倒数)
Math.SQRT2 2的平方根

8.2 min()和max()方法

min()Max()用于确定一组数值中的最小和最大值,接受任意多个数值参数。例如:

1
2
console.log(Math.min(1,2,3,4,5,0)); // 0
console.log(Math.max(83,123,35,125));// 125

8.3 舍入方法

有三个舍入方法:Math.ceil()Math.floor()Math.round()。他们的舍入规则如下:

  • Math.ceil():向上舍入,即总是将小数向上舍入为最接近的整数。
  • Math.floor():向下舍入。即总是向下舍入为最接近的整数。
  • Math.round():四舍五入。即总是将数值四舍五入为最接近的整数。

8.4 random()方法

Math.random()方法返回一个大于等于0小于1的一个随机数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
值 = Math.floor(Math.random()*总数 + 第一个可能的值);
var ran = Math.floor(Math.random()*10 + 1);
console.log(ran); // 随机返回一个10以内的整数
// 随机返回一个介于min和max之间的值(包括min和max)
function rangeRandom(min, max){
var choices = max - min + 1;
return Math.floor(Math.random() * choices + min);
}
console.log(rangeRandom(1,2));// 返回[1,2]闭区间的数值。

8.5 Math对象的其他方法

如下表:

方法 说明 方法 说明
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的正切值

历时3天,终于将JavaScript的引用类型总结完毕!知识点大都来自《JavaScript高级程序设计(第3版)》。之所以将书上的知识进行总结,是因为总结可以加深记忆,也可以随时查阅。

如果您觉得本文有用,欢迎收藏!码字不易,转发请注明出处,谢谢!

最后,奉上一张知识结构图:

JavaScript引用类型