Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

对象详解 #8

Open
Qhappyman opened this issue Mar 24, 2019 · 0 comments
Open

对象详解 #8

Qhappyman opened this issue Mar 24, 2019 · 0 comments

Comments

@Qhappyman
Copy link
Owner

对象详解

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

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

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

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

类型

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]] 链。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant