函数实际上都是对象,每个函数都是 Function 类型的实例,且与其他引用类型一样具有属性和方法。
由于函数是对象,因此,函数名实际上是一个指向函数对象的指针,不会绑定某个函数。
创建函数的方式
1、函数声明 :通常都是使用函数声明的方式创建函数
function name(params) {
console.log("111");
}
2、函数表达式 :在使用函数表达式的时候,没有必要使用函数名--可以通过变量引用这个函数
let fun1 = function () {
console.log("111");
};
3、使用 Function 构造函数 : 不推荐使用,需要解析两次,一次是解析为常规 js代码,二是解析传入构造函数中的字符串。但是,从这种方式创建的时候,我们可以比较清晰的看到 函数是对象,函数名是指针 这个概念性的问题。
let fun = new Function("num1","num2", "return num1+num2");
函数名-指针
由于函数名仅仅是指向函数对象的指针,因此函数名与其他其他的指向对象的指针没什么差别。说的直白一点就是,这个函数可以随便取名字,函数表达式的接收变量 也是随意的。
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(1, 2)); // 3
// 更换函数的名字 此时 otherSum 也是指向了该函数
let otherSum = sum;
console.log(otherSum(2, 3));// 5
console.log(sum(1, 2)); // 3
// 将最开始的函数名置为 null,消除对函数的引用
sum = null;
console.log(otherSum(2, 3));// 5 但是不影响 otherSum 对该函数的引用
console.log(sum(1, 2)); // TypeError: sum is not a function
同理,函数表达式也是类似的情况。
函数声明与函数表达式
函数声明和函数表达式都是创建函数的方式,但是在浏览器中的解析存在差异。
变量提升:在代码开始执行前,浏览器的js引擎会先读取函数声明,将函数声明提升到环境顶部,使得之后可以随时随地调用该函数。但是函数表达式则不行,必须等到解析器执行到这个表达式所在的位置,代码才会真正执行。
函数声明:相当于js引擎先将这个函数的函数名当作一个变量,在全局环境声明了,且引用了 sum 这个函数,所以可以在全局调用
console.log(sum(1, 2)); // 3
function sum(num1, num2) {
return num1 + num2;
}
函数表达式:报错信息说的是 初始化之前无法访问“ sum” ,这就表明了 在我 console sun 这个变量的时候,此时这个变量还未被声明,但是在 let sum 之后,console sum 是正常的。也就是上面说的,在我执行到表达式代码之前,sun变量是没有对于 这个函数的引用的,函数表达式创建的函数,只能在代码执行到表达式之后,才能真正执行。
console.log(sum); //ReferenceError: Cannot access 'sum' before initialization
let sum = function (num1, num2) {
return num1 + num2;
};
函数内部属性和方法
arguments :类数组对象,接收传入函数的所有参数,有一个属性 callee ,指向拥有这个 arguments 对象的函数,当在函数内部继续调用这个函数的时候,可以直接调用函数名( 高耦合,一旦函数名变了,内部调用的函数名也要变 ),也可以使用 arguments.callee(xxx) 来执行该函数( 低耦合,即使函数名改变也不影响,因为 arguments.callee 始终指向的是外部函数)
最明显的就是此类阶乘函数:只能使用 声明的 sum 这个变量去调用函数,一旦 sum 与函数断掉引用,
function sum(num) {
if (num <= 1) {
return 1;
} else {
// return num * arguments.callee(num - 1);
return num * sum(num - 1);
}
}
console.log(sum(5)); // 120
var otherSum = sum; // 新的变量也引用了这个函数对象
sum = function (params) { // 改写 sum 的引用
console.log(params);
};
console.log(sum(4)); // 4
console.log(otherSum(5)); // NaN 因为 sum 已经更改了引用,返回是一个undefined,所以是NaN
使用 arguments.callee 则不会有这种情况
function sum(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
console.log(sum(5)); // 120
var otherSum = sum; // 新的变量也引用了这个函数对象
sum = function (params) { // 改写 sum 的引用
console.log(params);
};
console.log(sum(4)); // 4
console.log(otherSum(5)); // 120 ,arguments.callee始终指向的是外部函数,即使外部函数由sum改为otherSum,arguments.callee也会自动更改
this指向 :特殊对象,指向的是函数运行是的环境对象
详情见 另外一篇博文 JS函数–this指向
函数属性和方法
函数是对象,也就存在属性和方法,其中 length 和 prototype 是每隔函数都包含的两个属性
length :表示函数希望接收的形参个数,这个比较简单,可以暂时忽略
prototype :对于js中所有引用类型而言,prototype 是保存他们所有实例方法的真正所在,只不过通过 各自的实例对象访问的。
类似的有 :valueOf() 、toString() 等等方法 。但是在prototype 上的方法,都是不能被遍历的,for-in,是无法发现的。
有关于 prototype 的相关知识点请参考 这一篇博文 js-原型与原型链
var o = new Test()
o.toString() //"[object Object]"
o.valueOf() //Test {x: 1}
前面的函数的属性,都是可以通过 prototype 继承而来,但是 apply和call 却不是通过继承而来的,而且也是每隔函数都存在的,具体情况 也可以参考 JS函数–this指向