在ES中,函数实际上是对象,和其他引用类型一样具有属性和方法。
函数名实际上是一个指向函数对象的指针,指向函数而不绑定函数,也就是说一个函数可能有多个函数名。
ES中没有函数重载,如果声明了两个同名函数,那后面的函数会覆该前面的函数。
函数的定义:
//声明式定义函数
function sum(num1,num2){
……
}
//函数表达式定义
var sum = function(num1,num2){
……
}
两种定义方式几乎一样,唯一的区别是:
解析器在执行环境中加载数据时,解析器会率先读取函数声明,并使其在执行任何代码前可用。
而函数表达式,则必须等到解析器执行到它所在的代码行,才会被解释执行。
例:
//声明式
console.log(sum(10,10));
function sum(num1,num2){
return num1 + num2;
}
/* 输出 20 */
//函数式
console.log(sum(10,10));
var sum = function(num1,num2){
return num1 + num2;
}
/* 输出报错 sum is not a function */
作为值的函数
- 把一个函数作为参数传递给另一个函数
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function add10(num){
return num + 10;
}
function getGreeting(name){
return "hello, " + name;
}
var result1 = callSomeFunction(add10, 10);
console.log('result1 = ', result1);
var result2 = callSomeFunction(getGreeting, 'cat');
console.log('result2 = ', result2);
/* 输出
result1 = 20
result2 = hello, cat
*/
- 将一个函数作为另一个函数的结果返回
function createComparisonFunction(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:'Tuanzi', age:'1'}, {name:'Weiwei', age: '0.8'}, {name:'Lanlan', age:'0.4'}];
data.sort(createComparisonFunction('age'));
console.log(data);
/* 输出1:
[ { name: 'Lanlan', age: '0.4' },
{ name: 'Weiwei', age: '0.8' },
{ name: 'Tuanzi', age: '1' } ]
*/
data.sort(createComparisonFunction('name'));
console.log(data);
/* 输出2:
[ { name: 'Lanlan', age: '0.4' },
{ name: 'Tuanzi', age: '1' },
{ name: 'Weiwei', age: '0.8' } ]
*/
注:要访问函数的指针而不调用函数就去掉后面的圆括号,函数名加上圆括号就是使用(调用)函数;
函数内部属性
在函数内部,有两个特殊的对象: ①arguments 和 ②this。
arguments
arguments
是一个类数组对象,包含着传入函数中的所有参数。
这个对象还有个callee
的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
//例子1:
function factorial(num){
if(num <= 1){
return 1;
}else{
return num * factorial(num - 1);
}
}
console.log(factorial(3));
//输出 6
//例子2:
function factorial(num){
if(num <= 1){
return 1;
}else{
return num * arguments.callee(num - 1);
}
}
console.log(factorial(3));
//输出 6
从以上两种方法可以看出,他们的结果相同。但最好用例2,因为假设在阶乘函数中,如果和函数名紧紧耦合在一起,那么如果后面遇到函数名变了的情况,就会报错或者运算出错,而使用arguments.callee
就可以避免这种情况。
this
this引用的是函数据以执行的环境对象——也可以说是this的值。
global.color = 'red';
var o = {color:'blue'};
function sayColor(){
console.log(this.color);
}
o.sayColor = sayColor;
sayColor(); //输出 'red'
o.sayColor(); //输出 'blue'
在上例中,sayColor()在全局环境中调用就指向全局执行性环境global,o.sayColor()在o对象中调用就指向o;
this——谁调用函数就指向谁,也可以说this指向当前函数的执行环境。
那在看一个额外的点,涉及到作用域问题,如下例:
global.color = 'red';
var o = {color:'blue'};
function sayColor(){
color = 'yellow'; //这里相当于一个隐式声明,隐式声明定义的是全局变量,var声明定义的是局部变量
console.log(this.color);
}
o.sayColor = sayColor;
sayColor(); //输出 'yellow'
o.sayColor(); //输出 'blue'
在函数内容,我新增了个赋值,调用后发现,sayColor的值改变了,o.sayColor输出的值还是没变,开始没想通,后来想通了:
相当于是在函数内声明了个隐式变量,隐式声明定义的是全局变量,var声明定义的是局部变量,因此,这里改变的是全局global的color 值,而o对象中的值没受影响。
注:函数名仅仅是一个包含指针的变量,即使不同的环境中执行,它们指向的仍是同一个函数
函数的属性和方法
每个函数都包含两个属性:①length ②prototype
① length
表示函数希望接收的命名参数的个数
function sayName(name){
console.log(name)
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
console.log('hi~');
}
console.log('sayName: ', sayName.length);
console.log('sum: ', sum.length);
console.log('sayHi: ', sayHi.length);
/* 输出
sayName: 1
sum: 2
sayHi: 0
*/
② prototype
对于ES中的引用类型而言,
prototype
是保存它们所有实例方法的真正所在。例如toString()
和vualeOf()
等方法都保存在prototype名下。
每个函数都包含两个非继承而来的方法:①apply() ②call()
用途: 在指定的作用域中调用函数,实际上等于设置函数体内this对象的值
① apply()
接收两个参数: ①在其中运行函数的作用域; ②参数数组(可以试array实例,也可以是arguments对象);
//第二个参数的使用例子
function sum(num1,num2){
return num1 + num2;
}
function callSum(num1,num2){
return sum.apply(this, arguments);
}
function callSum2(num1,num2){
return sum.apply(this, [num1,num2]);
}
console.log('callSum = ', callSum(5,5))
console.log('callSum2 = ', callSum2(5,5))
/* 输出
callSum = 10
callSum2 = 10
*/
从上例可以看出,在传入第二个参数时,无论传入arguments对象还是一个参数数组,两个函数都会正常执行并返回正确结果。
② call()
call()
的作用和apply()
相同,他们唯一的区别是:apply()
的第二参数可接收一个参数数组或者arguments对象,而call()
的第二参数需要逐个列出来, 例:
//call() 和 apply() 的使用区别
function sum(num1,num2){
return num1 + num2;
}
function callSum3(num1,num2){
return sum.call(this, num1,num2);
}
console.log('callSum3 = ', callSum3(5,5));
/* 输出
callSum3 = 10
*/
对于call()
方法来说,假设你第二个参数传入了一个数组,那么,它只会把这个数组当做第二个参数的第一个元素,剩下的如果没有传入,则会输出undefined
。
使用 apply()
还是使用 call()
?
取决你采用哪种函数传递参数的方式最方便。
如果你打算直接传入arguments或者接受一个参数数组则用apply()
否则就用call()
apply()
和 call()
最大的用途是扩充函数赖以运行的作用域:
window.color = 'red';
var o = {color: 'blue'};
function sayColor(){
console.log(this.color);
}
sayColor.call(this);
sayColor.call(window);
sayColor.call(o);
/* 输出
red
red
blue
*/
这样就可以直接扩展作用域。
这里需要注意的是: 如果在node中全局调用,由于node和ES6,this返回的当前模块,所以这是this会返回undefined。
global.color = 'red';
var o = {color: 'blue'};
function sayColor(){
console.log(this.color);
}
sayColor.call(this);
sayColor.call(global);
sayColor.call(o);
/* 输出
undefined
red
blue
*/