JS函数详解

一、函数介绍

函数就是将实现特定功能的代码封装起来,当我们需要实现特定功能时,直接调用函数实现即可,不需要每次都写一堆代码,实现代码的复用。

函数的作用:

  • 1、实现功能的封装,提高代码复用率
  • 2、用于构建对象的模板(构造函数)

函数实际上是对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法,由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

二、函数的声明

2.1、语法

function 函数名(形参列表){
    //函数体
}

我们也可以写成函数表达式的形式:

const 函数名 = function (形参列表) {
  //函数体
};

2.2、函数调用

// 函数声明
function sum(a, b) {
  return a + b;
}
// 函数声明之后,需要调用才能执行
// 函数调用:函数名(实参)
console.log(sum(1, 2)); //3

写成函数表达式的形式(含有运算符的式子叫表达式):

// 函数声明
const sum = function (a, b) {
  return a + b;
};
// 函数调用:函数名(实参)
console.log(sum(1, 2)); //3

2.3、函数声明提升

function关键词声明函数,存在声明提升。

sum(1, 2); //在此处调用函数不会报错,因为存在函数声明提升
function sum(a, b) {
  return a + b;
}

函数表达式的形式,没有声明提升

sum(1, 2) //报错sum is not defined
// 函数声明
const sum = function (a, b) {
  return a + b;
};

函数表达式就是把一个匿名函数赋值给一个变量,由于const、let声明变量没有变量提升,var有变量提升,但是会为变量赋值为undifined,所以不管哪一种方式定义函数表达式,函数都没有声明提升
var、let和const声明变量的区别

三、函数内部属性(默认参数)

函数内部属性(默认参数)只能在函数内部才能访问

3.1、arguments

arguments是一个类数组对象,包含着传入函数中的所有参数。arguments支持for…of…迭代,但不支持forEach循环。arguments主要用途是保存函数参数。

function foo() {
  console.log(arguments); // Arguments [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  console.log(arguments[1]); // 2
}
// 当传递的实参个数超过形参的个数的时候不会报错,所有的实参都会保存在arguments里
foo(1, 2, 3, 4);

注意:arguments 中存的是实参,而不会存形参

function foo(a, b = 2, c = 3) {
  console.log(arguments); // Arguments [1, callee: (...), Symbol(Symbol.iterator): ƒ]
  console.log(b); //2
  console.log(c); //3
}
//只传了一个实参,那么arguments中就只有一个值
foo(1);

arguments的callee 属性

argument.callee是一个指向正在执行的函数的指针,使用它来代替函数名,可以确保无论怎样调用函数都不会出问题

// 递归求n的阶乘
function factorial(n) {
  if (n == 1) {
    return 1;
  }
  return arguments.callee(n - 1) * n; //arguments.callee 相当于函数名factorial
}
console.log(factorial(10));

arguments的length 属性

arguments 对象的 length 属性返回实参个数。

function foo() {
  console.log(arguments.length); //5
}
foo(1, 2, 3, 4, 5);

四、关于this指向问题

JS的this指向问题

五、作用域

  • 全局作用域
  • 函数作用域
  • 块作用域

六、作用域链

6.1、什么是自由变量

如下代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,称为为 自由变量 。

var a = 100
function fn() {
    var b = 200
    console.log(a) // 这里的a 就是一个自由变量  // 100
    console.log(b)
}
fn()

6.2、作用域链

自由变量会顺着作用域向父作用域找,如果父级也没有找到,再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。

七、函数调用

  • 函数声明好之后并不会直接运行,需要进行调用才能运行。
  • 调用函数的方式不仅限于 () 执行。

调用函数的方式:

  • 函数名(实参列表);
  • 函数名.call(执行环境对象,实参列表);
  • 函数名.apply(执行环境对象,实参列表数组);
  • 函数名.bind(执行环境对象)(实参列表);

八、如何更改函数内部的this指向

call,apply和bind方法都可以用来更改this指向

区别

  • call、apply 在改变 this 指向的同时还会执行函数,一次性的。
    不同的是 call方法传递函数调用形参是以散列形式,而 apply 方法的形参是一个数组。在传参的情况下,call的性能要高于 apply,因为 apply 在执行时还要多一步解析数组。
  • bind 在改变 this 后是返回一个全新的绑定函数,即返回一个新的函数,不直接执行函数。并且此后 this 的指向无法再次通过 call、apply、bind 改变。

8.1、call

使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

function.call(thisArg, arg1, arg2, ...)

[thisArg] 在 func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装

[arg1, arg2, …] 参数列表

例子

let objCall = {
  name: "我是 Call",
};
function fn() {
  console.log("参数 => ", ...arguments);
  console.log("name => ", this.name);
}
fn.call(objCall, 1, 2, 3);
// 参数 =>  1 2 3
// name =>  我是Call

8.2、apply

调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数

func.apply(thisArg, [argsArray])
[thisArg] 必选的 同 call 一样
[argsArray] 可选的

例子

let objApply = {
  name: "我是 Apply",
};
function fn() {
  console.log("参数 => ", ...arguments);
  console.log("name => ", this.name);
}
fn.apply(objApply, [1, 2, 3]);
// 参数 =>  1 2 3
// name =>  我是 Apply

8.3、bind

创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

function.bind(thisArg,arg1,arg2,arg3,....)

例子

let objBind = {
  name: "我是 Bind",
};
let objApply = {
  name: "我是 Apply",
};

function fn() {
  console.log("参数 => ", ...arguments);
  console.log("name => ", this.name);
}
let bfn = fn.bind(objBind, 1, 2, 3);
//需要自己主动执行
bfn();
// 参数 =>  1 2 3
// name =>  我是 Bind

九、回调函数

回调,就是回头调用的意思。主函数的事先做完,回头再调用传进来的那个函数。

回调函数的作用:一般用在异步请求上面:当异步数据获取到,在执行回调函数,把数据传给回调函数,这个时候回调函数就可以获取到异步请求数据了

// 一个异步步请求,获取数据
fuction getList(callback) {
    uni.request({
      url: 'https://ku.qingnian8.com/dataApi/news/navlist.php',
      success: (res) => {
        callback(res)
      }
    })
}
// 定义获取异步数据的回调函数
fuction getData(res) {
    console.log(res)
}
//调用主函数,将回调函数传进,这个时候getData就可以获取异步请求数据了
getList(getData);

十、闭包

10.1、什么是闭包

闭包(closure)就是内部函数引用了外部函数中的数据(属性、函数)

10.2、闭包形成的条件

闭包的生成有三个必要条件:

1、函数嵌套函数
2、内部函数引用了外部函数中的数据(属性、函数)
3、参数和变量不会被回收

下面例子中的 closure 就是一个闭包:

function func() {
  var a = 1, b = 2;

  function closure() {
    return a + b;
  }
  return closure;
}
console.log(func()()); // 3

10.3、闭包的作用

  • 可以读取函数内部的变量
  • 就是让这些变量的值始终保持在内存中。
var nAdd
function f1() {
  var n = 999;
  nAdd = function () { 
  	n += 1 
  }
  function f2() {
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000

在这段代码中,result 实际上就是闭包 f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除

为什么会这样呢?原因就在于f1f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

10.4、使用闭包的注意点

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露,这是IE的BUG。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值