《JavaScript高级程序设计》回顾之引用类型(下)

437 阅读10分钟

这是我以前写的文章,如有错误,请指正,谢谢!


Data

ES中的Date是从1970年1月1日午夜零点开始计算的,并且由毫秒数来保存。

创建日期

最简单的创建日期的方法就是使用new关键字来调用Date构造函数:var date = new Date();在创建日期时,如果不传入参数,则会创建表示当前时间的日期对象;如果想要创建特定的日期对象,必须要传入表示这个日期的毫秒数。 为了方便日期的创建,ES提供了2个方法:Date.parse 和 Date.UTC。 - Date.parse()方法接受一个表示日期的字符串参数,并试着根据这个参数返回表示日期的毫秒数。这个方法的实现方式因地区而异; - Date.UTC()方法同样也返回一个表示日期的毫秒数,和Date.parse()方法不同的是,它规定了接收参数的格式。按照顺序,这些参数分别表示:年份、从0开始计算的月份、月份中的日期、从0开始计算的小时数、分钟、秒以及毫秒。在这七个参数中,只有第一个和第二个参数是必须的,如果第三个参数未传入,则默认为1,其他参数为传入,都默认为0.

console.log(new Date(Date.parse('Oct 7 1992'))); // Wed Oct 07 1992 00:00:00 GMT+0800 (CST) 
console.log(new Date(Date.UTC(2001, 11))); // Sat Dec 01 2001 08:00:00 GMT+0800 (CST)

示例中使用的 Date.UTC 方法得到的时间由于只传入了2个参数,所以得到的是2001年10月1日0点0分0秒0毫秒,经过是去转换后输出的时间是2001年10月1日8点。 在实际的实现过程中,如果按照给 Date.parse 方法和 Date.UTC 方法传递参数的方式向new Date 传递参数的话,会在后台自动调用 Date.parse 方法和 Date.UTC 方法:

console.log(new Date('Oct 7 1992')); // Wed Oct 07 1992 00:00:00 GMT+0800 (CST)
console.log(new Date(2001, 11)); // Sat Dec 01 2001 00:00:00 GMT+0800 (CST)

使用直接向日期构造函数中传递 Date.UTC 方法的参数的形式创建日期和调用 Date.UTC 方法后创建日期有一点不同,日期和时间都会依据本地时间而不是GMT时间来创建:上述的例子中创建出来的时间是0点而不是8点。

日期的方法

和其他的引用类型相同,Date 也重写了 valueOf、toString 和 toLocaleString 方法。 valueOf 方法返回日期的毫秒表示,可以用于日期大小的比对:较早的日期小于较晚的日期; toString 方法通常返回带有时区信息的日期和时间,其中的小时一般用0-23表示; toLocaleString 方法会按照浏览器设置的地区返回时间和日期,一般会包含 AM 和 PM(上午和下午); 值得注意得失,不同浏览器对 toString 方法和 toLocaleString 方法的返回结果有所不同。 Date 类型有一些将日期格式化为字符串的方法以及许多设定和获取日期中部分信息的方法,由于数量太多,这里也不展开了,有兴趣的可以自己去查找相关文档。

Function

ES中的函数实际上是对象,所有函数都是 Function 的实例,也都拥有属性和方法。 由于函数是对象,所以函数名和对象名一样,只是一个指向函数对象的指针。

创建函数

使用new关键字调用 Function 构造函数同样也能创建函数,它接收任意多的参数,但是最后一个参数表示的是函数体,而前面的参数则是传递给函数的参数,举个例子:

var func = new Function('a', 'b', 'c', 'return a + b + c');
console.log(func(1, 2, 3)); // 6

除了使用构造函数创建函数,还可以使用函数声明和函数表达式的方法来创建函数: - 函数声明:function func () {} - 函数表达式:var func = function () {} 在 JS 中,函数声明和变量声明一样都会在解析的时候被提升,函数声明的优先级甚至高于变量声明。举个例子:

let num = 1;
function func () {
    num = 2;
    function num () {}
}
func();
console.log(num); // 1

因为函数声明的优先级高于变量声明的缘故,这段代码得到的结果是1。 在这个例子中,执行函数 func 的时候,会在作用域链中寻找 num,如果在函数作用域中找到了,则会给它赋值为2;如果没有在函数作用域中找到,则会给全局作用域中的 num 赋值为2。由于函数声明提升的优先级高于变量声明,函数作用域中存在名为 num 的函数名变量,它会被赋值为2,而全局作用域中的变量 num 仍旧是1。 使用函数表达式另外一个特点就是函数的调用能够写在函数表达式之前,举个例子:

func(); // 1
function func () {
    return 1;
}

由于声明提升的原因,函数声明语句永远在函数调用语句之前执行。 但是函数表达式就不能这么使用了,举个例子:

func(); // TypeError: func is not a function
var func = function () {
    return 1;
}

这段代码等同于:

func(); // TypeError: func is not a function
var func;
func = function () {
    return 1;
}

var func;这句变量声明语句会因为提升而被先执行,但是变量 func 却没有被赋值为函数——只有执行到赋值语句的时候才它会被赋值,所以在执行func()的时候会报错,提示 func 不是一个函数。

函数重载

ES 中的函数不存在重载。 函数名不是函数本身,只是一个函数对象的指针,所以再次给函数名变量赋值新的函数会覆盖掉原本的函数对象指针。

一等公民函数

在 ES 中函数名是对函数对象的引用,它本身就是一个变量,所以能够像其他的变量一样作为值来使用,也就是说函数能够作为参数传递给另外一个函数,也能作为另外一个函数的返回值来使用,这是 ES 中闭包实现的基础。 但是需要注意的是,函数作为参数传递到另外一个函数中时,传递的是这个函数的引用而不是函数本身。 而函数作为返回值来使用是一种极为有用的技术,也是高阶函数和函数柯里化的实现方式。

arguments

arguments 是一个类似数组的对象,保存着传入函数的所有参数,即使在函数声明的时候没有声明的参数也在其中,举个例子:

function func (a, b) {
    console.log(...arguments);
}
console.log(func(1, 2, 3, 4)); // 1 2 3 4

这个例子中,我们只声明了2个参数,但是 arguments 中却包含着传入的4个参数。 arguments 有一个属性 callee,指向拥有这个 arguments 的函数,修改下上面这个例子:

function func (a, b) {
    console.log(arguments.callee);
}
console.log(func(1, 2, 3, 4)); // f func (a, b) { console.log(arguments.callee); }

当我们需要在函数内部调用函数自身的时候使用arguments.callee()能够将函数和函数名解藕,防止函数名变量被重新赋值而导致的错误,请看下面的例子:

function a (count) {
    if (count > 0) {
        a(count - 1);
    }
}
var b = a;
a = null;
b(3); // Uncaught TypeError: a is not a function
function a (count) {
    if (count > 0) {
        arguments.callee(count - 1);
    }
}
var b = a;
a = null;
a(3); 

第一个例子中,由于 a 被赋值为 null,切断了它对函数的引用,所以在调用a的时候报错了;而第二个例子由于调用的是 arguments.callee,始终是函数的引用,所以代码能够正常运行。 要注意的是,在严格模式下访问 arguments.callee 会导致错误。

this

this 是函数中一个极为常见同时也是很容易搞错的关键字,通常情况下,它表示的是函数被调用时候的执行上下文对象,请看例子:

function a () {
    console.log(this);
}
var obj = {};
obj.a = a;
a(); // Window {}
obj.a(); // { a: f }

在全局环境中调用函数,函数的 this 就是 Window 这个全局对象;而将函数 a 赋值给对象 obj 的属性 a 后,在执行obj.a()的时候,this 就变成了 obj。 函数的 this 不能够在函数执行的时候修改,但是可以在函数执行前或者执行后进行修改。每个函数都拥有三个非继承来的方法:call、apply 和 bind,它们能够修改函数执行时的作用域。 call 方法和apply 方法的作用相同,都是在函数执行的时候设置 this 的值,它们接受不了的第一个参数都是 this 的;而这两个方法不同的地方在于,apply 方法只能接收两个参数,第二个参数是一个数组,里面存储着需要依次传递给函数的参数,而 call 方法接收任意多的参数,除了第一个参数之外,其他的参数就是需要依次传递给函数的参数。请看例子:

var obj = {
    a: 0,
    b: 0
};
function a (arg1, arg2) {
    this.a = arg1 + arg2;
}
function b (arg1, arg2) {
    this.b = arg1 + arg2;
}
a.apply(obj, [1, 2]);
b.call(obj, 3, 4);
console.log(obj); // { a: 3, b: 7 }

而函数的 bind 方法则是会创建一个新的函数,这个函数的函数体和调用 bind 方法的函数的函数体相同,但是它的 this 的值会被绑定为 bind 方法传入的第一个参数。 bind 方法接收任意多的参数,除了第一个参数外,其他的参数是传递给被绑定函数的参数,它们会在被绑定函数调用的实参前传入,看个例子:

var obj = {
    a: 0
};
function a (arg1, arg2) {
    console.log(...arguments);
    obj.a = arg1 + arg2;
}
var b = a.bind(obj, 1, 2);
b(3, 4); // 1 2 3 4
console.log(obj); // { a: 3 }

可以看到,函数 b 的 this 被绑定到了 obj,而 bind 方法传入的两个参数1和2的传入顺序优先于函数b调用是传入的参数3和4。 关于绑定函数的 this 还有许多的方法可以做到,我们在这里就不往下讨论了。

函数的属性

函数是对象,所以它也拥有自己的属性。

  1. length:函数的 length 属性表示的是函数声明的参数个数,这个属性是无法修改的;
  2. caller:函数的 caller 属性保存着调用当前函数的函数的引用,如果当前函数在全局环境中被调用,则这个属性为 null。

函数的属性

函数是对象,所以它也拥有自己的属性。

  1. length:函数的length属性表示的是函数声明的参数个数,这个属性是无法修改的;
  2. caller:函数的caller属性保存着调用当前函数的函数的引用,如果当前函数在全局环境中被调用,则这个属性为null。在严格模式下不能给caller属性赋值,否则会导致错误发生。看个例子:
    function a () {
        b();
    }
    function b () {
        console.log(arguments.callee.caller);
    }
    a(); // function a () { b(); }
    
  3. prototype:函数的prototype属性是保存它的实例的所有方法和属性的地方;在创建自定义实例和使用继承的时候,这个属性是极为重要的。在ES5中,prototype是一个无法被枚举的属性,不能使用for-in来访问它。要的。在ES5中,prototype是一个无法被枚举的属性,不能使用for-in来访问它。

感谢阅读,个人成果,请勿转载:)