# 函数初识
# 函数简介
函数:就是将一些功能或语句进行封装,在需要的时候,通过调用的形式,执行这些语句。
函数也是一个对象
使用
typeof
检查一个函数对象时,会返回 function
函数的作用:
将大量重复的语句写在函数里,以后需要这些语句的时候,可以直接调用函数,避免重复劳动。
简化编程,让编程模块化。
console.log("你好");
sayHello(); // 调用函数
// 定义函数
function sayHello() {
console.log("欢迎");
console.log("welcome");
}
# 函数的定义和调用
# 第一步:函数的定义
方式一:使用函数声明
来创建一个函数。语法:
function 函数名([形参1,形参2...形参N]){ // 备注:语法中的中括号,表示“可选”
语句...
}
举例:
function sum(a, b) {
return a + b;
}
解释如下:
function:是一个关键字。中文是“函数”、“功能”。
函数名字:命名规定和变量的命名规定一样。只能是字母、数字、下划线、美元符号,不能以数字开头。
参数:可选。
大括号里面,是这个函数的语句。
方式二:使用函数表达式
来创建一个函数。语法:
var 函数名 = function([形参1,形参2...形参N]){
语句....
}
举例:
var fun3 = function() {
console.log("我是匿名函数中封装的代码");
};
从方式二的举例中可以看出:所谓的“函数表达式”,其实就是将匿名函数赋值给一个变量。
当然,我们还有方式三:使用构造函数来创建一个对象。这种方式,用的少。
# 第二步:函数的调用
函数调用的语法:
函数名字();
# 函数的参数:形参和实参
函数的参数包括形参和实参。来看下面的图就懂了:
注意:实际参数和形式参数的个数,要相同。
假设我们定义一个求和的函数。
形参:
可以在函数的
()
中来指定一个或多个形参。多个形参之间使用
,
隔开,声明形参就相当于在函数内部声明了对应的变量,但是并不赋值。
实参:
在调用函数时,可以在
()
中指定实参。实参将会赋值给函数中对应的形参。
举例:
sum(3, 4);
sum("3", 4);
sum("Hello", "World");
//函数:求和
function sum(a, b) {
console.log(a + b);
}
控制台输出结果:
7
34
helloworld
实参的类型:
函数的实参可以是任意的数据类型。
调用函数时解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查。
实参的数量:
调用函数时,解析器也不会检查实参的数量:
多余实参不会被赋值
如果实参的数量少于形参的数量,则没有对应实参的形参将是 undefined。例如:
# 函数的返回值
举例:
console.log(sum(3, 4));
//函数:求和
function sum(a, b) {
return a + b;
}
return 的作用是结束方法。
注意:
return 后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。
在函数中 return 后的语句都不会执行(函数在执行完 return 语句之后停止并立即退出)
如果 return 语句后不跟任何值,就相当于返回一个 undefined
如果函数中不写 return,则也会返回 undefined
返回值可以是任意的数据类型,可以是对象,也可以是函数。
# fn() 和 fn 的区别【重要】
fn()
:调用函数。相当于获取了函数的返回值。fn
:函数对象。相当于直接获取了函数对象。
# 作用域(Scope)的概念
作用域指一个变量的作用范围。在 js 中,一共有两种作用域:
全局作用域
函数作用域
# 全局作用域
直接编写在 script 标签中的 JS 代码,都在全局作用域。
全局作用域在页面打开时创建,在页面关闭时销毁。
在全局作用域中有一个全局对象 window,它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用。
在全局作用域中:
创建的变量都会作为 window 对象的属性保存。
创建的函数都会作为 window 对象的方法保存。
全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到。
# 函数作用域
- 在函数体内部的 声明的变量
- 如果一个变量在函数体内部声明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。
var y = 10; // 全局变量
function foo() {
var x = 1; //局部变量
x = x + 1;
console.log(y); // 10
}
foo();
console.log(x); // 报错
// 也就是说,函数体内部声明的,在函数体外部是不能使用的。
// 在全局作用域下声明的全局变量,可以在页面任意部分访问得到。
# 作用域的上下级关系
当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错 ReferenceError。
在函数中要访问全局变量可以使用 window 对象。(比如说,全局作用域和函数作用域都定义了变量 a,如果想访问全局变量,可以使用window.a
)
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
var z = 100;
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
function ins() {
x++; // ins 可以访问foo的变量x
y++; // 不可以访问 bar的变量y
var z = 1000;
}
// 注意 bar 和 ins 中声明的 变量z 是相互独立的,互不影响
}
# 变量和函数声明提升
变量声明提升:
- 使用 var 关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前被声明(但是不会赋值)
- 但是如果声明变量时不是用 var 关键字(比如直接写 a = 1),则变量不会被声明提前。
console.log(a); // 打印 undefined ,说明a已经被声明了,只是没有赋值
var a = 10;
console.log(b); // b is not defined 没有生命提升
b = 2; // 此时 b 相当于 window.b (全局变量)
console.log(b); // 2
以上代码相当于:
var a;
console.log(a);
a = 10;
console.log(b);
b = 2;
console.log(b);
函数声明提升:
使用函数声明的形式创建的函数 function foo(){},会被声明提前。也就是说,整个函数会在所有的代码执行之前就被创建完成,所以我们可以在函数声明之前,调用函数。
使用函数表达式创建的函数 var foo = function(){},不会被声明提前,所以不能在声明前调用。很好理解,因为此时 foo 被声明了,且为 undefined,并没有把 function(){} 赋值给 foo。
foo(); //执行
bar(); //报错 此时 bar 为undefined
function foo() {
console.log("我是foo函数");
}
var bar = function() {
console.log("我是bar函数");
};
在函数作用域也有声明提前的特性:
- 使用 var 关键字声明的变量,会在函数中所有的代码执行之前被声明。
- 函数声明也会在函数中所有的代码执行之前执行。
- 在函数中,没有 var 声明的变量都是全局变量,而且并不会提前声明。
# 立即执行函数
现有匿名函数如下:
function(a, b) {
console.log("a = " + a);
console.log("b = " + b);
};
立即执行函数如下:
(function(a, b) {
console.log("a = " + a);
console.log("b = " + b);
})(123, 456);
立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数。
立即执行函数往往只会执行一次。为什么呢?因为没有变量保存它,执行完了之后,就找不到它了。
# 参数的数据类型
# 基本数据类型作为参数
基本数据类型作为参数传递,函数内部会创建该数据的副本,一切修改不会影响传进来的数据本身。
var num = 2;
function ins (x) {
x++;
}
// 调用ins方法
ins(num);
console.log(num); // 2
# 复杂数据类型作为参数
复杂数据类型作为参数传递,在函数内部对该参数的修改,会直接影响到函数外部的该参数,因为本质上他们是同一个对象。
function add(arr, n) {
arr.push(n);
}
var arr = [2, 3];
// 调用add方法
add(arr, 4);
console.log(arr); // [2,3,4]
# 栈内存和堆内存
我们首先记住一句话:JS 中,所有的变量都是保存在栈内存中的。
然后来看看下面的区别。
基本数据类型:
基本数据类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量。
引用数据类型:
对象是保存到堆内存中的。每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存了对象的内存地址(对象的引用)。如果两个变量保存了同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响。