Webcourse/04-JavaScript基础/18-函数.md
2020-07-04 17:27:59 +08:00

596 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 前言
关于函数的核心内容
- 函数有哪几种定义和调用方式
- this函数内部的 this 指向如何改变 this 的指向
- 函数的严格模式
- 高阶函数函数作为参数传递函数作为返回值传递
- 闭包闭包的作用
- 递归递归的两个条件
- 深拷贝和浅拷贝的区别
## 函数的介绍
函数就是将一些功能或语句进行**封装**在需要的时候通过**调用**的形式执行这些语句
- **函数也是一个对象**
- 使用`typeof`检查一个函数对象时会返回function
**函数的作用**
- 将大量重复的语句抽取出来写在函数里以后需要这些语句的时候可以直接调用函数避免重复劳动
- 简化编程让编程模块化高内聚低耦合
来看个例子
```javascript
console.log("你好");
sayHello(); // 调用函数
// 定义函数
function sayHello(){
console.log("欢迎");
console.log("welcome");
}
```
## 函数的定义/声明
### 方式一利用函数关键字自定义函数命名函数
使用`函数声明`来创建一个函数也就是 function 关键字语法
```javascript
function 函数名([形参1,形参2...形参N]){ // 备注:语法中的中括号,表示“可选”
语句...
}
```
举例
```javascript
function fun1(a, b){
return a+b;
}
```
解释如下
- function是一个关键字中文是函数功能
- 函数名字命名规定和变量的命名规定一样只能是字母数字下划线美元符号不能以数字开头
- 参数可选
- 大括号里面是这个函数的语句
PS在有些编辑器中方法写完之后我们在方法的前面输入`/**`然后回车会发现注释的格式会自动补齐
### 方式二函数表达式匿名函数
使用`函数表达式`来创建一个函数语法
```javascript
var 变量名 = function([形参1,形参2...形参N]){
语句....
}
```
举例
```javascript
var fun2 = function() {
console.log("我是匿名函数中封装的代码");
};
```
解释如下
- 上面的 fun2 是变量名不是函数名
- 函数表达式的声明方式跟声明变量类似只不过变量里面存的是值而函数表达式里面存的是函数
- 函数表达式也可以传递参数
从方式二的举例中可以看出所谓的函数表达式其实就是将匿名函数赋值给一个变量
### 方式三使用构造函数 new Function()
使用构造函数`new Function()`来创建一个对象这种方式用的少
语法
```javascript
var 变量名/函数名 = new Function('形参1', '形参2', '函数体');
```
注意Function 里面的参数都必须是**字符串**格式也就是说形参也必须放在**字符串**函数体也是放在**字符串**里包裹起来放在 Function 的最后一个参数的位置
代码举例
```javascript
var fun3 = new Function('a', 'b', 'console.log("我是函数内部的内容"); console.log(a + b);');
fun3(1, 2); // 调用函数
```
打印结果
```
我是函数内部的内容
3
```
**分析**
方式3的写法很少用原因如下
- 不方便书写写法过于啰嗦和麻烦
- 执行效率较低首先需要把字符串转换为 js 代码然后再执行
### 总结
1**所有的函数都是 `Fuction` 实例**或者说是实例对象函数本质上都是通过 new Function 得到的
2函数既然是实例对象那么**函数也属于对象**还可以通过如下特征来佐证函数属于对象
1我们直接打印某一个函数比如 `console.log(fun2)`发现它的里面有`__proto__`这个是属于原型的知识后续再讲
2我们还可以打印 `console.log(fun2 instanceof Object)`发现打印结果为 `true`这说明 fun2 函数就是属于 Object
## 函数的调用
### 方式1普通函数的调用
函数调用的语法
```javascript
函数名();
```
或者
```
函数名.call();
```
代码举例
```javascript
function fn1() {
console.log('我是函数体里面的内容1');
}
function fn2() {
console.log('我是函数体里面的内容2');
}
fn1(); // 调用函数
fn2.call(); // 调用函数
```
### 方式2通过对象的方法来调用
```javascript
var obj = {
a: 'qianguyihao',
fn2: function() {
console.log('千古壹号,永不止步!');
},
};
obj.fn2(); // 调用函数
```
如果一个函数是作为一个对象的属性保存那么我们称这个函数是这个对象的**方法**
PS关于函数和方法的区别本文的后续内容里有讲到可以往下面翻
### 方式3立即执行函数
代码举例
```javascript
(function() {
console.log('我是立即执行函数');
})();
```
立即执行函数在定义后会自动调用
PS关于立即执行函数本文的后续内容里有讲到可以往下面翻
上面讲到的这三种方式是用得最多的接下来讲到的三种方式暂时看不懂也没关系可以等学完其他的知识点再回过头来看
### 方式4通过构造函数来调用
代码举例
```javascript
function Fun3() {
console.log('千古壹号,永不止步~');
}
new Fun3();
```
这种方式用得不多
### 方式5绑定事件函数
代码举例
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="btn">我是按钮,请点击我</div>
<script>
var btn = document.getElementById('btn');
//2.绑定事件
btn.onclick = function() {
console.log('点击按钮后,要做的事情');
};
</script>
</body>
</html>
```
这里涉及到DOM操作和事件的知识点后续再讲
### 方式6定时器函数
代码举例每间隔一秒 数字 加1
```javascript
let num = 1;
setInterval(function () {
num ++;
console.log(num);
}, 1000);
```
这里涉及到定时器的知识点
## 函数的参数形参和实参
函数的参数包括形参和实参先来看下面的图就很好懂了
![](http://img.smyhvae.com/20180118_1130.png)
**形参**
- 概念形式上的参数定义函数时传递的参数当时并不知道是什么值
- 定义函数时可以在函数的`()`中来指定一个或多个形参
- 多个形参之间使用`,`隔开声明形参就相当于在函数内部声明了对应的变量但是并不赋值
**实参**
- 概念实际上的参数调用函数时传递的参数实参将会传递给函数中对应的形参
- 在调用函数时可以在函数的 `()`中指定实参
注意实际参数和形式参数的个数一般要相同
举例
```javascript
// 调用函数
sum(3,4);
sum("3",4);
sum("Hello","World");
// 定义函数:求和
function sum(a, b) {
console.log(a + b);
}
```
控制台输出结果
```
7
34
helloworld
```
### 实参的类型
函数的实参可以是任意的数据类型
调用函数时解析器不会检查实参的类型所以要注意是否有可能会接收到非法的参数如果有可能则需要对参数进行类型的检查
### 实参的数量实参和形参的个数不匹配时
调用函数时解析器也不会检查实参的数量
- 如果实参的数量多余形参的数量多余实参不会被赋值
- 如果实参的数量少于形参的数量多余的形参会被定义为 undefined表达式的运行结果为 NaN
代码举例
```javascript
function sum(a, b) {
console.log(a + b);
}
sum(1, 2);
sum(1, 2, 3);
sum(1);
```
打印结果
```
3
3
NaN
```
注意 JS 形参的默认值是 undefined
## 函数的返回值
举例
```javascript
console.log(sum(3, 4)); // 将函数的返回值打印出来
//函数:求和
function sum(a, b) {
return a + b;
}
```
return 的作用是结束方法终止函数
注意
- return 的值将会作为函数的执行结果返回可以定义一个变量来接收该结果
- 在函数中return后的语句都不会执行函数在执行完 return 语句之后停止并立即退出函数
- 如果return语句后不跟任何值就相当于返回一个undefined
- 如果函数中不写return则也会返回undefined
- 返回值可以是任意的数据类型可以是对象也可以是函数
- return 只能返回一个值如果用逗号隔开多个值则以最后一个为准
## 函数名函数体和函数加载问题重要请记住
我们要记住**函数名 == 整个函数**举例
```javascript
console.log(fn) == console.log(function fn(){alert(1)});
//定义fn方法
function fn(){
alert(1)
};
```
我们知道当我们在调用一个函数时通常使用`函数()`这种格式可如果我们是直接使用`函数`这种格式它的作用相当于整个函数
**函数的加载问题**JS加载的时候只加载函数名不加载函数体所以如果想使用内部的成员变量需要调用函数
### fn() fn 的区别重要
- `fn()`调用函数调用之后还获取了函数的返回值
- `fn`函数对象相当于直接获取了整个函数对象
## breakcontinuereturn 的区别
- break 结束当前的循环体 forwhile
- continue 跳出本次循环继续执行下次循环 forwhile
- return 1退出循环2返回 return 语句中的值同时结束当前的函数体内的代码退出当前函数
## 立即执行函数
现有匿名函数如下
```javascript
function(a, b) {
console.log("a = " + a);
console.log("b = " + b);
};
```
立即执行函数如下
```javascript
(function(a, b) {
console.log("a = " + a);
console.log("b = " + b);
})(123, 456);
```
立即执行函数函数定义完立即被调用这种函数叫做立即执行函数
立即执行函数往往只会执行一次为什么呢因为没有变量保存它执行完了之后就找不到它了
## 方法
函数也可以成为对象的属性**如果一个函数是作为一个对象的属性保存那么我们称这个函数是这个对象的方法**
调用这个函数就说调用对象的方法method函数和方法有什么本质的区别吗它只是名称上的区别并没有其他的区别
函数举例
```javascript
// 调用函数
fn();
```
方法举例
```javascript
// 调用方法
obj.fn();
```
我们可以这样说如果直接是`fn()`那就说明是函数调用如果是`XX.fn()`的这种形式那就说明是**方法**调用
## 类数组 arguments
> 这部分小白可能看不懂所以这一段暂时可以忽略
在调用函数时浏览器每次都会传递进两个隐含的参数
- 1.函数的上下文对象 this
- 2.**封装实参的对象** arguments
例如
```javascript
function foo() {
console.log(arguments);
console.log(typeof arguments);
}
foo();
```
![](http://img.smyhvae.com/20180315_0903.png)
arguments 是一个类数组对象它可以通过索引来操作数据也可以获取长度
**arguments 代表的是实参**在调用函数时我们所传递的实参都会在 arguments 中保存有个讲究的地方是arguments**只在函数中使用**
### 1返回函数**实参**的个数arguments.length
arguments.length 可以用来获取**实参的长度**
举例
```javascript
fn(2, 4);
fn(2, 4, 6);
fn(2, 4, 6, 8);
function fn(a, b) {
console.log(arguments);
console.log(fn.length); //获取形参的个数
console.log(arguments.length); //获取实参的个数
console.log('----------------');
}
```
打印结果
![](http://img.smyhvae.com/20180125_2140.png)
我们即使不定义形参也可以通过 arguments 来使用实参只不过比较麻烦arguments[0] 表示第一个实参arguments[1] 表示第二个实参...
### 2返回正在执行的函数arguments.callee
arguments 里边有一个属性叫做 callee这个属性对应一个函数对象就是当前正在指向的函数对象
```javascript
function fun() {
console.log(arguments.callee == fun); //打印结果为true
}
fun('hello');
```
在使用函数**递归**调用时推荐使用 arguments.callee 代替函数名本身
### 3arguments 可以修改元素
之所以说 arguments 是伪数组是因为**arguments 可以修改元素但不能改变数组的长短**举例
```javascript
fn(2, 4);
fn(2, 4, 6);
fn(2, 4, 6, 8);
function fn(a, b) {
arguments[0] = 99; //将实参的第一个数改为99
arguments.push(8); //此方法不通过,因为无法增加元素
}
```
## arguments 的使用
当我们不确定有多少个参数传递的时候可以用 **arguments** 来获取 JavaScript arguments 实际上是当前函数的一个**内置对象**所有函数都内置了一个 arguments 对象只有函数才有 arguments 对象arguments 对象中存储了**传递的所有实参**.
arguments的展示形式是一个**伪数组**伪数组具有以下特点
- 可以进行遍历具有数组的 length 属性
- 按索引方式存储数据
- 不具有数组的 push()pop() 等方法
**代码举例**利用 arguments 求函数实参中的最大值
代码实现
```javascript
function getMaxValue() {
var max = arguments[0];
// 通过 arguments 遍历实参
for (var i = 0; i < arguments.length; i++) {
if (max < arguments[i]) {
max = arguments[i];
}
}
return max;
}
console.log(getMaxValue(1, 3, 7, 5));
```
## 我的公众号
想学习<font color=#0000ff>**代码之外的技能**</font>****id`qianguyihao`
扫一扫你将发现另一个全新的世界而这将是一场美丽的意外
![](http://img.smyhvae.com/2016040102.jpg)