定义函数的方式
- 函数是一个引用值,堆存
函数体,栈存内存地址。 - 如果同一个函数被多次声明,后面的声明会覆盖前面的声明。
函数声明
function test(){
// 函数体
}
test // 调用
函数表达式
// 匿名函数表达式
var test = function (){
// 函数体
}
// 命名函数表达式
var test = function abc(){
// 函数体
console.log(typeof abc); // 'function'
}
abc(); // Uncaught ReferenceError: abc is not defined
命名函数表达式,函数名只在该函数体内有效,外部无法访问。
Function 构造函数(了解)
var test = new Function(
'a',
'b',
'return a + b'
);
test(1, 2) // 3 调用且传参
// 等同于
function test(a, b){
return a + b;
}
参数
function x(a, b){ // a b 形参
return a + b;
}
x(1, 2) // 3 // 1 2 实参
x(1) // NaN 参数b没传,默认为undefined。 1 + undefined == NaN
传递方式
- 函数参数如果是
原始值则传递方式是值传递,这表示在函数体内修改参数值不会影响到调用时传入的原始数据。
var a = 1;
function x(a){
a = 2;
}
x(a);
a // 1 函数体内部修改不会影响外部
- 函数参数如果是
引用值(数组、对象、函数),则传递方式是引用传递,这表示在函数体内修改参数将会影响调用时传入的原始数据。
var o = {a: 1};
function x(obj){
obj.a = 2;
}
x(o);
o // {a: 2} 函数体内部修改影响了外部原始数据
- 接上,如果函数内部修改的不是引用值参数的某个属性,而是整个参数,则不会影响外部的原始数据。
var o = {a: 1};
function x(obj){
obj = {a: 2};
}
x(o);
o // {a: 1} 因为形参obj的值是o的引用地址,重新给obj赋值就导致obj指向了另一个引用地址,
// 所以原始数据的o当然不受影响
arguments对象
- arguments对象是实参
类数组,包含了函数运行时的所有参数,只有在函数体内部才可以使用。
function f(a, b){
console.log(arguments)
}
f(1, 2) // 类数组:[1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
f(1, 2, 3) // 类数组:[1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
与形参的映射关系
arguments会与形参一 一形成映射关系,一改全改。未形成映射关系的修改其中一个不会影响另一个。
function test(a, b, c) {
arguments[0] = 10;
console.log(a); // 10
// 修改 arguments[i]的数值,则对应的形参也会跟着变化(前提是实参与形参形成了映射)
b = 20;
console.log(arguments[1]); // 20
// 修改形参数值,对应的arguments[i]也变(前提是实参与形参形成了映射)
c = 30;
console.log(arguments[2]); // undefined
// 由于实参是两个,形参3个,第三个形参没有与实参形成映射关系,所以为 undefined
}
test(1, 2);
- 但是在
严格模式下arguments与形参不会形成映射关系,修改其中一个不会影响另一个。
function test(a, b, c) {
'use strict';
arguments[0] = 10;
console.log(a); // 1
}
test(1, 2);
callee
- 返回它所对应的原函数,适用于调用没有函数名的函数。
严格模式禁用。
function test() {
console.log(arguments.callee); // ƒ test() {console.log(arguments.callee);}
console.log(test == arguments.callee); // true 指向拥有自己的函数
}
test();
// 适用场景:立即执行函数无法再次调用,使用 arguments.callee
var num = (function(n) {
if (n == 1) {
return 1;
}
return n * arguments.callee(n - 1);
}(5));
console.log(num); // 120
返回值 return
return既能返回值,又能终止代码执行。- 函数中如果未指定返回值,则默认返回
undefined。
function test(){
return "abc";
}
var str = test(); // 调用函数,接收返回值
str // "abc"
函数名的提升
- JS将函数名视为变量名,所以采用
函数声明定义的函数会像变量声明一样,被提升到代码头部。
f(); // 不会报错
function f() {} // 函数声明定义的函数
a(); // Uncaught TypeError: a is not a function
var a = function(){}
// 该函数是以函数表达式定义的,所以整个函数不会被提升。
// 但是变量 a 的声明会被提升,值为 undefined,所以以函数的方式调用会报错。
// 等价于:
var a;
a();
a = function(){}
函数属性及方法
name
- 返回函数的名字。
function f(){}
f.name // 'f'
var f1 = function(){}
f1.name // 'f1' 匿名函数表达式返回的是变量名(也可以叫做函数名)
var f2 = function ff(){}
f2.name // 'ff' 命名函数表达式返回的是 function 后定义的函数名,但 'ff' 只在函数体中才可以使用
caller
- 获得调用当前函数的函数(window或全局调用指向 null)。
function a(){
console.log(a.caller)
}
function b(fn){
fn()
}
b(a) // b(fn){fn()} 打印的是b函数,因为函数a是在b函数中调用的
a() // null a函数是全局调用的
length
- 返回函数形参个数。
function test(a,b,c){
}
test.length // 3 无论调用时的实参是几个,永远打印形参的个数
toString()
- 返回函数源码字符串。
- 对于原生函数返回
function (){[native code]}。
function f() {
console.log(1);
}
f.toString(); // 'function f() {\n console.log(1);\n}'
函数作用域
- 作用域指的是变量存在的范围,在ES5中只有
全局作用域和函数作用域,ES6新增了块级作用域。 - 对于顶层函数来讲,函数外声明的变量就是
全局变量,函数内可以读取。但在函数内声明的变量是局部变量。外部无法读取。
函数内部变量声明提升
- 与全局作用域一样,函数作用域内部也会产生
变量提升现象,使用var命令的变量声明会被提升到函数体的头部。
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于:
function foo(x){
var tmp; // tmp的变量声明被提升到了函数体的头部
if(x > 100){
tmp = x - 100;
}
}
函数本身作用域
函数执行时的作用域是定义时的作用域,而不是执行时所在的作用域。
var a = 1; // 全局变量a
var x = function(){
console.log(a);
}
function f(){
var a = 2; // 局部变量a
x();
}
f(); // 1 打印的是全局变量a,即使x是在f中被调用。
// 因为x函数是在全局定义的,作用域绑定是全局,所以输出a就是全局变量的a
// 改动版:
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x) // ReferenceError: a is not defined
// x是在全局作用域定义的,没有全局变量a,所以报错
函数体内部定义的函数,该函数的作用域是整个函数体内部(闭包)
function x(){
var a = 1; // 局部变量a
function y(){
console.log(a);
}
return y;
}
var a = 2; // 全局变量a
var f = x();
f(); // 1 y 函数的作用域在定义时绑定的是 x 函数体,
// 即使保存到全局被调用依然指向的是局部变量 a
立即执行函数 IIFE
-
普通的函数声明如果不执行会一直占用内存空间,等待被调用(执行上下文里存在)。而立即执行函数没有声明,在一次执行过后立即释放。适合做初始化工作、插件。
-
立即执行函数另一大用处就是避免全局污染,写在立即执行函数中的变量与函数无法被外部访问。
语法
- JS中,() 跟在函数后表示调用该函数,但是
函数声明后不能加上 (),这会产生语法错误。
function test(){}(); // 报错 Uncaught SyntaxError: Unexpected token ')'
// 报错的原因是 function 关键字被当成了语句,而语句是不可以被执行的
function f(){} // 语句
var f = function f(){} // 表达式
var f = function x(){ // 1 表达式可以被执行
console.log(1);
}()
只有表达式可以被 () 执行。
- JS规定,如果
function关键字出现在行首一律被解释为语句,JS引擎会认为这一段都是函数的定义,不应该以 ()结尾,所以报错。解决办法是不要让function出现在行首,让JS引擎将其理解成为一个表达式。
// 标准写法:
var num = (function(a, b){ // 形参
return a + b; // 可以有返回值,定义变量 num接收。
}(1, 2)); // 结尾的 ; 必备,如果省略连续遇到两个IIFE可能会报错
num; // 3
// 其他写法:
true && function test(){
console.log(123);
}(); // 可以执行
test // test is not defined 函数表达式会自动忽略函数名
用正号(+)、负号(-)、()、!、||、&&等,只要可以将函数声明转为函数表达式的符号都可以执行。