一个函数返回不同的结构体_JavaScript — 函数

3866929fcd94c22608a492558530414c.png

一、函数

1.1 函数的定义和调用

• 函数(function),也叫作功能、方法,函数可以将一段代码一起封装起来,被封装起来的 函数具备某一项特殊的功能,内部封装的一段代码作为一个完整的结构体,要执行就都执 行,要不执行就都不执行。
• 函数的作用就是封装一段代码,将来可以重复使用。
• 使用typeof检查一个函数对象时,会返回function

1.1.1 函数声明

• 函数声明又叫函数定义,函数必须先定义然后才能使用。
• 如果没有定义函数直接使用,会出现一个引用错误。

• 函数声明语法:

function 函数名(参数){
    封装的结构体;
}

特点:函数声明的时候,函数体并不会执行,只有当函数被调用的时候才会执行。

1.1.2 函数调用

• 调用方法:函数名();
• 函数调用也叫作函数执行,调用时会将函数内部封装的所有的结构体的代码立即执行。
• 函数内部语句执行的位置,与函数定义的位置无关,与函数调用位置有关。
• 函数可以一次定义,多次执行。
//定义函数,函数就是一组语句的集合
function haha(){
    console.log(1);
    console.log(2);
    console.log(3);
    console.log(4);
}

//调用函数
haha();

函数的意义1:

在出现大量程序相同的时候,可以封装为一个function,这样只用调用一次,就能执行很多语句。

1.2 函数的参数

为什么要设置参数?

为了增强函数的功能性和函数的可拓展性,便于交互。

• 我们希望函数执行结果不是一成不变的,可以根据自定义的内容发生一些变化。
• 函数预留了一个接口,专门用于让用户自定义内容,使函数发生一些执行效果变化。
• 接口:就是函数的参数,函数参数的本质就是变量,可以接收任意类型的数据,导致函数 执行结果根据参数不同,结果也不同。
• 一个函数可以设置 0 个或者多个参数,参数之间用逗号分隔。
• 函数的参数根据书写位置不同,名称也不同:
• 形式参数:定义的 () 内部的参数,叫做形式参数,本质是变量,可以接收实际参数传 递过来的数据。简称形参。
• 实际参数:调用的 () 内部的参数,叫做实际参数,本质就是传递的各种类型的数据,传递给每个形参,简称实参。
• 实际参与运算的变量。形参为他占位置,真实参与运算的变量。

• 函数执行过程,伴随着传参的过程:

b46db5571f239412015bedc402ffef77.png

函数的参数优点

• 不论使用自己封装的函数,还是其他人封装的函数,只需要知道传递什么参数,执行什么 功能,没必要知道内部的结构什么。
• 一般自己封装的函数或者其他人封装的函数需要有一个 API 接口说明,告诉用户参数需要 传递什么类型的数据,实现什么功能。

参数可以有无数个,用逗号隔开。

//有多少形式参数都可以,都罗列出来
function fun(a,b){
    console.log(a + b);
}

fun(3,5);   //输出8
fun(8,11);  //输出19

3c942a1d13c1d181daa84b691c8d58bf.png

函数的意义2:

我们在调用一个函数的时候,不用关心函数内部的实现细节,甚至这个函数是你上网抄的,可以运用。所以这个东西,给我们团队开发带来了好处。

1.3 函数的返回值

• 函数能够通过参数接收数据,也能够将函数执行结果返回一个值。
• 利用函数内部的一个 return 的关键字设置函数的返回值。
• 作用 ①:函数内部如果结构体执行到一个 return 的关键字,会立即停止后面代码的执行。
• 作用 ②:可以在 return 关键字后面添加空格,空格后面任意定义一个数据字面量或者表达式, 函数在执行完自身功能之后,整体会被 return 矮化成一个表达式,表达式必须求出一个值继续 可以参与程序,表达式的值就是 return 后面的数据。

函数的返回值应用

• 函数如果有返回值,执行结果可以当成普通数据参与程序。
• 函数如果有返回值,可以作为一个普通数据赋值给一个变量,甚至赋值给其他函数的实际 参数。

• 注意:

如果函数没有设置 return 语句 ,那么函数有默认的返回值 undefined;
如果函数使用 return 语句,但是 return 后面没有任何值,那么函数的返回值也是 undefined。
如果函数使用 return语句,那么跟再return后面的值,就成了函数的返回值。

推荐的做法是要么让函数始终都返回一个值,要么不要有返回值。

function sum(a,b){
    return a + b;       //现在这个函数的返回值就是a+b的和
}

console.log(sum(3,8));  //sum没有输出功能,就要用console.log输出
                        //sum(3,8)实际上就成为了一个表达式,需要计算
                        //计算后就是11,console.log(11);

函数只能有唯一的return,但有if语句时除外。

程序遇见了return,将立即返回结果,返回调用它的地方,而不执行函数内的剩余的语句。

function fun(){
    console.log(1);
    console.log(2);
    return;             //返回一个空值

    console.log(3);     //这行语句不执行,因为函数已经return了
}

fun();

函数的意义3:

模块化编程,让复杂的逻辑变得简单。

1.4 函数常见的声明方式

1.4.1 函数声明方式

function add(num1,num2){
    return num1+num2;
}

1.4.2 函数表达式声明方式

var add= function(num1,num2){
    return num1+num2;
};

1.4.3 使用Function构造函数

var add = new Function('num1','num2','return num1+num2');

不推荐使用, 主要用于面向对象时理解"函数就是对象, 函数名就是指针"这一概念。

1.5 函数的数据类型

• 函数是一种单独的数据类型 Function。
• 由于函数是一种数据类型,可以参与其他程序。
• 例如,可以把函数作为另一个函数的参数,在另一个函数中调用。
• 或者,可以把函数可以作为返回值从函数内部返回。
// 定义一个函数
function fun() {
  console.log(1);
}
// 定义一个函数表达式
var foo = function () {
  console.log(2);
};
// 检测函数的数据类型
console.log(typeof (fun()));    // undefined

console.log(typeof (fun));      // function
console.log(typeof (foo));      // function

函数是一个引用类型

基本数据类型:number、string、boolean、undefined、null
引用数据类型也有很多种:object、function、array、RegExp、Math、Date。

归根结底,它还是属于object

基本类型保存值,引用类型保存地址

var a = 6 ; 那么这个a变量里面存储的是6这个数值;

而var a = function(){}; 那么这个a标签里面存储的是function的内存地址。

a、b指向的是内存中的同一个地址,所以a就是b,b就是a。

//定义了一个变量a,引用了一个funciton
//这个a变量存储的是这个匿名函数的内存地址
var a = function(){
    alert("我是一个函数,我执行了");
}

//就是把匿名函数的地址也给了b
var b = a;
//给b添加一个属性
b.haha = 1;
//输出a的haha属性,你会发现a也有这个属性了:
console.log(a.haha);
//b的haha属性和a的同步更改的,因为都是指向的同一个对象
b.haha++;
b.haha++;
b.haha++;
console.log(a.haha);

1.6 arguments 对象

• JavaScript 中,arguments 对象是比较特别的一个对象,实际上是当前函数的一个内置属性。也就是说所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有的实参。arguments 是一个伪数组(不是数组),因此及可以进行遍历。
• 函数的实参个数和形参个数可以不一致,所有的实参都会存储在函数内部的 arguments 类数组对象中。
• arguments对象还有一个名叫callee的属性, 该属性是一个指针, 指向拥有这个arguments对象的函数;
// 定义一个函数
function sum(a,b) {
  return a + b;
}

// 调用函数的时候,实参的个数可以与形参不同
console.log(sum(1,2));         // 3
console.log(sum(1));           // NaN
console.log(sum(1,2,3,4));     // 3

// 函数内部有一个 arguments 对象,会接收所有的实参
function fun() {
    console.log(arguments);
    console.log(arguments.length);
// 使用数组的遍历方法可以获取每一项实参
    for (var i = 0 ; i <= arguments.length - 1 ; i++) {
     console.log(arguments[i]);
    }
}
// 调用函数
fun(1,2,3,4,5,6,7);

1.7 函数递归

• 函数内部可以通过函数名调用函数自身的方式,就是函数递归现象。
• 递归的次数太多容易出现错误:超出计算机的计算最大能力。
• 更多时候,使用递归去解决一些数学中的现象。

• 例如可以输出斐波那契数列的某一项的值。

斐波那契数列就是经典的递归算法: 1、1、2、3、5、8、13、21、34、55、89、144、233……

function fun(n) {
  if (n === 1 || n === 2) {
    return 1;
  } else {
    return fun(n - 1) + fun(n - 2);
  }
}
// 调用函数
console.log(fun(1));              // 1
console.log(fun(2));              // 3
console.log(fun(3));              // 6

1.8 匿名函数

没有命名的函数:function () {};

作用:

1)、用在绑定事件的时候

document.onclick = function () {
    alert(1);
};

2)、定时器

setInterval(function () {
    console.log('每秒爱你1遍!');
},1000);

3)、立即执行函数(闭包) 函数定义完,立即被调用,这种函数叫做立即执行函数;立即执行函数往往只会执行一次

(function(num1, num2){
    console.log("mum1 = "+ num1);
    console.log("num2 = "+ num1);
    console.log("num2 = "+ num2);
})(100, 101);

1.9 作用域

• 作用域:变量可以起作用的范围。
• 如果变量定义在一个函数内部,只能在函数内部被访问到,在函数外部不能使用这个变量 ,函数就是变量定义的作用域。
• 任何一对花括号 {} 中的结构体都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。
• 在es5之前没有块级作用域的的概念,只有函数作用域,现阶段可以认为 JavaScript 没有块级作用域.

全局变量和局部变量

• 局部变量:定义在函数内部的变量,只能在函数作用域内部被访问到,在外面没有定义的。
• 全局变量:从广义上来说,也是一种局部变量,定义在全局的变量,作用域范围是全局, 在整个 js 程序任意位置都能够被访问到。
• 变量退出作用域之后会销毁,全局变量关闭网页或浏览器才会销毁。
// 全局、局部、块级

// 全局变量
var dog = '小花';

// 局部变量
function sum() {
    // 局部变量
    var sex = '男';
    console.log(sex);   // 男
    console.log(dog);   // 小花
}
sum();
console.log(sex);       // sex is not defined

// 块级
{
    var str = '小撩';
    var age = 19;
}
console.log(str, age);

作用域链

• 只有函数可以制造作用域结构, 那么只要是代码,就至少有一个作用域, 即全局作用域。凡是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域。
• 将这样的所有的作用域列出来,可以有一个结构: 函数内指向函数外的链式结构。就称作作用域链。
var a = 0;
// 定义函数
function fun() {
  var a = 1;
  console.log(a);       // 1
}
// 执行函数
fun();
// 函数外部调用 a
console.log(a);         // 0

// 函数的参数也是局部变量
function fun(a) {
  a = 2;
  console.log(a);    // 2
}
// 调用函数
fun(1);
console.log(a);      // 报错 a is not defined

遮蔽效应

• 程序在遇到一个变量时,使用时作用域查找顺序,不同层次的函数内都有可能定义相同名 字的变量,一个变量在使用时,会优先从自己所在层作用域查找变量,如果当前层没有变 量定义会按照顺序从本层往外依次查找,直到找到第一个变量定义。整个过程中会发生内 层变量遮蔽外层变量的效果,叫做“遮蔽效应”。

// 函数也有自己的作用域
var a = 1;
function outer() {
  var a = 2;
  function inner() {
    var a = 3;
    console.log(a);     // 3
  }
  // 函数内部调用子函数才能成功
  inner();
  console.log(a);       // 2
}
// 调用函数
outer();
// inner();   // 不能调用 inner() 子函数

console.log(a);       // 1

参数也是局部变量

• 函数的参数本质是一个变量,也有自己的作用域,函数的参数也是属于函数自己内部的局 部变量,只能在函数内部被使用,在函数外面没有定义。
不写 var 关键字的影响 • 在函数内部想要定义新的变量,如果不加关键字 var,相当于定义的全局变量。如果全局也 有相同的标识符,会被函数内部的变量影响,局部变量污染全局变量。

• 注意:每次定义变量时都必须写 var 关键字,否则就会定义在全局,可能污染全局。

1.10 不写 var 关键字的影响

• 在函数内部想要定义新的变量,如果不加关键字 var,相当于定义的全局变量。如果全局也 有相同的标识符,会被函数内部的变量影响,局部变量污染全局变量。

• 注意:每次定义变量时都必须写 var 关键字,否则就会定义在全局,可能污染全局。

// 全局作用域
var a = 1;
console.log(a);         // 1
// 创建函数
function outer() {
  a = 2;
  // 内部函数
  function inner() {
    var a = 3;
    console.log(a);     // 3
  }
  inner();
  console.log(a);       // 2
}
// 调用
outer();
console.log(a);         // 2 (污染全局造成的)

1.11 函数的作用域

• 函数也有自己的作用域,定义在哪个作用域内部,只能在这个作用域范围内被访问,出了 作用域不能被访问的。
• 函数定义在另一个函数内部,如果外部函数没有执行时,相当于内部代码没写。

1.12 预解析

• JavaScript 代码的执行是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器执行JavaScript 代码的时候,分为两个过程:预解析过程和代码执行过程。

• 预解析过程:

1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
2. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
3. 先提升 var,再提升 function。

• JavaScript 的执行过程:在预解析之后,根据新的代码顺序,从上往下按照既定规律执行 js 代码。

变量声明提升

• 在预解析过程中,所有定义的变量,都会将声明的过程提升到所在的作用域最上面,在 将来的代码执行过程中,按照先后顺序会先执行被提升的声明变量过程。
• 提升过程中,只提升声明过程,不提升变量赋值,相当于变量定义未赋值,变量内存储 undefined 值。
• 因此,在 js 中会出现一种现象,在前面调用后定义的变量,不会报错,只会使用 undefined 值。

函数声明提升

• 在预解析过程中,所有定义的函数,都会将声明的过程提升到所在的作用域最上面,在 将来的代码执行过程中,按照先后顺序会先执行被提升的函数声明过程。
• 在预解析之后的代码执行过程中,函数定义过程已经在最开始就会执行,一旦函数定义 成功,后续就可以直接调用函数。
• 因此,在 js 中会出现一种现象,在前面调用后定义的函数,不会报错,而且能正常执 行函数内部的代码。
// 模拟提升
/* 
    var a;  //相当于存了一个undefined的值
    var fun; 
*/

// 调用一个变量
console.log(a);     // undefined
console.log(fun);   // ƒ fun() {console.log(2);}

var a = 1;
function fun() {
  console.log(2);
}

// 后定义变量
var a = 1;
a = 1;
var fun = "haha";
fun = "haha";
// 先调用函数
fun();
// 定义函数
function fun() {
  console.log(2);
}
// 调用
fun();

函数表达式的提升

• 在预解析过程中,函数表达式进行的是变量声明提升,而不是函数声明提升。提升后变 量内部存的是一个 undefined。在前面进行函数方法调用,数据类型会提示错误。

• 建议:定义函数时,最好使用 function 关键字定义方式,这样函数声明提升可以永远生 效。

console.log(foo);       // undefined
foo();

// 函数表达式进行的是变量声明提升,而不是函数声明提升
var foo = function () {
  console.log(3);
};

提升顺序

• 预解析过程中,先提升 var 变量声明,再提升 function 函数声明。
• 假设出现变量名和函数名相同,那么后提升的函数名标识符会覆盖先提升的变量名,那 么在后续代码中出现调用标识符时,内部是函数的定义过程,而不是 undefined。
• 如果调用标识符的过程在源代码函数和变量定义后面,相当于函数名覆盖了一次变量名, 结果在执行到变量赋值时,又被新值覆盖了函数的值,那么在后面再次调用标识符,用 的就是变量存的新值。

• 建议:不要书写相同的标识符给变量名或函数名,避免出现覆盖。

1.13 IIFE 自调用函数(立即执行函数)

• IIFE:immediately-invoked function expression,叫做即时调用的函数表达式,也叫做自调用函数,表示函数在定义时就立即调用。
• 函数调用方式:函数名或函数表达式的变量名后面加 () 运算符。
• 函数名定义的形式不能实现立即执行自调用,函数使用函数表达式形式可以实现立即执 行,原因是因为函数表达式定义过程中,将一个函数矮化成了一个表达式,后面加()运 算符就可以立即执行。

• 启发:如果想实现 IIFE,可以想办法将函数矮化成表达式。

• 函数矮化成表达式,就可以实现自调用。
• 函数矮化成表达式的方法,可以让函数参与一些运算,也就是说给函数前面加一些运算 符。
数学运算符:+ - () 逻辑运算符:!非运算
• IIFE 结构可以关住函数的作用域,在结构外面是不能调用函数的。
• IIFE 最常用的是 () 运算符,而且函数可以不写函数名,使用匿名函数。
// 关键字定义的方式,不能立即执行
// function fun() {
//   console.log(1);
// }();

// 函数表达式方式,可以在定义时被立即执行
var foo = function fun() {
  console.log(2);
}();

// 通过在函数前面添加操作符,可以将函数矮化成表达式
+ function fun() {
  console.log(1);
}();

- function fun() {
  console.log(1);
}();

(function fun() {
  console.log(1);
})();

!function fun() {
  console.log(1);
}();

// *function fun() {
//   console.log(1);
// }();
// IIFE 关住了函数的作用域,在外面是调用不了函数的
// fun();

// 常用的 IIFE 结构
(function (a) {
  console.log(a);
})(4);
// console.log(a);

二、闭包

function outer(){
    var a = 333;
    function inner(){
        console.log(a);
    }
    return inner;
}

var inn = outer();
inn(); //弹出333

inner()这个函数不能在outer外面调用,因为outer外面没有inner的定义:

function outer(){
    var a = 888;
    function inner(){
        console.log(a);
    }
}
//在全局调用inner但是全局没有inner的定义,所以报错
inner();

但是我们现在就想在全局作用域下,运行outer内部的inner,此时我们必须想一些奇奇怪怪的方法。 有一个简单可行的办法,就是让outer自己return掉inner:

function outer(){
    var a = 333;
    function inner(){
        console.log(a);
    }
    return inner;     //outer返回了inner的引用
}

var inn = outer();    //inn就是inner函数了
inn();                //执行inn,全局作用域下没有a的定义
                      //但是函数闭包,能够把定义函数的时候的作用域一起记忆住
                      //能够输出333

这就说明了,inner函数能够持久保存自己定义时的所处环境,并且即使自己在其他的环境被调用的时候,依然可以访问自己定义时所处环境的值。 一个函数可以把它自己内部的语句,和自己声明时所处的作用域一起封装成了一个密闭环境,我们称为“闭包”

每个函数都是闭包,每个函数天生都能够记忆自己定义时所处的作用域环境。但是,我们必须将这个函数,挪到别的作用域,才能更好的观察闭包。这样才能实验它有没有把作用域给“记住”。

我们发现,把一个函数从它定义的那个作用域,挪走,运行。嘿,这个函数居然能够记忆住定义时的那个作用域。不管函数走到哪里,定义时的作用域就带到了哪里。这就是闭包。

闭包在工作中是一个用来防止产生隐患的事情,而不是加以利用的性质。

闭包的性质

每次重新引用函数的时候,闭包是全新的。

function outer(){
    var count = 0;
    function inner(){
        count++;
        console.log(count);
    }
    return inner;
}

var inn1 = outer();
var inn2 = outer();

inn1();   //1
inn1();   //2
inn1();   //3
inn1();   //4

inn2();   //1
inn2();   //2

inn1();   //5

无论它在何处被调用,它总是能访问它定义时所处作用域中的全部变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值