1. 简述
函数实际上就是对象,每一个函数都是 Function 类型的实例,与其他引用一样具有属性和方法。由于函数是对象,因此,函数名实际上就是一个指向函数对象的指针,不会与某个函数绑定。
2. 创建
// 函数声明语法function sum(num1, num2) {return num1 + num2;}// 函数表达式var sum = function (num1, num2) {return num1 + num2;}
注意第一种后面不加分号,有函数名,而第二个相当于一条声明变量语句,要加分号,且不需要函数名,通过变量就可以引用函数。
函数声明和函数表达式,在解析上有着很大区别,所以建议使用函数声明
解析器会率先读取函数声明,并使其在执行任何代码之前可用,至于函数表达式,则必须等到解析器执行到所在代码,才会真正被解析执行。
在代码开始执行之前,解析器就已经通过函数声明提升的过程,读取并将函数声明添加到执行环境中,JavaScript 引擎会将函数声明放在源代码顶部,所以在函数声明之前使用函数不会出错
函数表达式,在函数表达式代码前就使用函数,就会导致错误
除了什么时候可以通过变量访问函数的区别外,函数声明和函数表达式是等价的
另外一种使用 Function 构造函数的方法,不使用,只是说明有这种方法
vat sum = new Function("num1", "num2", "return num1 + num2");
接受任意数量参数,前面的参数枚举传入函数的参数,最后一个始终被看做是函数体。
由于这种语法会导致两次解析代码,第一次是解析常规的 ECMAScript 代码,第二次是解析传入构造函数的字符串,从而影响性能。
注意:访问带圆括号的函数名是调用函数,而不带圆括号的函数名是访问函数的指针
3. 没有重载
ECMAScript 函数没有重载,就是因为函数是对象,函数名是函数对象的指针
var addSomeNumber = function (num) {return num + 100;}addSomeNumber = function (num) {return num + 200;}
从上例中就很容易理解为什么没有重载。因为函数名是函数对象的指针,也就相当于变量,再次赋值,会覆盖前面的值,指向另一个函数对象地址。所以没有重载。
通过对传入参数的类型和数量进行检测,可以模拟重载
4. 作为值的函数
ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是,不仅可以将函数当做参数传递给另一个函数,而且可以作为另一个函数的结果返回
对数组排序的 sort() 方法的比较函数,需要传递两个参数,就是要比较的两个值。但我们需要一种方式来指明按照哪个属性来排序。
function creatComparisonFunction(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;}}}var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];data.sort(creatComparisonFunction("name"));console.log(data[0].name); // Nicholasdata.sort(creatComparisonFunction("age"));console.log(data[0].name); // Zachary
定义一个函数,接受一个属性名,然后根据属性名来创建比较函数。
5. 函数内部属性
在函数内部,有两个特殊对象:arguments 和 this。
arguments 是一个类数组对象,包含传入函数的所有参数,这个对象还有一个 callee 属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。
多使用于递归算法
function factorial(num) {if (num <= 1) {return 1;}else {return num * factorial(num - 1);}}
由于函数自身调用自身,这个函数的执行就与函数名紧紧耦合在了一起,也就是说,一旦修改函数名,需要修改两处,不利于维护。为了消除这种紧密耦合现象,就可以:
function factorial(num) {if (num <= 1) {return 1;}else {return num * arguments.callee(num - 1);}}
这样,无论引用函数时使用什么名字,都可以保证正常完成递归调用。
另一个特殊对象 this 引用的是函数据以执行的环境对象。
window.color = "red";var o = { color: "blue" };function sayColor() {console.log(this.color);}sayColor(); // "red"o.sayColor = sayColor;o.sayColor(); // "blue"
另一个函数对象的属性 caller,保存着调用当前函数的函数的引用。如果是全局作用域中调用当前函数,它的值为 null
function outer() {inner();}function inner() {alert(inner.caller);//alert(arguments.callee.caller)}outer(); // 显示 outer() 函数的源代码
注意:在严格模式下,访问
arguments、callee、caller等,都会导致错误。都是为了增强这门语言的安全性,这样第三方代码就不能在相同的环境窥视其他代码。
6. 函数的属性和方法
length 属性表示希望接受的命名参数个数
prototype 属性保存着引用类型的所有实例和方法的真正所在。
每个函数都包含两个非继承而来的方法:apply() 和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内的 this 对象值。
apply() 方法接受两个参数,一个是在其中运行的函数的作用域,另一个是参数数组。第二个参数可以是 Array 的实例,也可以是 arguments 对象
function sum(num1, num2) {return num1 + num2;}function callSum1(num1, num2) {return sum.apply(this, arguments);}function callSum2(num1, num2) {return sum.apply(this, [num1, num2]);}alert(callSum1(10, 10)); //20alert(callSum2(10, 10)); //20
call() 方法与 apply() 方法的作用相同,区别仅是接收参数的方式不同。
第一个参数是 this 值,其余参数都是直接传递给函数。也就是说,使用 call() 方法,传递给函数的参数必须逐个列举
function sum(num1, num2) {return num1 + num2;}function callSum(num1, num2) {return sum.call(this, num1, num2);}alert(callSum(10, 10)); //20
这两个方法真正强大的地方是能够扩充函数赖以运行的作用域,最大好处就是对象不需要与方法有任何的耦合。
bind() 方法会创建一个函数的实例,其 this 值会绑定到传入参数的值
window.color = "red";var o = { color: "blue" };function sayColor() {alert(this.color);}var objectSayColor = sayColor.bind(o);objectSayColor(); // blue
toLocaleString()、toString()、valueOf() 方法会返回函数的源代码,但是源代码的格式因浏览器而异,所以无法根据返回结果来实现任何功能,只能用于调试。
