JavaScript语言精粹第三章对象学习笔记

162 阅读7分钟

第三章 对象

  JavaScript的简单类型包括:数字、字符串、布尔值(true和false)、null值和undefined值,其他所有的值都是对象。数字、字符串和布尔型“貌似”对象,因为它们拥有方法,但他们是不可变的。JavaScript中的对象是可变的键控集合(keyed collections)。在JavaScript中,数组是对象,函数是对象,正值表达式是对象,对象自然也是对象。

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

  JavaScript 中的对象是无类别(class-free)的。它对新属性的名字和值没有约束。对象适合用于收集和管理数据。对象可以包含其他对象。

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

3.1 对象字面量

一个对象字面量就是包围在一对花括号中的零或多个“名/值”对。对象字面量可以出现在任何允许表达式出现的地方

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

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

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

    var flight = {
            airline: "Oceanic",
            number: 815,
            departure: {
                IATA: "SYD",
                time: "2020-07-01 12:00",
                city: "sydney"
            },
            arrival: {
                IATA: "LAX",
                time: "2020-07-10 08:40",
                city: "Los Angeles"
            }
        }

3.2 检索

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

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

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

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

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

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

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

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

3.3 更新

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

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

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

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

3.4 引用

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

    var x = stooge
    x.nickname = 'Curly'
    var nick = stooge.nickname
        //因为x和stooge是指向同一个对象的引用,所以nick为'Curly'
    var a = {}, b = {}, c = {}
        //a、b和c每个都引用一个不同的空对象
    a = b = c = {}
        //a、b和c都引用一个空对象

3.5 原型

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

  当你创建一个新对象时,你可以选择某个对象作为它的原型。JavaScript 提供的实现机制杂乱而复杂,但其实它可以被明显的简化。我们将给 Object 增加一个 beget 方法。这个 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);

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

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

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

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

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

3.6 反射

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

    typeof flight.number    //'number'
    typeof flight.statue    //'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

3.7枚举

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

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

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

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

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

3.8删除

delete 运算符可以用来删除对象属性。他将会移除对象中确定包含的属性。他不会触及原链接中的任何对象。

删除对象的属性可能会让来自原链接中的属性浮现出来:

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

3.9减少全局变量污染

  JavaScript可以很随意地定义那些可保存所有应用资源的全局变量。不幸的是,全局变量削弱了程序的灵活性,所以应该避免。

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

    var MYAPP = {};

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

    MYAPP.stooge = {
        "first-name": "Joe",
        "last-name": "Howard"
    };
    MYAPP.flight = {
        airline: "Oceanic",
        number: 815,
        departuer: {
            IATA: "SYD",
            time: "2020-06-30 12:14",
            city: "Sydney"
        },
        arrival: {
            IATA: "LAX",
            time: "2020-07-10 20:10",
            city: "Los Angeles"
        }
    };

  只要把多个全局变量都整理在一个名称空间下,你将显著降低与其他应用程序、组件或类库之间产生糟糕的相互影响的可能性。你的程序也会变得更加容易阅读,因为很明显 MYAPP.stooge 指向的是顶层结构。