在学习面向对象编程的更高级的问题之前,必须要完全理解函数的作用域和闭包,以及变量访问
然后将讨论参数this以及原型委托(prototype delegation),
这是实现面向对象编程的JavaScript最主要的两个语言特性,最后,提到写JavaScript类和子类的多种方法
深刻地理解JavaScript面向对象编程的特点,以及代码如何在运行时影响存储模型中的解释器
一、作用域(scopes)
什么是作用域,以及为什么使用
第一种用法:词法作用域
这种用法描述了源代码中的范围,在这个范围中可以使用变量名引用变量,而不出现访问错误
这种作用域的具体规则是 即便不运行代码,也很容易理解,因为它涉及到代码中不同变量名有意义的区域
全局作用域:
全局作用域被多个不同文件共享,使得程序的任意部分都可以和另一部分交互
在全局作用域中声明变量之后,就可以在该词法作用域的任意位置引用这个变量了
每当定义一个函数,就会创建一个新的词法作用域,但是比其外层的词法作用域,要多一些限制
虽然你仍然可以访问,其外层作用域中的变量,也可以访问在这个内层作用域中定义的变量,
但该内层作用域中的变量,无法从该作用域之外访问。
在函数这对花括号范围之外,引用内层作用域中的局部变量,将导致错误
注意:
请注意一个容易出错的地方,JavaScript允许给未声明的变量赋值,
它将被自动添加到全局作用域,而非你赋值的这个作用域
这是很不好的行为,因为漏写var关键字,通常是由疏忽造成,而非故意为之
即使你是刻意为之,其他人阅读代码时,也会非常困惑,他们会认为这个是错误疏忽导致的
所以请记住,在任何词法作用域中声明变量时,务必加上var关键词。
总结:
如何区分不同的词法作用域
如何控制它们的规则
作用域的另一种用法:执行环境(或称为内存作用域)
当程序运行的时候,就创建一个用于保存变量和变量值的存储系统,
这些内存中的作用域结构就被称为执行环境
词法作用域与执行环境(内存作用域)的不同:
执行环境它是在代码运行时才被创建,而不是在代码被输入时,
规则可以控制在程序执行过程中的不同点可以访问哪些变量
程序运行时,将会创建内部数据存储,以记录可供不同函数对象访问的所有变量
由于函数的每一次运行,都完全独立于此前的任何一次运行,每次函数运行时,就会创建一个新的运行环境
因此每个词法作用域在运行的过程中,可能会创建多个内存作用域,也可能一个都没有,这完全取决于此函数运行了多少次
在第一行代码执行之前,
解释器(interpreter)就开始配置执行环境(execution environment)
第一步是在内存中,创建一个全局作用域环境,以保存所有全局变量,
在这里,解释器为全局作用域创建一个存储系统
此时这里就是解释器当前的执行环境,也就是说这是解释器开始查找变量的区域
当第一行代码运行时:
解释器会在执行环境中,创建一个新的键值映射,以记录变量名对应的值
注意:
现在这个执行环境看起来像是键值对的集合,这与对象有点类似,
会让人误以为内存作用域和内存对象是一回事
这个相似之处非常具有欺骗性,因为他们都是由解释器完全分开保存,
而且,在访问执行环境时的很多限制,在访问对象时并不存在
除了它们长得有点类似,其实它们存在于完全不同的世界,几乎永不交互
举例说明一下,你绝不可能存储一个包含很多环境的数组,但却可以存储一个包含很多对象的数组
你无法像在对象中遍历键那样,在执行环境中去遍历变量名,因此,虽然它们都是键值数据存储结构
但你只能使用完全不同的方式与它们交互
二、闭包
学习如何利用闭包保持对函数和JavaScript输出的访问权
JavaScript开发者会经常使用闭包, 简单来说,每个函数都可以访问,其外围作用域中的所有变量
闭包是指 一些函数通过某种方式,可以随时被访问,即使它的外部代码已经执行完毕
来了解闭包是如何工作,以及我们该如何使用闭包
保留函数访问权限
在newSaga调用完成后仍然可以访问函数saga?
将saga传入setTimeout
在newSaga中返回saga
saga保存为某个全局变量
以上三种方法都可以帮助我们在函数newSaga执行完毕后,仍然能访问函数saga