• JavaScript学习笔记


    在HTML中使用JavaScript

    只要一提到把JavaScript放到网页中,就不得不涉及Web的核心语言——HTML。在当初开发JavaScript的时候,Netscape要解决的一个重要问题就是如何做到让JavaScript既能与HTML页面共存,又不影响那些页面在其他浏览器中的呈现效果。经过尝试、纠错和争论,最终的决定就是为Web增加统一的脚本支持。而Web诞生早期的很多做法也都保留了下来,并被正式纳入HTML规范当中。

    <script>元素

    向HTML页面中插入JavaScript的主要方法,就是使用<script> 元素。这个元素由Netscape创造并在Netscape Navigator 2中首先实现。后来,这个元素被加入到正式的HTML规范中。HTML 4.01为<script> 定义了下列6个属性。
    async :可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。

    charset :可选。表示通过src 属性指定的代码的字符集。由于大多数浏览器会忽略它的值,因此这个属性很少有人用。

    defer :可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。IE7及更早版本对嵌入脚本也支持这个属性。

    language :已废弃。原来用于表示编写代码使用的脚本语言(如JavaScript 、JavaScript1.2 或VBScript )。大多数浏览器会忽略这个属性,因此也没有必要再用了。

    src :可选。表示包含要执行代码的外部文件。

    type :可选。可以看成是language 的替代属性;表示编写代码使用的脚本语言的内容类型(也称为MIME类型)。虽然text/javascript 和text/ecmascript 都已经不被推荐使用,但人们一直以来使用的都还是text/javascript 。实际上,服务器在传送JavaScript文件时使用的MIME类型通常是application/x–javascript ,但在type 中设置这个值却可能导致脚本被忽略。另外,在非IE浏览器中还可以使用以下值:application/javascript 和application/ ecmascript 。考虑到约定俗成和最大限度的浏览器兼容性,目前type 属性的值依旧还是text/javascript 。不过,这个属性并不是必需的,如果没有指定这个属性,则其默认值仍为text/javascript 。

    使用<script> 元素的方式有两种:直接在页面中嵌入JavaScript代码和包含外部JavaScript文件。

    在使用<script> 元素嵌入JavaScript代码时,只须为<script> 指定type 属性。然后,像下面这样把JavaScript代码直接放在元素内部即可:

    <script type="text/javascript">
        function sayHi(){ 
            alert("Hi!");
        }
    </script>
    

    如果要通过<script> 元素来包含外部JavaScript文件,那么src 属性就是必需的。这个属性的值是一个指向外部JavaScript文件的链接,例如:

    <script type="text/javascript" src="example.js"></script>
    

    在这个例子中,外部文件example.js 将被加载到当前页面中。外部文件只须包含通常要放在开始的<script> 和结束的 之间的那些JavaScript代码即可。与解析嵌入式JavaScript代码一样,在解析外部JavaScript文件(包括下载该文件)时,页面的处理也会暂时停止。

    需要注意的是,带有src 属性的<script> 元素不应该在其<script> 和 标签之间再包含额外的JavaScript代码。如果包含了嵌入的代码,则只会下载并执行脚本文件,嵌入的代码会被忽略。

    另外,通过<script> 元素的src 属性还可以包含来自外部域的JavaScript文件。这一点既使<script> 元素倍显强大,又让它备受争议。在这一点上,<script> 与 元素非常相似,即它的src属性可以是指向当前HTML页面所在域之外的某个域中的URL,例如:

    <script type="text/javascript" src="http://www.somewhere.com/afile.js"></script>
    

    这样,位于外部域中的代码也会被加载和解析,就像这些代码位于加载它们的页面中一样。利用这一点就可以在必要时通过不同的域来提供JavaScript文件。不过,在访问自己不能控制的服务器上的JavaScript文件时则要多加小心。如果不幸遇到了怀有恶意的程序员,那他们随时都可能替换该文件中的代码。因此,如果想包含来自不同域的代码,则要么你是那个域的所有者,要么那个域的所有者值得信赖。

    无论如何包含代码,只要不存在defer 和async 属性,浏览器都会按照<script> 元素在页面中出现的先后顺序对它们依次进行解析。换句话说,在第一个<script> 元素包含的代码解析完成后,第二个<script> 包含的代码才会被解析,然后才是第三个、第四个……

    标签的位置

    按照惯例,所有<script> 元素都应该放在页面的 元素中,例如:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Example HTML Page</title>
        <script type="text/javascript" src="example1.js"></script>
        <script type="text/javascript" src="example2.js"></script>
      </head>
      <body>
        <!-- 这里放内容 -->
      </body>
    </html>
    

    这种做法的目的就是把所有外部文件(包括CSS文件和JavaScript文件)的引用都放在相同的地方。可是,在文档的 元素中包含所有JavaScript文件,意味着必须等到全部JavaScript代码都被下载、解析和执行完成以后,才能开始呈现页面的内容(浏览器在遇到 标签时才开始呈现内容)。对于那些需要很多JavaScript代码的页面来说,这无疑会导致浏览器在呈现页面时出现明显的延迟,而延迟期间的浏览器窗口中将是一片空白。为了避免这个问题,现代Web应用程序一般都把全部JavaScript引用放在 元素中,放在页面的内容后面,如下例所示:

    <!DOCTYPE html>
    <html>
      <head>
      <title>Example HTML Page</title>
      </head>
      <body>
        <!-- 这里放内容 -->
        <script type="text/javascript" src="example1.js"></script>
    
    
        <script type="text/javascript" src="example2.js"></script>
    
    
      </body>
    </html>
    

    这样,在解析包含的JavaScript代码之前,页面的内容将完全呈现在浏览器中。而用户也会因为浏览器窗口显示空白页面的时间缩短而感到打开页面的速度加快了。

    延迟脚本

    HTML 4.01为<script> 标签定义了defer 属性。这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script> 元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行。

    <!DOCTYPE html>
    <html>
      <head>
        <title>Example HTML Page</title>
        <script type="text/javascript" defer="defer" src="example1.js"></script>
    
    
        <script type="text/javascript" defer="defer" src="example2.js"></script>
    
    
      </head>
      <body>
        <!-- 这里放内容 -->
      </body>
    </html>
    

    在这个例子中,虽然我们把<script> 元素放在了文档的<head> 元素中,但其中包含的脚本将延迟到浏览器遇到 标签后再执行。HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded 事件(详见第13章)执行。在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded 事件触发前执行,因此最好只包含一个延迟脚本。

    前面提到过,defer 属性只适用于外部脚本文件。这一点在HTML5中已经明确规定,因此支持HTML5的实现会忽略给嵌入脚本设置的defer 属性。IE4~IE7还支持对嵌入脚本的defer 属性,但IE8及之后版本则完全支持HTML5规定的行为。
    IE4、Firefox 3.5、Safari 5和Chrome是最早支持defer 属性的浏览器。其他浏览器会忽略这个属性,像平常一样处理脚本。为此,把延迟脚本放在页面底部仍然是最佳选择。
    在XHTML文档中,要把defer 属性设置为defer="defer" 。

    异步脚本

    HTML5为<script> 元素定义了async 属性。这个属性与defer 属性类似,都用于改变处理脚本的行为。同样与defer 类似,async 只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer 不同的是,标记为async 的脚本并不保证按照指定它们的先后顺序执行。例如:

    <!DOCTYPE html>
    <html>  
      <head>
        <title>Example HTML Page</title>
        <script type="text/javascript" async src="example1.js"></script>
    
    
        <script type="text/javascript" async src="example2.js"></script>
    
    
      </head>
      <body>
        <!-- 这里放内容 -->
      </body>
    </html>
    

    在以上代码中,第二个脚本文件可能会在第一个脚本文件之前执行。因此,确保两者之间互不依赖非常重要。指定async 属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。为此,建议异步脚本不要在加载期间修改DOM。

    异步脚本一定会在页面的load 事件前执行,但可能会在DOMContentLoaded 事件触发之前或之后执行。支持异步脚本的浏览器有Firefox 3.6、Safari 5和Chrome。
    在XHTML文档中,要把async 属性设置为async="async" 。

    嵌入代码与外部文件

    在HTML中嵌入JavaScript代码虽然没有问题,但一般认为最好的做法还是尽可能使用外部文件来包含JavaScript代码。不过,并不存在必须使用外部文件的硬性规定,但支持使用外部文件的人多会强调如下优点。

    • 可维护性 :遍及不同HTML页面的JavaScript会造成维护问题。但把所有JavaScript文件都放在一个文件夹中,维护起来就轻松多了。而且开发人员因此也能够在不触及HTML标记的情况下,集中精力编辑JavaScript代码。
    • 可缓存 :浏览器能够根据具体的设置缓存链接的所有外部JavaScript文件。也就是说,如果有两个页面都使用同一个文件,那么这个文件只需下载一次。因此,最终结果就是能够加快页面加载的速度。
    • 适应未来 :通过外部文件来包含JavaScript无须使用前面提到XHTML或注释hack。HTML和XHTML包含外部文件的语法是相同的。

    文档模式

    TODO.

    早期浏览器都面临一个特殊的问题,即当浏览器不支持JavaScript时如何让页面平稳地退化。对这个问题的最终解决方案就是创造一个

    • 浏览器不支持脚本;
    • 浏览器支持脚本,但脚本被禁用。

    符合上述任何一个条件,浏览器都会显示

    基本概念

    ECMAScript的语法大量借鉴了C及其他类C语言(如Java和Perl)的语法。因此,熟悉这些语言的开发人员在接受ECMAScript更加宽松的语法时,一定会有一种轻松自在的感觉。

    注释

    // 单行注释
    
    /*
     *  这是一个多行
     *  (块级)注释
     */
    

    严格模式

    ECMAScript 5引入了严格模式(strict mode)的概念。严格模式是为JavaScript定义了一种不同的解析与执行模型。在严格模式下,ECMAScript 3中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误。要在整个脚本中启用严格模式,可以在顶部添加如下代码:

    "use strict";
    

    这行代码看起来像是字符串,而且也没有赋值给任何变量,但其实它是一个编译指示(pragma),用于告诉支持的JavaScript引擎切换到严格模式。这是为不破坏ECMAScript 3语法而特意选定的语法。

    在函数内部的上方包含这条编译指示,也可以指定函数在严格模式下执行:

    function doSomething(){
        "use strict"; 
        //函数体
    }
    

    严格模式下,JavaScript的执行结果会有很大不同,因此本书将会随时指出严格模式下的区别。支持严格模式的浏览器包括IE10+、Firefox 4+、Safari 5.1+、Opera 12+和Chrome。

    变量

    ECMAScript的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值的占位符而已。定义变量时要使用var 操作符(注意var 是一个关键字),后跟变量名(即一个标识符),如下所示:

    var message;
    

    这行代码定义了一个名为message 的变量,该变量可以用来保存任何值(像这样未经过初始化的变量,会保存一个特殊的值——undefined )。ECMAScript也支持直接初始化变量,因此在定义变量的同时就可以设置变量的值,如下所示:

    var message = "hi";
    
    var message = "hi",
        found = false,
        age = 29;
    

    在严格模式下,不能定义名为eval 或arguments 的变量,否则会导致语法错误。

    数据类型

    ECMAScript中有5种简单数据类型(也称为基本数据类型):Undefined 、Null 、Boolean 、Number 和String 。还有1种复杂数据类型——Object ,Object 本质上是由一组无序的名值对组成的。ECMAScript不支持任何创建自定义类型的机制,而所有值最终都将是上述6种数据类型之一。乍一看,好像只有6种数据类型不足以表示所有数据;但是,由于ECMAScript数据类型具有动态性,因此的确没有再定义其他数据类型的必要了。

    typeof 操作符

    鉴于ECMAScript是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof 就是负责提供这方面信息的操作符。对一个值使用typeof 操作符可能返回下列某个字符串:
    "undefined" ——如果这个值未定义;

    	"boolean" ——如果这个值是布尔值;
    
    	"string" ——如果这个值是字符串;
    
    	"number" ——如果这个值是数值;
    
    	"object" ——如果这个值是对象或null ;
    
    	"function" ——如果这个值是函数。
    

    Undefined 类型

    Undefined 类型只有一个值,即特殊的undefined 。在使用var 声明变量但未对其加以初始化时,这个变量的值就是undefined

    一般而言,不存在需要显式地把一个变量设置为 undefined 值的情况。字面值 undefined 的主要目的是用于比较,而ECMA-262第3版之前的版本中并没有规定这个值。第3版引入这个值是为了正式区分空对象指针与未经初始化的变量。

    Null 类型

    Null 类型是第二个只有一个值的数据类型,这个特殊的值是null 。从逻辑角度来看,null 值表示一个空对象指针,而这也正是使用typeof 操作符检测null 值时会返回"object" 的原因.

    Boolean 类型

    Boolean 类型是ECMAScript中使用得最多的一种类型,该类型只有两个字面值:true 和false 。这两个值与数字值不是一回事,因此true 不一定等于1,而false 也不一定等于0。

    Number 类型

    Number 类型应该是ECMAScript中最令人关注的数据类型了,这种类型使用IEEE754格式来表示整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。为支持各种数值类型,ECMA-262定义了不同的数值字面量格式。

    由于内存的限制,ECMAScript并不能保存世界上所有的数值。ECMAScript能够表示的最小数值保存在Number.MIN_VALUE 中——在大多数浏览器中,这个值是5e-324;能够表示的最大数值保存在Number.MAX_VALUE 中——在大多数浏览器中,这个值是1.7976931348623157e+308。如果某次计算的结果得到了一个超出JavaScript数值范围的值,那么这个数值将被自动转换成特殊的Infinity 值。具体来说,如果这个数值是负数,则会被转换成-Infinity (负无穷),如果这个数值是正数,则会被转换成Infinity (正无穷)。

    NaN ,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。例如,在其他编程语言中,任何数值除以0都会导致错误,从而停止代码执行。但在ECMAScript中,任何数值除以0会返回NaN ,因此不会影响其他代码的执行。

    NaN 本身有两个非同寻常的特点。首先,任何涉及NaN 的操作(例如NaN /10)都会返回NaN ,这个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括NaN 本身。

    数值转换

    有3个函数可以把非数值转换为数值:Number() 、parseInt() 和parseFloat() 。第一个函数,即转型函数Number() 可以用于任何数据类型,而另两个函数则专门用于把字符串转换成数值。这3个函数对于同样的输入会有返回不同的结果。

    String 类型

    String 类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串。字符串可以由双引号(")或单引号(')表示.

    Object 类型

    ECMAScript中的对象其实就是一组数据和功能的集合。对象可以通过执行new 操作符后跟要创建的对象类型的名称来创建。而创建Object 类型的实例并为其添加属性和(或)方法,就可以创建自定义对象.

    with 语句

    with 语句的作用是将代码的作用域设置到一个特定的对象中。with 语句的语法如下:

    with (expression) statement;
    

    定义with 语句的目的主要是为了简化多次编写同一个对象的工作,如下面的例子所示:

    var qs = location.search.substring(1);
    var hostName = location.hostname;
    var url = location.href;
    

    上面几行代码都包含location 对象。如果使用with 语句,可以把上面的代码改写成如下所示:

    with(location){
        var qs = search.substring(1);
        var hostName = hostname;
        var url = href;
    }
    

    严格模式下不允许使用with 语句,否则将视为语法错误。
    由于大量使用 with 语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用 with 语句。

    函数

    函数对任何语言来说都是一个核心的概念。通过函数可以封装任意多条语句,而且可以在任何地方、任何时候调用执行。ECMAScript中的函数使用function 关键字来声明,后跟一组参数以及函数体。函数的基本语法如下所示:

    function functionName(arg0, arg1,...,argN) {
        statements
    }
    

    以下是一个函数示例:

    function sayHi(name, message) {
        alert("Hello " + name + "," + message);
    }
    

    理解参数

    ECMAScript函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的函数只接收两个参数,在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递参数,而解析器永远不会有什么怨言。之所以会这样,原因是ECMAScript中的参数在内部是用一个数组来表示的。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话)。如果这个数组中不包含任何元素,无所谓;如果包含多个元素,也没有问题。实际上,在函数体内可以通过arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数。

    其实,arguments 对象只是与数组类似(它并不是Array 的实例),因为可以使用方括号语法访问它的每一个元素(即第一个元素是arguments[0] ,第二个元素是argumetns[1] ,以此类推),使用length 属性来确定传递进来多少个参数。在前面的例子中,sayHi() 函数的第一个参数的名字叫name ,而该参数的值也可以通过访问arguments[0] 来获取。因此,那个函数也可以像下面这样重写,即不显式地使用命名参数:

    function sayHi() {
        alert("Hello " + arguments[0] + "," + arguments[1]);
    }
    

    没有重载

    ECMAScript函数不能像传统意义上那样实现重载。而在其他语言(如Java)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。如前所述,ECMAScirpt函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。

    如果在ECMAScript中定义了两个名字相同的函数,则该名字只属于后定义的函数。

    变量、作用域和内存问题

    基本类型和引用类型的值

    ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值 指的是简单的数据段,而引用类型值 指那些可能由多个值构成的对象。

    在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。第3章讨论了5种基本数据类型:Undefined 、Null 、Boolean 、Number 和String 。这5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

    引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。

    在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型的。ECMAScript放弃了这一传统。

    动态的属性

    定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。但是,当这个值保存到变量中以后,对不同类型值可以执行的操作则大相径庭。对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。请看下面的例子:

    var person = new Object();
    person.name = "Nicholas";
    alert(person.name);       //"Nicholas"
    

    以上代码创建了一个对象并将其保存在了变量person 中。然后,我们为该对象添加了一个名为name 的属性,并将字符串值"Nicholas" 赋给了这个属性。紧接着,又通过alert() 函数访问了这个新属性。如果对象不被销毁或者这个属性不被删除,则这个属性将一直存在。

    复制变量值

    当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量,如下面的例子所示:

    var obj1 = new Object();
    var obj2 = obj1;
    obj1.name = "Nicholas";
    alert(obj2.name);  //"Nicholas"
    

    没有块级作用域

    JavaScript没有块级作用域经常会导致理解上的困惑。在其他类C的语言中,由花括号封闭的代码块都有自己的作用域(如果用ECMAScript的话来讲,就是它们自己的执行环境),因而支持根据条件来定义变量。例如,下面的代码在JavaScript中并不会得到想象中的结果:

    if (true) {
        var color = "blue";
    }
    
    alert(color);    //"blue"
    

    这里是在一个if 语句中定义了变量color 。如果是在C、C++或Java中,color 会在if 语句执行完毕后被销毁。但在JavaScript中,if 语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。在使用for 语句时尤其要牢记这一差异,例如:

    for (var i=0; i < 10; i++){
        doSomething(i);
    }
    
    alert(i);      //10
    

    对于有块级作用域的语言来说,for 语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于JavaScript来说,由for 语句创建的变量i 即使在for 循环执行结束后,也依旧会存在于循环外部的执行环境中。

    引用类型

    引用类型的值(对象)是引用类型 的一个实例。在ECMAScript中,引用类型 是一种数据结构,用于将数据和功能组织在一起。它也常被称为类 ,但这种称呼并不妥当。尽管ECMAScript从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构。引用类型有时候也被称为对象定义 ,因为它们描述的是一类对象所具有的属性和方法。

    如前所述,对象是某个特定引用类型的实例 。新对象是使用new 操作符后跟一个构造函数 来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。请看下面这行代码:

    var person = new Object(); 
    

    Object类型

    到目前为止,我们看到的大多数引用类型值都是Object 类型的实例;而且,Object 也是ECMAScript中使用最多的一个类型。虽然Object 的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择。

    创建Object 实例的方式有两种。第一种是使用new 操作符后跟Object 构造函数,如下所示:

    var person = new Object();
    person.name = "Nicholas";
    person.age = 29;
    

    另一种方式是使用对象字面量 表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。下面这个例子就使用了对象字面量语法定义了与前面那个例子中相同的person 对象:

    var person = {
        name : "Nicholas",
        age : 29
    };
    

    Array类型

    除了Object 之外,Array 类型恐怕是ECMAScript中最常用的类型了。而且,ECMAScript中的数组与其他多数语言中的数组有着相当大的区别。虽然ECMAScript数组与其他语言中的数组都是数据的有序列表,但与其他语言不同的是,ECMAScript数组的每一项可以保存任何类型的数据。也就是说,可以用数组的第一个位置来保存字符串,用第二位置来保存数值,用第三个位置来保存对象,以此类推。而且,ECMAScript数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据。

    创建数组的基本方式有两种。第一种是使用Array 构造函数,如下面的代码所示。

    var colors = new Array();
    

    如果预先知道数组要保存的项目数量,也可以给构造函数传递该数量,而该数量会自动变成length 属性的值。例如,下面的代码将创建length 值为20的数组。

    var colors = new Array(20);
    

    检测数组

    自从ECMAScript 3做出规定以后,就出现了确定某个对象是不是数组的经典问题。对于一个网页,或者一个全局作用域而言,使用instanceof 操作符就能得到满意的结果:

    if (value instanceof Array){ 
        //对数组执行某些操作
    }
    

    Function类型

    说起来ECMAScript中什么最有意思,我想那莫过于函数了——而有意思的根源,则在于函数实际上是对象。每个函数都是Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的,如下面的例子所示。

    function sum (num1, num2) {
        return num1 + num2;
    }
    

    这与下面使用函数表达式定义函数的方式几乎相差无几。

    var sum = function(num1, num2){
        return num1 + num2;
    };
    

    单体内置对象

    ECMA-262对内置对象的定义是:“由ECMAScript实现提供的、不依赖于宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在了。”意思就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了。前面我们已经介绍了大多数内置对象,例如Object 、Array 和String 。ECMA-262还定义了两个单体内置对象:Global 和Math 。

    Global 对象

    Global (全局)对象可以说是ECMAScript中最特别的一个对象了,因为不管你从什么角度上看,这个对象都是不存在的。ECMAScript中的Global 对象在某种意义上是作为一个终极的“兜底儿对象”来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是Global 对象的属性。本书前面介绍过的那些函数,诸如isNaN() 、isFinite() 、parseInt() 以及parseFloat() ,实际上全都是Global 对象的方法。除此之外,Global 对象还包含其他一些方法。

    eval() 方法

    通过eval() 执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过eval() 执行的代码可以引用在包含环境中定义的变量,举个例子:

    var msg = "hello world";
    eval("alert(msg)");    //"hello world"
    

    面向对象的程序设计

    面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。前面提到过,ECMAScript中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。

    ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样(以及其他将要讨论的原因),我们可以把ECMAScript的对象想象成散列表:无非就是一组名值对,其中值可以是数据或函数。

    每个对象都是基于一个引用类型创建的,这个引用类型可以是第5章讨论的原生类型,也可以是开发人员定义的类型。

    var person = {
        name: "Nicholas", 
        age: 29,
        job: "Software Engineer",
    
        sayName: function(){
            alert(this.name);
        }
    };
    

    模仿块级作用域

    如前所述,JavaScript没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的,来看下面的例子。

    function outputNumbers(count){
        for (var i=0; i < count; i++){
            alert(i);
        }
        alert(i);   //计数
    }
    

    这个函数中定义了一个for 循环,而变量i 的初始值被设置为0。在Java、C++等语言中,变量i 只会在for 循环的语句块中有定义,循环一旦结束,变量i 就会被销毁。可是在JavaScrip中,变量i 是定义在ouputNumbers() 的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它。即使像下面这样错误地重新声明同一个变量,也不会改变它的值。

    function outputNumbers(count){
        for (var i=0; i < count; i++){
            alert(i);
        }
    
        var i;         //重新声明变量
    
    
        alert(i);      //计数
    }
    

    JavaScript从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。匿名函数可以用来模仿块级作用域并避免这个问题。

    用作块级作用域(通常称为私有作用域 )的匿名函数的语法如下所示。

    (function(){
        //这里是块级作用域
    })();
    

    以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。

    无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:

    function outputNumbers(count){
    
    
        (function () {
            for (var i=0; i < count; i++){
                alert(i);
            }
        })();
    
    
    
        alert(i);   //导致一个错误!
    }
    
  • 相关阅读:
    Salesforce和SAP Netweaver里数据库表的元数据设计
    Salesforce平台支持多租户Multi tenant的核心设计思路
    S/4HANA生产订单增强WORKORDER_UPDATE方法BEFORE_UPDATE参数分析
    S/4HANA生产订单的标准状态和透明工厂原型状态的映射
    1048. Find Coins (25)
    1101. Quick Sort (25)
    1009. Product of Polynomials (25)
    pta 奇数值结点链表&&单链表结点删除
    1007. Maximum Subsequence Sum (25)
    1006. Sign In and Sign Out (25)
  • 原文地址:https://www.cnblogs.com/chendeqiang/p/12861610.html
Copyright © 2020-2023  润新知