谈谈javascript语法里一些难点问题(二)
3) 作用域链相关的问题
作用域链是javascript语言里非常红的概念,很多学习和使用javascript语言的程序员都知道作用域链是理解javascript里很重要的一些概念的关键,这些概念包括this指针,闭包等等,它非常红的另一个重要原因就是作用域链理解起来太难,就算有人真的感觉理解了它,但是碰到很多实际问题时候任然会是丈二和尚摸不到头脑,例如上篇引子里讲到的例子,本篇要讲的主题就是作用域链,再无别的内容,希望看完本文的朋友能有所收获。
讲作用域链首先要从作用域讲起,下面是百度百科里对作用域的定义:
作用域在许多程序设计语言中非常重要。 通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。 作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
在我最擅长的服务端语言java里也有作用域的概念,java里作用域是以{}作为边界,不过在纯种的面向对象语言里我们没必要把作用域研究的那么深,也没必要思考复杂的作用域嵌套问题,因为这些语言关于作用域的深度运用并不会给我们编写的代码带来多大好处。但是在javascript里却大不相同,如果我们不能很好的理解javascript的作用域我们就没办法使用javascript编写出复杂的或者规模宏大的程序。
由百度百科里的定义,我们知道作用域的作用是保证变量的名字不发生冲突,用现实的场景来理解有个人叫做张三,张三虽然只是一个名字,但是认识张三的人根据名字就能唯一确认这个人到底是谁,但是这个世界上叫做张三的人可不止一个,特别是两个叫张三的人有交集的时候我们就要有个办法明确指定这个张三绝不是另外一个张三,这时我们可能会根据两大张三年龄的差异来区分:例如一个张三叫大张三,相对的另外一个张三叫小张三了。编程语言里的作用域其实就是为了做类似的标记,作用域会设定一个范围,在这个范围里我们是不会弄错变量的真实含义。
前面我讲到在java里通过{}来设置作用域,在{}里面的变量会得到保护,这种保护就是不让{}里的变量被外部变量混淆和污染。那么{}的方式适合于javascript吗?我们看看下面的例子:
var s1 = "sharpxiajun";
function ftn(){
var s2 = "xtq";
console.log(this);// 运行结果: window
console.log("s1:" + this.s1 + ";s2:" + this.s2);//运行结果:s1:sharpxiajun;s2:undefined
console.log("s1:" + this.s1 + ";s2:" + s2);// 运行结果:s1:sharpxiajun;s2:xtq
}
ftn();
在javascript世界里有一个大的作用域环境,这个环境就是window,window环境不需要我们自己使用什么方式构建,页面加载时候页面会自动构造的,上面代码里有一个大括号,这个大括号是对函数的定义,运行之,我们发现函数作用域内部定义的s2变量是不能被window对象访问的,因此s2变量是被{}保护起来了,它的生命周期和这个函数的生命周期有关。
由这个例子是不是说明在javascript里,变量也是被{}保护起来了,在javascript语言里还有非函数的{},我们再看看下面的例子:
if (true){
var a = "aaaa";
}
console.log(a);// 运行结果:aaaa
我们发现javascript里{}有时是起不到定义作用域的功能。这也说明javascript里的作用域定义是和其他语言例如java不同的。
在javascript里作用域有一个专门的定义execution context,有的书里把这个名字翻译成执行上下文,有的书籍里把它翻译成执行环境,我更倾向于后者执行环境,下文我提到的执行环境就是execution context。这个命名非常形象,这个形象体现在execution这个单词,execution含义就是执行,我们来想想javascript里那些情况是执行:
情况一:当页面加载时候在script标签下的javascript代码会按顺序执行,而这些能被执行的代码都是属于window的变量或函数;
情况二:当函数的名字后面加上小括号(),例如ftn(),这也是在执行,不过它执行的是函数。
如此说来,javascript里的执行环境有两类一类是全局执行环境,即window代表的全局环境,一类是函数代表的函数执行环境,这也就是我们常说的局部作用域。
执行环境在javascript语言里并非是一个抽象的概念,而是有具体的实现,这个实现其实是个对象,这个对象也有个名字叫做variable object,这个变量有的书里翻译为变量对象,这是直译,有的书里把它称为上下文变量,这里我还是倾向于后者上下文变量,下文里提到的上下文变量就是指代variable object。上下文变量存储的是上下文变量所处执行环境里定义的所有的变量和函数。
全局执行环境的上下文变量是可以访问到的,它就是window对象,所以我们说window能代表全局作用域是有道理的,但是局部作用域即函数的执行环境里的上下文变量是代码不能访问到的,不过javascript引擎在处理数据时候会使用到它。
在javascript语言里还有一个概念,它的名字叫做execution context stack,翻译成中文就是执行环境栈,每个要被执行的函数都会先把函数的执行环境压入到执行环境栈里,函数执行完毕后,这个函数的执行环境就会被执行环境栈弹出,例如上面的例子:函数执行时候函数的执行环境会被压入到执行环境栈里,函数执行完毕,执行环境栈会把这个环境弹出,执行环境栈的控制权就会交由全局环境,如果函数后面还有代码,那么代码就是接着执行。如果函数里嵌套了函数,那么嵌套函数执行完毕后,执行环境栈的控制权就交由了外部函数,然后依次类推,最后就是全局执行环境了。
讲到这里我们大名鼎鼎的作用域链要登场了,函数的执行环境被压入到执行环境栈里后,函数就要执行了,函数执行的第一步不是执行函数里的第一行代码而是在上下文变量里构造一个作用域链,作用域链的英文名字叫做scope chain,作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,这个概念里有两个关键意思:有权访问和有序,我们看看下面的代码:
var b1 = "b1";
function ftn1(){
var b2 = "b2";
var b1 = "bbb";
function ftn2(){
var b3 = "b3";
b2 = b1;
b1 = b3;
console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 运行结果:b1:b3;b2:bbb;b3:b3
}
ftn2();
}
ftn1();
console.log(b1);// 运行结果:b1
有这个例子我们发现,ftn2函数可以访问变量b1,b2,这个体现了有权访问的概念,当ftn1作用域里改变了b1的值并且把b1变量重新定义为ftn1的局部变量,那么ftn2访问到的b1就是ftn1的,ftn2访问到b1后就不会在全局作用域里查找b1了,这个体现了有序性。
下面我要总结下上面讲述的知识:
本篇的小标题是:作用域链的相关问题,这个标题定义的含义是指作用域链是大名鼎鼎了,但是作用域链在广大程序员的理解里其实包含的意义已经超越了作用域链在javascript语言本身的定义。广大程序员对作用域链的理解有两块一块是作用域,而作用域在javascript语言里指的是执行环境execution context,执行环境在javascript引擎里是通过上下文变量体现的variable object,javascript引擎里还有一个概念就是执行环境栈execution context stack,当某一个函数的执行环境压入到了执行环境栈里,这个时候就会在上下文变量里构造一个对象,这个对象就是作用域链scope chain,而这个作用域链就是广大程序员理解的第二块知识,作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。
很多人常常认为作用域链是理解this指针的关键,这个理解是不正确的的,this指针构造是和作用域链同时发生的,也就是说在上文变量构建作用域链的同时还会构造一个this对象,this对象也是属于上下文变量,而this变量的值就是当前执行环境外部的上下文变量的一份拷贝,这个拷贝里是没有作用域链变量的,例如代码:
var b1 = "b1";
function ftn1(){
console.log(this);// 运行结果: window
var b2 = "b2";
var b1 = "bbb";
function ftn2(){
console.log(this);// 运行结果: window
var b3 = "b3";
b2 = b1;
b1 = b3;
console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 运行结果:b1:b3;b2:bbb;b3:b3
}
ftn2();
}
ftn1();
我们看到函数ftn1和ftn2里的this指针都是指向window,这是为什么了?因为在javascript我们定义函数方式是通过function xxx(){}形式,那么这个函数不管定义在哪里,它都属于全局对象window,所以他们的执行环境的外部的执行上下文都是指向window。
但是我们都知道现实代码很多this指针都不是指向window,例如下面的代码:
var obj = {
name:"sharpxiajun",
ftn:function(){
console.log(this);// 运行结果: Object { name="sharpxiajun", ftn=function()}
console.log(this.name);//运行结果: sharpxiajun
}
}
obj.ftn();// :
运行之,我们发现这里this指针指向了Object,这就怪了我前文不是说javascript里作用域只有两种类型:一个是全局的一个是函数,为什么这里Object也是可以制造出作用域了,那么我的理论是不是有问题啊?那我们看看下面的代码:
var obj1 = new Object();
obj1.name = "xtq";
obj1.ftn = function(){
console.log(this);// 运行结果: Object { name="xtq", ftn=function()}
console.log(this.name);//运行结果: xtq
}
obj1.ftn();
这两种写法是等价的,第一种对象的定义方法叫做字面量定义,而第二种写法则是标准写法,Object对象的本质也是个function,所以当我们调用对象里的函数时候,函数的外部执行环境就是obj1本身,即外部执行环境上下文变量代表的就是obj1,那么this指针也是指向了obj1。
哦,11点了,明天要上班,今天就写到这里,关于作用域链还有执行环境以及this的关系还有点没讲完,它们的关系会牵涉new的使用,(下面文字我要加粗,因为本文未讲this与new的关系,因此this的结论还不完整)写起来内容很多,所以这些内容就放在本系列的第三篇吧。
最后祝大家晚安。
谈谈javascript语法里一些难点问题(二)的更多相关文章
- 谈谈javascript语法里一些难点问题(一)
1) 引子 前不久我建立的技术群里一位MM问了一个这样的问题,她贴出的代码如下所示: var a = 1; function hehe() { window.alert(a); var a = ...
- <JavaScript>谈谈javascript语法里一些难点问题(一)
1) 引子 前不久我建立的技术群里一位MM问了一个这样的问题,她贴出的代码如下所示: var a = 1; function hehe() { window.alert(a); var a = ...
- JavaScript(二):JavaScript语法及数据类型
一.JavaScript语法 1.区分大小写ECMAScript中的一切,包括变量.函数名和操作符都是区分大小写的.例如:text和Text表示两种不同的变量.2.标识符所谓标识符,就是指变量.函数. ...
- 教你如何在 Javascript 文件里使用 .Net MVC Razor 语法
摘录 文章主要是介绍了通过一个第三方类库RazorJS,实现Javascript 文件里使用 .Net MVC Razor 语法,很巧妙,推荐给大家 相信大家都试过在一个 View 里嵌套使用 jav ...
- 网站开发综合技术 一 JavaScript简介 二JavaScript语法
第1部分 JavaScript简介 1.JavaScript它是个什么东西? 它是个脚本语言,需要有宿主文件,他的宿主文件是html文件. 2.它与Java有什么关系? 没有什么直接联系,java是S ...
- Javascript语法基础
Javascript语法基础 一.基本数据类型 JavaScript中支持数字.字符串和布尔值三种基本数据类型: 1.数字 数字型是JavaScript中的基本数据类型.在JavaScript ...
- JavaScript DOM 编程艺术(1)---> JavaScript语法
一. JavaScript语法目录 语法 操作 条件语句 循环语句 函数 对象 二. 具体内容 2.1 语法 javaScript代码要通过HTML/XHTML文档才能执行.可以有两种方式完成这一 ...
- JavaScript语法详解:JS简介&变量
本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文. JavaScript简介 Web前端有三层: HTML:从语义的角度,描 ...
- 读《javascript语法精粹》知识点总结
昨天泡了大半天的读书馆,一口气看完了<javascript语法精粹>这本书,总体来说这本书还是写的不错,难怪那么多的推荐.<javascript语法精粹>主要是归纳与总结了ja ...
随机推荐
- iOS所有的子视图
for (id view in [self.view subviews]) { if ([view isEqual:[UITextField class]]) { NSLog(@"你想要?& ...
- windows平台升级ORACLE11.2.0.1到11.2.0.4
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://4445027.blog.51cto.com/4435027/1674217 一 ...
- HttpWebRequest向路由器提交基本身份验证
HttpWebRequest向路由器提交基本身份验证 服务端IIS设置为不允许匿名访问,只选择了基本身份验证,客户端使用HttpWebRequest发送一个get请求,请求一个页面. 基本身份验证,客 ...
- Java多线程基本概念
基本概念 线程与任务的概念不一样. 任务:通常是一些抽象的且离散的工作单元,比如在Web请求中,针对用户的请求需要返回相应的页面是一个任务,在Java中实现Runnable接口的类也是一个任务. 线程 ...
- wpf 空白汉字占位符
<TextBlock xml:space="preserve" Text="主 编" /> 表示一个汉字占位符
- 自己关于cocoapods的使用的一些理解和总结
老大让我自己学习用一下cocoapods的使用,于是自己上网查了很多的信息,在安装使用过程中,总是出现了很多问题,然后发现有些人的教程好像并不完全好用,我的感觉是应该每个人遇到的问题都不尽相同,所以 ...
- html input的file文件输入框onchange事件触发一次失效解决方法
最近在做一个图片上传的功能,出现提交一次后,file输入框的change事件无法再次触发的bug,就是说提交一次后必须刷新才能再次提交,这就坑了~ 于是想办法解决它~ 在网上找了一些资料,找到这几种方 ...
- Xamarin studio配置问题
最近对Xamarin很感兴趣,就下班抽空在家里的电脑上进行配置,于是乎出现了各种问题,对此进行总结. 1. Cannot find `aapt.exe`. Please install the And ...
- KIWI Syslog配置
日志服务器Kiwi+Syslogd+8.3.7破解版 Window收集服务器日志evtsys_exe_32 默认地,kiwi使用UDP 514端口接收日志数据,安装成功后即可接收日志 使用命令nets ...
- <Oracle Database>数据库启动与关闭
启动和关闭Oracle数据库 要启动和关闭数据库,必须要以具有Oracle 管理员权限的用户登陆,通常也就是以具有SYSDBA权限的用户登陆.一般我们常用INTERNAL用户来启动和关闭数据库(INT ...