Function 类型
每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。
由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字,如下面的例子所示:
function sum(num1, num2){
return num1 + num2;
}
alert(sum(,)); //20
var anotherSum = sum;
alert(anotherSum(,)); //20
sum = null;
alert(anotherSum(,)); //20
函数声明与函数表达式
函数声明和函数表达式虽然都可以随一个函数进行声明,但是解析器在加载数据时还是有着区别的。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真
正被解释执行。请看下面的例子:
alert(sum(,));
function sum(num1, num2){
return num1 + num2;
}
以上代码完全可以正常运行。因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript 引擎也能把函数声明提升到顶部。如果像下面例子所示的,把上面的函数声明改为等价的函数表达式,就会在执行期间导致错误。
alert(sum(,));
var sum = function(num1, num2){
return num1 + num2;
};
除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。
函数内部属性
在函数内部,有两个特殊的对象:arguments 和 this。arguments 的主要用途是保存函数参数,但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。请看下面这个非常经典的阶乘函数:
function factorial(num){
if (num <=) {
return ;
} else {
return num * factorial(num-)
}
}
定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用 arguments.callee。
function factorial(num){
if (num <=) {
return ;
} else {
return num * arguments.callee(num-)
}
}
this引用的是函数据以执行的环境对象——或者也可以说是 this 值(当在网页的全局作用域中调用函数时,this 对象引用的就是 window)。来看下面的例子:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red"
o.sayColor = sayColor;
o.sayColor(); //"blue"
函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的 sayColor()函数与 o.sayColor()指向的仍然是同一个函数
ECMAScript 5 也规范化了另一个函数对象的属性:caller。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null。例如:
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
以上代码会导致警告框中显示 outer()函数的源代码。因为 outer()调用了 inter(),所以inner.caller 就指向 outer()。为了实现更松散的耦合,也可以通过 arguments.callee.caller来访问相同的信息:
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller);
}
outer();
函数属性和方法
每个函数都有两个非继承来而来的方法,
apply() 和 call()
。这两个方法在初学js的时候被绕的迷迷糊糊,而且在面试中也是经常会被问到,下面就详细说一下这两个函数的作用以及区别。
用途:这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值,说白了就是改变函数体内部this的指向
区别:区别仅仅就在于接收参数的方式不同
具体的区别请参考这两篇博文:
https://blog.csdn.net/ganyingxie123456/article/details/70855586
https://www.cnblogs.com/faithZZZ/p/6999327.html
这两位大神的讲解通俗易懂,要比红宝书上的讲解更加明了。
基本包装类型
为了便于操作基本类型值,ECMAScript 还提供了 3 个特殊的引用类型:Boolean、Number 和String。这些类型与本章介绍的其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。
var s1 = "some text";
var s2 = s1.substring();
如上例子,s1是字符串,字符串当然是基本类型值,基本类型值不是对象,因而从逻辑上讲它们不应该有方法(尽管如我们所愿,它们确实有方法)。其实,为了让我们实现这种直观的操作,后台已经自动完成了一系列的处理。当第二行代码访问 s1 时,访问过程处于一种读取模式,也就是要从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成下列处理:
(1) 创建 String 类型的一个实例;
(2) 在实例上调用指定的方法;
(3) 销毁这个实例。
可以将以上三个步骤想象成是执行了下列 ECMAScript 代码。
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;
引用类型与基本包装类型的主要区别就是对象的生存期。使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。来看下面的例子:
var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined
对基本包装类型的实例调用 typeof 会返回"object",而且所有基本包装类型的对象都会被转换为布尔值 true
var obj = new Object("some text");
alert(obj instanceof String); //true
要注意的是,使用 new 调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。例如:
var value = "25";
var number = Number(value); //转型函数
alert(typeof number); //"number"
var obj = new Number(value); //构造函数
alert(typeof obj); //"object"
注:博客中所引用的例子或者语句有的来自书中原文(JavaScript高级程序设计 第3版),在此做补充说明。