函数——IIFE、作用域、函数调用、函数应用、闭包

IIFE 立即调用的函数表达式

IIFE的作用:

1、页面加载完成后只执行一次的设置函数
2、将设置函数中的变量包裹在局部作用域中,不会泄露全局变量

先来看一下不采用IIFE时的函数声明和函数调用:

function foo(){
  var a = 10;
  console.log(a);
}
foo();

下面是IIEF形式的函数调用

(function foo(){
  var a = 10;
  console.log(a);
})();
  • 注意:如果普通函数和IIFE函数写在一起,记得使用;进行分隔

函数的声明和IIFE的区别在于,在函数声明中,我们首先看到的是function关键字,而IIEF我们首先看到的是左边的(。也就是说,使用一对()将函数的声明括起来,使得js编译器不在认为这是一个函数声明,而是一个IIFE,就是需要立即执行声明的函数。两者达到的目的是相同的,都是声明一个函数foo并且随后调用函数。

IIFE的写法

1、对返回结果不进行处理

(function(形参){
	函数体内容
})(实参);
(function(形参){
	函数体内容
}(实参));

2、返回的是一个布尔值,然后进行取反

!function(形参){
	return true
}(实参)

3、对于数字返回的是原来的结果,非数字返回NAN

+function(形参){
	return 123
}(实参)

返回的是对结果的取反

-function(形参){
	return 123
}(实参)

4、对于数字返回的是正负号相反再减1,非数字返回-1

~function(形参){
	return 123
}(实参)

5、返回的结果是undefined

void function(形参){
	函数体内容
}(实参)
IIFE的基本使用
//就像其他任何函数一样,一个立即执行函数也能返回值
// 并且可以赋值给其他变量
var sum = (function(a,b){
    return a+b
}(1,2))
console.log(sum);
经典面试题–IIFE
for (var i = 0; i < 6; i++) {
  function output() {
    console.log(i); // 为什么输出的是6,而不是0,1,2,3,4,5
    // 因为输出的 i 是全局作用域的,当循环结束后 i 的值是 6,所以输出的 i 就是6。
  }
}
output()
for (var i = 0; i < 6; i++) {
  (function (j) {
    console.log(j); //0,1,2,3,4,5
  })(i)
  // 因为 JS 中调用函数传递参数都是值传递 ,所以当立即执行函数执行时,首先会把参数 i 的值复制一份,然后再创建函数作用域来执行函数,循环5次就会创建5个作用域,所以每个输出访问的都是不同作用域的 i 的值 。
}
为什么需要IIFE?

1、弥补js(ES5)在作用域方面的缺陷:因为js(针对ES5来说)只有全局作用域和函数作用域,等到了es6才用块级作用域。
2、那么如何实现作用域隔离呢? 通常我们是将一段代码中的变量、函数等封装到一个函数中,但是,函数通常是为了实现函数复用的。
3、但是我们经常能看到只使用一次的函数,这样的函数通常也是为了隔离作用域,因此,立即执行函数就可以拿来进行隔离作用域,反正只使用一次的函数可以直接变成立即执行函数,这样既隔离了作用域,也减少了空间内存的浪费。

作用域

在ES5中的作用域分为

  • 函数作用域:函数内部声明的变量,在函数外部不能访问。
  • 全局作用域:函数之外声明的函数,会变成全局变量。函数外部声明的变量,在函数内部也能访问。

当函数嵌套,在这个时候,内部函数与外部函数的这个这个变量就组成了闭包

//全局作用域:global/window/本文件内
var v1 = 10;
v2 = 20; // 所有末定义直接赋值的变量自动声明为拥有全局作用域,在函数里面也是如此
function foo() {
  //函数作用域、局部作用域
  var a = 3;
  console.log(v1, v2); 
  console.log(this);
}
foo()
console.log(a); // a is not defined
var a = 10;
function foo() {
  // 当函数内部有变量a时,会产生局部作用域,外界全局作用域中的a不会对函数内部造成影响
  // 如果把函数内部的变量a注释掉是,函数内部的a输出的就是全局作用域中的a
 
  console.log(a); //undefined
  var a = 100;
  console.log(a); // 100

  function fn() {
    console.log(a); //undefined
    var a = 200;
    console.log(a); // 200
  }
  fn()
}
foo()

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

作用域链

什么是作用域链??

首先先来认识一下什么是自由变量

当前作用域没有定义的变量,又能给当前作用域访问得到的变量。
那么要如何得到该变量的值呢??
就要到创建这个函数的那个父级作用域寻找,如果没有,就一直向上级祖先元素寻找
这就是所谓的静态作用域,静态作用域就是指函数的作用域在函数定义时就已经确定了

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

所以作用域链就是
自由变量寻找值的过程,由内到外一层一层的往上寻找,直到找到全局作用域还是没找到的话,就宣布放弃。
这种一层一层的关系,就是作用域链。

var a = 100
function F1() {
  var b = 200
  function F2() {
    var c = 300
    console.log(a) // 自由变量,顺作用域链向父作用域找 //100
    console.log(b) // 自由变量,顺作用域链向父作用域找 //200
    console.log(c) // 本作用域的变量  //300
  }
  F2()
}
F1()
  • a变量寻找值的过程就是,现在F2中寻找,没找到,就到F1寻找,没找到,就到body中寻找,找到了。
  • F2<F1<body 作用域链
作用域链-闭包-经典题型
var a = 10
function fn() {
  var b = 20
  function bar() {
    console.log(a + b) //30
  }
  return bar
}
var x = fn(), // 执行fn() 返回的是bar
b = 200
x() //执行x,就是执行bar函数 30

函数调用

调用函数的方式不仅限于()执行,还有其他几种方式:

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

调用call方法,第一个参数就是要把b添加到哪个环境中,简单来说,this会指向那个对象

var obj = {
  name: 'zhangsan',
  sayName: function () {
    console.log(this.name);
  }
}
var b = obj.sayName;
b.call(obj); // zhangsan

在使用call调用的时候,还可以传递多个参数

var obj = {
  name: 'zhangsan',
  sayName: function (a,b) {
    console.log(this.name);
    console.log(a,b); // 1,2
  }
}
var b = obj.sayName;
b.call(obj,1,2); // zhangsan
2、apply(执行环境对象,实参列表数组)

apply方法和call方法有些相似,它也可以改变this的指向

var obj = {
  name: 'zhangsan',
  sayName: function () {
    console.log(this.name);
  }
}
var b = obj.sayName;
b.apply(obj); // zhangsan

同样apply也可以有多个参数,但是不同的是,第二个参数必须是一个数组,如下:

var obj = {
  name: 'zhangsan',
  sayName: function (a,b) {
    console.log(this.name);
    console.log(a,b); // 100,200
  }
}
var b = obj.sayName;
b.apply(obj,[100,200]); // zhangsan

注意:如果call和apply的第一个参数是null,那么this在node环境下指向的是global对象,在HTML中指向的是window对象

var obj = {
  name: 'briup',
  sayName: function () {
    console.log(this); // global
  }
}
var b = obj.sayName;
b.apply(null); 
3、bind(执行环境对象)(实参列表)
var obj = {
    name:'zhangsan',
    sayName:function(){
        console.log(this.name);
    }
}
var b = obj.sayName
b.bind(obj)
// 代码没有被打印,这就是bind和call、apply方法的不同
// 实际上bind方法返回的是一个修改过后的函数
// 新建一个变量来接收bind修改过后的函数
var c = b.bind(obj)
console.log(c);//发现c是一个[Function:bound sayName]函数
// 执行c
c()//zhangsan

同样bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。

var obj = {
  name: 'zhangsan',
  sayName: function (a,b,c) {
    console.log(this.name);
    console.log(a,b,c); // 1,2,3
  }
}
var b = obj.sayName;
b.bind(obj); // 代码没有被打印,这就是bind和call、apply方法的不同,实际上bind方法返回的是一个修改过后的函数。
// 新建一个变量c来接收bind修改后的函数
var c = b.bind(obj, 1, 2);
console.log(c); // 发现c是一个[Function: bound sayName]函数
// 执行c
c(3); // zhangsan

总结:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加

区别:

  • apply和call基本类似,他们的区别只是传入的参数不同。
  • apply传入的参数是包含多个参数的数组
  • call传入的参数是若干个参数列表
  • bind方法会创建一个新的函数,当被调用的时候,将其this关键字设置为提供的值,我们必须手动去调用

函数调用

函数本质上是一种对象,可以将其当做普通对象来使用。

回调函数

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

在这里插入//定义主函数,回调函数作为参数
function A(callback) {
  callback();
  console.log('我是主函数');
}
//定义主函数,回调函数作为参数
function A(callback) {
  callback();
  console.log('我是主函数');
}
//定义回调函数
function B() {
  // 模仿延时操作
  setTimeout(() => {
    console.log('我是回调函数');
  }, 3000);
}
//调用主函数,将函数B传进去
A(B);

回调函数的作用:回调函数一般都用在耗时操作上面:因为主函数不用等待回调函数执行完,可以接着执行自己的代码。比如ajax请求,比如处理文件等。

作为返回值

在上面的作用域链的案例中,就使用到了函数作为返回值的用法。

var a = 10
function fn() {
  var b = 20
  function bar() {
    console.log(a + b) //30
  }
  return bar
}
var x = fn(), // 执行fn() 返回的是bar
o
x() //执行x,就是执行bar函数

闭包

简单来讲,闭包就是有权访问另一个函数作用域的变量的函数

产生闭包的三个必要条件
  • 函数嵌套函数
  • 内部函数引用了外部函数中的数据(变量、函数)
  • 参数和变量不会被回收

这样就形成了一个不会销毁的函数空间

例子中的closure就是一个闭包

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

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

闭包的作用域包含着它自己的作用域,以及包含它的函数的作用域和全局作用域
在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途
  • 可以读取函数内部的变量
  • 可以让变量的值始终保存在内容中
function f1() {
  var n = 999;
  nAdd = function () { n += 1 }
  function f2() {
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值