关注前端小讴,浏览更多原创技术文章
相干代码 →
10.14 闭包
-
闭包 是指 援用了另一个函数作用域中变量 的函数,通常在嵌套函数中实现(如果一个函数拜访了它的内部变量,那么它就是一个闭包)
- 闭包中函数的作用域链中,有对外部函数变量的援用
- 为了 在全局作用域能够拜访到闭包函数 ,通常在内部函数内 返回 外部闭包函数
- 因而内部函数 被闭包援用的流动对象 ,并 不能 在内部函数执行后被销毁,仍保留在内存中
- 若要 开释内存 ,需 解除闭包函数对外部函数流动对象的援用
function arraySort(key, sort) {return function (a, b) {
// 外部匿名函数,援用内部函数变量 key 和 sort,造成闭包
let va = a[key]
let vb = b[key]
if (sort === 'asc' || sort === undefined || sort === '') {
// 正序:va > vb
if (va > vb) return 1
else if (va < vb) return -1
else return 0
} else if (sort === 'desc') {
// 倒序:va < vb
if (va < vb) return 1
else if (va > vb) return -1
else return 0
}
}
}
let compareNames = arraySort('name') // 执行函数并返回匿名函数,匿名函数的作用域链仍对 arraySort 的流动对象 key 和 sort 有援用,因而不会被销毁
let result = compareNames({name: 'Nicholas'}, {name: 'Matt'}) // 执行匿名函数
compareNames = null // 解除匿名函数对 arraySort 流动对象的援用,开释内存
10.14.1 this 对象
-
在 闭包内 应用
this
会让代码更简单,若外部函数没有应用箭头函数,this
绑定给 执行函数的上下文:- 全局函数中调用,非严格模式下
this
指向window
,严格模式等于undefined
- 作为某个对象的办法调用,
this
指向该对象 - 匿名函数 不会绑定到某个对象,其
this
指向 调用该匿名函数的对象
- 全局函数中调用,非严格模式下
global.identity = 'The Window' // vscode 是 node 运行环境,无奈辨认全局对象 window,测试时将 window 改为 global
let object = {
identity: 'My Object',
getIdentityFunc() {return function () {return this.identity}
},
}
console.log(object.getIdentityFunc()) // ƒ () { return this.identity},返回匿名函数
console.log(object.getIdentityFunc()()) // 'The Window',立刻调用匿名函数返回 this.identity,this 指向全局对象
- 函数被调用时主动创立变量
this
和arguments
,外部函数不能间接拜访内部函数的这两个变量 ,若想拜访需 将其援用先保留到闭包能拜访的另一个变量中
let object2 = {
identity: 'My Object',
getIdentityFunc() {
let that = this // 内部函数的变量 this 保留在 that 中
return function () {return that.identity // 闭包(匿名函数)中援用 that,that 指向 getIdentityFunc()上下文的 this(而非闭包内的 this)}
},
}
console.log(object2.getIdentityFunc()()) // 'My Object',立刻调用匿名函数返回 that.identity,that 指向闭包内部函数 getIdentityFunc 的 this
- 一些非凡状况下的
this
值
let object3 = {
identity: 'My Object',
getIdentity() {return this.identity},
}
console.log(object3.getIdentity()) // 'My Object'
console.log(object3.getIdentity) // [Function: getIdentity],函数 getIdentity()
console.log((object3.getIdentity = object3.getIdentity)) // [Function: getIdentity],函数 getIdentity()赋值给 object3.getIdentity
console.log((object3.getIdentity = object3.getIdentity)()) // 'The Window',赋值后在全局立刻调用匿名函数,this 指向全局对象
console.log((object3.funcA = object3.getIdentity)()) // 'The Window',函数 getIdentity()赋值给对象其余属性,后果雷同
object3.funcB = object3.getIdentity
console.log(object3.funcB()) // 'My Object',赋值后在 object3 调用,this 指向 object3
10.14.2 内存透露
- 因为应用了不同的垃圾回收机制,闭包在 IE9 之前的 IE 浏览器会导致问题:一旦 HTML 元素保留在某个闭包的作用域中,其不会被销毁
function assignHandler() {let element = document.getElementById('someElement')
element.onclick = () => {console.log(element.id) // 援用内部函数的流动对象 element,匿名函数始终存在因而 element 不会被销毁
}
}
function assignHandler2() {let element = document.getElementById('someElement')
let id = element.id // 保留 element.id 的变量 id
element.onclick = () => {console.log(id) // 不间接援用 element,改为援用改为援用保留着 element.id 的变量 id
}
element = null // 解除对 element 对象的援用,开释闭包内存
}
10.15 立刻调用的函数表达式
- 立刻调用的匿名函数 又称 立刻调用的函数表达式(IIFE),其相似于函数申明,被 蕴含在括号中
;(function () {// 块级作用域})()
- ES5 尚未反对块级作用域,能够应用 IIFE 模仿
;(function () {for (var i = 0; i < 3; i++) {console.log(i) // 0、1、2
}
})()
console.log(i) // ReferenceError: i is not defined,i 在函数体作用域(模仿块级作用域)内
- ES6 反对块级作用域,毋庸 IIFE 即可实现同样的性能
{
let i = 0
for (i = 0; i < 3; i++) {console.log(i) // 0、1、2
}
}
console.log(i) // ReferenceError: i is not defined
for (let i = 0; i < 3; i++) {console.log(i) // 0、1、2
}
console.log(i) // ReferenceError: i is not defined
- 执行 单击处理程序 时,迭代变量 的值是 循环完结时的最终值 ,能够用 IIFE 或块级作用域 锁定每次单击要显示的值
let divs = document.querySelectorAll('div')
for (var i = 0; i < divs.length; ++i) {divs[i].addEventListener(
'click',
// 谬误的写法:间接打印(单击处理程序迭代变量的值是循环完结时的最终值)// function(){// console.log(i);
// }
// 正确的写法:立刻执行的函数表达式,锁定每次要显示的值
(function (_i) {return function () {console.log(_i)
}
})(i) // 参数传入每次要显示的值
)
}
for (let i = 0; i < divs.length; ++i) {
// 用 let 关键字,在循环外部为每个循环创立独立的变量
divs[i].addEventListener('click', function () {console.log(i)
})
}
- 同理,执行 超时逻辑 时,迭代变量 的值是 导致循环退出的值 ,同样可用 IIFE 或块级作用域 锁定每次要迭代的值
for (var i = 0; i < 5; i++) {
// 超时逻辑在退出循环后执行,迭代变量保留的是导致循环退出的值 5
setTimeout(() => {console.log(i) // 5、5、5、5、5
}, 0)
}
for (var i = 0; i < 5; i++) {
// 用立刻调用的函数表达式,传入每次循环的以后索引,锁定每次超时逻辑应该显示的索引值
;(function (_i) {setTimeout(() => {console.log(_i) // 0、1、2、3、4
}, 0)
})(i)
}
for (let i = 0; i < 5; i++) {
// 应用 let 申明:为每个迭代循环申明新的迭代变量
setTimeout(() => {console.log(i) // 0、1、2、3、4
}, 0)
}
10.16 公有变量
- 任何定义在 函数 或块中 的变量,都能够认为是 公有 的(函数或块的内部无法访问其中的变量)
- 公有变量包含 函数参数 、 局部变量 和函数外部定义的其余函数
function add(num1, num2) {
// 3 个公有变量:参数 num1、参数 num2、局部变量 sum
let sum = num1 + num2
return sum
}
- 特权办法 是可能拜访函数的公有变量(及公有函数)的公共办法,可在 构造函数 中实现
function MyObject() {
let privateVariable = 10
function privateFunction() {console.log('privateFunction')
return false
}
// 特权办法(闭包):拜访公有变量 privateVariable 和公有办法 privateFunction()
this.publicMethod = function () {console.log('privateVariable', privateVariable++)
return privateFunction()}
}
let obj = new MyObject()
obj.publicMethod()
/*
privateVariable 10
privateFunction
*/
- 同 构造函数的毛病 ,在构造函数中实现公有变量的问题是: 每个实例都从新创立办法(公有办法 & 特权办法),机制雷同的 Function 对象被屡次实例化
function Person(name) {/* 公有变量 name 无奈被间接拜访到,只能通过 getName()和 setName()特权办法读写 */
this.getName = function () {return name}
this.setName = function (_name) {name = _name}
}
let person = new Person('Nicholas') // 每创立一个实例都创立一遍办法(公有办法 & 特权办法)console.log(person.getName()) // 'Nicholas'
person.setName('Greg')
console.log(person.getName()) // 'Greg'
10.16.1 动态公有变量
-
应用 匿名函数表达式 创立 公有作用域,实现特权办法:
-
定义 公有变量 和公有办法
- 公有变量作为 动态公有变量 ,被 共享 ,但 不存在 于每个实例中
-
定义 构造函数
- 应用 函数表达式 定义构造函数(函数申明会创立外部函数)
- 不应用关键字 定义构造函数,使其创立在 全局作用域 中
-
定义 私有办法(特权办法)
- 定义在构造函数的 原型 上
-
;(function () {
/* 匿名函数表达式,创立公有作用域 */
// 公有变量和公有办法,被暗藏
let privateVariable = 10
function privateFunction() {return false}
// 构造函数:应用函数表达式 & 不应用关键字(创立在全局作用域)MyObject = function () {}
// 私有办法 / 特权办法(闭包):定义在构造函数的原型上
MyObject.prototype.publicMethod = function () {console.log('privateVariable', privateVariable++)
return privateFunction()}
})()
- 该形式下,公有变量和公有办法由实例 共享 , 特权办法 定义在原型上,也由实例 共享
- 创立实例 不会从新创立办法 ,但 调用特权办法并批改动态公有变量 会影响所有实例
;(function () {
// 公有变量 name,被暗藏
let name = ''
// 构造函数,创立在全局作用域中
Person = function (_name) {name = _name}
// 特权办法,定义在构造函数原型上
Person.prototype.getName = function () {return name}
Person.prototype.setName = function (_name) {name = _name}
})()
let person1 = new Person('Nicholas')
console.log(person1.getName()) // 'Nicholas'
person1.setName('Matt')
console.log(person1.getName()) // 'Matt'
let person2 = new Person('Michael')
console.log(person2.getName()) // 'Michael',调用特权办法并批改动态公有变量
console.log(person1.getName()) // 'Michael',影响所有实例
10.16.2 模块模式
-
在 单例对象 根底上加以扩大,通过 作用域链 关联公有变量和特权办法:
- 将单例对象的 对象字面量 扩大为 立刻调用的函数表达式
- 在匿名函数外部,定义公有变量和公有办法
- 在匿名函数外部,返回 只蕴含能够公开拜访属性和办法的 对象字面量
let singleton = (function () {
/* 立刻调用的函数表达式,创立公有作用域 */
// 公有变量和公有办法,被暗藏
let privateVariable = 10
function privateFunction() {return false}
// 返回只蕴含能够公开拜访属性和办法的对象字面量
return {
publicProperty: true,
publicMethod() {console.log(++privateVariable)
return privateFunction
},
}
})()
console.log(singleton) // {publicProperty: true, publicMethod: [Function: publicMethod] }
singleton.publicMethod() // 11
- 实质 上,该模式用对象字面量定义了 单例对象的公共接口
function BaseComponent() {} // BaseComponent 组件
let application = (function () {let components = new Array() // 创立公有数组 components
components.push(new BaseComponent()) // 初始化,将 BaseComponent 组件的新实例增加到数组中
/* 公共接口 */
return {// getComponentCount()特权办法:返回注册组件数量
getComponentCount() {return components.length},
// registerComponent()特权办法:注册组件
registerComponent(component) {if (typeof component === 'object') {components.push(component)
}
},
// getRegistedComponents()特权办法:查看已注册的组件
getRegistedComponents() {return components},
}
})()
console.log(application.getComponentCount()) // 1
console.log(application.getRegistedComponents()) // [BaseComponent {} ],已注册组件 BaseComponent
function APPComponent() {} // APPComponent 组件
application.registerComponent(new APPComponent()) // 注册组件 APPComponent
console.log(application.getComponentCount()) // 2
console.log(application.getRegistedComponents()) // [BaseComponent {}, APPComponent {}],已注册组件 BaseComponent 和 APPComponent
10.16.3 模块加强模式
-
利用 模块模式 ,在 返回对象前 进行加强,适宜 单例对象为某个特定类型的实例,但必须给它增加额定属性或办法 的场景:
- 在匿名函数外部,定义公有变量和公有办法
- 在匿名函数外部,创立某(特定)类型的实例
- 给实例对象增加共有属性和办法(加强)
- 返回实例对象
function CustomType() {} // 特定类型
let singleton2 = (function () {
// 公有变量和公有办法,被暗藏
let privateVariable = 10
function privateFunction() {return false}
// 创立特定类型的实例
let object = new CustomType()
// 增加私有属性和办法
object.publicProperty = true
object.publicMethod = function () {console.log(++privateVariable)
return privateFunction
}
// 返回实例
return object
})()
console.log(singleton2) // CustomType {publicProperty: true, publicMethod: [Function: publicMethod] }
singleton2.publicMethod() // 11
- 以 模块模式 的单例对象公共接口 为例,若
application
必须是BaseComponent
组件的实例,能够应用模块加强模式来创立:
let application2 = (function () {let components = new Array() // 创立公有数组 components
components.push(new BaseComponent()) // 初始化,将 BaseComponent 组件的新实例增加到数组中
let app = new BaseComponent() // 创立局部变量保留实例
/* 公共接口 */
// getComponentCount()特权办法:返回注册组件数量
app.getComponentCount = function () {return components.length}
// registerComponent()特权办法:注册组件
app.registerComponent = function (component) {if (typeof component === 'object') {components.push(component)
}
}
// getRegistedComponents()特权办法:查看已注册的组件
app.getRegistedComponents = function () {return components}
return app // 返回实例
})()
console.log(application2) // BaseComponent {getComponentCount: [Function (anonymous)], registerComponent: [Function (anonymous)], getRegistedComponents: [Function (anonymous)] }
console.log(application2.getComponentCount()) // 1
console.log(application2.getRegistedComponents()) // [BaseComponent {} ],已注册组件 BaseComponent
application2.registerComponent(new APPComponent()) // 注册组件 APPComponent
console.log(application2.getComponentCount()) // 2
console.log(application2.getRegistedComponents()) // [BaseComponent {}, APPComponent {}],已注册组件 BaseComponent 和 APPComponent
公有变量 & 公有办法 | 特权办法 | 毛病 | |
---|---|---|---|
构造函数 | 实例中,独立 | 实例中 | 每个实例从新创立办法(公有办法 & 特权办法) |
公有作用域 | 公有作用域中,动态,共享 | 构造函数原型上 | 调用特权办法批改公有变量,影响其余实例 |
模块模式 | 公有作用域中,独立 | 单例对象上 | |
模块加强模式 | 公有作用域中,独立 | 实例对象上 |
总结 & 问点
- 什么是闭包?其作用是什么?
- 在没有应用箭头函数的状况下,this 在全局和部分办法调用时,别离指向哪里?若是匿名函数中的 this 呢?
- 函数嵌套时,外部函数如何拜访内部函数的 this 和 arguments?
- 什么是立刻调用的函数表达式?请用代码用其模仿块级作用域
- 请用代码实现性能:获取所有的 div 元素,点击不同的 div 显示其相应的索引值。要求别离用 IIFE 和块级作用域实现
- 请用代码实现性能:1 秒后实现 0~4 的数字迭代。要求别离用 IIFE 和块级作用域实现
- 什么是公有变量?其可能包含哪些内容?
- 什么是特权办法?请写一段代码,在构造函数中实现特权办法,并说说这种形式有什么问题
- 请写一段代码,通过公有变量实现特权办法,说说并证实这种形式有什么局限
- 请写一段代码,通过模块模式定义单例对象的公共接口,实现 Web 组件注册
- 模块加强模式适宜什么场景?请用代码实现其模式下的 Web 组件注册