Javascript读书笔记(六)函数作用域,声明提前,作用域链,闭包
参照内容来自《JavaScript权威指南》,《JavaScript高级程序设计》
函数作用域
类似C语言的编程语言中有块级作用域,JavaScript中没有块级作用域,取而代之的是函数作用域。
声明提前
在Javascript函数中,所有声明的变量都会提前到函数体的顶部,这个特性称为声明提前。虽然函数内声明的变量被提前了,但是只有程序执行到赋值语句时,该变量才会被赋值。例如:
var thestring = "watermelon";
function text(){
console.log(thestring); //此时输出的并不是watermenlon,而是undefined
var thestring = "apple";
console.log(thestring); //此时输出的为apple
}
函数中第一行代码输出undefined,是因为函数内声明的thestring变量覆盖了之前的全局变量thestring,同时该局部变量被提前,但程序尚未执行到局部变量thestring的赋值语句,第一行输出局部变量thestring自然为undefined。
作为属性的变量
声明一个全局变量即定义了全局对象的一个属性(仅仅是对全局变量的强制要求,对局部变量没有如此要求)。当使用var声明全局变量时,该变量无法通过delete运算符删除。当没有使用严格模式声明全局变量时,可以删除它们。例如:
var a = "apple"; //不可删除
delete a; //变量没有被删除
b = "watermelon"; //可删除
delete b; //变量被删除
JavaString中可以使用this引用全局对象,但没有办法可以引用局部变量中存放的对象。
如果没有使用var声明变量,则该变量会成为全局变量。(不管该变量在哪里声明)例如:
function fn(){
a = "apple";
}
console.log(a); //结果为apple,因为此时a为全局变量。
作用域链
作用域链可以描述为一个有序的对象集合,每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链(scope chain)。即:
作用域链的结构:
许多函数(或者全局代码(仅一段))
↓
一个函数(或者全局代码)
↓
一个对应的作用域链→可描述为一个有序的对象集合
↓
↓
↓ →第一个对象A,引用本函数的参数和局部变量
包括了多个对象 →第二个对象A1,引用外层函数的参数和局部变量
→其他更外层的函数,即对象A2,A3,A4...
→最后一个对象An,引用的全局的执行环境对象,即window对象
产生作用域链的过程:
例如:
依据1:当定义一个函数时,它实际上保存一个作用域链。
+
依据2:如果是嵌套函数,则每次调用外部函数时,内部函数又会重新定义一遍。
(为什么要重新定义:因为每次调用外部函数的时候,作用域链都是不同的。)
重新定义后与之前的区别:在每次调用外部函数时,内部函数的代码都是相同的,而关联这段代码的作用域链不相同。
↓
定义函数
↓
function A( ) {
function B( ) { //theString1
function C( ) {} //theString2
C( ); //调用函数C
}
B( ); //调用函数B
}
A( );
↓ 如果调用函数C时,它创建一个新的对象来存储它的局部变量, 并将这个对象添加至保存的那个作用域链上, 同时创建一个新的更长的表示函数调用作用域的“链”。 假设“链”为theString1, 这个“链”包含了上面提及的多个对象,即 对象A,它包括本函数(即函数C)的参数和局部变量, 对象A1,它包括外层函数(即函数B)的参数和局部变量, 对象A2,它包括外层函数(即函数A)的参数和局部变量, 。。。 最后一个对象为window对象 |
↓ 如果调用函数B时,函数B也会重复函数C的过程, 即创建一个新的对象来存储它的局部变量, 并将这个对象添加至保存的那个作用域链上, 同时创建一个新的更长的表示函数调用作用域的“链”。 假设“链”为theString2, 此时本函数则指函数C,这个“链”包含的对象: 对象A,它包括本函数(即函数B)的参数和局部变量, 对象A1,它包括外层函数(即函数A)的参数和局部变量, 。。。 最后一个对象为window对象 |
↓ 如果调用函数A时,函数A也会重复函数C的过程, 即创建一个新的对象来存储它的局部变量, 并将这个对象添加至保存的那个作用域链上, 同时创建一个新的更长的表示函数调用作用域的“链”。 假设“链”为theString3, 此时本函数则指函数A,这个“链”包含的对象: 对象A,它包括本函数(即函数A)的参数和局部变量, 。。。 最后一个对象为window对象 |
↓
总结:每次调用JavaScript函数的时候,都会为其创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。
↓
当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。
↓
即意味着
↓
↓ 如果不存在嵌套的函数,也没有其他引用指向这个绑定对象, 它就会被当做垃圾回收掉。例如:调用函数C,函数C不存在 嵌套函数,当没有引用指向作用域链的第一个对象时,则会被 回收掉。 |
↓ 如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链, 并且这个作用域链指向一个变量绑定对象。如果这个函数定义了嵌套 的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会 有一个外部引用指向这个嵌套的函数。它就不会被当做垃圾回收,并且 它所指向的变量绑定对象也不会被当做垃圾回收。例如: function A(){ function B(){ //定义了嵌套的函数 console.log("ink"); } return B; //将嵌套的函数作为返回值 } var C = A(); //外部引用指向这个嵌套的函数 C(); |
作用域链的使用过程:
标识符解析是沿着作用域链一级一级地搜索标识符地过程。搜索过程始终从作用域链地前端开始,然后逐级向后回溯,直到找到标识符为止,如果找不到标识符,通常会导致错误发生。
即从第一个对象开始搜索,然后一级一级往上搜索,直到最后一个对象(window对象)为止。外层的函数无法访问嵌套在内部的函数的局部变量。
当JavaScript需要查找变量x的值的时候(这个过程称做“变量解析”(variable resolution)),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,JavaScript会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,直到最后一个对象(window对象)。如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。
例如:
var fruit = "watermelon";
function A(){var fruit = "apple";function B(){var fruit = "banana";console.log(fruit);}function C(){console.log(fruit);}B();C();
}
当调用函数B时,形成这样的作用域链:B->A->window, 此时,JS引擎沿着该作用域链查找变量fruit, 查到的是函数B中的fruit,输出banana。
当调用函数C时,形成这样的作用域链:C->A->window,此时,JS引擎沿着该作用域链查找变量fruit,函数C中没有变量fruit,然后继续向上查找,找到了函数A中的变量fruit,输出apple。
闭包
闭包即变量解析过程。当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
例如:
function A(){function B(){ //定义了嵌套的函数console.log("ink");}return B; //将嵌套的函数作为返回值
}
var C = A; //外部引用指向这个嵌套的函数
C();
结合上文提及的作用域链的回收,当A执行之后,通常该作用域链会被销毁。而在这个段代码中,该作用域链依旧存在,没有被回收。因为A定义了嵌套的函数B,并将它作为返回值返回,同时在这段代码中有一个外部引用(C)指向这个嵌套的函数B。B依然在使用该作用域。此时该作用域链就不会被当作垃圾回收。
B 持有对该作用域的引用,而这个引用就叫作闭包。
B 在定义时的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的作用域。
Javascript读书笔记(六)函数作用域,声明提前,作用域链,闭包相关推荐
- 你不知道的javaScript读书笔记(六)
this全面解析 笔记: this的运行机制: (1) this是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this的绑定和函数声明的位置没有任何关系,只取决于函数的调 ...
- 3d游戏设计读书笔记六
3d游戏设计读书笔记六 一.改进飞碟(Hit UFO)游戏: 游戏内容要求: 按 adapter模式 设计图修改飞碟游戏 使它同时支持物理运动与运动学(变换)运动 更改原 UFO_action 类 为 ...
- JavaScript进阶系列01,函数的声明,函数参数,函数闭包
本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...
- 【JavaScript 笔记】— 函数高级(变量作用域、解构赋值、方法、高阶函数、闭包、箭头函数、generator)
JavaScript个人笔记 变量作用域 变量提升 全局对象 windows 命名空间 局部作用域 常量 解构赋值 使用场景 方法 apply 装饰器 高阶函数(Array) map reduce m ...
- 【JavaScript基础】js中关于声明提前的几个误区
声明提前: 在程序正式执行之前,都会将所有的var声明的变量提前到开始位置,集中创建,而赋值留在原地. 例如这样一段代码 console.log(a) var a = 100; console.log ...
- 菜鸟教程-Javascript学习笔记-JS函数之前
教程连接是: https://www.runoob.com/js/js-tutorial.html DOM(一些操作页面元素的方法) BOM(一些操作浏览器的方法) ################# ...
- C专家编程--读书笔记六 运行时数据结构
第六章 一.知识点 1.代码和数据的区别也可以认为是编译时和运行时的分界线.编译器的绝大部分工作都跟翻译代码有关:必要的数据存储管理的绝大部分都在运行时进行.(P121) 2."a.out& ...
- kotlin读书笔记之函数基本知识以及泛型
1.函数内容 1.1 函数的声明与用法 kotlin的函数使用fun关键字声明,如下所示: fun double(x: Int): Int {return 2 * x } double(2).tost ...
- python定义函数的组成部分有_Python学习笔记之函数的定义和作用域实例详解
本文实例讲述了Python函数的定义和作用域.分享给大家供大家参考,具体如下: 定义函数 默认参数: 可以向函数中添加默认参数,以便为在函数调用中未指定的参数提供默认值 # 如果调用 cylinder ...
最新文章
- CentOS yum源
- 如何开发一个用户脚本系列(3)——脚本一:百度首页和搜索页面添加 Google 搜索框...
- v-show 与 v-if区别
- java多态和继承_Java学习--继承与多态
- android 支付宝月账单 统计图_记账其实很简单,用微信、支付宝就够了!
- IDEA MySql之增删改查
- iMeta | 华中科大宁康组综述宏基因组数据用于蛋白质三维结构预测的方法论
- linux 路由访问不了php文件,linux系统nginx服务器不能访问php文件问题
- http://my.oschina.net/huangyong/blog/372491?fromerr=hobPLCmQ#OSC_h2_5
- Codejock Xtreme MFC 图形界面控件包
- Android SDK 下载安装及配置
- 计算机类603高数,拟录取ING ,谈谈603高等数学
- 第二章注解深入浅出(三、插桩)
- 杜红超、彭志红担任BCF理事
- linux环境下添加定时任务,linux下添加定时任务。
- 初学者学习插画原画以后就业方向有哪些?和大家聊聊插画原画师就业、薪资等
- Excel2007中固定表头或列
- 腾讯、阿里、字节跳动三家公司有何区别!?
- LK光流法与反向LK光流法
- 如何开发一款报表引擎(一) 框架梳理