Date 类型
ECMAScript使用UTC(国际协调时间)为标准的指定时间与1970年1月1日之间相差的毫秒数来保存日期。
创建日期对象
var date1 = new Date(); //当前时间
var date2 = new Date(1517752591225);
var date3 = new Date("Feb 4, 2018");
可以使用new
操作符调用Date
构造函数来创建一个新的日期实例,如果不传入参数,那么返回当前时间,如果传入数值参数,会将其视为与1970年1月1日相差的毫秒数,然后解析成时间。如果传入字符串参数,会尝试对其执行Date.parse()
,然后获得时间,如果有两个数值,会尝试对其执行Date.UTC()
。
然而我们很难直接获得毫秒数,大多数时候,只知道年、月、日、小时、分钟、秒,ECMAScript提供了两个可以将比较好理解的时间格式转化为毫秒数的方法。
-
Date.parse()
根据几种日期格式解析传入的字符串参数,日期格式因国家而异,如果无法解析,返回NaN
。 -
Date.UTC()
Date.UTC()的参数分别是年份、基于0 的月份(一月是0,二月是1,以此类推)、月中的哪一天(1 到31,可选)、小时数(0 到23,可选)、分钟(可选)、秒(可选)以及毫秒数(可选),返回基于本地时间的毫秒数,如果未传入可选参数,则都设置为0。
var date = new Date(Date.UTC(2018, 0)); // 本地时间2018年一月一号零时
-
Date.now()
获得调用时日期和时间的毫秒数,如果不支持此方法,可以用+new Date()
代替。
Date类型的方法
RegExp 类型
在ECMAScript中,我们如何创建正则表达式?
使用字面量
第一种是使用字面量的形式:
var expression = /pattern/flags
parttern
表示正则表达式,flags
表示正则表达式的行为。
flags
有三种:
- g:即使发现了匹配项也不停止,模式被应用于整个字符串。
- i:忽略大小写
- m:多行模式,更改
^
和$
的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配
正则表达式中使用的所有元字符都必须经过转义,元字符包括:
( [ { \ ^ $ | ) ? * + .]}
关于正则表达式的语法,请参考
我们举几个例子:
var expression1 = /a/g;
var expression2 = /a/i;
var expression3 = /^a/mg;
var expression4 = /a$/mg;
var str = "aba\naba";
str.match(expression1); // ["a", "a", "a", "a"]
str.match(expression2); // ["a", index: 0, input: "aba↵aba"]
str.match(expression3); // ["a", "a"]
str.match(expression4); // ["a", "a"]
使用RegExp构造函数
var expression = new RegExp(pattern, flags);
传递给RegExp()
的两个参数都是字符串,因此元字符以及已经经过转义的字符都要经过双重转义\\
,比如:
字面量 | 对应的字符串 |
---|---|
/\d/ | "\d" |
由于很多转义在简书显示不出来,故省略...
IE9+、Firefox 4+和Chrome的浏览器中,使用正则表达式字面量每次都创
建新的RegExp
实例,而在其他浏览器中,所有的正则表达式字面量共享同一个RegExp
实例。
RegExp 实例属性
属性 | 类型 | 含义 |
---|---|---|
global | 布尔值 | 是否设置g
|
ignoreCase | 布尔值 | 是否设置i
|
multiline | 布尔值 | 是否设置m
|
lastIndex | 数值 | 开始搜索下一个匹配项的字符位置 |
source | 字符串 | 正则表达式的字面量字符串表示 |
举一些例子:
var expression1 = /\^10/gm;
var expression2 = new RegExp("\\^10", "gm");
expression1.global; // true
expression2.global === expression1.global; // true
expression1.ignoreCase; // false
expression2.ignoreCase === expression1.ignoreCase; // true
expression1.multiline; // true
expression2.multiline === expression1.multiline; // true
expression1.lastIndex; // 0
expression2.lastIndex === expression1.lastIndex; // true
expression1.source; // "\^10"
expression2.source === expression1.source; // true
RegExp实例方法
exec
表达式 | 含义 |
---|---|
(pattern) | 匹配pattern并捕获结果,自动设置组号 |
\num | 对捕获组的反向引用。其中 num 是一个正整数。 |
exec()
接收一个参数,即要用应用模式的字符串,返回包含第一个匹配项信息的数组(Array实例),或者在没有匹配项的时候返回null
,包含额外的两个属性,index
input
,分别表示第一个匹配项的位置,和模式表达式字符串。exec()
是专门为捕获组设计的,返回的数组中除第一项的其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。
不知道大家是否和我一样,第一次听说捕获组这个概念,下面补充一下这方面的知识:
表达式 | 含义 |
---|---|
(pattern) | 匹配pattern并捕获结果,自动设置组号 |
\num | 对捕获组的反向引用。其中 num 是一个正整数。 |
举个例子:
var expression=/(\d+)([\+\-\*\/])(\d+)/;
var matches=reg1.exec("10*20");
var comp=matches[0]; //"10*20"
//序号0为匹配的内容,分组从1开始
var num1=matches[1]; //"10"
var sign=matches[2]; //"*"
var num2=matches[3]; //"20"
从上述例子中可以看出,exec()
返回的数组第二项之后代表子表达式匹配到的内容。
捕获组还可以进行反向引用,即在表达式中直接使用某个分组的内容。
var expression=/(\d+)([\+\-\*\/])\1/;
var result = expression.test("25-25"); // true
result[0]; // "25-25"
result.index; // 0
result[1]; // "25"
result[2]; // "-"
exec()
方法中传入的模式无论是否设置g
都只返回一个匹配项,区别是,如果设置g
,再次调用exec()
会在上次匹配的基础上继续查找下一个匹配项,直至字符串末尾,而不设置g
,每次均返回同一个第一个匹配项。
举一个例子:
var expression1 = /d/g;
var expression2 = /d/;
var str = "dead";
expression1.exec(str); // ["d", index: 0, input: "dead"]
expression1.lastIndex; // 1
expression2.exec(str); // ["d", index: 0, input: "dead"]
expression2.lastIndex; // 0
expression1.exec(str); // ["d", index: 3, input: "dead"]
expression1.lastIndex; // 4
expression2.exec(str); // ["d", index: 0, input: "dead"]
expression2.lastIndex; // 0
test
test()
接受一个字符串参数,在模式与该参数匹配的情况下返回
true,否则,返回false,经常用于验证用户输入,比如:
var expression = /\d$/;
var username = "sue";
expression.test(username ); // false
toString toLocaleString
返回正则表达式的字面量,比如:
var expression new RegExp("/\\d/", "g");
expression.toString(); // "/\/\d\//g"
valueOf
返回正则表达式本身。
RegExp构造函数属性
RegExp
构造函数包含一些属性,记载了最近一次正则表达式的操作信息,如下:
长属性名 | 短属性名 | 含义 |
---|---|---|
input | $_ * | 最近一次要匹配的字符串 * |
lastMatch | $& * | 最近一次的匹配项 * |
lastParen | $+ * | 最近一次匹配的捕获组 * |
leftContext | $` * | input字符串中lastMatch之前的文本 |
multiline | $* * | 是否所有表达式都使用多行模式 * # |
rightContext | $' * | input字符串中lastMatch之后的文本 |
$num | 获取捕获组 |
*标注的是Opera未实现的,#标注的是IE未实现的。
举一个例子:
var str = "abcabcdef"
var expression = /(ab)c/g;
if(expression.test(str)) {
console.log(RegExp.input); //abcabc(de)f
console.log(RegExp["$_"]); //abcabc(de)f
console.log(RegExp.lastMatch); // abc
console.log(RegExp["$&"]); // abc
console.log(RegExp.lastParen); // ab
console.log(RegExp["$+"]); // ab
console.log(RegExp.leftContext); // ""
console.log(RegExp["$`"]); // ""
console.log(RegExp.multiline); // undefined
console.log(RegExp["$*"]); // undefined
console.log(RegExp.rightContext); // abc(de)f
console.log(RegExp["$'"]); // abc(de)f
console.log(RegExp.$1); // ab
}
Function 类型
函数其实就是对象,拥有属性和方法,函数名为指向函数对象的指针。
创建函数
函数声明
function someFunc(arg) {
// do something here.
}
函数表达式
var someFunc = function() {
// do something here
}; // 注意这里有分号
构造函数创建
var some = new Function("num1", "num2", "return num1 + num2");
some(1, 2); //3
some(1, "2"); //12
不推荐使用,因为浏览器既要解析ECMAScript代码,又要解析构造函数中的字符串,影响性能,但可以帮助我们理解函数即对象,函数名即指针。
书中举了一个例子:
function sum(num1, num2){
return num1 + num2;
}
alert(sum(10,10)); //20
var anotherSum = sum;
alert(anotherSum(10,10)); //20
sum = null;
alert(anotherSum(10,10)); //20
复习一下
sum
只是指向一个函数对象的指针,anotherSum
同样也指向这个函数对象,后来将sum
指向空对象,而内存中的对象没有被GC,因为还没有退出当前环境,anotherSum
仍然可以指向它。
没有重载
既然函数名是指针,那么声明两个名字相同的函数,只是把后面声明的函数名指向的对象覆盖掉前一次声明的。
函数声明和函数表达式
二者是有区别的,解析器在加载代码时,首先通过一个名为函数声明提升
(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中,而函数表达式,只有在执行到它的时候才会解析。
var result = sum(1, 2); // 4
var result = add(1, 2); // Uncaught TypeError: add is not a function
function sum(num1, num2) {
return preAdd(num1) + num2;
}
var add = function (num1, num2) {
return num1 + num2;
}
function preAdd(num) {
return ++num;
}
add(2, 3);
作为值的函数
我们可以把一个函数当作参数传入另一个函数,或者作为另一个返回值, 比如:
function callMethod(method) {
if (typeof(method) === "function") return method();
}
function returnMethod(method) {
if (typeof(method) === "function") return method;
}
var add = function() {
return 1+2;
};
callMethod(add); // 3
returnMethod(add);
/**
* ƒ () {
* return 1+2;
*}
**/
第一种方式是回调函数,第二种方式是闭包,这两种是ECMAScript中比较高级的用法,小编猜测这本书后面会讲,这里不做扩展。
书中在这里提到了一个很不错的例子,如果我们想对如下类型的数组构建一个可以基于某个属性进行排序的方法:
var students = [{name: 'Sue', age: 18},
{name: 'Bob', age: 16}];
放法如下:
function createComparisonFunction(propertyName) {
return function(object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if(value1 < value2) return -1;
else if(value1 > value2) return 1;
else return 0;
}
}
students.sort(createComparisonFunction("age"));
函数内部属性
arguments
argument
包含传入数组中的所有参数,有一个callee
属性,只想拥有这个arguments
的函数,这个属性可以使我们在函数内部获得函数的引用,在递归调用中非常有用。
function factorial(num) {
if(num<1) return 1;
else return num * factorial(num - 1);
}
factorial(10); // 3628800
使用callee
:
function factorial(num) {
if(num<1) return 1;
else return num * arguments.callee(num - 1);
}
factorial(10); // 3628800
在factorial
在递归期间始终指向最初的函数对象时,第一种写法可以完成我们的目的,如果factorial
不再指向最初的函数,那么就不能完成正确的计算。比如我们继续执行一下代码:
var trueFactorial = factorial;
factorial = function() {
return 0;
}
trueFactorial(10); // 0
首先我们创建一个新的变量trueFactorial
, 它不是指向factorial
,而是factorial
指向的函数对象,因此即使factorial
已经指向另外一个函数,trueFactorial
仍然指向之前的函数对象,它的arguments.callee
也指向之前的函数对象,而它内部的factorial
则指向新对象。
this
指向当前执行的环境对象。
可以参考之前写的一篇文章:
Javascript中this关键词
caller
ECMAScript 规范化了另一个属性caller
,指向调用当前函数的函数,除Opera早期版本,都支持这个属性。
function outer() {
inner();
}
function inner() {
console.log(inner.caller); // or arguments.callee.caller
}
outer();
/**
* ƒ outer() {
* inner();
*}
**/
严格模式下,不能访问arguments.callee
, 也不能为函数的caller
属性赋值
函数的属性和方法
属性
- length
表示函数希望接收的参数个数,比如:
function sayZero() {
return 0;
}
function sayTwo(name, age) {
return name + age;
}
console.log(sayZero.length, sayTwo.length); // 0 2
- prototype
之后会详细介绍
方法
-
apply()
设置函数体内this
值,第一个参数是作用域,第二个参数是参数数组。
function callSum(num1, num2) {
sum.apply(this, arguments); // or [num1, num2]
}
function sum(num1, num2) {
return num1 + num2;
}
-
call()
设置函数体内this
值,第一个参数是作用域,之后的参数是要传入调用函数的一个个参数。
function callSum(num1, num2) {
sum.call(this, num1, num2); // or [num1, num2]
}
function sum(num1, num2) {
return num1 + num2;
}
-
bind()
ECMAScript 5中新定义的方法,可以创建一个拥有特定this
的函数实例。
function sayName() {
return this.name;
}
var sue = {name: 'Sue', age: 18};
var saySueName = sayName.bind(sue);
saySueName(); // "Sue"
使用apply()
和call()
可以更改函数的作用域,这一点非常有用。比如:
function sayName() {
return this.name;
}
var sue = {name: 'Sue', age: 18};
var bob = {name: 'Bob', age: 16};
sayName.call(sue); // "Sue"
sayName.call(bob); // "Bob"
上述例子的写法可以将对象与方法解耦。
-
toString()
toLocaleString()
valueOf()
返回函数代码。