函数声明与函数表达式

  • JavaScript引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义
  • 函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义
  • 函数声明提升:函数声明会在任何代码执行之前先被读取并添加到执行上下文。
  • 在执行代码时,JavaScript引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。
  • 如果把代码中的函数声明改为等价的函数表达式,那么执行的时候就会出错
1
2
3
4
5
6
7
8
9
10
// 没问题 
console.log(sum(10, 10));
function sum(num1, num2) {
return num1 + num2;
}
// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) {
return num1 + num2;
};
  • 以上代码出错的原因在于并没有执行到let定义函数那一行,并不是因为使用let造成的。

函数作为值

  • 函数名在ECMAScript中就是变量。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
function callSomeFunction(someFunction, someArgument) { 
return someFunction(someArgument);
}
function add10(num) {
return num + 10;
}
let result1 = callSomeFunction(add10, 10);
console.log(result1); // 20
function getGreeting(name) {
return "Hello, " + name;
}
let result2 = callSomeFunction(getGreeting, "Nicholas");
console.log(result2); // "Hello, Nicholas"

函数内部

  • 函数内部存在两个特殊的对象:arguments和this;一个属性new.target属性。

1、arguments

  • arguments是一个类数组对象,包含调用函数时传入的所有参数。
  • 只有以 function关键字定义函数(相对于使用箭头语法创建函数)时才会有。
  • arguments对象其实还有一个callee属性,是一个指向arguments对象所在函数的指针。
  • 使用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 * factorial(num - 1);
}
}
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}

2、this

  • 在标准函数中,this引用的是把函数当成方法调用的上下文对象。
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
window.color = 'red'; 
let o = {
color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red'
  • 箭头函数中的this会保留定义该函数时的上下文

3、caller

  • 这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为null。
1
2
3
4
5
6
7
function outer() { 
inner();
}
function inner() {
console.log(inner.caller);
}
outer();//显示 outer()函数的源代码
  • 同样可以通过arguments.callee.caller来降低耦合度。
1
2
3
4
5
6
7
function outer() { 
inner();
}
function inner() {
console.log(arguments.callee.caller);
}
outer();//显示 outer()函数的源代码
  • ECMAScript5也定义了arguments.caller,但在严格模式下访问它会报错,在非严格模式下则始终是undefined。
    • 这是为了分清arguments.caller和函数的caller而故意为之的。
    • 严格模式下还有一个限制,就是不能给函数的caller属性赋值,否则会导致错误。

new.target

  • 如果函数是正常调用的则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"