Skip to content

Latest commit

 

History

History
360 lines (257 loc) · 11.7 KB

对象详解.md

File metadata and controls

360 lines (257 loc) · 11.7 KB

对象详解

前面已经涉及过很多关于对象的知识,那么对象到底是什么呢?

通俗来讲,对象就是一系列属性的方法的集合

对象的俩种创建方式(语法)

前面已经涉及过,在对象,包装类里面讲过一种是字面量创建,一种是构造函数实例对象。唯一的区别就是在字面量创建时可以添加多个属性在里面,而构造函数则需要一个一个添加。我们绝大部分用的是文字创建

类型

JavaScript有六种基本类型,这些类型本质上不是对象,JavaScript中有许多特殊的对象子类型,我们称之为

复杂基本类型,比如函数,数组等

JavaScript中还有一些对象子类型,通常被称为内置对象

基本包装类

  • String

  • Number

  • Boolean

    引用类型

    • Object
    • Function
    • Array
    • Date
    • RegExp
    • Error

    这些内置函数可以当做构造函数来使用,就是通过new操作符来创建实例

    对于 Object、Array、Function 和 RegExp(正则表达式)来说,无论使用文字形式还是构 造形式,它们都是对象,不是字面量。

    内容

    对象中的内容是一些存储在特定位置上的值组成的,在引擎内部,这些值的存储方式是多种多样的,一般并不会存在对象容器内部。存储在对象容器内部的是这些属性的名称,它们就像指针(从技术角度 来说就是引用)一样,指向这些值真正的存储位置。

    var myObject = {
    a: 2
    };
    myObject.a; // 2
    myObject["a"]; // 2
    

    访问对象属性有俩种访问方式,.a 语法通常被称为“属性访问”,["a"] 语法通常被称为“键访问”。

    这两种语法的主要区别在于 . 操作符要求属性名满足标识符的命名规范,而 [".."] 语法可以接受任意 UTF-8/Unicode 字符串作为属性名。比如

    [super-fun]
    .[super-fun]  //error
    

    这样不符合标识符规则的属性名只能通过键访问

    在对象中,属性名永远都是字符串,虽然在数组下标中使用的的确是数字,但是在对象属性名中数字会被转换成字符串

    var myObject = { };
    myObject[true] = "foo";
    myObject[3] = "bar";
    myObject[myObject] = "baz";
    myObject["true"]; // "foo"
    myObject["3"]; // "bar"
    myObject["[object Object]"]; // "baz"
    

    可计算属性名

    ES6 增加了可计算属性名,可以在文字形式中使用 [] 包裹一个表达式来当作属

    var prefix = "foo";
    var myObject = {
    [prefix + "bar"]:"hello",
    [prefix + "baz"]: "world"
    };
    myObject["foobar"]; // hello
    myObject["foobaz"]; // world
    

    可计算属性名最常用的场景应该是ES6的symbol,这里不做过多解释

    属性与方法

    我们常常将对象中的函数叫做方法,其实,从技术的角度来说,函数永远不会“属于”一个对象,当对象的属性中有方法是,属性访问返回的函数和其他函数没有任何的区别,况且,在this中,调用位置不同函数的执行也不同,不管从哪个方面看,函数都不可能属于对象,他只是对对象的一个引用。

    数组

    数组也支持 [ ] 访问形式,不过就像我们之前提到过的,数组有一套更加结构化的值存储机制(不过仍然不限制值的类型)。数组期望的是数值下标,也就是说值存储的位置(通常被称为索引)是整数

    数组也是对象,所以虽然每个下标都是整数,你仍然可以给数组添加属性:

var myArray = [ "foo", 42, "bar" ];
myArray.baz = "baz";
myArray.length; // 3
myArray.baz; // "baz"

虽然给对象添加了属性,但是对象的长度仍然没有发生变化

如果你试图向数组添加一个属性,但是属性名“看起来”像一个数字,那它会变成一个数值下标

var myArray = [ "foo", 42, "bar" ];
myArray["3"] = "baz";
myArray.length; // 4
myArray[3]; // "baz"

复制对象

属性描述符

var myObject = {
a:2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }

这个普通的对象属性对应的属性描述符不仅有value值,而且加入了writable(可写),enumerable(可枚举),configurable(可配置)

我们可以使用 Object.defineProperty(..)来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置,举例来说

var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
} );
myObject.a; // 2

我们使用这个方法来添加了一个属性,同时添加了特性,当然我们一般给对象添加属性不会使用这种繁琐的方法,除非你想改变其特性

  1. writable

    writable决定是否可以修改属性的值

    var myObject = {};
    Object.defineProperty( myObject, "a", {
    value: 2,
    writable: false, // 不可写!
    configurable: true,
    enumerable: true
    } );
    myObject.a = 3;
    myObject.a; // 2
    

    这样,我们对属性的修改失败,如果在严格模式下还会出错(TypeError)

  2. configurable

    只要属性是可配置的,就可以通过defineProperty(..)方法来修改属性描述符

    var myObject = {
    a:2
    };
    myObject.a = 3;
    myObject.a; // 3
    Object.defineProperty( myObject, "a", {
    value: 4,
    writable: true,
    configurable: false, // 不可配置!
    enumerable: true
    } );
    myObject.a; // 4
    myObject.a = 5;
    myObject.a; // 5
    Object.defineProperty( myObject, "a", {
    value: 6,
    writable: true,
    configurable: true,
    enumerable: true
    } ); // TypeError
    

    不管是不是处于严格模式,尝试修改一个不可配置的属性描述符都会出错。因此,把 configurable 修改成false 是单向操作,无法撤销!

    注意:即便属性是 configurable:false,我们还是可以把 writable 的状态由 true 改为 false,但是无法由 false 改为 true,除了无法修改,configurable:false 还会禁止删除这个属性

  3. enumberable

从名字就可以看出,这个描述符控制的是属性是否会出现在对象的属性枚举中,比如说for..in 循环。如果把 enumerable 设置成 false,这个属性就不会出现在枚举中,虽然仍然可以正常访问它。相对地,设置成 true 就会让它出现在枚举中。

不变性

  1. 对象常量

    结合 writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改、重定义或者删除)

  2. 禁止扩展

    如 果 你 想 禁 止 一 个 对 象 添 加 新 属 性 并 且 保 留 已 有 属 性, 可 以 使 用 Object.prevent Extensions(..):

    var myObject = {
    a:2
    };
    Object.preventExtensions( myObject );
    myObject.b = 3;
    myObject.b; // undefined
    

    在非严格模式下,创建属性 b 会静默失败。在严格模式下,将会抛出 TypeError 错

  3. 密封

    Object.seal(..) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上隐式调用Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。(可以修改属性值)

  4. 冻结

    Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们的值。感觉这样的对象就是一个所谓的“活死人”了,不可操作,永生永世

    [[Get]]

    yObject = {
    a: 2
    };
    myObject.a; // 2
    

    一段简单的访问属性的代码,看起来是在对象里面寻找这个属性,其实他真的在对象里面寻找,不过有一个更详细的过程:

    1. 对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值

    2. 如果没有找到,遍历可能存在的 [[Prototype]] 链也就是原型链。

    3. 最终都没有找到[[Get]]操作符就会返回undefined

      由于仅根据返回值无法判断出到底变量的值为 undefined 还是变量不存在,所以 [[Get]]操作返回了 undefined。不过稍后我们会介绍如何区分这两种情况。

      [[Put]]

      [[Put]]相对比较容易理解,有得到就要有添加,而这又与描述符有不可分割的关系,[[Put]] 被触时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这是最重要的因素)

    4. 属性是否是访问描述符(参见 3.3.9 节)?如果是并且存在 setter 就调用 setter。

    5. 属性的数据描述符中 writable 是否是 false ?如果是,在非严格模式下静默失败,在严格模式下抛出 TypeError 异常。

    6. 如果都不是,将该值设置为属性的值。

      如果对象中不存在这个属性,后续过程会更复杂,我们在后面详细介绍

      Getter与Setter

      用 getter 和 setter 部分改写默认操作,但是只能应用在单个属性上,无法应用在整个对象上。getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏函数,会在设置属性值时调用。

      当你给一个属性定义 getter、setter 或者两者都有时,这个属性会被定义为“访问描述符”(和“数据述符”相对)。对于访问描述符来说,JavaScript 会忽略它们的 value 和writable 特性,取而代之的是关心 set 和 get(还有 configurable 和 enumerable)特性

var myObject = {
// 给 a 定义一个 getter
get a() {
return 2;
}
};
Object.defineProperty(
myObject, // 目标对象
"b", // 属性名
{ // 描述符
// 给 b 设置一个 getter
get: function(){ return this.a * 2 },
// 确保 b 会出现在对象的属性列表中
enumerable: true
}
);
myObject.a; // 2
myObject.b; // 4

不管是对象文字语法中的 get a() { .. },还是 defineProperty(..) 中的显式定义,二者 都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数, 它的返回值会被当作属性访问的返回值

什么是getter与setter?

  • getter 是一种获得属性值的方法,setter是一种设置属性值的方法

  • getter负责查询值,它不带任何参数,setter则负责设置键值,值是以参数的形式传递,在他的函数体中,一切的return都是无效的

  • get/set访问器不是对象的属性,而是属性的特性,特性只有内部才用,因此在javaScript中不能直接访问他们,为了表示特性是内部值用两队中括号括起来表示如[[Value]]

  • 对象属性分为访问器属性和对象属性

    所以在使用getter时最好使用getter,如果不使用,可能会使getter无效

    var myObject = {
    // 给 a 定义一个 getter
    get a() {
    return 2;
    }
    };
    myObject.a = 3;
    myObject.a; // 2
    

    无效

    var myObject = {
    // 给 a 定义一个 getter
    get a() {
    return this._a_;
    },
    // 给 a 定义一个 setter
    set a(val) {
    this._a_ = val * 2;
    }
    };
    myObject.a = 2;
    myObject.a; // 4
    

    有效

    存在性

    在前面说过,访问对象的属性返回undefined,可能是对象没有这个属性,也可能是属性没有值,那么怎样区分呢?

    var myObject = {
    a:2
    };
    ("a" in myObject); // true
    ("b" in myObject); // false
    myObject.hasOwnProperty( "a" ); // true
    myObject.hasOwnProperty( "b" ); // false
    

    in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。相比之下, hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。