Javascript 引用类型(Date RegExp Function)

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类型的方法

JS Date Methods

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()
    返回函数代码。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容