标签 javascript 下的文章

JavaScript 规范编程笔记:继承

JavaScript是一门弱类型的语言,从不需要类型转换。对象的起源是无关紧要的。对于一个对象来说重要的事它能做什么,而不是它从哪里来。JavaScript提供了一套更为丰富的代码重用模式。它可以模拟那些基于类的模式,同时它也可以支持其他更具表现力的模式。在JavaScript中可能的继承模式有很多。下面将介绍几种最为直接的模式。

在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。

1. 伪类

当一个函数对象被创建时,Function构造器产生的函数对象会运行类似这样的一些代码:

this.prototype = {constructor: this};

该prototype对象是存放继承特征的地方。因为JavaScript语言没有提供一种方法去确定哪个函数是打算用来作构造器的,所以每个函数都会得到一个prototype对象。

当采用构造器调用模式,即使用new前缀去调用一个函数时,这将修改函数执行的方式。如果new运算符是一个方法而不是一个运算符,它将可能会像这样执行:

Function.method('new', function(){

    // 创建一个新对象,它继承自构造器函数的原型对象
    var that = Object.beget(this.prototype);

    // 调用构造器函数,绑定 this 到新对象上
    var other = this.apply(that, arguments);

    // 如果它的返回值不是一个对象,就返回该新对象
    return (typeof other === 'object' && other) || that;
});

以下是一个示例:

var Mammal = function(name){
    this.name = name;
};

Mammal.prototype.get_name = function(){
    return this.name;
};

Mammal.prototype.says = function(){
    return this.saying || '';
};

var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name();

以下我们可以构造另一个伪类来继承Mammal,通过定义它的构造函数并替换它的prototype为一个Mammal的实例来实现:

var Cat = function(name){
    this.name = name;
    this.saying = 'meow';
};

Cat.prototype = new Mammal();

Cat.prototype.purr = function(n){
    var i, s = '';
    for(i = 0; i < n; i += 1){
        if(s){
            s += '-';
        }
        s += 'r';
    }
    return s;
};

Cat.prototype.get_name = function(){
    return this.says() + ' ' + this.name + ' ' + this.says();
};  

var myCat = new Cat('Henrietta');
var says = myCat.says();
var purr = myCat.purr(5);
var name = myCat.get_name();

document.writeln('says : ' + says + '; purr : ' + purr + '; name : ' + name);
// says : meow; purr : r-r-r-r-r; name : meow Henrietta meow

伪类模式本意是想向面向对象靠拢,但是它看起来格格不入。我们甚至可以自行定义一个inherits方法来隐藏内部的细节:

Function.method('inherits', function(Parent){
    this.prototype = new Parent();
    return this;
});

伪类形式可以给不熟悉JavaScript的程序员提供便利,但是它也隐藏了该语言的真实本质。借鉴类的表示法可能误导程序员去编写过于深入复杂的层次结构。许多复杂的类层次结构产生的原因就是静态类型检查的约束。JavaScript完全摆脱了那些约束。在基于类的语言中,类的继承是代码重用的唯一方式。显然,JavaScript有着更多且更好的选择。

2. 对象说明符

var myObject = maker({
    first: f,
    last: l,
    state: s,
    city: c
});

当与JSON一起工作时,这还可以有一个间接的好处。JSON文本只能描述数据,但有时数据表示的是一个对象,如果构造器取得一个对象说明符,可以容易的做到,因为我们可以简单的传递该JSON对象给构造器,而它将返回一个构造完全的对象。

3. 原型

基于原型的继承相比于基于类的继承在概念上更为简单:一个新对象可以继承一个旧对象的属性。通过构造一个有用的对象开始,接着可以构造更多和那个对象类似的对象。可以完全避免把一个应用拆解成一系列嵌套抽象类的分类过程。

var myMa = {
    name : 'Herb the Mammal',
    get_name : function(){
        return this.name;
    },
    says : function(){
        return this.saying || '';
    }
}; 

// 差异化继承
var myCatMa = Object.beget(myMa);

myCatMa.name = 'Henrietta';
myCatMa.saying = 'meow';
myCatMa.purr = function(n){
    var i, s = '';
    for(i = 0; i < n; i += 1){
        if(s){
            s += '-';
        }
        s += 'r';
    }
    return s;
};
myCatMa.get_name = function(){
    return this.says() + ' ' + this.name + ' ' + this.says();
};

document.writeln(myCatMa.get_name());

4. 函数化

上述几种继承方式的一个弱点是我们没法保护隐私,对象的所有属性都是可见的。幸运的是,我们有一个更好的选择,那就是模块模式的应用。以下是构造一个产生对象的函数的步骤:

  1. 创建一个新对象,可以使用多种方式;
  2. 选择性的定义私有实例变量和方法;
  3. 给这个新对象扩充方法,这些方法将拥有特权去访问参数,以及新定义的变量;
  4. 返回新对象。

以下是上述步骤的伪码模板:

var constructor = function (spec, my){
    var that, ……;// 其他的私有实例变量
    my = my || {};

    // 把共享的变量和函数添加到my中

    that = // 一个新对象

    // 添加给 that 的特权方法

    return that;
};

spec对象包含构造器需要构造一个新实例的所有信息。spec的内容可能会被复制到私有变量中,或者被其他函数改变,或者方法可以在需要的时候访问spec的信息。my对象是一个为继承链中的构造器提供共享的容器。my 对象可以选择性的使用。

让我们将这个步骤应用到mammal例子中,此处不需要my,所以我们先抛开它,但将使用一个spec对象:

var mammal = function(spec){
    var that = {};
    that.get_name = function(){
        return spec.name;
    };

    that.says = function(){
        return spec.saying || '';
    };

    return that;
};

var myMammal = mammal({name: 'Herb'});

document.writeln(myMammal.get_name());

var cat = function(spec){
    spec.saying = spec.saying || 'meow';
    var that = mammal(spec);
    that.purr = function(n){
        var i, s = '';
        for(i = 0; i < n; i += 1){
            if(s){
                s += '-';
            }
            s += 'r';
        }
        return s;
    };
    that.get_name = function(){
        return that.says() + ' ' + spec.name + ' ' + that.says();
    };

    return that;
};

var myCats = cat({name: 'Cate',saying: 'Ni Ma B'});

document.writeln(myCats.get_name());

函数化模式还给我们提供了一个处理父类方法的方法:

Object.method('superior',function(name){
    var that = this,
        method = that[name];
    return function(){
        return method.apply(that, arguments);
    };
});

以下是在coolcat上的实验,它将返回父对象cat中的方法执行结果:

var coolcat = function(spec){
    var that = cat(spec),
        super_get_name = that.superior('get_name');
    that.get_name = function(n){
        return 'like ' + super_get_name() + ' bady';
    };
    return that;
};

var myCoolCat = coolcat({name: 'Bix'});
// like meow Bix meow baby

函数化模式有很大的灵活性。它不仅不像伪类模式那样需要很多功夫,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。

如果使用函数化的样式创建一个对象,并且该对象的所有方法都不使用this 或 that,那么该对象就是持久性的。一个持久性的对象就是一个简单功能函数的集合。

JavaScript 规范编程笔记:函数(二)

1. 给类型增添方法

JavaScript 允许给语言的基本类型增加方法。以下是各种例子:

// 通过给 Function.prototype 增加方法使得该方法对所有函数可用
Function.prototype.method = function(name, func){
    this.prototype[name] = func;
    return this;    
};

// 给数字类型添加一个提取数字中的整数部分的方法
Number.method('integer', function(){
    return Math[this < 0 ? 'ceiling' : 'floor'](this);
});

document.writeln((-10 / 3).integer());  // -3

// 给字符串添加一个 trim 方法
String.method('trim', function(){
    return this.replace(/^\s+|\s+$/g, '');
});

document.writeln('"' + " neat ".trim() + '"');

基本类型的原型是公共的结构,所以在类库混用时需要判断,只在确定没有该方法时才添加它。

// 有条件的增加一个方法
Function.prototype.method = function(name, func){
    if(!this.prototype[name]){
        this.prototype[name] = func;
    }
    return this;
};

2. 变量作用域

JavaScript只有函数作用域,而不支持会计作用域。那意味着定义在函数中德参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都可见。

3. 闭包

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量。一个更有趣的情形是内部函数拥有比它的额外部函数更长的生命周期。

// 此函数定义了一个value变量,该变量对increment和getValue方法总是可用的,
// 但函数的作用域使得它对其他的程序来说是不可见的

// 该函数将返回一个包含两个方法的对象。
var myObject = function(){
    var value = 0;
    return {
        increment: function(inc) {
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function(){
            return value;
        }
    };
}();

下面是一个更有意义的函数。

// 创建一个名为quo的构造函数
// 它构造出带有get_status方法和status私有属性的一个对象。
var quo = function(status){
    return {
        get_status: function(){
            return status;
        }
    };
};

var myQuo = quo('amazed');
document.writeln(myQuo.get_status());
当我们调用quo时,它返回包含get_status方法的一个新对象。该对象的一个引用保存在myQuo中。即使quo已经返回了,但get_status方法仍然享有访问quo对象的status属性的特权。get_status方法并不是访问该参数的一个拷贝,它访问的就是该参数本身。这使可能的,因为函数可以访问它被创建时所处的上下文环境,这被称为**闭包**。
// 另一个更有用的例子
var fade = function(node){
    var level = 1;
    var step = function(){
        var hex = level.toString(16);
        node.style.backgroundColor = '#FFFF' + hex + hex;
        if(level < 15){
            level += 1;
            setTimeout(step, 100);
        }
    };

    setTimeout(step, 100);
}

fade(document.body);

理解内部函数能访问外部函数的实际变量而无需复制是很重要的:

// 糟糕的例子

// 当点击一个节点时,按照预想应该弹出一个对话框显示节点的序号
// 但它总是会显示节点的数目
var add_the_handlers = function(nodes){
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function(){
            alert(i);
        }
    };
};

add_the_handlers 函数目的是给每个事件处理器一个唯一值(i)。它未能达到目的是因为事件处理器函数绑定了变量i,而不是函数在构造时的变量i的值。

// 更好的例子
var add_the_handlers_bb = function(nodes){
    var i;
    for (i = 0; i < nodes.length; i += 1) {
        nodes[i].onclick = function(b){
            return function(){
                alert(b);
            };
        }(i);
    };
};

现在,我们定义了一个函数并立即传递i进去执行,而不是把一个函数赋值给onclick。那个函数将返回一个事件处理器函数。这个事件处理器函数绑定的是传递进去的i的值,而不是定义在add_the_handlers函数里的i的值。那个被返回的函数将被赋值给onclick。

4. 模块

我们可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。

以下是一个模块的例子,它的任务是寻找字符串中的HTML字符实体并替换为它们对应的字符。它保存字符实体的名字和它们对应的字符放入一个闭包中。

String.method('deentityify', function(){
    // 字符实体表,它映射字符实体的名字到对应的字符。
    var entity = {
        quot: '"',
        lt: '<',
        gt: '>'
    };

    return function(){
        // 这才是 deentityify方法
        return this.replace(/&([^&;]+);/g, 
                function(a,b){
                    var r = entity[b];
                    return typeof r === 'string' ? r : a;
                }
            );
    };

}());

document.writeln('&lt;&quot;&gt;'.deentityify()); // <">

请注意最后一行,我们用()运算符立即调用我们刚刚构造出来的函数。这个调用所创建并返回的函数才是deentityify方法。在这个例子中,只有deentityify方法有权访问字符实体表这个数据对象。

模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可访问到的地方。

由此,我们可以使用模块模式产生安全的对象,类似JavaBean的形式:

var serial_maker = function(){

    var prefix = '';
    var seq = 0;
    return {
        set_prefix: function(p){
            prefix = String(p);
        },
        set_seq: function(s){
            seq = s;
        },
        gensym: function(){
            var result = prefix + seq;
            seq += 1;
            return result;
        }
    };
};

var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();

JavaScript 规范编程笔记:函数(一)

一般来说,所谓编程就是将一组需求分解成一组函数与数据结构的技能。

JavaScript中最好的特性就是它对函数的实现,它几乎无所不能。函数包含子组语句,它们是JavaScript的基础模块单元,用于代码复用、信息隐藏和组合调用。函数指定对象的行为。

1. 函数对象

在JavaScrpit中函数也是对象。对象字面量产生的对象连接到Object.prototype上,而函数对象连接到Function.prototype上(该原型对象本身连接到Object.prototype)。每个函数在创建时附有两个附加的隐藏属性:函数的上下文和实现函数行为的代码(JavaScript创建一个函数对象时,会给该对象设置一个“调用”属性)。

每个函数对象在创建时也随带有一个prototype属性。它的值是一个拥有constructor属性且值即为该函数的对象。

因为函数是对象,所以它们可以像任何其他的值一样被使用。函数可以存放在变量、对象和数组中,函数可以被当作参数传递给其他函数,函数也可以再返回函数。而且,因为函数是对象,所以函数可以拥有方法。

2. 函数字面量

// 创建一个名为 add 的变量,并用来把两个数字相加的函数赋值给它
var add = function (a, b){
    return a + b;   
};

函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其他函数中。一个内部函数自然可以访问自己的参数和变量,同时它也能方便的访问它被嵌套在其中的那个函数的参数与变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包

3. 调用

在JavaScript中一共有四种调用模式:方法调用模式、函数调用模式、构造器调用模式和apply调用模式。调用函数时,除了声明时定义的形式参数,每个函数接收两个附加的参数:this和arguments。参数this在面向对象变成中非常重要,它的值取决于调用的模式。调用运算符是跟在任何产生一个函数值的表达式之后的一对圆括号。圆括号中包含了参数(如果有的话),当实际参数的个数过多时,超出的参数值将被忽略,如果实际参数值过少,缺失的值将会被替代为undefined。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。

// 常见 myObject,它有一个value属性和一个increment方法
// increment方法接受一个可选的参数。如果参数不是数字,那么默认使用数字1。
var myObject = {
    value: 0,
    increment: function(inc){
        this.value += typeof inc === 'number' ? inc : 1;
    }
};

myObject.increment();
document.writeln(myObject.value); // 1

myObject.increment(2);
document.writeln(myObject.value); // 3

this到对象的绑定发生在调用的时候,这个“超级”迟绑定使得函数可以对this高度复用。通过this可取得它们所属对象的上下文的方法称为公共方法

函数调用模式

当一个函数并非一个对象的属性时,那么它被当作一个函数来调用:

var sum = add(3, 4);   // sum 为 7

当函数以此模式调用时,this被绑定到全局对象。这是一个设计错误,这个错误的后果是方法不能利用内部函数来帮助它工作。解决方法是:如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过哪个变量访问到this。按照约定,我给这个变量命名为that:

// 给myObject增加一个double方法。
myObject.double = function(){
    var that = this; // this指向全局变量的解决方法

    var helper = function(){
        that.value = add(that.value, that.value);
    };

    helper(); // 以函数的形式调用helper。
};

// 以方法的形式调用double。
myObject.double();
document.writeln(myObject.value); // 6

构造器调用模式

尽管原型继承有着强大的表现力,但它并不被广泛理解。JavaScript本身对其原型的本质也缺乏信心,所以它提供了一套和基于类的语言类似的对象构建语法。

如果在一个函数前面带上new来调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象,同时this将会被绑定到那个新对象上。

// 创建一个名为Quo的构造器函数,它构造一个带有status属性的对象。
var Quo = function(string){
    this.status = string;
};

// 给Quo的所有实例提供一个名为get_status的公共方法。
Quo.prototype.get_status = function(){
    return this.status;
};

// 构造一个Quo实例
var myQuo = new Quo("confused");

document.writeln(myQuo.get_status()); // 令人困惑

结合new前缀调用的函数被称为构造器函数。按照约定,它们保存在以大写格式命名的变量里。如果调用构造器函数时没有在前面加上new,可能会发生非常糟糕的事情,既没有编译时警告,也没有运行时警告,因此大写约定非常重要。不推荐使用这种形式的构造器函数。

Apply 调用模式

因为JavaScript是一门函数式的面向对象编程语言,所以函数可以拥有方法。apply方法让我们创建一个参数数组并用其去调用函数。它允许我们选择this的值。apply方法接收两个参数,第一个将被绑定给this的值,第二个就是一个参数数组。

var array = [3, 4];
var sum = add.apply(null, array); // 7

var statusObject = {
    status: 'A-OK'
};

// get_status 没有参数,statusObject指定了将被绑定给this的值
var status = Quo.prototype.get_status.apply(statusObject);
document.writeln(status); // A-OK

4. 参数

当函数被调用时,会得到一个“免费”奉送的参数,那就是 arguments 数组。通过它函数可以访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的形式参数的多余参数。这使得编写一个无须指定参数个数的函数成为可能:

// 构造一个将很多个值想加的函数
var sum = function(){
    var i, sum = 0;
    for(i = 0; i < arguments.length; i += 1){
        sum += arguments[i];
    }   
    return sum;
};

document.writeln(sum(4, 8, 15, 16, 23, 42));  // 108

因为语言的一个设计错误,arguments并不是一个真正的数组,arguments拥有一个length属性,但它缺少所有数组方法。

5. 返回

return 语句可用来使函数提前返回。当return被执行时,函数立即返回而不再执行余下的语句。一个函数总是会返回一个值。如果没有指定返回值,则返回undefined。

6. 异常

JavaScrpit提供了一套异常处理机制。throw语句中断函数的执行,它应该抛出一个exception对象,该对象包含可识别异常类型的name属性和一个描述性的message属性。你也可以添加其他的属性。

var add_throw = function(a,b){
    if(typeof a !== 'number' || typeof b !== 'number'){
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        };
    }
    return a + b;
};

该exception对象将被传递到一个try语句的catch从句:

var try_it = function(){
    try {
        add_throw('sever');
    } catch (e) {
        document.writeln(e.name + ': ' + e.message);
    }
};

try_it();

一个try语句只会有一个将捕获所有异常的catch代码块。如果你的处理手段取决于异常的类型,那么异常处理器必须检查异常对象的name属性以确定异常的类型。

JavaScript 规范编程笔记:对象

对象

没有对象我可以试着 new 一个
在其中我可以重载甚至覆盖任何一种方法
但是我却无法重载对你的思念
也许命中注定
你在我的世界里永远的烙上了静态的属性
而我不慎调用了爱你这个方法
当我义无返顾的把自己作为参数传进这个方法时
我才发现爱上你是一个死循环

JavaScript的简单类型包括数字、字符串、布尔值(true和false)、null值和undefined值,其他所有的值都是对象。在JavaScript中,数组是对象,函数是对象,正则表达式是对象,当然,对象自然也是对象。对象是属性的容器,其中每个属性都拥有名字和值。属性的名字可以是包括空字符串在内的任意字符串。属性值可以是除undefined值之外的任何值。

JavaScript包括一个原型链特性,允许对象继承另一个对象的属性。

1. 对象字面量

对象字面量提供了一种非常方便的创建新对象值得表示法。一个对象字面量就是包裹在一对花括号中的零或多个“名/值”对。

var empty_object = {};

var stooge = {
    "first-name": "Jerome",
    "last-name": "Howard"   
};

对象是可嵌套的。

var flight = {
    airline: "Oceanic",
    number: 815,
    departure: {
        IATA: "SYD",
        time: "2004-09-22",
        city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2004-09-23",
        city: "Los Angeles"
    }
};

2. 检索

要检索对象中包含的值,可以采用在[]后缀中括住一个字符串表达式的方式。如果字符串表达式是一个常数,也可以使用.表示法代替。

stooge["first-name"]   // "Joe"
flight.departure.IATA  // "SYD"

如果尝试检索一个并不存在的成员元素的值,将返回一个undefined值。

stooge["middle-name"]   // undefined
flight.status           // undefined

尝试检索一个undefined值将会导致TypeError异常。这可以通过&&运算符来避免错误。

flight.equipment    // undefined
flight.equipment.model // throw "TypeError"
flight.equipment && flight.equipment.model  // undefined

3. 引用

对象通过引用来传递,它们永远不会被拷贝:

var x = stooge;
x.nickname = 'Curly';
var nick = stoogee.nickname;   // 'Curly'

var a = {}, b = {}, c = {};  // a、b和c每个都引用一个不同的空对象
a = b = c = {};  // a、b和c都应用同一个空对象

4. 原型

每个对象都连接到一个原型对象,并且它可以从中继承属性。所有通过对象字面量创建的对象都连接到 Object.prototype 这个JavaScript中标准的对象。

当你创建一个新对象时,你可以选择某个对象作为它的原型。如下我们将给Object增加一个beget方法,这个方法将创建一个使用原对象作为其原型的新对象。

if(typeof Object.beget !== 'function'){
    Object.beget = function(o){
        var F = function(){};
        F.prototype = o;
        return new F();
    };
}

var another_stooge = Object.beget(stooge);

如果我们尝试去获取对象的某个属性值,且该对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果哪个原型对象也没有该属性,那么再从它的原型中寻找,依次类推,直到该过程最后到达终点Object.prototype.如果想要的属性完全不存在于原型链中,那么结果就是undefined值。这个过程称为委托

原型关系是一种动态的关系,如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。

stooge.profession = 'actor';
another_stooge.profession   // 'actor'

5. 反射

检查对象并确定对象有什么属性是很容易的事情,只要试着去检索该属性并验证取得的值。typeof操作符对确定属性的类型很有帮助:

typeof flight.number    // 'number'
typeof flight.status    // 'string'
typeof flight.arrival   // 'object'
typeof flight.manifest  // 'undefined'

但是注意原型链中德任何属性也会产生一个值:

typeof flight.toString  // 'function'
typeof flight.constructor // 'function'

可以使用 hasOwnProperty 方法剔除这些不需要的属性:

flight.hasOwnProperty('number')       // true
flight.hasOwnProperty('constructor')  // false

6. 枚举

for in 语句可用来遍历一个对象中德所有属性名。并且遍历过程将会列出所有的属性——包括函数和你可能不关心的原型中德属性——所以有必要过滤掉那些不想要的值。

var name;
for (name in another_stooge) {
    if(typeof another_stooge[name] !== 'function') {
        document.writeln(name + ': ' + another_stooge[name]);
    }
}

属性名出现的顺序是不确定的,如果想要确保属性以特定的顺序出现,最好的方法就是完全避免使用 for in 语句,而是创建一个数组,在其中以正确的顺序包含属性名,通过使用 for 而不是 for in,可以得到我们想要的属性。

7. 删除

delete 运算符可以用来删除对象的属性。它将会移除对象中确定包含的属性。它不会触及原型中德任何对象。删除对象的属性可能会让来自原型中的属性浮现出来:

another_stooge.nickname // 'Moe'
// 删除 another_stooge 的 nickname 属性,从而暴露出原型的 nickname 属性
delete another_stooge.nickname;
another_stooge.nickname  // 'Curly'

8. 减少全局变量污染

最小化使用全局变量的一个方法是在你的应用中只创建唯一一个全局变量:

var MYAPP = {};

然后将此变量作为你的应用的容器:

MYAPP.stooge = {
    "first-name": "Joe",
    "last-name": "Howard"   
};

MYAPP.flight = {
    ....
}

JavaScript 规范编程笔记:语法

JavaScript 语法

本节将简要介绍JavaScript的语法,并简要地概述其语言结构。

1. 注释

JavaScript提供了两种注释形式,一种是用/* */包围的块状注释,另一种是以//为开头的行注释。注释应该被充分用来提高程序的可读性。必须注意的是,注释一定要精确的描述代码。没有用的注释比没有注释更糟糕。

其中,块注释对于被注释的代码块来说是不安全的额。例如下面的代码将导致一个语法错误:

/*
    var rm_a = /a*/.match(s);
*/

因此应避免使用/* */注释,而用//注释代替它。

2. 标识符

JavaScript保留字列表:

breakdeletefunctionreturntypeof
casedoifswitchvar
catchelseinthisvoid
continuefalseinstanceofthrowwhile
debuggerfinallynewtruewith
defaultfornulltry 

JavaScript未来保留字(这些单词虽然现在没有用到JavaScript中,但将来有可能用到):

abstractdoublegotonativestatic
booleanenumimplementspackagesuper
byteexportimportprivatesynchronized
charextendsintprotectedthrows
classfinalinterfacepublictransient
constfloatlongshortvolatile

注意,其中不包括undefinedNaNInfinity等这些本应被保留的字。

3. 数字

JavaScript中只有一个单一的数字类型,它在内部被表示为64位的浮点数,和Java的double一样。因此11.0是相同的值。

指数表示法,如100可表示为1e2。负数可以用前缀运算符-来构成。值NaN是一个数值,它表示一个不能产生正常结果的运算结果。NaN不等于任何值,包括它自己。你可以使用函数isNaN(number)检测NaN。值Infinity表示所有大于1.79769313486231570e+308的值。JavaScript提供了一个对象Math,它包括一套作用于数字的方法。

4. 字符串

字符串字面量使用单引号或双引号包围,它可能包含0个或多个字符。\(反斜杠)是转义符。JavaScript在被创建的时候,Unicode是一个16位的字符集,因此JavaScript中德所有字符都是16位的。

字符串有一个length属性,例如"seven".length是5。字符串使用+运算符进行连接,此外JavaScript还为字符串提供了一些方法。

5. 语句

一个编译单元包含一组可执行的语句。在web浏览器中,每个<script>标签都提供一个被编译且立即执行的编译单元。因为缺少链接器,JavaScript把它们一起抛入一个公共的全局名字空间中。

代码块是包在一对花括号的一组语句。不像许多其他的语言,JavaScript中德代码块不会创建一个新的作用域,因此变量应该被定义在函数的顶端,而不是代码块中。

var语句被用在函数的内部时,它定义了这个函数的私有变量。

下面的值被当作假(false):

  • false
  • null
  • undefined
  • 空字符串 ''
  • 数字 0
  • 数字 NaN

其他所有的值都被当作真,包括true、字符串"false",以及所有的对象。

for in 语句会枚举一个对象的所有属性名(或键名),在每次循环中,对象的另一个属性名字符串被赋值给for和in之间的变量。通常你需要通过检测object.hasOwnProperty(variable)来确定这个属性名就是该对象的成员,还是从其原型链里找到的。

for(myvar in obj){
    if(obj.hasOwnProperty(myvar)){
        ...
    }
}

6. 表达式

最简单的表达式是字面量值(比如字符串或数字)、变量、内置的值(true、false、null、undefined、NaN和Infinity)、以new前导的调用表达式、以delete前导的属性存取表达式、包在圆括号中德表达式、以一个前缀运算符作为前导的表达式,或者表达式后面跟着:

  • 一个插入运算符与另一个表达式;
  • 三元运算符?后面跟着另一个表达式,然后接一个:,再然后接第三个表达式;
  • 一个函数调用;
  • 一个属性存取表达式。

函数调用引发函数的执行,函数调用运算符是跟随在函数名后面的一对圆括号。圆括号中可能包含将会传递给这个函数的参数。一个属性存取表达式用于指定一个对象或数组的属性或元素。

7. 字面量

对象字面量是一种方便指定新对象的表示法。属性名可以是标示符或字符串。这些名字被当作字面量名而不是变量名来对待,所以对象的属性名在编译时才能知道。属性的值就是表达式。

8. 函数

函数字面量定义了函数值,它可以有一个可选的名字,用于递归的调用自己。它可以指定一个参数列表,这些参数将作为变量由调用时传递的实际参数(arguments)初始化。函数的主体包括变量定义的语句。