我眼中的js编程(2)--详解作用域内变量和函数的声明与访问

我眼中的js编程(1)主要介绍了js是用来做什么的,这一篇开始及以后总结js具体该怎么用。本篇总结了作用域内变量和函数的声明与访问。先看一段有意思的代码。

var a = a;
console.log(a) // undefined

let b = b;
console.log(b) // ReferenceError

let c;
c = c;
console.log(c) // undefined

很有意思的结果。为什么是这样呢?这个问题放一边,我先扯点没用的,等读到文章最后,如果你理解了,就一定会知道为什么。

(ps:先扯会儿淡)所有的编程语言都有相通之处,也有其各自擅长的地方。js和其他语言一样,数组、函数、对象等数据类型以及各个数据类型处理数据的api、运算符、变量的作用域、对象的创建和继承,这些概念,虽然各种语言的语法规则不一样,但是本质上是一样的。一通百通,没有太多新鲜之处。

js的独特之处在于能够处理网页的交互效果。这事儿只有js能干,因为浏览器只有js引擎,没有php引擎、java引擎,为什么是这样?这和js与浏览器的历史有关,阮一峰老师有一篇文章Javascript诞生记很好的做了诠释。

作用域

作用域有什么用?某个功能的代码,把其中没有必要暴露的函数和变量封装起来,实现最小暴露。

(ps:继续扯淡)软件设计中有最小暴露原则,就是用来实现某功能的代码,应该最大限度的暴露最少的东西。不止是软件,生活中的事物也遵循着这个原则,比如数据线,只是暴露了一个和手机的接口,其余的线路都在包装线内部隐藏,比如智能手机,暴露在外面的就是个外壳和屏幕,复杂的线路和芯片被隐藏在手机内部,而拆解开手机内部的具体零件,每个零件同样也是遵循着最小暴露原则,甚至社会组织结构比如饭店,大厅的餐桌、服务员对外暴露,而后厨的炊具、厨师隐藏在内部,而且仔细观察生活中的各类事物,无不遵循这这样的原则,而且是递归最小暴露,即拆解开来的零件同样也遵守,零件的零件仍然遵守。脑洞大开扯远了。。。下面说一下作用域怎么使用?聊聊作用域的相关规则(先聊有啥用,再聊怎么用,要知其然和所以然)。

变量的作用域在写代码的时候就确定了。es6之前js只有全局作用域和函数作用域(try-catch语句的作用域、eval()方法的作用域等暂不考虑),在es6中有了块作用域、新的全局let作用域、for循环作用域、模块作用域(参考自深入浅出es6)。js中变量的作用域是整个前后封闭的函数代码块,而不是开始于变量声明之处(有些编程语言的作用域是这样的)。嵌套作用域是编程语言的核心理念之一,js中常见的作用域()有:

  • 函数作用域
    var声明的变量所在的函数的整个代码块。
  • 块作用域
    let(const)声明的变量(常量)所在的外层块{ }
if(true){
  let a = 5
}
console.log(a) // ReferenceError
  • 新的全局let作用域
    let声明的全局变量不是全局对象的属性,let声明的全局变量存在于一个不可见的块作用域中,理论上是页面中包含所有js代码的不可见的外层块。
let a = 1
console.log(window.a) // undefined
  • for作用域
    for循环中()中变量是let声明的时候,比如for(let i = 0;i<5;i++){...},每次迭代都为i绑定新的块作用域,这个块就是for(){ }的{ }
for(let i = 0;i<5;i++){
  setTimeout(function(){
    console.log(i) // 每个1s输出一次,分别输出0 1 2 3 4
  },1000 * i)
}
  • 模块作用域

常量和变量

js中的变量和常量是需要用关键字声明的(php不用声明)
关键字var声明变量:var age = 18
关键字let声明变量:let name = 'yanhaoqi'
关键字const声明常量:const STUDENT_NUM = 30
常量和变量有全局局部之分,下面以变量为例说明。

  • 全局变量
    全局变量定义在全局对象中,可在任何作用域访问到。
    ECMAScript本身具有全局对象,但全局对象不是任何其他对象的属性,所以它没有名字。浏览器环境的全局对象是window,表示允许js代码的浏览器窗口,浏览器窗口就是浏览器端js的最大操作范围(权限)。node环境下的全局对象是globle
  • 局部变量
    局部变量声明在局部作用域,只在局部作用域内可见。

下面主要讲 作用域内变量的访问规则
先看两段代码:

console.log(name) // undefined
var name
console.log(name) // undefined
name = 'yanhaoqi'
console.log(name) // yanhaoqi
console.log(age) // ReferenceError
let age
console.log(age) // undefined
age = 18
console.log(age) // 18

为什么会有这样的区别呢?你可能会说,let声明的变量没有变量提升,其实这样说是不完全准确的,在这里我深入讨论下一些细节。

js引擎在编译和解释代码的时候,声明的变量有三个阶段:
声明阶段(Declaration Phase) :在当前作用域中注册一个变量(作用域在编译和解释之前已经确定)
初始化阶段(Initialization Phase):在作用域中为变量绑定内存,变量初始化为undefined。
赋值阶段(Assignment Phase):为初始化的变量分配一个具体的值。

var声明的变量

上面第一段代码,js引擎编译和解释过程如下:

  • 第一步
    在执行任何语句之前,先找到这段代码中所有的声明var name进行处理(引擎在编译时候的任务之一)
    name变量在任何代码执行前先在作用域顶部通过了 声明阶段 ,在作用域注册了变量name
    然后紧跟着来到 初始化阶段 ,name初始化为undefined,两个阶段之间没有任何间隙
    这个过程叫变量提升
  • 第二步
    开始执行第一句代码console.log(name),此时结果是undefined
    然后开始执行第一句代码console.log(name) 结果是undefined
  • 第三步
    执行 var name,没什么实际意义,因为一开始引擎就找到了var声明进行了处理,继续执行后面console.log(name)结果仍然是undefined,这里就是为了对比下面let代码的结果。
  • 第四部
    执行后面的console.log(name)结果是yanhaoqi
屏幕快照 2017-09-13 下午4.39.20.png
let声明的变量

上面第二段代码,js引擎编译和解释过程如下:

  • 第一步
    在执行任何语句之前,先找到这段代码中所有的声明let age进行处理,age变量在任何代码执行前先在作用域顶部通过了 声明阶段 ,在作用域中注册了变量name。不会紧接着进行初始化阶段。这算不算let声明的变量的提升我查到的资料上说法不一,但本质的过程就是这样的。
  • 第二部
    开始执行第一句代码console.log(age),因为age还没有经历初始化阶段,没有被分配内存和初始化为undefined,所以会报错ReferenceError
  • 第三步
    执行代码let age此时age变量才会进行初始化。接着执行console.log(age)结果是undefined
  • 第四部
    执行代码age = 18。此时完成 赋值阶段

变量提升的问题,弄清楚变量的声明和访问的过程就ok了,至于有人说let声明的变量没有变量提升,有人说let声明的变量是不完全提升,说法不同而已,管他呢,本质就是这样的。

屏幕快照 2017-09-13 下午5.54.49.png

var声明的变量在一开始就完成了 声明阶段初始化阶段,两个阶段是连在一起的,而let声明的变量要执行到let时候才会完成 初始化阶段let声明的变量完成了声明阶段还没有到达初始化阶段的时候如果访问该变量就会报错ReferenceError,我们称变量此时处在临时死区(Temporal Dead Zone,简称TDZ)

函数声明的提升

既然上面详细解释了变量的声明和访问的过程,顺便接着说一下函数声明的提升。首先要搞清楚函数声明和函数表达式的区别,如果关键字function是函数定义的第一个词,那这就是一个函数声明,否则就是一个函数表达式。

function foo(){
  console.log(123)
}
函数声明
var foo = function(){
  console.log(123)
} 
函数表达式
(function foo(){
  console.log(123)
})
函数表达式

明确了什么是函数声明后,下面我们讨论下函数声明的访问。

[2,3,4,5].reduce(multiplier); // 120
function multiplier(a,b){
  return a * b;
}

在定义multiplier函数之前就把它作为参数传入了reduce()函数,为什么函数在定义之前就可以使用?我们看下js引擎执行这段代码的具体编译和解释的过程。

  • 第一步
    js引擎在执行任何代码之前先找到函数声明,并在对应的作用域的顶部完成 声明阶段初始化阶段赋值阶段
  • 第二步
    开始执行第一句代码[2,3,4,5].reduce(multiplier);,函数multiplier作为reduce()的参数。
屏幕快照 2017-09-13 下午6.34.14.png

最后,关于js语言的设计中的变量提升和函数提升的规则,为什么有变量提升的设计,这要问js作者Brendan Eich,网上这方面信息较少,我在这里给一篇我觉得解释的不错的博客点击查看

总结var let声明的变量和函数声明的访问的区别就是,var声明的变量,声明阶段、初始化阶段2个阶段是耦合的,let声明的变量,声明阶段和初始化阶段是解耦的,而函数声明的声明阶段、初始化阶段、赋值阶段三个阶段都是耦合的。

点击查看上一篇我眼中的js编程(1)
点击查看下一篇我眼中的js编程(3)
我眼中的js编程系列是我个人的学习总结,如有错误,烦请包涵、不吝赐教,O(∩_∩)O谢谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容