1. 基本概念

JavaScript的核心语言特性在ECMA-262中是以名为ECMAScript的伪语言的形式来定义的。

ECMAScript中包含了所有基本的语法、操作符、数据类型以及完成基本的计算任务所必需的对象,但没有对取得输入和产生输出的机制作出规定。

理解ECMAScript及其纷繁复杂的各种细节,是理解其在Web浏览器中的实现JavaScript的关键。

目前大多数实现所遵循的都是ECMA-262第3版,但很多也已经着手开始实现第5版了。以下简要总结了ECMAScript中基本的要素。

  • CMAScript中的基本数据类型包括Undefined, Nul1, Boolean, Number和string。
  • 与其他语言不同, ECMScript没有为整数和浮点数值分别定义不同的数据类型, Number类型可用于表示所有数值。
  • ECMAScript中也有一种复杂的数据类型,即object类型,该类型是这门语言中所有对象的基础类型。
  • 严格模式为这门语言中容易出错的地方施加了限制。
  • ECMAScript提供了很多与c及其他类C语言中相同的基本操作符,包括算术操作符、布尔操作符、关系操作符、相等操作符及赋值操作符等。
  • ECMAScript从其他语言中借鉴了很多流控制语句,例如if语句、for语句和switch语句等

ECMAScript中的函数与其他语言中的函数有诸多不同之处。

  • 无须指定函数的返回值,因为任何ECMAScript函数都可以在任何时候返回任何值。
  • 实际上,未指定返回值的函数返回的是一个特殊的undefined值
  • ECMAScript中也没有函数签名的概念,因为其函数参数是以一个包含零或多个值的数组的形式传递的。
  • 可以向ECMAScript函数传递任意数量的参数,并且可以通过arguments对象来访问这些参数。
  • 由于不存在函数签名的特性, ECMAScript函数不能重载。
  1. 变量、作用域和内存问题

JavaScript变量可以用来保存两种类型的值:基本类型值和引用类型值基本类型的值源自以下5

种基本数据类型: Undefined、Null、 Boolean、 Number和 String。

基本类型值和引用类型值具有以下特点:

  • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中
  • 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
  • 引用类型的值是对象,保存在堆内存中;
  • 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
  • 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;
  • 确定一个值是哪种基本类型可以使用 typeof操作符,而确定一个值是哪种引用类型可以使用 instanceof操作符。

所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的点总结:

  • 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
  • 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
  • 函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;
  • 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
  • 变量的执行环境有助于确定应该何时释放内存。

JavaScript是一门具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题。可以对 JavaScript的垃圾收集例程作如下总结。

  • 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。
  • “标记清除”是目前主流的垃圾收集算法这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。
  • 另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数 JavaScript引擎目前都不再使用这种算法;但在IE中访问非原生 JavaScript对象(如DOM元素)时,这种算法仍然可能会导致问题。
  • 当代码中存在循环引用现象时,“引用计数”算法就会导致问题。
  • 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。
  1. 引用类型

对象在 JavaScript中被称为引用类型的值,而且有一些内置的引用类型可以用来创建特定的对象,

现简要总结如下:

  • 引用类型与传统面向对象程序设计中的类相似,但实现不同;
  • object是一个基础类型,其他所有类型都从 object继承了基本的行为;
  • Array类型是一组值的有序列表,同时还提供了操作和转换这些值的功能;
  • Date类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;
  • RegExp类型是 ECMAScript支持正则表达式的一个接口,提供了最基本的和一些高级的正则表达式功能。

函数实际上是 Function类型的实例,因此函数也是对象;而这一点正是JavaScript最有特色的地方。由于函数是对象,所以函数也拥有方法,可以用来增强其行为。

因为有了基本包装类型,所以 JavaScript中的基本类型值可以被当作对象来访问。三种基本包装类型分别是: Boolean、 Number和 String。以下是它们共同的特征:

  • 每个包装类型都映射到同名的基本类型;
  • 在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作;
  • 操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。

在所有代码执行之前,作用域中就已经存在两个内置对象: Global和 Math。在大多数 ECMAScript

实现中都不能直接访问 Global1对象;不过,Web浏览器实现了承担该角色的 window对象。全局变量和函数都是 Global1对象的属性。Math对象提供了很多属性和方法,用于辅助完成复杂的数学计算任务。

  1. 面向对象的程序设计

ECMASeript支持面向对象(OO)编程,但不使用类或者接口。对象可以在代码执行过程中创建和

增强,因此具有动态性而非严格定义的实体。在没有类的情况下,可以采用下列模式创建对象。

  • 工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。
  • 构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数可以不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。
  • 原型模式,使用构造函数的 prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。

JavaScript主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。

原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。

此外,还存在下列可供选择的继承模式。

  • 原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造
  • 寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。
  • 寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
  1. 函数表达式

在 JavaScript编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名

从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用 JavaScript函数的强大方式。以下总结了函数表达式的特点。

  • 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫做匿名函数。
  • 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
  • 递归函数应该始终使用 arguments. callee来递归地调用自身,不要使用函数名函数名可能会发生变化。

当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下。

  • 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域
  • 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
  • 但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。

使用闭包可以在 JavaScript中模仿块级作用域( JavaScript本身没有块级作用域的概念),要点如下。

  • 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用
  • 结果就是函数内部的所有变量都会被立即销毁—除非将某些变量赋值给了包含作用域(即外部作用域)中的变量。

闭包还可以用于在对象中创建私有变量,相关概念和要点如下。

  • 即使 JavaScript中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
  • 有权访问私有变量的公有方法叫做特权方法。
  • 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。

JavaScript中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为

创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存

8.小结

浏览器对象模型(BOM)以 window对象为依托,表示浏览器窗口以及页面可见区域。同时, window

对象还是 ECMAScript中的 Globa1对象,因而所有全局变量和函数都是它的属性,且所有原生的构造函数及其他函数也都存在于它的命名空间下。本章讨论了下列BOM的组成部分。

  • 在使用框架时,每个框架都有自己的 window对象以及所有原生构造函数及其他函数的副本每个框架都保存在 frames集合中,可以通过位置或通过名称来访问。
  • 有一些窗口指针,可以用来引用其他框架,包括父框架。
  • top对象始终指向最外围的框架,也就是整个浏览器窗口。
  • parent对象表示包含当前框架的框架,而self对象则回指 windows。
  • 使用1 location对象可以通过编程方式来访问浏览器的导航系统。设置相应的属性,可以逐段或整体性地修改浏览器的URL
  • 调用 replace()方法可以导航到一个新URL,同时该URL会替换浏览器历史记录中当前显示的页面。

navigator对象提供了与浏览器有关的信息。到底提供哪些信息,很大程度上取决于用户的浏

览器;不过,也有一些公共的属性(如userAgent)存在于所有浏览器中。

BOM中还有两个对象: screen和 history,但它们的功能有限。 screen对象中保存着与客户端显示器有关的信息,这些信息一般只用于站点分析。 history对象为访问浏览器的历史记录开了一个小缝隙,开发人员可以据此判断历史记录的数量,也可以在历史记录中向后或向前导航到任意页面。