# 函数初识

# 函数简介

函数:就是将一些功能或语句进行封装,在需要的时候,通过调用的形式,执行这些语句。

  • 函数也是一个对象

  • 使用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 是相互独立的,互不影响
}

# 变量和函数声明提升

变量声明提升:

  1. 使用 var 关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前被声明(但是不会赋值)
  2. 但是如果声明变量时不是用 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);

函数声明提升:

  1. 使用函数声明的形式创建的函数 function foo(){},会被声明提前。也就是说,整个函数会在所有的代码执行之前就被创建完成,所以我们可以在函数声明之前,调用函数。

  2. 使用函数表达式创建的函数 var foo = function(){},不会被声明提前,所以不能在声明前调用。很好理解,因为此时 foo 被声明了,且为 undefined,并没有把 function(){} 赋值给 foo。

foo(); //执行
bar(); //报错 此时  bar 为undefined
function foo() {
	console.log("我是foo函数");
}
var bar = function() {
	console.log("我是bar函数");
};

在函数作用域也有声明提前的特性:

  1. 使用 var 关键字声明的变量,会在函数中所有的代码执行之前被声明。
  2. 函数声明也会在函数中所有的代码执行之前执行。
  3. 在函数中,没有 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 = 2function 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 中,所有的变量都是保存在栈内存中的。

然后来看看下面的区别。

基本数据类型

基本数据类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量。

引用数据类型

对象是保存到堆内存中的。每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存了对象的内存地址(对象的引用)。如果两个变量保存了同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响。

上次更新: 9/2/2019, 5:09:05 PM