函数也是对象,使对象不同于其它对象的决定性特点是函数存在一个被称为 [[Call]]
的内部属性。
内部属性无法通过代码访问而是定义了代码执行时的行为。ECMAScript为JavaScript的对象定义了多种内部属性,这些内部属性都用双重中括号来标注。
[[Call]]属性是函数独有的,表明该对象可以被执行。由于仅函数拥有该属性,ECMAScript 定义typeof操作符对任何具有[[Call]]属性的对象返回"function"。过去因某些浏览器曾在正则表达式中包含 [[Call]]
属性,导致正则表达式被错误鉴别为函数。
2.1 声明还是表达式
两者的一个重要区别是:函数声明会被提升至上下文(要么是该函数被声明时所在的函数范围,要么是全局范围)的顶部。
2.2 函数就是值
可以像使用对象一样使用函数(因为函数本来就是对象,Function构造函数更加容易说明)。
2.3 参数
函数参数保存在类数组对象 argument
(Array.isArray(arguments)
返回 false
)中。可以接收任意数量的参数。
函数的 length
属性表明其期望的参数个数。
2.4 重载
大多数面向对象语言支持函数重载,它能让一个函数具有多个签名。函数签名由函数的名字、参数的个数及其类型组成。
而JavaScript可以接收任意数量的参数且参数类型完全没有限制。这说明JavaScript函数根本就没有签名,因此也不存在重载。
function sayMessage(message){
console.log(message);
}
function sayMessage(){
console.log("Default Message");
}
sayMessage("Hello!"); // 输出"Default Message";
在Javscript里,当你试图定义多个同名的函数时,只有最后的定义有效,之前的函数声明被完全删除(函数也是对象,变量只是存指针)。
var sayMessage = new Function("message", "console.log(message)");
var sayMessage = new Function("console.log(\"Default Message\");");
sayMessage("Hello!");
当然,你可以根据传入参数的数量来模仿重载。
2.5 对象方法
对象的值是函数,则该属性被称为方法。
2.5.1 this对象
JavaScript 所有的函数作用域内都有一个 this
对象代表调用该函数的对象。在全局作用域中,this
代表全局对象(浏览器里的window)。当一个函数作为对象的方法调用时,默认 this
的值等于该对象。
this在函数调用时才被设置。
function sayNameForAll(){
console.log(this.name);
}
var person1 = {
name: "Nicholas",
sayName: sayNameForAll
}
var name = "Jack";
person1.sayName(); // 输出 "Nicholas"
sayNameforAll(); // 输出 "Jack"
2.5.2 改变this
有 3
种函数方法运行你改变 this
值。
- fun.call(thisArg[, arg1[, arg2[, ...]]]);
- fun.apply(thisArg, [argsArray]);
- fun.bind(thisArg[, arg1[, arg2[, ...]]])
使用 call
或 apply
方法,就不需要将函数加入每个对象——你显示地指定了 this
的值而不是让JavaScript引擎自动指定。
call
与 apply
的不同地方是,call
需要把所有参数一个个列出来,而 apply
的参数需要一个数组或者类似数组的对象(如 arguments
对象)。
bind
是ECMAScript 5 新增的,它会创建一个新函数返回。其参数与 call
类似,而且其所有参数代表需要被永久设置在新函数中的命名参数(绑定了的参数(没绑定的参数依然可以传入),就算调用时再传入其它参数,也不会影响这些绑定的参数)。
function sayNameForAll(label){
console.log(label + ":" + this.name);
}
var person = {
name: "Nicholas"
}
var sayNameForPerson = sayNameForAll.bind(person);
sayNameForPerson("Person"); // 输出"Person:Nicholas"
var sayName = sayNameForAll.bind(person, "Jc");
sayName("change"); // 输出"Jc:Nicholas" 因为绑定的形参,会忽略调用时再传入参数
2.6 总结
- 函数也是对象,所以它可以被访问、复制和覆盖。
- 函数与其他对象最大的区别在于它们有一个特殊的内部属性
[[Call]]
,包含了该函数的执行指令。 - 函数声明会被提升至上下文的顶部。
- 函数是对象,所以存在一个
Function
构造函数。但这会使你的代码难以理解和调试,除非函数的真实形式要直到运行时才能确定的时候才会利用它。
网友评论