你不知道的JavaScript:对象

在前面我们看到过关于函数调用的位置不同this所绑定的对象也不同,但是对象是何物呢?

  • 语法

对象的定义可以通过声明形式和构造形式。

//对象字面量形式
var obj = {};

//构造形式
var obj = new Object();
obj.name = "bob";

构造形式和文字形式生成的对象是一样的。唯一的区别是,在字面量形式中我们可以任意添加多个属性,但是在构造形式中我们必须逐个添加属性。

  • 类型

在JavaScript中主要有6中数据类型:

  • string
  • number
  • boolean
  • null
  • undefined
  • object

注意,简单的基本类型并不是对象(string,number,boolean,null,undefined)。null有时会被当做是一种对象类型。对null进行typeof null时会返回字符串"object"。实际上null的本身是基本类型。

  • 内置对象

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

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Math
  • Error

这些内置对象可以理解像是其他语言中的type或者class。但是在JavaScript中,他们只是一些内置函数,这些内置函数可以当做构造函数来使用。

  • 内容

对象的内容是由一些存储在特定命名位置的,任意类型的值组成的,我们称之为属性。

在引擎内部,对象的值得存储方式是多样的,一般并不会存在对象容器内部。存储在对象容器内部的是这些属性的名称,它们类似于指针(引用)一样,指向这些值真正的存储位置。

访问对象属性的值有两种方式,“.”和“[]”,其访问并没有什么区别。

var obj = {a: 2};
obj.a;  //称为属性访问
obj['a']; //称为键访问
  • 数组

在数组中也支持[]访问,不过数组有一套更加结构化的值存储机制。数组期望的是数值下标,也就是说值存储的位置是非负整数。

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

var arr = [1, 2, 3];
arr.baz = 'baz';
arr.length; //3
arr.baz; //'baz'

可以看到给数组添加了属性,但是并未对数组的长度进行改变。单数如果你给数组添加一个“数字”属性,那么它会将整个“数字”属性转换为数组的下标。

var arr = [1, 2, 3];
aa['3'] = 4;
arr.length = 4;
arr[3]; // 4
  • 属性描述符

在ES5之前,JavaScript没有提供直接检测属性特性的方法,但是在此之后,所有属性都具备了属性描述符。

var myObj = {
  a: 2
}
myObj.getOwnPropertyDescriptor(myObj, 'a');
// {
//   configurable: true,
//   enumerable: true,
//   writable: true,
//   value: 2
// }

如上,该对象对应的属性描述符包含configurable,enumerable,writable,value四个特性。分别代表可配置,可枚举,可写,数据值。

在创建普通属性时属性描述符会使用默认值,我们还可以通过Object.defineProperty(...)来添加一个新属性或者修改一个已有的属性(如果它的configuable为true)。

var obj = {

}

Object.defineProperty(obj, 'a', {
  configurable: true,
  enumerable: true,
  writable: true,
  value: 4
});

obj.a; //4

一. Writable

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

var myObject = {

}

Object.defineProperty(myObject, 'a', {
  value: 2,
  writable: false,  //不可写
  enumerable: true,
  configurable: true
});

myObject.a; //2
myObject.a = 4;
myObject.a; //2

如上,我们将writable设置为false,所以后来我们对该属性值的修改失败,如果在严格模式下,这种方法会出错。

二. Configurable

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

var myObj = {
  a: 2
};

myObj.a = 3;
myObj.a; //3

Object.defineProperty(myObj, 'a', {
  value: 4,
  writable: true,
  configurable: false, //不可配置
  enumerable: true
});

myObj.a; //4
myObj.a = 5;
myObj.a; //5

myObj.defineProperty(myObj, 'a', {
  value: 6,
  writable: true,
  configurable: true,
  enumerable: true
}); //TypeError

如上操作,最后使用defineProperty(...)会产生一个报错,不管是否处于严格模式下,尝试修改一个不可配置的描述符属性都会出错。注意,将configurable设置为FALSE是一个单向操作。(configurable为false的情况下,我们还是可以将writable设置为false,但是不可以将writable从false设置为true。)

除了无法设置该属性,configuable为false的情况下,也是禁止删除掉该属性。

三. Enumerable

该属性是用来控制属性时否会出现在对象的属性枚举中,比如for-in循环,如果将enumerable设置为false,那么这个属性就不会出现在枚举当中。

四. [[GET]]

当访问某个对象的属性的时候,例如查看obj对象的a属性,看起来只是查找到名字为a的属性。其实是在obj上实现了[[Get]]操作。对象默认的内置[[Get]]操作首先会在对象中查找是否有名称相同的属性。如果找到则返回该值。

然而如果没有找到,按照[[Get]]的算法的定义,遍历可能存在的[[Prototype]]链,就是原型链。如果最终都没有找到则会返回undefined。

五. [[Put]]

[[Put]]和[[Get]]是相对应的,但是[[Put]]被触发时,会取决于多种因素,包括对象中是否已经存在该属性。

如果对象中已经存在该属性那么会进行如下操作:

  • 属性是否是访问描述符,如果是并且存在setter就调用setter。

  • 属性的描述符中writable是否为false。如果是则在非严格模式下失败,在严格模式下会提示异常。

  • 如果都不是则将该值设置为属性的值。

  • Getter和Setter

对象默认的[[Put]]和[[Get]]操作分别可以控制属性值的设置和获取。

在ES5中可以通过getter和setter部分改写默认操作。getter是一个隐藏函数,会在获取属性值时调用,setter也是一个隐藏函数,会在设置属性值时调用。

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

Object.defineProperty(myobj, 'b', {
  get: function() {
    return this.a*2
  },
  enumerable: true
});

myobj.a; //2
myobj.b; //4

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

  • 存在性

我们如何判断某个属性是否在该对象中?

var myobj = {
  a: 2
};

('a' in myobj); //true
('b' in myobj); //false

myobj.hasOwnProperty('a'); //true
myobj.hasOwnProperty('b'); //false

如上,in操作符会检查属性是否在对象中以及该对象的[[Prototype]]原型链中,hasOwnProperty只会检查该属性是否在该对象中。

所有的普通对象都可以通过Object.prototype的委托来访问hasOwnProperty方法。但是例如Object.create(null)来创建的对象并不会委托到。

  • 枚举

关于对象属性是否可枚举,这个关系到了对象属性的数据描述符中的enumerable属性。

var obj = {};

Object.defineProperty(obj, 'a', {
  enumerable: true,
  value: 2
});

Object.defineProperty(obj, 'b', {
  enumerable: false,
  value: 3
});

for( var i in obj){
  console.log(i, obj[i])
} // 'a' 2

如上,属性b并没有出现在for-in循环中,所以可枚举可以理解为可以出现在对象属性的遍历中。

我们还可以通过propertyIsEnumerable(...)检查,该方法判断属性是否属于该对象而不是出现在原型链中,并且enumerable为true。

在ES6中Object.keys()会返回一个数组,包括该对象所有可以枚举的属性的数组。Object.getOwnPropertyNames(...)会返回一个数组,包括所有的属性,无论是否可以枚举。

你可能感兴趣的:(你不知道的JavaScript:对象)