《JavaScript语言精粹》第3章 对象

97 阅读7分钟

第三章 对象

1、JS的简单数据类型包括:数字、字符串、布尔值(true和false)、null值、undefined值。

2、其他所有的值都是对象

3、数字、字符串和布尔值【貌似】对象,因为它们拥有方法,但它们是不可变的。

4、JS中的对象是可变的键控集合(keyed collections)。

5、在JS中,数组是对象、函数是对象、正则表达式是对象,当然对象也是对象。

对象是属性的容器,其中每个属性都拥有名字和值。属性的名字可以是包含空字符串在内的任意字符串。属性值可以是除undefined值之外的任何值。

JS中的对象是无类型(class-free)的。它对新属性的名字和属性的值没有限制。对象适合用于汇集和管理数据。对象可以包含其他对象,所以它们可以容易地表示成数或者图结构。

JS包含一种原型链的特性,允许对象继承另一个对象的属性。正确地使用它能减少对象初始化时小号的时间和内存。

1、对象字面量(Object Literals)

对象字面量提供了一种非常方便地创建新对象值的表示法。一个对象字面量就是包围在一对花括号中的零或多个"名值"对。对象字面量可以出现在任何允许表达式出现的地方。

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

属性名可以是包括空字符串在内的任何字符串。【 var obj = {'': 'aaa','name': 'dd'} 】

在对象字面量中,如果属性名是一个合法的JS标识符且不是保留字,则并不强制要求用引号括住属性名。所以用引号括住"first-name"是必须的,但是否括住first_name是可选的。逗号可以用来分隔多个"名值"对。

属性的值可以从包括另一个对象字面量在内的任意表达式中获得。对象是可嵌套的:

var obj = {'': 'aaa','name': 'dd'}

var flight = {
    airline: "Oceanic",
    number: 8.15,
    departure: {
        IATA: "SYD",
        time: "2022-11-30 16:28"city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2022-11-30 16:28"city: "Los Angeles"
    }
};

2、检索(Retrieval)

要检索对象里包含的值,可以采用在 [ ] 后缀中括住一个字符串表达式的方式。如果字符串表达式是一个字符串字面量,而且他是一个合法的JS标识符且不是保留字,那么也可以用 . 表示法代替。优先考虑用 . 表示法,因为它更紧凑且可读性更好。

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

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

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

|| 运算符可以用来填充默认值:

var middle = stooge["middle-name"] || "(none)";
var status = flight.status || "unknow";

尝试从undefined的成员属性中取值将会导致TypeError异常。这时可以通过&&运算符来避免错误。

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

3、更新(Update)

对象里的值可以通过赋值语句来更新。如果属性名已经存在于对象里,那么这个属性的值就会被替换。

stooge['first-name'] = 'Jerome';

如果对象之前没有拥有那个属性名,那么该属性就被扩充到对象中。

stooge['middle-name'] = 'Lester';
stooge.nickname = 'Curly';
flight.equipment = {
    model: 'Boeing 777'
};
flight.status = 'overdue';

4、引用(Reference)

对象通过引用来传递。它们永远不会被复制:

// 因为x和stooge是指向同一个对象的引用,所以nick为'Curly'
var x = stooge;
x.nickname = 'Curly';
var nickname = stooge.nickname;

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

// a、b、c都引用同一个不同的空对象
a = b = c = {}

5、原型(Prototype)

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

2、当你创建一个新对象时,你可以选择某个对象作为它的原型。JS提供的实现机制杂乱而复杂,但其实可以被明显的简化。我们将Object增加一个create方法。这个方法创建一个使用原对象作为其原型的新对象。

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

原型连接在更新时是不起作用的。当我们对某个对象作出改变时,不会触及该对象的原型:

another_stooge['first-name'] = 'Harry';
another_stooge['middle-name'] = 'Moses';
another_stooge.nickname = 'Moe';

运行连接只有在检索值的时候才会被用到。如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,那么JS会试着从原型对象中获取属性值。如果那个原型对象也没有该属性,那么再从它的原型中寻找,以此类推,直到该过程最后到达终点Object.prototype。如果想要的属性完全不存在于原型链中,那么结果就是undefined值。这个过程被称为委托

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

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

6、反射(Reflection)

检查对象并确定对象有什么属性是很容易的事情,只要试着去检索该属性并验证取得的值。

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方法,如果对象拥有独有的属性,它将返回true。hasOwnProperty方法不会检查原型链。

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

7、枚举(Enumeration)

for in语句可以用来遍历一个对象中的所有属性名。该枚举过程将会列出所有的属性------包括函数和你可能不关心的原型中的属性------所以有必要过滤掉那些你不想要的值。最为常用的过滤器是hasOwnProperty方法,以及使用typeof来排除函数。

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

属性名出现的顺序是不确定的,因此要对任何可能出现的顺序有所准备。如果你想要确保属性以特定的顺序出现,最好的办法就是完全避免使用for in语句,而是创建一个数组,在其中以正确的顺序包含属性名:

var i;
var properties = [
    'first-name',
    'middle-name',
    'last-name',
    'profession',
];
for(let i = 0; i < properties.length; i++) {
   document.writeln(properties[i] + ': ' + another_stooge[properties[i]);
}

通过使用for而不是for in,可以得到我们想要的属性,而不用担心可能发掘出原型链中的属性,并且我们按照正确的顺序取得了它们的值。

8、删除(Delete)

1、delete运算符可以用来删除对象的属性。如果对象包含该属性,那么该属性就会被移除。

2、它不会触及原型链中的任何对象

3、删除对象的属性可能会让来自原型链中的属性透现出来:

another_stooge.nickname    // 'Mos'

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

9、减少全局变量污染(Global Abatement)

JS可以很随意地定义全局变量来容纳你的应用的所有资源。遗憾的是,全局变量削弱了程序的灵活性,应该避免使用。

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

var MYAPP = {}

该变量此时变成了你的应用的容器:

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

MYAPP.flight = {
    airline: "Oceanic",
    number: 815,
    departure: {
        IATA: "SYD",
        time: "2022-11-30 14:55",
        city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2022-11-30 14:55",
        city: "Los Angeles"
    }
}

只要把全局变量的资源纳入一个名称空间之下,你的程序与其他应用程序、组件或类库之间发生冲突的可能性就会显著减低。你的程序也会变得更容易阅读,因为很明显,MYAPP.stooge指向的是顶层结构。使用闭包来进行信息隐藏的方式,它是另一种减少全局污染的方法。