函数
函数的声明
声明方式
函数的声明有三种方式:
- function
- 函数表达式
- Function 构造函数
// function 关键字
function print(s) {
console.log(s);
}
// 函数表达式
var print = function(s) {
console.log(s);
};
// Function构造函数
var add = new Function(
'x',
'y',
'return x + y'
);
// Function的最后一个参数是函数体重复声明时,后声明会覆盖前声明的,由于存在函数提升
函数声明提升
下面给出3个案例,涉及函数提升和var变量提升的一些关系和影响。
// 1. OK 的
f();
function f() {}
=====================
// 2. error: 即便变量f的声明提升了,function(){}也提升了,但让f指向function是在调用之后干的事。
f();
var f = function (){};
=====================
// 3. 函数提升,导致最终函数是函数表达式赋值的结果
var f = function () {
console.log('1');
}
function f() {
console.log('2');
}
f() // 1函数的属性与方法
name属性- 函数对象自带name属性,其值为函数名
length属性- 函数的 length 返回函数定义时预期传入的参数个数,这里是函数定义时就确定了的参数个数
- 这里要区分
arguments.length,arguments.length是运行时拿到的参数的个数
bind/call/apply- 三个绑定函数内部this的函数
函数作用域
ES5 规定,JS只有两种作用域,一个是全局作用域,一个是函数作用域 ES6 新增了块级作用域,学习ES6的时候再探讨, 以下均已ES6以前的标准为主
注意:函数作用域一定是函数内部(不是什么分支判断的内部啊)其他的一律视为全局作用域
// 函数作用域中定义的变量,外部无法访问
function f(){
var v = 1;
}
v // ReferenceError: v is not defined
// 重名的话,内部的会覆盖外部的
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1函数本身的作用域
函数本身也是个变量,具有自己的作用域,函数执行时所在的作用域,取决于定义时的作用域,而不是运行时所在的作用域。
在A函数内调用B函数,B函数如果是在其他地方定义的,那么他是访问不到A内部的变量的,这种场景我们一般通过给B函数传参实现访问。
var a = 1;
var x = function () {
console.log(a); // 注意,因为x这个函数是定义在全局的,它只能访问到全局的这个a
};
function f() {
var a = 2;
x();
}
f() // 1
function y(f) {
var a = 2;
f();
}
y(x) // 依然是1function foo() {
var x = 1;
function bar() {
console.log(x); // 注意,因为x这个函数的作用域是全局,它访问的a也就是全局的这个a
}
return bar;
}
var x = 2;
var f = foo();
f() // 1函数的参数
在不传递时,拿到的形参会是 undefined, 相当于前面说的,变量声明了但未赋值则为undefined的情况
值传递和引用传递
老话常说:原始类型值传递,对象类型引用传递。但我觉得,就是值传递,如果你这个变量指向一个基本类型,那么他的值就在栈中,就把值给传过去了;如果你传递的是一个对象类型,他的值是一个引用,存在栈中,那么你传过去的同样是这个变量的值,只是说这个变量的值就是一个引用。
所以要思考,更改形参会不会引起原来变量的值的改变?原始类型包不变,对象类型包变的呀(除非你传了一个deepClone的对象,那改变的就是那个深拷贝对象而不是原始对象)。
arguments对象
由于 JS 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。
arguments对象包含了函数运行时的所有参数,是一个类数组对象,可以用整数下标索引获取参数。
这个对象只有在函数体内部,才可以使用。
function add(a,b){
console.log(add.length) // 2,定义时是两个参数
console.log(arguments.length) // 1,运行时是一个参数
return a + b
}
add(1)函数的高级应用
JS 视函数为一种值,和其他值地位相同,可以把函数赋给对象的属性,也可以当作参数传入其他函数,也可以作为函数的返回值。这支撑了各种高级函数应用
闭包
闭包是JS最有趣的地方,很多高级应用都依赖闭包实现。对闭包的理解基于对作用域的理解,我们还是以ES6之前的作用域规定为例,在函数内部,它可以访问外部作用域的变量,但外部无法访问函数内部作用域的变量。
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999闭包 = 函数 + 函数所在的词法上下文 闭包通常在一个返回函数的函数中得到体现 闭包使得函数依赖的词法上下文安全的一直存在内存中而不被销毁
// 在这里,start这个词法上下文中的变量 + 函数A 就构成了一个闭包
function createIncrementor(start) {
return function A() {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7合理应用闭包可以实现许多高级特性,但同时也可能引起内存泄漏问题,闭包中的变量一直被引用,垃圾回收机制无法回收这一块memory,所以需要正确使用闭包。
IIFE 立即执行函数表达式
IIFE(Immediately Invoked Function Expression)是一个函数表达式,在定义后立即执行。
通常情况下,目的:省去函数命名,避免污染全局变量, 形成了一个单独的作用域以封装一些外部无法读取的私有变量。
function(){ /* code */ }(); // Error !
(function(){ /* code */ }()); // OK
// or
(function(){ /* code */ })(); // OK