javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于javascript作用域的相关知识。
很多人使用javascript时候会把{}作为作用域的边界,所以我们可以看看下面的代码:
function ftn01(){ var i = 1; if (i == 1){ var a = "ok"; } console.log("a = " + a);// a = ok {var b = "bok";} console.log("b = " + b);// b = bok } ftn01();
我们发现变量a和b都能被打印出来,这就说明if下的{}和单独的{}并不能保护变量a和b的作用域范围,我们将上面的代码修改下:
function ftn01(){ var i = 1; if (i == 1){ var a = "ok"; } console.log("a = " + a);// a = ok {var b = "bok";} console.log("b = " + b);// b = bok } console.log(a); ftn01();
在firebug里会报出如下的错误:
可见函数的{}是可以保护变量的作用域的。
其实在javascript语言里只有函数是可以提供作用域,换句话说javascript里有且只有函数作用域,没有其他的作用域。因此要理解作用域必须从函数讲起,javascript里的函数同时也是一个对象,函数同时也是对象这句话让很多初学者误解,javascript这个特性和其他很多语言不太一样,要解释这个问题就必须从javascript创建函数的机制,javascript语言里有一个对象叫做Function,所有的函数都是该对象的实例,下面我们看看javascript里创建函数的三种方式:
var add01 = function add(a,b){ return a + b; } var add02 = function(a,b){ return a+ b; } var add03 = new Function("a","b","return a + b"); console.log(add01(1,2));// 3 console.log(add02(1,2));// 3 console.log(add03(1,2));// 3 console.log(typeof add01);//function console.log(typeof add02);//function console.log(typeof add03);//function
第一种方式叫做命名函数方式,第二种叫做匿名函数方式,第三种是直接使用Function对象来定义函数,三种方式是等价的,并且都可以用一个变量保存该函数。其实前两种创建函数的方式是第三种的变种,由此可以看到所有函数都是Function对象的实例。
函数的作用域功能是由函数内置的一个属性scope体现的,scope属性是一个类数组的集合,这个类数组的集合叫做函数的作用域链,作用域是函数的一个属性,作用域链其实就是该属性的数据类型。当我们创建一个函数时候,就是上面定义函数add01时候,函数的scope属性就会被同步创建,并且作用域链也会被构建,上面的例子里创建的函数都是属于全局的,因此作用域链只有一个元素即该类数组的length是1,类数组所包含的元素也是一个键值对类型,该元素包含所有全局变量例如:window,document,add01等等。
上面的例子里add01是函数的标识符,当add01(1,2)加上了小括号的时候就是执行该函数了,执行一个函数时候,函数会创建一个运行期上下文,英文全称是:execution context,运行期上下文是函数的一个内部对象和作用域一样也是不能被外部访问的,每个运行期的函数都会有一个自己独有的运行上下文,当函数执行完毕后,该函数的运行上下文也会被销毁。当函数执行的时候,首先会初始化函数自带的scope属性,然后将函数自带的scope里的作用域链复制到运行期上下文里,运行期上下文里也包含一个作用域的属性,该属性也是用一个作用域链的类数组表示,复制出来的函数的作用域链会放到运行期上下文的作用域链里,这一步做好后,运行期上下文会初始化函数内部的局部变量和命名参数例如add01函数里的a,b,把这些变量存储到一个活动对象的变量里,初始化完成后这个活动对象也会加入到运行期上下文的作用域链里,这个活动对象会放到运行期上下文作用域链的最前端。其实在函数执行完毕销毁的对象就是这个活动对象,活动对象被销毁了对应的运行期上下文也就被销毁,但是原来存储在函数里的作用域还是保留的。
作用域链的作用是对变量标识符进行解析,标识符就是变量的名字,例如add01、add02这些就是标识符,标识符的解析就是获取数据的位置或是如何存储数据。当函数执行时候,遇到每一个变量就会搜索运行期上下文的作用域链,这个过程都是从作用域链的头开始查找,也就是我上面说的从活动对象开始找起,如果找到与之对应的标识符,那么搜索过程便会停止,如果没有找到那么接着就会搜索作用域链的下一个对象,直到找到为止。不管是函数的作用域链还是运行期上下文的作用域链,链条的最后一个都是全局对象,其实全局对象是一个特殊的对象,如果我还是套用前面函数作用域的方式去理解全局对象,把一个网页当做一个最大的函数,这个函数对象就是window,网页的打开时这个window函数生命周期的开始,网页的关闭就是window函数的销毁,与window函数对应的还有一个全局的运行期上下文,那么用上面的理论理解全局变量应该就会简单多了。全局变量可以当做所有函数作用域的父作用域,当标识符解析到了全局变量时候,前面一定经过了n多个的作用域链中元素的遍历,因此到了遍历全局变量其实程序执行的效率是很低了,所以所有写高性能javascript代码的建议里都是要尽量减少全局变量的使用,如果非要使用全局变量也要在函数作用域内用一个局部变量替代全局变量,这是高性能javascript代码的一个基本要求,不过时下最新版的浏览器几乎都对这个特性进行了优化,访问全局变量不再像以前那么消耗性能了,不过ie的老版本的市场还是很大,而老版本的ie对全局访变量的访问那就是代码效率的毒瘤了。
下面我要讲讲闭包和作用域的关系,有很多人把闭包等价于作用域或者是作用域链,这个有一定的道理,但是如果真的以为闭包就是等价于的这些的话,这个等价于就是错误的,闭包在javascript语言里是一个特殊的函数,闭包产生于函数执行的时候,也就是函数的名字加上了小括号使用的时候,这个时候就会创建闭包或者叫做定义闭包,这个过程和函数创建作用域的过程一样,而闭包的作用域链就是函数的运行期上下文,当执行闭包或者说使用闭包的时候,那么就会构建出闭包的运行期上下文,这个时候闭包也会构建一个活动对象,这个活动对象被置于它的作用域链的最前端,同时闭包还包含执行函数的活动对象,但这个活动变量是在闭包活动变量的后面,这样就会导致函数销毁时候内存无法及时回收,造成大量的内存资源的占用,因此使用闭包是个十分消耗计算资源的应用,前面我讲到要尽量把全局变量用局部变量替换,其实碰到跨作用域的变量引用,都要将其用局部变量替代,这样代码的效率和安全性会更好。
还有很多童鞋认为this指针和作用域链没什么联系,这种理解是不正确的,其实this指针就是指向作用域链的上一级对象,但这种理解常常会误导某些人对this的理解,因为很多人在函数内部调用函数时候,发现被调用的函数this指针指向了全局变量,我们在实际开发里使用this指针最好是换个角度,this指针是调用某个函数上一级对象,例如obj.ftn(),那么ftn函数内部的this指针就是指向的obj,如果有个函数直接是ftn(),前面没有点,那么ftn函数里的内部指针就是window对象,this指针都是指向点号前面的对象,如果没有点号就是window对象,通过点号理解this指针比较方便,也不容易出错,但是通过作用域理解this为什么会有偏差了,原因就是全局作用域在作祟,在javascript里不管哪里直接调用函数,前面没有点的时候this都是指向全局的,我们不应该看这个方法是放到哪个函数里执行,其实this和作用域关系是紧密的,大家千万别怀疑这点。此外在作用域链的数组型的数据结构里,数组的每一个元素都含有一个this指针,this都是作为一个键值对预先定义好的,它的取值不由作用域链的创建所决定。因此有人认为this指针的使用其实也是跨域访问的观点也是不对,this指针的使用不存在跨域,它的效率也是非常高的。
理解了上面这个问题,那么我们对函数的apply和call方法深入理解就比较容易了,这两个方法的本质作用就是在特定的作用域里调用函数,这个解释比较抽象,这样我们先看下面的例子:
function test(){ console.log(this); } test.call(window);// window var obj = {}; test.call(obj);// object function ftn01(){ test.call(this); } ftn01();// window
这两个方法的第一个参数就是改变当前活动对象里this指针指向哪个对象,第一个参数就是函数this指针指向的对象,其本质也是改变作用域的一种方式,改变作用域的方法可以扩展方法的使用范围,因此调用对象和方法解耦,这样可以精简代码,提高代码的复用程度。
尽量少使用全局变量,多用局部变量是写高效javascript程序的一个基本要求,大家不要怀疑它会过时,不管浏览器如何演进,这点规则都是一条黄金规则。记住最优秀的javascript代码里全局变量最好只有一个。
最后我还要插一句,做过javascript都知道javascript代码压缩是一个提升网站效率的重要手段,我们使用雅虎或者谷歌的压缩代码的工具时候,会发现很多变量都会被A,B这样简单的代码所替换,这个替换规则都是对局部变量进行的,因此多使用局部变量会javascript压缩效果更好。
相关推荐
【狂神说系列 JavaScript笔记】是一份全面且深入的JavaScript学习资源,旨在帮助开发者和初学者深入理解这门广泛应用于Web开发的脚本语言。这份笔记涵盖了JavaScript的基础语法、核心概念以及高级特性,旨在构建一个...
- 变量的作用域:局部变量(在函数内声明)和全局变量(在函数外声明)。 - 变量的生命周期:局部变量在函数执行完毕后销毁,全局变量在整个页面生命周期中存在。 5. 数据类型 JavaScript支持的数据类型包括: -...
### JavaScript基础教程笔记知识点 #### 一、JavaScript简介 - **定义**:JavaScript是一种轻量级的编程语言,主要用于Web浏览器中的网页交互控制。 - **发展历史**:1995年由Netscape公司的Brendan Eich设计并...
《李炎恢Javascript笔记》是一本深入浅出的JavaScript学习资料,它涵盖了JavaScript的基础到实践应用的诸多方面。这本书的特点是将复杂的编程概念分解为易于理解的小知识点,并且提供了源码示例,使得读者能够更好地...
* 函数的作用域:全局作用域、局部作用域 六、JavaScript数组 * 数组的定义:使用[]或new Array()创建数组 * 数组的操作:push、pop、shift、unshift、splice、slice、indexOf、lastIndexOf * 数组的遍历:for、...
这篇学习笔记主要涉及了JavaScript的基础概念和一些高级特性,包括预编译、作用域、函数、对象原型、原型链、函数调用方式(如call、apply)、继承模式、对象克隆、数组操作、自定义类型判断以及错误处理机制。...
笔记可能详细解析了原型链、闭包、作用域等概念。 3. **DOM操作**:文档对象模型(DOM)是JavaScript操作HTML和XML文档的标准接口。学习者将了解到如何通过JavaScript选择、添加、修改和删除DOM元素,以及事件处理...
2. 作用域:JavaScript有两种作用域——全局作用域和局部作用域。函数内部声明的变量具有局部作用域,外部声明的为全局作用域。ES6引入了块级作用域,通过`let`和`const`关键字实现。 三、对象与数组 1. 对象:...
韩顺平老师的JavaScript笔记全面涵盖了基础语法、面向对象编程以及DOM编程,这些都是学习JavaScript时至关重要的知识点。 首先,基础语法是JavaScript学习的基石。包括变量声明(var、let、const)、数据类型(如...
JavaScript有全局作用域和局部作用域,函数内部创建的变量仅在函数内部可见。 八、异步编程 JavaScript的异步编程主要依赖回调函数、Promise和async/await。它们解决了JavaScript的单线程执行模型下避免阻塞UI的...
- 变量声明:JavaScript支持var、let和const关键字声明变量,理解它们的作用域和提升特性至关重要。 - 数据类型:JavaScript有七种数据类型,包括原始类型(如字符串、数字、布尔、null和undefined)和引用类型...
- **变量作用域**:生成器函数内部的变量遵循函数的作用域规则。 - **方法**:生成器函数通过 `yield` 关键字暂停和恢复执行。 #### 内部对象 - **Date**:用于处理日期和时间的对象。 ```javascript var date =...
在JavaScript中,变量的作用域可以分为两种:全局作用域和函数作用域。 - **全局作用域**: 在函数外部定义的变量具有全局作用域,可以在任何地方访问。 - **函数作用域**: 在函数内部定义的变量仅在该函数内部有效...
### JavaScript DOM 编程艺术读书笔记关键知识点解析 #### 一、JavaScript简史与相关技术简介 - **XHTML(可扩展的超文本标记语言)**:这是一种更加严格、更加强大的HTML版本,旨在提高网页的可读性和可扩展性。 ...
- `scope` (Object):可选参数,设置回调函数的作用域。 - **Ext.MessageBox.confirm**:显示一个确认对话框,通常包含“确定”和“取消”两个按钮。 - `confirm(title, msg, fn, scope)` - `title` (String):...
### JavaScript 个人总结笔记 #### 一、JavaScript 简介 JavaScript 是一种广泛应用于网页开发的编程语言,主要用于创建动态交互式的网页。它是一种基于对象的语言,支持事件驱动编程,具有与 C 语言和 Java 类似的...
综上所述,这篇JavaScript笔记涵盖了从基础语法到高级特性的全面学习,同时也涉及到了与之相关的工具和技术,如模板引擎Freemarker和开发环境的构建。通过深入理解和实践这些知识点,开发者可以提升JavaScript编程...
- **作用域**:变量的有效范围。 #### 9. 常量 - **声明**:使用关键字`final`声明不可更改的变量。 ### 第二章:数组 - **定义**:一组有序元素的集合。 - **初始化**:创建数组并为元素赋值。 - **访问**:通过...