更新于 

10.3 函数特性

10.3.1 函数声明与函数表达式

函数声明和函数表达式的主要区别就在于是否会进行函数声明提升(function

declaration hoisting)

函数声明会进行函数声明提升

函数声明提升 即 函数声明会在任何代码执行之前先被读取并添加到执行上下文。

1
2
3
4
console.log(sum(1, 2)); //3
function sum(num1, num2) {
return num1 + num2;
}
函数表达式 必须要等到代码执行到那一行才会执行函数定义

这和let还是var声明无关。

1
2
3
4
5
//ReferenceError: Cannot access 'num' before initialization 
console.log(sum(10, 20));
let sum = function (num1, num2) {
return num1 + num2;
}

10.3.2 函数作为值

因为函数名本身就是变量,因此函数可以作为参数返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function callSomeFunction(someFunction, someArgument) {
return someFunction(someArgument);
}

function add10(num) {
return num + 10;
}
console.log(callSomeFunction(add10, 10)); //20

function getGreeting(name) {
return 'Hello, ' + name;
}
console.log(callSomeFunction(getGreeting, 'Nicholas')); //Hello, Nicholas

从一个函数返回另一个函数可以非常有用

下面的程序中创建了一个可以按照对象数组中任意对象属性对数组进行排序的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createComparisonFunction(propertyName) {
return function (object1, object2) {
let value2 = object2[propertyName];
let value1 = object1[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
let data = [
{ name: 'Zachary', age: 28 },
{ name: 'Nicholas', age: 29 }
];
data.sort(createComparisonFunction('name'));
console.log(data[0].name); //Nicholas

10.3.3 函数内部

函数内部存在三个特殊对象
  • arguments
  • this
  • new.target
arguments

arguments.callee 是一个指向arguments对象所在函数的指针。

经典阶乘函数
1
2
3
4
5
6
7
8
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
console.log(factorial(10)); //3628800
使用arguments.callee重写迭代

这样的话就可以让函数逻辑与函数名解耦,解决了硬编码带来的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
console.log(factorial(5)); //120
let trueFactorial = factorial;
factorial = function () {
return 0;
}
console.log(trueFactorial(5)); //120
console.log(factorial(10)) //0
this
标准函数中的this

引用的是把函数当成方法调用的上下文对象,
全局上下文中调用函数,this指向 windows

1
2
3
4
5
6
7
8
9
10
window.color = 'red';
let o = {
color: 'blue'
};
function sayColor() {
console.log(this.color);
}
sayColor(); //red
o.sayColor = sayColor;
o.sayColor(); //blue
箭头函数中的this

引用的是定义箭头函数的上下文。

1
2
3
4
5
6
7
8
9
10
window.color = 'red';
let o = {
color: 'blue'
}
let sayColor = () => {
console.log(this.color);
}
sayColor(); //red
o.sayColor = sayColor;
o.sayColor(); //red

因此在使用事件回调定时回调时,
为了保证回调函数内this值指向定义函数的上下文,
常使用箭头函数定义回调函数。

1
2
3
4
5
6
7
8
9
10
function King() {
this.royaltyName = 'Henry';
setTimeout(() => console.log(this.royaltyName), 1000);
}
function Queen() {
this.royaltyName = 'Elizabeth';
setTimeout(function () { console.log(this.royaltyName); }, 1000);
}
new King(); //Henry
new Queen(); //undefined
caller

caller 引用的是调用当前函数的函数

1
2
3
4
5
6
7
function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer(); //output:outer source code

为了降低耦合度,可以引用arguments.callee.caller

1
2
3
4
5
6
7
function outer() {
inner();
}
function inner() {
console.log(arguments.callee.caller);
}
outer(); //outer source code
严格模式下
  • 访问arguments.callee会报错
  • 不能给caller属性赋值
new.target(ES6)

用于检测函数是否是使用new关键字调用的。

  • 正常调用 new.target = undefined
  • new关键字调用 new.target = 被调用的构造函数
    1
    2
    3
    4
    5
    6
    7
    8
    function King() {
    if (!new.target) {
    throw 'King must be instantiated using "new"'
    }
    console.log('King instantiated using "new"');
    }
    new King(); //King instantiated using new
    King(); //Error:King must be instantiated using "new"

10.3.4 函数属性与方法

函数固有属性(2个)
length

length保存函数定义的命名参数个数

1
2
3
4
5
6
7
8
9
10
11
12
function sayName(name) {
console.log(name)
}
function sum(num1, num2) {
return num1 + num2;
}
function sayHi() {
console.log('hi')
}
console.log(sayName.length) //1
console.log(sum.length) //2
console.log(sayHi.length) //0
prototype

prototype用于保存引用类型所有实例方法
在自定义类型时特别重要。
注意prototype不可枚举。

函数方法(apply,call,bind)

这3种方法都是用于 设置调用函数时函数体内this对象的值

strict模式下,没有指定上下文的调用函数,函数的this不会指向window,而会变成undefined

apply()

接收2个参数:

  • param1 指定this
  • param2 参数数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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]);
    }
    console.log(callSum1(10, 20)); //30
    console.log(callSum2(20, 30)); //50
call()

call方法和apply的不同之处在于:
需要传递的参数是逐个传递的

1
2
3
4
5
6
7
8
function sum(num1, num2) {
return num1 + num2;
}
function callSum(num1, num2) {
return sum.call(this, ...arguments);
}

console.log(callSum(10, 20)); //30

apply和call最强大就是控制函数调用上下文this的能力。

1
2
3
4
5
6
7
8
9
10
11
window.color = 'red'
let o = {
color:'blue'
}
function sayColor() {
console.log(this.color)
}
sayColor();
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
bind(ES5)

bind和call、apply的区别在于
bind会创建一个绑定了指定this的函数实例

1
2
3
4
5
6
7
8
9
window.color = 'red';
var o = {
color:'blue'
}
function sayColor() {
console.log(this.color);
}
let objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
提示

toLocaleString()toString() 始终返回函数代码,
但是返回代码的具体格式会因浏览器而异,
因此应该在重要功能是避免依赖这些方法的返回值。

10.3.5 函数表达式

1
let functionName = function (arg1, arg2, arg3) { };

像这样创建的函数叫做 匿名函数(anonymous function)
有时也被称为兰姆达(λ)函数

函数声明提升案例

使用函数声明会进行声明提升,如下面这段危险的代码:

1
2
3
4
5
6
7
8
9
10
11
let condition = true;
if (condition) {
function sayHi() {
console.log('Hi!')
}
} else {
function sayHi() {
console.log('Yo!')
}
}
sayHi() // Hi

也许浏览器会帮你纠正这段声明,
关键在于不同浏览器纠正问题的方式不一致,
所以尽量不要这么写。