javascript从作用域链谈闭包

神马是闭包
关于闭包的概念,是婆说婆有理。

闭包是指有权访问另外一个函数作用域中的变量的函数
这概念有点绕,拆分一下。从概念上说,闭包有两个特点:

  • 1、函数
  • 2、能访问另外一个函数作用域中的变量

在ES 6之前,Javascript只有函数作用域的概念,没有块级作用域(但catch捕获的异常 只能在catch块中访问)的概念(IIFE可以创建局部作用域)。每个函数作用域都是封闭的,即外部是访问不到函数作用域中的变量。

function getName() {
 var name = "美女的名字";
 console.log(name);  //"美女的名字"
}
function displayName() {
 console.log(name); //报错
}

但是为了得到美女的名字,不死心的单身汪把代码改成了这样:

function getName() {
 var name = "美女的名字";
 function displayName() {
 console.log(name);
 }
 return displayName;
}
var 美女 = getName();
美女() //"美女的名字"

这下,美女是一个闭包了,单身汪想怎么玩就怎么玩了。(但并不推荐单身汪用中文做变量名的写法,大家不要学)。

关于闭包呢,还想再说三点:
1、闭包可以访问当前函数以外的变量

function getOuter(){
 var date = '815';
 function getDate(str){
 console.log(str + date); //访问外部的date
 }
 return getDate('今天是:'); //"今天是:815"
}
getOuter();

getDate是一个闭包,该函数执行时,会形成一个作用域A,A中并没有定义变量date,但它能在父一级作用域中找到该变量的定义。

2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量

function getOuter(){
 var date = '815';
 function getDate(str){
 console.log(str + date); //访问外部的date
 }
 return getDate;  //外部函数返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"

3、闭包可以更新外部变量的值

function updateCount(){
 var count = 0;
 function getCount(val){
 count = val;
 console.log(count);
 }
 return getCount;  //外部函数返回
}
var count = updateCount();
count(815); //815
count(816); //816

作用域链
为毛闭包就能访问外部函数的变量呢?这就要说说Javascript中的作用域链了。
Javascript中有一个执行环境(execution context)的概念,它定义了变量或函数有权访问的其它数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。你可以把它当做Javascript的一个普通对象,但是你只能修改它的属性,却不能引用它。

变量对象也是有父作用域的。当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不再存在父作用域了,这就是作用域链。

作用域链和原型继承有点类似,但又有点小区别:如果去查找一个普通对象的属性时,在当前对象和其原型中都找不到时,会返回undefined;但查找的属性在作用域链中不存在的话就会抛出ReferenceError。

作用域链的顶端是全局对象。对于全局环境中的代码,作用域链只包含一个元素:全局对象。所以,在全局环境中定义变量的时候,它们就会被定义到全局对象中。当函数被调用的时候,作用域链就会包含多个作用域对象。

  • 全局环境

关于作用域链讲得略多(红皮书上有关于作用域及执行环境的详细解释),看一个简单地例子:

// my_script.js
"use strict";
var foo = 1;
var bar = 2;

在全局环境中,创建了两个简单地变量。如前面所说,此时变量对象是全局对象。

  • Non-nested functions

改动一下代码,创建一个没有函数嵌套的函数:

"use strict";
var foo = 1;
var bar = 2;
function myFunc() {
 //-- define local-to-function variables
 var a = 1;
 var b = 2;
 var foo = 3;
 console.log("inside myFunc");
}
console.log("outside");
//-- and then, call it:
myFunc();

当myFunc被定义的时候,myFunc的标识符(identifier)就被加到了当前的作用域对象中(在这里就是全局对象),并且这个标识符所引用的是一个函数对象(function object)。函数对象中所包含的是函数的源代码以及其他的属性。其中一个我们所关心的属性就是内部属性[[scope]]。[[scope]]所指向的就是当前的作用域对象。也就是指的就是函数的标识符被创建的时候,我们所能够直接访问的那个作用域对象(在这里就是全局对象)。

比较重要的一点是:myFunc所引用的函数对象,其本身不仅仅含有函数的代码,并且还含有指向其被创建的时候的作用域对象。

当myFunc函数被调用的时候,一个新的作用域对象被创建了。新的作用域对象中包含myFunc函数所定义的本地变量,以及其参数(arguments)。这个新的作用域对象的父作用域对象就是在运行myFunc时我们所能直接访问的那个作用域对象。

  • Nested functions

如前面所说,当函数返回没有被引用的时候,就会被垃圾回收器回收。但是对于闭包(函数嵌套是形成闭包的一种简单方式)呢,即使外部函数返回了,函数对象仍会引用它被创建时的作用域对象。

"use strict";
function createCounter(initial) {
 var counter = initial;
 function increment(value) {
 counter += value;
 }
 function get() {
 return counter;
 }
 return {
 increment: increment,
 get: get
 };
}
var myCounter = createCounter(100);
console.log(myCounter.get()); // 返回 100
myCounter.increment(5);
console.log(myCounter.get()); // 返回 105

当调用createCounter(100)时,内嵌函数increment和get都有指向createCounter(100) scope的引用。如果createCounter(100)没有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。但是因为createCounter(100)实际上是有返回值的,并且返回值被存储在了myCounter中,所以对象之间的引用关系发生变化。

需要用点时间思考的是:即使createCounter(100)已经返回,但是其作用域仍在,并能且只能被内联函数访问。可以通过调用myCounter.increment() 或 myCounter.get()来直接访问createCounter(100)的作用域。

当myCounter.increment() 或 myCounter.get()被调用时,新的作用域对象会被创建,并且该作用域对象的父作用域对象会是当前可以直接访问的作用域对象。

当执行到return counter;时,在get()所在的作用域并没有找到对应的标示符,就会沿着作用域链往上找,直到找到变量counter,然后返回该变量,调用increment(5)则会更有意思。当单独调用increment(5)时,参数value会存贮在当前的作用域对象。函数要访问value,能马上在当前作用域找到该变量。但是当函数要访问counter时,并没有找到,于是沿着作用域链向上查找,在createCounter(100)的作用域找到了对应的标示符,increment()就会修改counter的值。除此之外,没有其他方式来修改这个变量。闭包的强大也在于此,能够存贮私有数据。

Similar function objects, different scope objects
对于上面的counter示例,再说点扩展的事。看代码:

//myScript.js
"use strict";
function createCounter(initial) {
 /* ... see the code from previous example ... */
}
//-- create counter objects
var myCounter1 = createCounter(100);
var myCounter2 = createCounter(200);

myCounter1 和 myCounter2创建之后,关系图是酱紫的:

在上面的例子中,myCounter1.increment和myCounter2.increment的函数对象拥有着一样的代码以及一样的属性值(name,length等等),但是它们的[[scope]]指向的是不一样的作用域对象。

这才有了下面的结果:

var a, b;
a = myCounter1.get(); // a 等于 100
b = myCounter2.get(); // b 等于 200
myCounter1.increment(1);
myCounter1.increment(2);
myCounter2.increment(5);
a = myCounter1.get(); // a 等于 103
b = myCounter2.get(); // b 等于 205

作用域和this
作用域会存储变量,但this并不是作用域的一部分,它取决于函数调用时的方式。关于this指向的总结,可以看这篇文章:JavaScript面试问题:事件委托和this

(0)

相关推荐

  • js 函数的执行环境和作用域链的深入解析

    第一步. 定义后:每个已定义函数,都有一个内在属性[scope],其对应一个对象的列表,列表中的对象仅能内部访问. 例如:建立一个全局函数A,那么A的[Scope]内部属性中只包含一个全局对象(Global Object),而如果我们在A中创建一个新的函数B,那么B的[Scope]属性中就包含两个对象,函数A的Activation Object对象在前面,全局对象(Global Object)排在后面. 简而言之,一个函数的[Scope]属性中对象列表的顺序是上一层函数的Activation O

  • 深入理解JavaScript高级之词法作用域和作用域链

    主要内容:1.分析JavaScript的词法作用域的含义 2.解析变量的作用域链 3.变量名提升时什么 最近在传智播客讲解JavaScript的课程,有不少朋友觉得JavaScript是如此的简单,但是又如此的不知如何使用,因此我准备了一些内容给大家分享一下. 这个系列主要讲解JavaScript的高级部分的内容,包括作用域链.闭包.函数调用模式.原型以及面向对象的一些东西. 在这里不包含JavaScript的基本语法,如果需要了解基础的同学可以到http://net.itcast.cn里面去下

  • js作用域及作用域链概念理解及使用

    (1)作用域 一个变量的作用域(scope)是程序源代码中定义的这个变量的区域. 1. 在JS中使用的是词法作用域(lexical scope) 不在任何函数内声明的变量(函数内省略var的也算全局)称作全局变量(global scope) 在函数内声明的变量具有函数作用域(function scope),属于局部变量 局部变量优先级高于全局变量 var name="one"; function test(){ var name="two"; console.log

  • 深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解

    函数表达式 1.JavaScript中定义函数有2钟方法: 1-1.函数声明: 复制代码 代码如下: function funcName(arg1,arg2,arg3){  //函数体} ①name属性:可读取函数名.非标准,浏览器支持:FF.Chrome.safari.Opera. ②函数声明提升:指执行代码之前会先读取函数声明.即函数调用可置于函数声明之前. 1-2.函数表达式: 复制代码 代码如下: var funcName = function(arg1,arg2,arg3){  //函

  • javascript 作用于作用域链的详解

    javascript 作用于作用域链的详解 一.JavaScript作用域 任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在JavaScript中,变量的作用域有全局作用域和局部作用域两种. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥有全局作用域,一般来说一下几种情形拥有全局作用域: (1)最外层函数和在最外层函数外面定义的变量拥有全局作用域, 例如: var authorName="Bu

  • JavaScript中的作用域链和闭包

    作用域 全局作用域 局部作用域 作用域链 执行上下文 活动对象 闭包 闭包优化 JavaScript中出现了一个以前没学过的概念--闭包.何为闭包?从表面理解即封闭的包,与作用域有关.所以,说闭包以前先说说作用域. 作用域(scope) 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥有全局作用域,以下几种情形拥有全局作

  • JS 作用域与作用域链详解

    (1)作用域 一个变量的作用域(scope)是程序源代码中定义的这个变量的区域. 1. 在JS中使用的是词法作用域(lexical scope) 不在任何函数内声明的变量(函数内省略var的也算全局)称作全局变量(global scope) 在函数内声明的变量具有函数作用域(function scope),属于局部变量 局部变量优先级高于全局变量 复制代码 代码如下: var name="one"; function test(){   var name="two"

  • javascript从作用域链谈闭包

    神马是闭包 关于闭包的概念,是婆说婆有理. 闭包是指有权访问另外一个函数作用域中的变量的函数 这概念有点绕,拆分一下.从概念上说,闭包有两个特点: 1.函数 2.能访问另外一个函数作用域中的变量 在ES 6之前,Javascript只有函数作用域的概念,没有块级作用域(但catch捕获的异常 只能在catch块中访问)的概念(IIFE可以创建局部作用域).每个函数作用域都是封闭的,即外部是访问不到函数作用域中的变量. function getName() { var name = "美女的名字&

  • 详解JavaScript作用域、作用域链和闭包的用法

    1. 作用域 作用域是指可访问的变量和函数的集合. 作用域可分为全局作用域和局部作用域. 1.1 全局作用域 全局作用域是指最外层函数外面定义的变量和函数的集合. 换言之,这些最外层函数外面定义的变量和函数在任何地方都能访问. 举个例子: // 最外层定义变量 var a = 1; console.log(a); // 最外层可以访问 function fnOne() { // 最外层函数 console.log(a); // 函数内可以访问 function fnTwo() { // 子函数

  • Javascript的作用域、作用域链以及闭包详解

    一.javascript中的作用域 ①全局变量-函数体外部进行声明 ②局部变量-函数体内部进行声明 1)函数级作用域 javascript语言中局部变量不同于C#.Java等高级语言,在这些高级语言内部,采用的块级作用域中会声明新的变量,这些变量不会影响到外部作用域. 而javascript则采用的是函数级作用域,也就是说js创建作用域的单位是函数. 例如: 在C#当中我们写如下代码: static void Main(string[] args) { for (var x = 1; x < 1

  • 全面了解JavaScript的作用域链

    JavaScript的作用域链 这是一个非常重要的知识点了,了解了JavaScript的作用域链的话,能帮助我们理解很多'异常'问题. 下面我们来看一个小例子,前面我说过的声明提前的例子. var name = 'Skylor.min'; function echo() { alert(name); var name = 'mm'; alert(name); alert(age); } echo(); 对于这个例子,没有接触过这方面的时候,第一反应是会纠结下,这第一个的name,到底调用全局变量

  • JavaScript中作用域链的概念及用途讲解

    从零开始讲解JavaScript中作用域链的概念及用途 引言 之前我写过一篇关于JavaScript中的对象的一篇文章,里面也提到了作用域链的概念,相信大家对这个概念还是没有很深的理解,并且这个概念也是面试中经常问到的,因为这个概念实在太重要了,在我们平时写代码时,也可能会因为作用域链的问题,而出现莫名其妙的bug,导致我们花费大量的时间都查找不出原因.所以我就准备单独写一篇关于作用域链的文章,来帮大家更好地理解这个概念. 正文 一.执行环境 首先,我们要引入一个概念,叫做执行环境(下面简称环境

  • 深入理解Javascript中的作用域链和闭包

    首先我们回顾下之前一篇关于介绍数组遍历的文章: 请先看上一篇中提到的for循环代码: var array = []; array.length = 10000000;//(一千万) for(var i=0,length=array.length;i<length;i++){ array[i] = 'hi'; } var t1 = +new Date(); for(var i=0,length=array.length;i<length;i++){ } var t2 = +new Date();

  • 图解Javascript——作用域、作用域链、闭包

    什么是作用域? 作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围.全局变量拥有全局作用域,局部变量则拥有局部作用域. js是一种没有块级作用域的语言(包括if.for等语句的花括号代码块或者单独的花括号代码块都不能形成一个局部作用域),所以js的局部作用域的形成有且只有函数的花括号内定义的代码块形成的,既函数作用域. 什么是作用域链? 作用域链是作用域规则的实现,通过作用域链的实现,变量在它的作用域内可被访问,函数在它的作用域内可被调用. 作用域链是一个只能单向访问的链

  • 通过函数作用域和块级作用域看javascript的作用域链

    在ES6之前,javascript只有全局作用域和函数作用域.所谓作用域就是一个变量定义并能够被访问到的范围.也就是说如果一个变量定义在全局(window)上,那么在任何地方都能访问到这个变量,如果这个变量定义在函数内部,那么就只能在函数内部访问到这个变量. 全局作用域只要页面没关闭就会一直存在,而函数作用域只有在函数执行的时候才存在,执行完就销毁.且每次执行函数都会创建一个新的作用域. 那么什么是作用域链呢? 在了解作用域链之前,我们先了解一个执行期上下文的概念. 执行期上下文:当函数执行时,

  • JavaScript闭包与作用域链实例分析

    本文实例讲述了JavaScript闭包与作用域链.分享给大家供大家参考,具体如下: 闭包定义 闭包指的是有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式,就是在一个函数A内部创建另一个函数B,那么函数B就是一个闭包,可以访问函数A作用域中的所有变量. JavaScript的闭包与作用域链密不可分,因此本文可以和JavaScript的作用域链相对照分析,一定可以对JavaScript的闭包和作用域链有更深的理解. 下面我们仍然以createComparisonFunction为例进行闭包

随机推荐