# 闭包,new,箭头函数

# 闭包

# 什么是闭包

闭包意味着内部函数始终可以访问外部函数的变量和参数,即使外部函数已经返回。

我们可以在 JavaScript 中创建嵌套函数。内部函数可以访问外部函数的变量和参数(但是,不能访问外部函数的参数对象 arguments)。看看下面这个例子:

function OuterFunction() {
  var outerVariable = 1
  function InnerFunction() {
    console.log(outerVariable)
  }
  InnerFunction() // 1
}
1
2
3
4
5
6
7

InnerFunction() 可以访问 outerVariable

  • 闭包特性 1:可以访问外部函数作用域中的变量
function OuterFunction() {
  var outerVariable = 100
  function InnerFunction() {
    alert(outerVariable)
  }
  return InnerFunction
}
var innerFunc = OuterFunction()
innerFunc() // 100
1
2
3
4
5
6
7
8
9

在上面的例子, 当调用 OuterFunction() 时,从 OuterFunction 返回 InnerFunction 函数。变量 innerFunc 只引用 InnerFunction(),而不是 OuterFunction()。现在,当你调用 innerFunc() 时,它仍然可以访问在 OuterFunction() 中声明的 outerVariable。这就是闭包。

  • 闭包特性 2:外部变量可以在多次调用之间保持其状态
function Counter() {
  var counter = 0
  function IncreaseCounter() {
    return (counter += 1)
  }
  return IncreaseCounter
}
var counter = Counter()
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3
console.log(counter()) // 4
1
2
3
4
5
6
7
8
9
10
11
12

请记住,内部函数不保留外部变量的单独副本,但它引用外部变量,这意味着如果您使用内部函数改变它,外部变量的值将被改变。在上面的例子中,外部函数 Counter 返回内部函数 IncreaseCounter。增加 IncreaseCounter 将外部变量 counter 加 1。因此多次调用 IncreaseCounter 函数将使 counter 多次加 1。

  • 闭包特性 3
function Counter() {
  var counter = 0
  setTimeout(function() {
    var innerCounter = 0
    counter += 1
    console.log('counter = ' + counter)
    setTimeout(function() {
      counter += 1
      innerCounter += 1
      console.log('counter = ', counter)
      console.log('innerCounter = ', innerCounter)
    }, 500)
  }, 1000)
}
Counter()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

闭包在多层内部函数中是有效的。

什么时候使用闭包

  • 隐藏内部实现细节
var counter = (function() {
  var privateCounter = 0
  function changeBy(val) {
    privateCounter += val
  }
  return {
    increment: function() {
      changeBy(1)
    },
    decrement: function() {
      changeBy(-1)
    },
    value: function() {
      return privateCounter
    }
  }
})()
alert(counter.value()) // 0
counter.increment()
counter.increment()
alert(counter.value()) // 2
counter.decrement()
alert(counter.value()) // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

在上面的示例中,increment()、decrement() 和 value()成为公共函数,因为它们包含在返回对象中,而 changeBy() 函数成为私有函数,因为它没有返回,只被 increment() 和 decrement() 内部使用。

  • 函数防抖
window.onresize = function() {
  debounce(fn, 1000)
}
var fn = function() {
  console.log('fn')
}
function debounce(fn, time) {
  let timer = null
  return function() {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments)
    }, time)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 使用场景

  1. 函数防抖

比如要缩放窗口 触发 onresize 事件 需要在这时候做一件事情,但是我们希望拖动的时候只触发一次,比如

window.onresize = function() {
  console.log('onresize') //只想触发一次
}
1
2
3

一般方法 vs 闭包

// 不使用闭包
window.onresize = function() {
  debounce(fn, 1000)
}
var fn = function() {
  console.log('fn')
}
var time = ''
function debounce(fn, timeLong) {
  if (time) {
    clearTimeout(time)
    time = ''
  }

  time = setTimeout(function() {
    fn()
  }, timeLong)
}

// 使用闭包
window.onresize = debounce(fn, 500)

function debounce(fn) {
  var timer = null
  return function() {
    if (timer) {
      //timer第一次执行后会保存在内存里 永远都是执行器 直到最后被触发
      clearTimeout(timer)
      timer = null
    }
    timer = setTimeout(function() {
      fn()
    }, 1000)
  }
}
var fn = function() {
  console.log('fn')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  1. 使用闭包设计单例模式
class CreateUser {
  constructor(name) {
    this.name = name
    this.getName()
  }
  getName() {
    return this.name
  }
}
// 代理实现单例模式
var ProxyMode = (function() {
  var instance = null
  return function(name) {
    if (!instance) {
      instance = new CreateUser(name)
    }
    return instance
  }
})()
// 测试单体模式的实例
var a = ProxyMode('aaa')
var b = ProxyMode('bbb')
// 因为单体模式是只实例化一次,所以下面的实例是相等的
console.log(a === b) //true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  1. 设置私有变量

内部属性 在 java 里使用 private 就可以,但是 js 还没有这个东东

let _width = Symbol()

class Private {
  constructor(s) {
    this[_width] = s
  }

  foo() {
    console.log(this[_width])
  }
}

var p = new Private('50')
p.foo()
console.log(p[_width]) //可以拿到

//使用闭包设置私有变量
let sque = (function() {
  let _width = Symbol()

  class Squery {
    constructor(s) {
      this[_width] = s
    }

    foo() {
      console.log(this[_width])
    }
  }
  return Squery
})()

let ss = new sque(20)
ss.foo()
console.log(ss[_width]) // Uncaught ReferenceError: _width is not defined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  1. 拿到正确的值(老掉牙的问题了 😝)
for (var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i) //10个10
  }, 1000)
}
1
2
3
4
5

遇到这种问题 如何用解决呢

for (var i = 0; i < 10; i++) {
  ;(j => {
    setTimeout(function() {
      console.log(j) //1-10
    }, 1000)
  })(i)
}
1
2
3
4
5
6
7

原理是 声明了 10 个自执行函数,保存当时的值到内部

下面代码输出的结果?

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i)
  }, 0)
}
1
2
3
4
5

答案:3,3,3

解决方法

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i)
  }, 0)
}
// 0 1 2

for (var i = 0; i < 3; i++) {
  ;(function(i) {
    setTimeout(
      function() {
        console.log(i)
      },
      0,
      i
    )
  })(i)
}
// 0 1 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# new

分析一下 new 的整个过程:

  1. 创建一个空对象
  2. 继承构造函数的原型
  3. this 指向 obj,并调用构造函数(为这个新对象添加属性)
  4. 如果构造函数没有 return 的时候,或者 return 的不是 Object(string,number,布尔类型等),相当于默认 return this。如果构造函数 return 一个 object,则 bar = object。

简单实现一下 new:

function myNew(fn, ...args) {
  // 第一步:创建一个空对象
  const obj = {}
  // 第二步:继承构造函数的原型
  obj.__proto__ = fn.prototype
  // 第三步:this指向obj,并调用构造函数
  fn.apply(obj, args)
  // 第四步:返回对象
  return obj
}
1
2
3
4
5
6
7
8
9
10

# 箭头函数和普通函数的区别

箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?

  • 普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(this 取决于调用者,和是否独立运行)

  • 箭头函数使用被称为 “胖箭头” 的操作符 => 定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this,且箭头函数的 this 绑定无法被修改(new 也不行)。

    • 箭头函数常用于回调函数中,包括事件处理器或定时器
    • 箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
    • 没有原型、没有 this、没有 super,没有 arguments,没有 new.target
    • 不能通过 new 关键字调用
      • 一个函数内部有两个方法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上
      • 当直接调用时,执行 [[Call]] 方法,直接执行函数体
      • 箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。
function foo() {
  return a => {
    console.log(this.a)
  }
}

var obj1 = {
  a: 2
}

var obj2 = {
  a: 3
}

var bar = foo.call(obj1)
bar.call(obj2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

参考资料

在线客服