Javascript高级程序设计 读书笔记

Javascript 引用类型(Date RegExp Func

2018-02-06  本文已影响32人  Sue1024

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提供了两个可以将比较好理解的时间格式转化为毫秒数的方法。

var date = new Date(Date.UTC(2018, 0)); // 本地时间2018年一月一号零时

Date类型的方法

JS Date Methods

RegExp 类型

在ECMAScript中,我们如何创建正则表达式?

使用字面量

第一种是使用字面量的形式:

var expression = /pattern/flags

parttern表示正则表达式,flags表示正则表达式的行为。
flags有三种:

正则表达式中使用的所有元字符都必须经过转义,元字符包括:

( [ { \ ^ $ | ) ? * + .]}

关于正则表达式的语法,请参考
我们举几个例子:

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属性赋值

函数的属性和方法

属性
function sayZero() {
  return 0;
}
function sayTwo(name, age) {
  return name + age;
}
console.log(sayZero.length, sayTwo.length); // 0 2
方法
function callSum(num1, num2) {
  sum.apply(this, arguments); // or [num1, num2]
}
function sum(num1, num2) {
  return num1 + num2;
}
function callSum(num1, num2) {
  sum.call(this, num1, num2); // or [num1, num2]
}
function sum(num1, num2) {
  return num1 + num2;
}
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"

上述例子的写法可以将对象与方法解耦。

上一篇下一篇

猜你喜欢

热点阅读