# 闭包,new,箭头函数
# 闭包
# 什么是闭包
闭包意味着内部函数始终可以访问外部函数的变量和参数,即使外部函数已经返回。
我们可以在 JavaScript 中创建嵌套函数。内部函数可以访问外部函数的变量和参数(但是,不能访问外部函数的参数对象 arguments)。看看下面这个例子:
function OuterFunction() {
var outerVariable = 1
function InnerFunction() {
console.log(outerVariable)
}
InnerFunction() // 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
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
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()
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
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)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 使用场景
- 函数防抖
比如要缩放窗口 触发 onresize 事件 需要在这时候做一件事情,但是我们希望拖动的时候只触发一次,比如
window.onresize = function() {
console.log('onresize') //只想触发一次
}
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')
}
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
- 使用闭包设计单例模式
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 设置私有变量
内部属性 在 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
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
- 拿到正确的值(老掉牙的问题了 😝)
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i) //10个10
}, 1000)
}
2
3
4
5
遇到这种问题 如何用解决呢
for (var i = 0; i < 10; i++) {
;(j => {
setTimeout(function() {
console.log(j) //1-10
}, 1000)
})(i)
}
2
3
4
5
6
7
原理是 声明了 10 个自执行函数,保存当时的值到内部
下面代码输出的结果?
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i)
}, 0)
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# new
分析一下 new 的整个过程:
- 创建一个空对象
- 继承构造函数的原型
- this 指向 obj,并调用构造函数(为这个新对象添加属性)
- 如果构造函数没有 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
}
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)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
参考资料
在线客服