javascript基础第二章

## 对象字面量

我们可以将JavaScript中的对象简单地理解为名值对组成的散列表(hash table,也叫哈希表)。在其他编程语言中被称作“关联数组”。其中的值可以是原始值也可以是对象。不管是什么类型,它们都是“属性”(property),属性值同样可以是函数,这时属性就被称为“方法”(method)。
JavaScript中自定义的对象(用户定义的本地对象)任何时候都是可变的。内置本地对象的属性也是可变的。你可以先创建一个空对象,然后在需要时给它添加功能。“对象字面量写法(object literal notation)”是按需创建对象的一种理想方式。
看一下这个例子:

// 定义空对象 var  dog = {}; // 添加一个属性 dog.name =  "Benji" ; // 添加一个方法 dog.getName =  function  () {        return  dog.name; };

在这个例子中,我们首先定义了一个空对象,然后添加了一个属性和一个方法,在程序的生命周期内的任何时刻都可以:
更改属性和方法的值,比如:
dog.getName = function () { // 重新定义方法,返回一个硬编码的值 return “Fido”; };
删除属性/方法
delete dog.name;
添加更多的属性和方法
dog.say = function () { return “Woof!”; };

dog.fleas = true;
每次都创建空对象并不是必须的,对象字面量模式可以直接在创建对象时添加功能,就像下面这个例子:

var  dog = {        name:  "Benji" ,        getName:  function  () {              return  this .name;        } };

其实“空对象”(“blank object”和“empty object”),这只是一种简称,在JavaScript中根本不存在真正的空对象,理解这一点至关重要。即使最简单的{}对象也会包含从Object.prototype继承来的属性和方法。我们提到的“空(empty)对象”只是说这个对象没有自有属性(own properties),不考虑它是否有继承来的属性。

## 对象字面量语法

如果你从来没有接触过对象字面量的写法,可能会感觉怪怪的。但越到后来你就越喜欢它。本质上讲,对象字面量语法包括:
       将对象主体包含在一对花括号内({ 和 })。
       对象内的属性或方法之间使用逗号分隔。最后一个名值对后也可以有逗号,但在IE下会报错,所以尽量不要在最后一个属性或方法后加逗号。
       属性名和值之间使用冒号分隔。
       如果将对象赋值给一个变量,不要忘了在右括号}之后补上分号。

## 通过构造函数创建对象

JavaScript中没有类的概念,这给JavaScript带来了极大的灵活性,因为你不必提前知晓关于对象的任何信息,也不需要类的“蓝图”(译注:指类的结构)。但JavaScript同样具有构造函数,它的语法和Java或其他语言中基于类的对象创建非常类似。
你可以使用自定义的构造函数来创建对象实例,也可以使用内置构造函数来创建,比如Object()、Date()、String()等等。
下面这个例子展示了用两种等价的方法分别创建两个独立的实例对象:
// 一种方法,使用字面量
var car = {goes: “far”};

// 另一种方法,使用内置构造函数
// 注意:这是一种反模式
var car = new Object();
car.goes = “far”;
从这个例子中可以看到,字面量写法的一个明显优势是,它的代码更少。“创建对象的最佳模式是使用字面量”还有一个原因,它可以强调对象就是一个简单的可变的散列表,而不必一定派生自某个类。
另外一个使用字面量而不是Object()构造函数创建实例对象的原因是,对象字面量不需要“作用域解析”(scope resolution)。因为存在你已经创建了一个同名的构造函数Object()的可能,当你调用Object()的时候,解析器需要顺着作用域链从当前作用域开始查找,直到找到全局Object()构造函数为止。

## Object()构造函数的参数

译注:这小节的标题是Object Constructor Catch。
创建实例对象时能用对象字面量就不要使用new Object()构造函数,但有时你可能是在别人写的代码基础上工作,这时就需要了解构造函数的一个“特性”(也是不使用它的另一个原因),就是Object()构造函数可以接收参数,通过这个参数可以把对象实例的创建过程委托给另一个内置构造函数,并返回另外一个对象实例,而这往往不是你想要的。
下面的示例代码中展示了给new Object()传入不同的参数(数字、字符串和布尔值),最终得到的对象是由不同的构造函数生成的:

// 注意:这是反模式 // 空对象 var  o =  new  Object(); console.log(o.constructor === Object);  // true // 数值对象 var  o =  new  Object(1); console.log(o.constructor === Number);  // true console.log(o.toFixed(2));  // "1.00" // 字符串对象 var  o =  new  Object( "I am a string" ); console.log(o.constructor === String);  // true // 普通对象没有substring()方法,但字符串对象有 console.log( typeof  o.substring);  // "function" // 布尔值对象 var  o =  new  Object( true ); console.log(o.constructor === Boolean);  // true

Object()构造函数的这种特性会导致一些意想不到的结果,特别是当参数不确定的时候。最后再次提醒不要使用new Object(),尽可能的使用对象字面量来创建实例对象。

## 自定义构造函数

除了对象字面量和内置构造函数之外,你也可以通过自定义的构造函数来创建对象实例,正如下面的代码所示:

var  adam =  new  Person( "Adam" ); adam.say();  // "I am Adam"

这种写法非常像Java中用Person类创建了一个实例,两者的语法非常接近,但实际上JavaScript中没有类的概念,Person()是一个函数。
Person()构造函数是如何定义的呢?看下面的代码:

var  Person =  function  (name) {        this .name = name;        this .say =  function  () {              return  "I am "  this .name;        }; };

当你通过new来调用这个构造函数时,函数体内将发生这些事情:
创建一个空对象,将它的引用赋给this,并继承函数的原型。
通过this将属性和方法添加至这个对象。
最后返回this指向的新对象(如果没有手动返回其他的对象)。
用代码表示这个过程如下:

var  Person =  function  (name) {        // 使用对象字面量创建新对象        // var this = {};        // 添加属性和方法        this .name = name;        this .say =  function  () {              return  "I am "  this .name;        };        //return this; };

上例中,为简便起见,say()方法被添加至this中,结果就是不论何时调用new Person(),在内存中都会创建一个新函数(say()),显然这是效率很低的,因为所有实例的say()方法是一模一样的。最好的办法是将方法添加至Person()的原型中。

 

Person.prototype.say =  function  () {        return  "I am "  this .name; };

刚才提到,构造函数执行的时候,首先创建一个新对象,并将它的引用赋给this:
// var this = {};
其实事实并不完全是这样,因为“空”对象并不是真的空,这个对象继承了Person的原型,看起来更像:
// var this = Object.create(Person.prototype);

### 构造函数的返回值

当使用new调用的时候,构造函数总是会返回一个对象,默认情况下返回this所指向的对象。如果构造函数内没有给this赋任何属性,则返回一个“空”对象(除了继承构造函数的原型之外,没有自有属性)。
尽管在构造函数中没有return语句的情况下,也会隐式返回this。但事实上我们是可以返回任意指定的对象的,在下面的例子中就返回了新创建的that对象。

 

var  Objectmaker =  function  () {        // name属性会被忽略,因为返回的是另一个对象        this .name =  "This is it" ;        // 创建并返回一个新对象        var  that = {};        that.name =  "And that's that" ;        return  that; }; // 测试 var  o =  new  Objectmaker(); console.log(o.name);  // "And that's that"

可以看到,构造函数中其实是可以返回任意对象的,只要你返回的东西是对象即可。如果返回值不是对象(字符串、数字或布尔值),程序不会报错,但这个返回值被忽略,最终还是返回this所指的对象。

## 强制使用new的模式

我们知道,构造函数和普通的函数本质一样,只是通过new调用而已。那么如果调用构造函数时忘记new会发生什么呢?漏掉new不会产生语法错误也不会有运行时错误,但可能会造成逻辑错误,导致执行结果不符合预期。这是因为如果不写new的话,函数内的this会指向全局对象(在浏览器端this指向window)。
当构造函数内包含this.member之类的代码,并直接调用这个函数(省略new),实际上会创建一个全局对象的属性member,可以通过window.member或member访问到。这不是我们想要的结果,因为我们要努力确保全局命名空间干净。

// 构造函数 function  Waffle() {        this .tastes =  "yummy" ; } // 新对象 var  good_morning =  new  Waffle(); console.log( typeof  good_morning);  // "object" console.log(good_morning.tastes);  // "yummy" // 反模式,漏掉new var  good_morning = Waffle(); console.log( typeof  good_morning);  // "undefined" console.log(window.tastes);  // "yummy"

ECMAScript5中修正了这种出乎意料的行为逻辑。在严格模式中,this不再指向全局对象。如果在不支持ES5的JavaScript环境中,也有一些方法可以确保有没有new时构造函数的行为都保持一致。

### 命名规范

一种简单解决上述问题的方法就是命名规范,前面的章节已经讨论过,构造函数首字母大写(MyConstructor()),普通函数和方法首字母小写(myFunction)。

### 使用that

遵守命名规范有一定的作用,但规范毕竟不是强制,不能完全避免出现错误。这里给出了一种模式可以确保构造函数一定会按照构造函数的方式执行,那就是不要将所有成员添加到this上,而是将它们添加到that上,并返回that。

function  Waffle() {        var  that = {};        that.tastes =  "yummy" ;        return  that; } 如果要创建更简单一点的对象,甚至不需要局部变量that,直接返回一个对象字面量即可,就像这样: function  Waffle() {        return  {              tastes:  "yummy"        }; } 不管用什么方式调用它(使用 new 或直接调用),它都会返回一个实例对象: var  first =  new  Waffle(), second = Waffle(); console.log(first.tastes);  // "yummy" console.log(second.tastes);  // "yummy"

这种模式的问题是会丢失原型,因此在Waffle()的原型上的成员不会被继承到这些对象中。
需要注意的是,这里用的that只是一种命名规范,that并不是语言特性的一部分,它可以被替换为任何你喜欢的名字,比如self或me。

### 调用自身的构造函数

为了解决上述模式的问题,能够让对象继承原型上的属性,我们使用下面的方法:在构造函数中首先检查this是否是构造函数的实例,如果不是,则通过new再次调用自己:

function  Waffle() {        if  (!( this  instanceof  Waffle)) {              return  new  Waffle();        }        this .tastes =  "yummy" ; } Waffle.prototype.wantAnother =  true ; // 测试 var  first =  new  Waffle(), second = Waffle(); console.log(first.tastes);  // "yummy" console.log(second.tastes);  // "yummy" console.log(first.wantAnother);  // true console.log(second.wantAnother);  // true 还有一种比较通用的用来检查实例的方法是使用arguments.callee,而不是直接将构造函数名写死在代码中: if  (!( this  instanceof  arguments.callee)) {        return  new  arguments.callee(); }

这种模式利用了一个事实,即在任何函数内部都会创建一个arguments对象,它包含函数调用时传入的参数。同时arguments包含一个callee属性,指向正在被调用的函数。需要注意,ES5严格模式中已经禁止了arguments.callee的使用,因此最好对它的使用加以限制,并尽可能删除现有代码中已经用到的地方。

## 数组字面量

和JavaScript中大多数“东西”一样,数组也是对象。可以通过内置构造函数Array()来创建数组,也可以通过字面量形式创建,就像对象字面量那样。而且更推荐使用字面量创建数组。
这里的示例代码给出了创建两个具有相同元素的数组的两种方法,使用Array()和使用字面量模式:

// 有三个元素的数组 // 注意:这是反模式 var  a =  new  Array( "itsy" "bitsy" "spider" ); // 完全相同的数组 var  a = [ "itsy" "bitsy" "spider" ]; console.log( typeof  a);  // "object",因为数组也是对象 console.log(a.constructor === Array);  // true

### 数组字面量语法

数组字面量写法非常简单:整个数组使用方括号括起来,数组元素之间使用逗号分隔。数组元素可以是任意类型,包括数组和对象。
数组字面量语法简单直观而且优雅,毕竟数组只是从0开始索引的一些值的集合,完全没必要引入构造器和new运算符(还要写更多的代码)。

### Array()构造函数的“陷阱”

我们对new Array()敬而远之还有一个原因,就是为了避免构造函数带来的陷阱。
如果给Array()构造函数传入一个数字,这个数字并不会成为数组的第一个元素,而是设置了数组的长度。也就是说,new Array(3)创建了一个长度为3的数组,而不是某个元素是3。如果你访问数组的任意元素都会得到undefined,因为元素并不存在。下面的示例代码展示了字面量和构造函数的区别:

// 含有1个元素的数组 var  a = [3]; console.log(a.length);  // 1 console.log(a[0]);  // 3 // 含有3个元素的数组 var  a =  new  Array(3); console.log(a.length);  // 3 console.log( typeof  a[0]);  // "undefined" 构造函数的行为可能有一点出乎意料,但当给 new  Array()传入一个浮点数时情况就更糟糕了,这时会出错(译注:给 new  Array()传入浮点数会报“范围错误”RangError),因为数组长度不可能是浮点数。 // 使用数组字面量 var  a = [3.14]; console.log(a[0]);  // 3.14 var  a =  new  Array(3.14);  // RangeError: invalid array length console.log( typeof  a);  // "undefined"

为了避免在运行时动态创建数组时出现这种错误,强烈推荐使用数组字面量来代替new Array()。
有些人用Array()构造器来做一些有意思的事情,比如用来生成重复字符串。下面这行代码返回的字符串包含255个空格(请读者思考为什么不是256个空格)。var white = new Array(256).join(‘ ‘);

### 检查是否数组

如果typeof的操作数是数组的话,将返回“object”。
console.log(typeof [1, 2]); // “object”
这个结果勉强说得过去,毕竟数组也是一种对象,但对我们来说这个结果却没什么用,实际上你往往是需要知道一个值是不是真正的数组。有时候你会见到一些检查数组的方法:检查length属性、检查数组方法比如slice()等等,但这些方法非常脆弱,非数组的对象也可以拥有这些同名的属性。还有些人使用instanceof Array来判断数组,但这种方法在某些版本的IE里的多个iframe的场景中会出问题(译注:原因就是在不同iframe中创建的数组不会相互共享其prototype属性)。
ECMAScript5定义了一个新的方法Array.isArray(),如果参数是数组的话就返回true。比如:

 

Array.isArray([]);  // true // 尝试用一个类似数组的对象去测试 Array.isArray({        length: 1,        "0" : 1,        slice:  function  () {} });  // false

如果你的开发环境不支持ECMAScript5,可以通过Object.prototype.toString()方法来代替。如调用toString的call()方法并传入数组上下文,将返回字符串“[object Array]”。如果传入对象上下文,则返回字符串“[object Object]”。因此可以这样做:

 

if  ( typeof  Array.isArray ===  "undefined" ) {        Array.isArray =  function  (arg) {              return  Object.prototype.toString.call(arg) ===  "[object Array]" ;        }; }

## JSON

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,可以很容易地用在多种语言中,尤其是在JavaScript中。
JSON格式及其简单,它只是数组和对象字面量的混合写法,看一个JSON字符串的例子:
{“name”: “value”, “some”: [1, 2, 3]}
JSON和对象字面量在语法上的唯一区别是,合法的JSON属性名均需要用引号包含。而在对象字面量中,只有属性名是非法的标识符时才使用引号包含,比如,属性名中包含空格{“first name”: “Dave”}。
在JSON字符串中,不能使用函数和正则表达式字面量。

### 使用JSON

在前面的章节中讲到,出于安全考虑,不推荐使用eval()来粗暴地解析JSON字符串。最好是使用JSON.parse()方法,ES5中已经包含了这个方法,并且现代浏览器的JavaScript引擎中也已经内置支持JSON了。对于老旧的JavaScript引擎来说,你可以使用JSON.org所提供的JS文件(http://www.json.org/json2.js)来获得JSON对象和方法。

// 输入JSON字符串 var  jstr =  '{"mykey": "my value"}' ; // 反模式 var  data = eval( '('  + jstr +  ')' ); // 更好的方式 var  data = JSON.parse(jstr); console.log(data.mykey);  // "my value" 如果你已经在使用某个JavaScript库了,很可能这个库中已经提供了解析JSON的方法,就不必再额外引入JSON.org的库了,比如,如果你已经使用了YUI3,你可以这样: // 输入JSON字符串 var  jstr =  '{"mykey": "my value"}' ; // 使用YUI来解析并将结果返回为一个对象 YUI().use( 'json-parse' function  (Y) { var  data = Y.JSON.parse(jstr);        console.log(data.mykey);  // "my value" }); 如果你使用的是jQuery,可以直接使用它提供的parseJSON()方法: // 输入JSON字符串 var  jstr =  '{"mykey": "my value"}' ; var  data = jQuery.parseJSON(jstr); console.log(data.mykey);  // "my value" 和JSON.parse()方法相对应的是JSON.stringify()。它将对象或数组(或任何原始值)转换为JSON字符串。 var  dog = {        name:  "x" ,        dob:  new  Date(),        legs: [1,2,3,4] }; var  jsonstr = JSON.stringify(dog); // jsonstr的值为 // {"name":"x","dob":"2015-05-06T06:01:06.806Z","legs":[1,2,3,4]}

 

点赞