JavaScript 闭包

为了更好地理解 JavaScript 闭包,笔者将先从 JavaScript 执行上下文以及 JavaScript 作用域开始写起,如果读者对这方面已经了解了,可以直接跳过。

1. 执行上下文

简单来说,JavaScript 有三种代码运行环境,分别是:

  1. Global Code 是 JavaScript 代码开始运行的默认环境
  2. Function Code 是 JavaScript 函数运行的环境
  3. Eval Code 是 利用 eval 函数执行的代码环境

执行上下文可以理解为上述为了执行对应的代码而创建的环境。

例如在第一个环境执行前,我们需要考虑

  1. 该环境下的所有变量对象

    例如用 let const var 定义的变量,或者是函数声明,函数参数 arguments

  2. 该环境下的作用域链

    包括 该环境下的所用变量对象 以及父亲作用域 (我们当然可以用到父亲作用域提供的函数和变量

  3. 是谁执行了这个环境 (this)

拥有了这些东西后,我们才可以分配内存,起到一个准备的作用。

我们用下述代码加深对执行上下文的理解

let global = 1;

function getAgeByName(name) {
let xxx = 1;
function age() {
console.log(this);
const age = 10;
if (name === "huro")
return age;
else
return age * 10;
}
return age();
}

假设我们执行 age 函数

  1. 创建当前环境下的作用域链
  2. 创建当前环境下的变量
  3. 设置 this 是谁

这里作用域链显然是 当前环境下的变量(还没初始化)以及父亲作用域(这里面包括了 global 变量以及 xxx 变量, name 形参)等,这些我们当然都可以在 age 中使用。

当前环境下的变量包括接收到的形参 arguments age 变量

由于没有明确指定是谁调用 age 方法,因此 this 在浏览器环境下设置为 window

在创建好环境进行变量的搜索的时候,会先搜索当前环境下的变量,如果没有随着作用域链往上搜索。

由于 ES6 箭头函数本身不创建 this 因此他会向上寻找 this

上述提到了作用域,作用域也分几种

作用域

  1. 块级作用域

    在很多语言的规范里经常告诉我们,如果你需要一个变量再去定义,但是如果你使用 JavaScriptvar 定义变量,你最好别这么干。最好是都定义在头部。

    因为 var 没有块级作用域

if (true) {
var name = "huro";
}
console.log(name); // huro

​ 不过当你使用 letconst 定义的话,就不存在这样的问题。

if (true) {
let name = "huro";
}
console.log(name); // name is not defined
  1. 函数和全局作用域

    这个和大部分语言是一致的。

let a = 1;
function fn() {
let a = 2;
console.log(a); // 2
}

闭包

拥有了作用域和作用域链,内部函数可以访问定义他们的外部函数的参数和变量,这非常好。

如果我们希望一个对象不被外界更改(污染)

const myObject = () => {
let value = 1;
return {
increment: (inc) => {
value += inc;
}
getValue: () => {
return value;
}
}
}

由于外界不可能直接访问到 value 因此就不可能修改他。

利用闭包

在构造函数中,对象的属性都是可见的,没法得到私有变量和私有函数。一些不知情的程序员接受了一种伪装私有的模式。

例如

function Person() {
this.________name = "huro";
}

用于保护这个属性,并且希望使用代码的用户假装看不到这种奇怪的成员元素,但是其实编译器并不知情,仍会在你输入 xxx.__ 的时候提示你有 xxx.________name 属性

利用闭包可以很轻易的解决这个问题。

function Person(spec) {
let { name } = spec; this.getName = () => {
return name;
}
this.setName = (name) => {
name = "huro";
}
return this;
}
const p = new Person({ name: "huro" });
console.log(p.name) // undefined
console.log(p.getName()) // "huro"

注意闭包带来的问题

<body>
<div class="name">
huro
</div>
<div class="name">
lero
</div>
</body>
const addHandlers = (nodes) => {
let i ;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].addEventListener("click", () => {
alert(i); // 总是 nodes.length
})
}
}
const doms = document.getElementsByClassName("name");
addHandlers(doms);

你会发现,打印出来的结果总是 2,这是作用域的原因,由于 i 是父作用域链的变量,当向上查找的时候,i 已经变成 2 了。

正确的写法应该是

const addHandlers = (nodes) => {
for (let i = 0; i < nodes.length; i += 1) {
nodes[i].addEventListener("click", () => {
alert(i);
})
}
}
const doms = document.getElementsByClassName("name");
addHandlers(doms);

一篇文章图文并茂地带你轻松学完 JavaScript 闭包的更多相关文章

  1. 一篇文章图文并茂地带你轻松学完 JavaScript 设计模式(一)

    JavaScript 设计模式(一) 本文需要读者至少拥有基础的 ES6 知识,包括 Proxy, Reflect 以及 Generator 函数等. 至于这次为什么分了两篇文章,有损传统以及标题的正 ...

  2. 一篇文章图文并茂地带你轻松学完 JavaScript 设计模式(二)

    JavaScript 设计模式(二) 本篇文章是 JavaScript 设计模式的第二篇文章,如果没有看过我上篇文章的读者,可以先看完 上篇文章 后再看这篇文章,当然两篇文章并没有过多的依赖性. 5. ...

  3. 一篇文章图文并茂地带你轻松学完 JavaScript 原型和原型链

    JavaScript 原型和原型链 在阅读本文章之前,已经默认你了解了基础的 JavaScript 语法知识,基础的 ES6 语法知识 . 本篇文章旨在为 JavaScript继承 打下基础 原型 在 ...

  4. 一篇文章图文并茂地带你轻松学完 JavaScript 事件循环机制(event loop)

    JavaScript 事件循环机制 (event loop) 本篇文章已经默认你有了基础的 ES6 和 javascript语法 知识. 本篇文章比较细致,如果已经对同步异步,单线程等概念比较熟悉的读 ...

  5. 一篇文章图文并茂地带你轻松学完 JavaScript 继承

    JavaScript 继承 在阅读本文章之前,已经默认你了解了基础的 JavaScript 语法知识,基础的 ES6 语法知识 . 继承种类 简单的继承种类可以分为 构造函数继承 原型链继承 clas ...

  6. 一篇文章图文并茂地带你轻松实践 HTML5 history api

    HTML5 history api 前言 由于笔者在网络上没有找到比较好的关于 history api 的实践案例,有的案例过于杂乱,没有重点,有些案例只是告诉读者 api 是什么,却没告诉怎么用,本 ...

  7. 一篇文章图文并茂地带你轻松学会 HTML5 storage

    html5 storage api localStorage 和 sessionStorage 是 html5 新增的用来存储数据的对象,他们让我们可以以键值对的形式存储信息. 为什么要有 stora ...

  8. 一篇文章让你快速入门 学懂Shell脚本

    Shell脚本,就是利用Shell的命令解释的功能,对一个纯文本的文件进行解析,然后执行这些功能,也可以说Shell脚本就是一系列命令的集合. Shell可以直接使用在win/Unix/Linux上面 ...

  9. 学完JavaScript基础有感

    紧接上一篇回来了,这几天一直学js,会不自觉的和其他的编程语言联系在一起,在没有学jQuery之前,结合我所学的c,java,数据结构,数据库以及部分html感觉到JavaScript里面又很多相似的 ...

随机推荐

  1. 【MySQL】centos6中/etc/init.d/下没有mysqld启动文件,怎么办

    如果/etc/init.d/下面没有mysqld的话,service mysqld start也是不好使的,同样,chkconfig mysqld on也是不能用 解决办法: 将mysql的mysql ...

  2. 【Oracle】delete表后commit后怎么找回,方法

    有些时候,不小心删除了一些需要的表,而且数据库不能停止,只能一直运行下去,这样的话很麻烦 下面介绍的方法就是删除表后通过时间戳后者scn找回删除的数据 模拟实验环境: 创建一个新表 SQL> c ...

  3. Python hashlib的简单使用

    hashlib模块针对不同的安全哈希和消息摘要算法实现了一个通用的接口,其中包括SHA1, SHA224, SHA256, SHA384, SHA512算法以及RSA的MD5算法. 使用方法 第一步 ...

  4. 解决JavaScript中构造函数浪费内存的问题!

    解决JavaScript中构造函数浪费内存的问题! 把构造函数中的公共的方法放到构造函数的原型对象上! // 构造函数的问题! function Gouzaohanshu(name, age, gen ...

  5. 如何将python中pip源设置为国内源

    1.Windows Python的学习过程中,往往会学习到很多库,而安装各类库的时候,往往不尽人意,下载速度从几KB到十几KB.甚至下载到一半还超时报错.这都是因为pip源是访问国外的官方源,如果需要 ...

  6. 原生js使用面向对象的方法开发选项卡实例教程

    本教程通过js面向对象的方法来封装一个选项卡的实例,在实例中讲解js的面向对象如何实现功能. 一般封装好的选项卡程序,只需要一个div元素即可.其它元素都是通过json数据来生成,所以封装好的选项卡实 ...

  7. Java多线程--两种实现方式

    进程概述: 在这之前,有必要了解一下什么是进程? 在一个操作系统中,每个独立的执行的程序都可称为一个进程,也就是"正在运行的程序".如图所示: 线程概述: 如上所述,每个运行的程序 ...

  8. CF1416D 做题心得

    CF1416D 做题心得 上次在某trick中提到了这个题,一开始觉得太毒瘤没有写,现在把它补上了. 感觉实现这个东西,比单纯收获一个trick,收获的东西多太多了. 主要思路 它的主要trick是& ...

  9. LOJ10199轻拍牛头

    题目描述 原题来自:USACO 2008 Dec. Silver 今天是 Bessie 的生日,并且现在是聚会的游戏时间.Bessie 让编号为 1~N 的 N 头奶牛围成一个圈坐(所以除了最后一头牛 ...

  10. LOJ10196越狱

    题目描述 原题来自:HNOI 2008 监狱有连续编号为 1 到 n 的 n 个房间,每个房间关押一个犯人.有 m 种宗教,每个犯人可能信仰其中一种.如果相邻房间的犯人信仰的宗教相同,就可能发生越狱. ...