Leon 浅谈闭包

浅谈 js 闭包

1、前言

闭包, js 前端永远绕不过去的面试题。这个问题其实考察的相对全面, 主要考察是否理解js 的运行机制,还有对于js 的作用域 和作用域链 是否理解。 如果这两个都很清楚 , 那闭包是什么? 怎么造成的? 又要怎么解决? 有什么作用? 又会造成什么? 这些通通就不是问题了

2、什么是作用域

谈闭包前,无可避免的需要知道什么是作用域。 作用域 就是该函数声明时 函数会通过内部的属性 [scope] 来记录创建的范围, 这个范围 就是该函数的作用域。

3、什么是闭包?

闭包, 顾名思义,一个封闭的区间。在这个封闭区间内,自成一套。在该作用域里面的 私有变量变量不与外界沟通,无法在外界获取,也不自动销毁,需要手动回收。

js 作者说: 闭包是一个函数加上到创建函数的作用域连接, 闭包 关闭了函数的自由变量。

这句话前半句的意思是 闭包是设计出来的一种机制,它是一个函数 和 该函数创建者作用域到该函数的连接的一个组合体。 这句话很绕, 其实说白了 就是一个函数包裹另外一个函数, 第一个函数作用域里面的 跟第二个函数相关的部分的统一组合体就叫做闭包。

后半句阐明了闭包都干了啥, 他关闭了函数的自由变量, 导致它内部的自由变量外界无法访问, 也暂时无法自动销毁,常常需要去手动销毁。

重点:

两个 函数, 这两个函数的作用域[scope] 还需要连接上。
关闭函数的自由变量,它暂时不会销毁

4、代码诠释

下面用代码来诠释闭包

function fn(n){
	return function(m) {
		n+=m
		return n
	}
}
// 第一步
const f = fn(1); // 无输出,记录了 n=1

// 第二步
f(5)  // 输出 6

如果这么写
console.log(f(5)) // 6
console.log(f(5)) //11
console.log(f(5)) // 16

可以看到n 不会消失
console.log(n) // 报错 n is not defined
这里就是 n 可以用但是 外界无法直接访问

5、作用

作者既然设计了闭包, 那就是为了用的, 那它又解决了什么问题呢?

其实作者也有说明: 无意间贡献环境

直接代码说明:

下面这段代码输出的啥?

for(var i=0;i<5;i++){
	setTimeout(() => {
		console.log(i)
	})
}

这里就是考虑到 js 的运行机制 , 咱们都知道,js 是服务于客户端的语言,用户是不肯能多线程操作的, 所以js 语言设计初衷就是单线程的。
我们都知道 作者是花了十几天的时间设计出来的这门语言。  初衷就是为了简单易学 易上手,所以他定义的初衷就是 js 万物皆对象, 函数也是一种特殊的对象!
但是又不得不考虑继承的因素,又不想用 c\java 之类的 class 类来做继承 ,因为那样又显得太正式 太沉重。 所以作者设计了 protoType(函数),和 __proto__(对象) 来解决继承的问题。 

上面讲的多了啊, 讲的原型与原型链 这里并不涉及,只做了解就好

切回 上述所说,js 是单线程的, 所以在js 的运行机制里面是 分堆和栈的概念的 
基础数据类型  (在js 栈里面按顺序 后进先出,先进后出的方式存储
string\number\undifined\null\Boolean\Symbol 

引用类型 Object /函数/定时器 等需要消耗时间 的操作 放在堆里面储存, 然后只是在栈里面有个 标记, 标记的是该函数或者对象的引用地址。

在js 运行机制里面, 同步的先执行, 执行查到异步操作 就放到异步队列里面 啥时候执行完 啥时候放到同步执行的队列里面去渲染

在上述的代码里面 for 循环是同步进行的操作, 而 setTimeout 是一个异步的定时器, 哪怕时间没有设置 它也是异步的。 这样的话 先执行的 就是 for循环, 五次循环 i ++ 分别为1、2、3、4、5,所以这里 先执行同步的for 循环 i自增为5, 再执行 定时器异步  所以输出 5次 5。

for(var i=0;i<5;i++){
    var index= i
	setTimeout(() => {
		console.log(index)
	})
} //  输出 5次 4

那要解决上述问题 想要依次输出 那怎么办?  这里就要用到闭包去解决

代码如下

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

上面就是闭包使用的经典案例, 在这个函数内部 index 成了私有变量,它临时继承了i的值  不会被销毁也不会被更改 五次循环, 依次输出  0,1,2,3,4

知识点(不相关,了解即可 不常用):

Symbol 是 ES6 新增的一种原始数据类型,它的字面意思是:符号、标记。代表独一无二的值 。

在 ES6 之前,对象的属性名只能是字符串,这样会导致一个问题,当通过 mixin 模式为对象注入新属性的时候,就可能会和原来的属性名产生冲突 。而在 ES6 中,Symbol 类型也可以作为对象属性名,凡是属性名是 Symbol 类型的,就都是独一无二的,可以保证不会与其他属性名产生冲突。

Symbol 值通过函数生成,如下所示:

let s = Symbol(); //s是独一无二的值
typeof s ; // symbol  

和其他基本类型不同的是,Symbol 作为基本类型,没有对应的包装类型,也就是说 Symbol 本身不是对象,而是一个函数。因此,在生成 Symbol 类型值的时候,不能使用 new 操作符 。

Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 值的描述,当有多个 Symbol 值时,比较容易区分

var s1 = Symbol('s1');

var s2 = Symbol('s2');

console.log(s1,s2); // Symbol(s1) Symbol(s2)
注意,Symbol 函数的参数只是表示对当前 Symbol 值的描述,因此,相同参数的 Symbol 函数的返回值也是不相等的


用 Symbol 作为对象的属性名时,不能直接通过点的方式访问属性和设置属性值。因为正常情况下,引擎会把点后面的属性名解析成字符串。







var s = Symbol();

var obj = {};

obj.s = 'Jack';

console.log(obj); // {s: "Jack"}

obj[s] = 'Jack';

console.log(obj) ; //{Symbol(): "Jack"}

Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols() 方法,专门获取指定对象的所有 Symbol 属性名。











var obj = {};

var s1 = Symbol('s1');

var s2 = Symbol('s2');

obj[s1] = 'Jack';

obj[s2] = 'Tom';

Object.keys(obj); //[]

for(var i in obj){ 

    console.log(i); //无输出 

}

Object.getOwnPropertySymbols(obj); //[Symbol(s1), Symbol(s2)]

另一个新的API,Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。








var obj = {};

var s1 = Symbol('s1');

var s2 = Symbol('s2');

obj[s1] = 'Jack';

obj[s2] = 'Tom';

obj.name = 'Nick';

Reflect.ownKeys(obj); //[Symbol(s1), Symbol(s2),"name"]

有时,我们希望重新使用同一个 Symbol 值,Symbol.for 方法可以做到这一点。它接受一个字符串作为参数,然后全局搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。




var s1 = Symbol.for('foo');

var s2 = Symbol.for('foo');

s1 === s2 //true

Symbol.for() 也可以生成 Symbol 值,它 和 Symbol() 的区别是:

Symbol.for() 首先会在全局环境中查找给定的 key 是否存在,如果存在就返回,否则就创建一个以 key 为标识的 Symbol 值
Symbol.for() 生成的 Symbol 会登记在全局环境中供搜索,而 Symbol() 不会。
因此,Symbol.for() 永远搜索不到 用 Symbol() 产生的值。




var s = Symbol('foo');

var s1 = Symbol.for('foo');

s === s1; // false

Symbol.keyFor() 方法返回一个已在全局环境中登记的 Symbol 类型值的 key 。





var s1 = Symbol.for('s1');

Symbol.keyFor(s1); //foo

var s2 = Symbol('s2'); //未登记的 Symbol 值

Symbol.keyFor(s2); //undefined 

你可能感兴趣的:(面试,javascript,前端,html5)