一. 5种函数声明方式
(1)function f(x, y) { return x+y; }
(2)var f1 = function(x, y) { return x+y; }
(3)var f1 = function f2(x, y) { return x+y; }
(4)var f = new Function("x", "y", "return x+y")
(5)var f = (x, y) => x+y;
二. 函数的name属性
函数其实自带一个name属性, 但是有时候我们分不清这个name到底指代的是谁。
function f(x, y) { return x+y; }
f.name; // "f"
var f1 = function(x, y) { return x+y; }
f1.name; // "f1"
上述代码,直接声明一个函数f,f.name就是"f", 然后声明一个变量f1,然后将一个匿名函数赋值给这个f1变量。
我们再看看第(3)(4)(5)中函数声明方式。
var f1 = function f2(x, y) { return x+y; }
f1.name; // "f2"
f2.name; // 报错
var f = new Function("x", "y", "return x+y")
f.name; // "anonymous"
var f2 = (x, y) => x+y;
f2.name; // "f2"
三. 函数的本质
平常我们声明一个这样的函数:
function f(x, y) { return x+y; }
直接这样执行:
f(1, 2) // 3
但其实应该这么执行:
f.call(undefined, 1, 2)
如果我们直接输出f,这个f只是一个变量指代这个函数,内存图分析如下:

call方法为后面的this,arguments知识打下铺垫。
四. this和arguments
我们看上述用call方法调用函数的代码:
function f(x, y) {
console.log(this) // window对象
console.log(arguments) // arguments[1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ] (伪数组)
return x+y;
}
f.call(undefined, 1, 2)
简单来说,call方法的第一个参数就是this,除了第一个参数外的所有参数就是arguments,其中这个arguments是一个“伪数组”.
注意在“严格模式下”,this是undefined。
function f(x, y) {
'use strict'
console.log(this) // undefined
console.log(arguments) // arguments[1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ] (伪数组)
return x+y;
}
f.call(undefined, 1, 2)
五. call back调用栈
当一个函数执行的时候,会进入一个“调用栈”,我们知道“栈”的特性:先进后出。我们分析下面一段代码:
function a() {
console.log("a1");
b.call(); // (1)
console.log("a2");
return "a";
}
function b() {
console.log("b1");
c.call(); // (2)
console.log("b2");
return "b";
}
function c() {
console.log("c")
return "c";
}
a.call(); // (3)
console.log("end");
在全局范围,我们有a, b, c三个变量,然后看到a.call();执行a函数,输出“a1”,然后看到b.call(),所以在输出“a2”前,进入b函数,输出“b1”,看到c.call(),就进入c函数,输出“c”,c函数执行完后,回到刚刚c.call()的位置,即代码中(2)位置,并执行下一步,输出“b2”,这样b函数就执行完了,回到b.call()的位置,即代码中的(1)位置,并执行下一步,输出“a2”,执行完a函数,即回到a.call()的位置,即代码中的(3)位置,然后输出“end”。
六. 作用域面试题(略略提下“闭包”)
var a = 1;
function f1() {
var a =2;
f2.call();
console.log(a); // (1)
function f2() {
var a = 3;
console.log(a) // (2)
}
}
f1.call();
console.log(a) // (3)
全局作用域有两个变量: a和f1,f1作用域中有两个变量:a和f2,f2作用域中有一个变量:a,
var a的赋值只会影响当前作用域,故(1)(2)(3)处输出分别为2,3,1。
我们现在把代码变形下:
var a = 1;
function f1() {
a =2;
f2.call();
console.log(a); // (1)
function f2() {
var a = 3;
console.log(a); // (2)
}
}
f1.call();
console.log(a); // (3)
f1作用域中的a=2;首先会被看成一个赋值语句,在f1作用域中找变量a并且赋值,如果没找到就往父作用域找,对于上述代码,父作用域(全局作用域)中有一个var a = 1,则会改变全局作用域的变量a为2,同时(1)处的log会从当前作用域(f1作用域)开始寻找变量a,如果没有,则往父作用域寻找,故(1)和(3)处的log输出都是全局作用域的a,即输出2。此处就有“闭包”,即:如果一个函数可以使用该函数作用域外的变量,那么该(这个函数+这个变量)就叫“闭包”。
我们再次变形:
var a = 1;
function f1() {
f2.call();
console.log(a); // (1)
var a =2;
function f2() {
var a = 3;
console.log(a); // (2)
}
}
f1.call();
console.log(a); // (3)
在我们声明变量的时候我们要注意“变量提升”,乍一看(1)处的log可能是1或者2,但是其实都不是,上述代码应该实际上是这么写。
var a;
a = 1;
function f1() {
var a;
f2.call();
console.log(a); // (1)
a =2;
function f2() {
var a;
a = 3;
console.log(a); // (2)
}
}
f1.call();
console.log(a); // (3)
进行变量提升的改写后,我们就可以很清楚地看出log输出什么了,(1)处会输出“undefined”。
网友评论