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