1
头图

什么是对象

对象是JavaScript的基本数据类型。是属性的无序集合,每个属性都是一个键值对。属性又分为两种:一种是数据属性,一种是方法属性。(另一种说法是:对象是键值对的集合,对象是由属性和方法构成的)
对象属性名是字符串,所以对象又可以看成是字符串到值的映射。字符串到值的映射这种结构,通常还有一些其它的叫法,比如:散列表、字典、关联数组。
JavaScript对象是动态的,我们可以对对象执行新增属性,删除属性的操作。在JS当中除了字符串、数字、true、false、null、undefined这些值之外,其它所有的值都是对象。

对象的创建方式

对象直接量

创建对象最简单的方式就是使用对象直接量。比如如下代码:

// 创建一个空对象
const o1 = {};

// 创建一个带有属性和方法的对象
const o2 = {
  name: "thirteen",
  class: "class",
  age: 18,
  say: function () {
    console.log("console.log: say hello");
  }
};
console.log(o2.class, "关键字当做属性名");
o2.say();

new 运算符(构造函数)

new 运算符创建并初始化一个新对象,new运算符后面紧跟一个函数调用。new 后跟随的函数被成为构造函数。
使用构造函数创建对象

let o = new Object(); // 创建一个空对象,和{}(对象直接量)一样
let a = new Array(); // 创建一个空数组对象,和[]一样

function papa(){};

let child = new papa(); // 通过实例化构造函数papa来创建一个对象。

Object.create

Object.create方法是ES5中新增创建对象的一个方法。该方法有两个参数,第一个参数必选是新创建对象的原型,第二个参数是对对象属性的描述。Object.create方法返回一个新的对象

语法

Object.create(proto,[propertiesObject])

// 创建没有原型的新对象
const o1 = Object.create(null);

// 从该方法的介绍中我们可以看到,第一个参数是新创建对象的原型,
// o1创建的时候传入null,则表示o1是没有原型的新对象,没有原型,也就没有继承任何的方法和属性。

// 创建一个具有原型的对象
const o2 = Object.create(Object.prototype); // 相当于{}和new Object()创建对象一样

// 创建一个以另一个空对象为原型,且拥有一个属性p的对象
const o3 = Object.create({}, { p: { value: 42 } })


const o4 = Object.create(Object.prototype, {
  // foo会成为所创建对象的数据属性
  foo: {
    writable:true,
    configurable:true,
    value: "hello"
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});


// 创建一个继承现有对象的新对象
const person = {
    name:"thirteen",
  age:18,
  say:function(){
      console.log(`name:${this.name},age:${this.age} `);
  }
}
const o3 = Object.create(person);

o3.name === "thirteen" // true
o3.age === 18 // true 
o3.say(); // name:thirteen,age:18

用Object.create实现类式继承

// 父类
function parent(){
    this.x = 0;
  this.y = 0;
}

// 父类的方法
parent.prototype.move = function(x,y){
    this.x += x;
  this.y += y;
  console.log('move 执行了');
}

// 子类
function child(){
    parent.call(this);
}

// 子类继承父类
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;

// 实例化
var o1 = new child();

console.log(o1 instanceof child);

console.log(o1 instanceof parent);

o1.move(2,2);

对象的属性

一个JavaScript对象有很多属性。一个对象的属性可以被解释成一个附加到对象上的变量。对象属性和普通的JavaScript变量基本没有什么区别,仅仅是属性属于某个对象。

属性查询和设置(修改)

属性的查询和设置有两种方式,第一种是使用(•)点运算符.,第二种是使用([])方括号运算符。在ES3,使用"."点运算符后面是不能跟JS当中的保留字的,例如:for,class,function等,不过在ES5之后就不存在这种问题了,所以下面的代码在现代浏览器里面是不会有问题的.obj.for = "for"。如果使用方括号[],方括号中间在ES6之前必须是一个计算结果为字符串的表达式。在现代浏览器当中,方括号表达式中间还可以是Symbol类型。

点运算符

// 定义一个对象o1
const o1 = {
    name:"thirteen",
  age:18
}

// 读取对象o1的属性
o1.name // thirteen
o1.age  // 18

// 修改对象o1的属性,如果属性名原来就有,就是修改,如果原来对象上没有该属性,就是新增
o1.sex = "男";
o1.say = function(){
    console.log('i say');
}

console.log(o1.name);

// 
const name = "thirteen";
const age = 18;
const o2 = {
    name,
  age
}

o2.name // thirteen
o2.age  // 18

方括号运算符

当通过[]来访问(新增,修改)对象的属性时,属性名通过字符串来表示。字符串是JS的基本数据类型,在程序运行时我们可以修改和创建他们。因为有方括号这种访问对象的方式,有时候对象也被叫做关联数组,只不过数组的索引是数字,对象的索引是字符串。

const addr = "";
const obj = {
  p0:"p0",
  p1:"p1",
  p2:"p2",
  p3:"p3"
};
for(i=0;i<4;i++){
  addr += obj["p"+i] + '\n';
}

// 当对象的属性名字无法确认(动态)的时候,只能通过方括号访问
function addStock(portfolio,name,shares){
    portfolio[name] = shares;
}

//同时创建四个变量,用逗号分割
const myObj = new Object(),
      str = "myString",
      rand = Math.random(),
      obj = new Object();


myObj.type = "点运算符";
myObj['data create'] = '有空格的字符串';
myObj[str] = "字符串值";
myObj[rand] = "随机数";
myObj[obj] = "Object 对象";
myObj[""] = "空字符串";

// PS:方括号中所有的键都将转换为字符串类型。

两种方式比较

点运算符

  1. 使用方便灵活
  2. 有一定的限制,当对象的属性是数字开头的字符串、空字符串、连字符就不能使用点运算符来访问
  3. 点运算符后面紧跟的标识符是静态的,必须写死在程序中

方括号运算符

  1. 使用全面,所有点运算符能访问的不能访问的属性,方括号都可以访问
  2. 当对象的属性是无法提前得知的时候,只能使用方括号运算符

属性的删除

delete 运算符可以删除对象的属性;不过只能删除对象自身的属性,不能删除对象继承的属性。另外,如果对象的属性的可配置值是false的时候,也是不能够被删除的。delete 表达式执行成功后,会返回一个true

const o1 = {
    name:"thirteen"
}


Object.defineProperty(o1, 'age', {
  value: 42,
  configurable: false
});

// 可以删除
delete o1.name 

console.log(o1.name) // 返回undefined

// 不可以删除,因为属性的可配置值configurable是false
delete o1.age

console.log(o1.age) // 返回42

// 不可以删除
delete o1.toString

属性的枚举

属性遍历的次序

  • 先遍历数字键,按照谁小谁先的方式
  • 再遍历字符串键,按照在对象中的位置排列
  • 最后遍历Symbol,按照加入时间升序排列。
// 对象属性遍历的次序
Reflect.ownKeys({
    [Symbol()]:0,
  b:0,
  10:0,
  2:0,
  a:0
})

// 返回结果
['2','10','a','b',Symbol()]

for...in循环

该方法依次访问一个对象以及其原型链中所有可枚举的属性

const o = {
  x: 1,
  y: 2,
  z: 3,
};

// 对象 o1 继承 对象o
const o1 = Object.create(o, {
  foo: {
    writable: true,
    configurable: true,
    enumerable:true, // 可以被枚举
    value: "hello",
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function () {
      return 10;
    },
    set: function (value) {
      console.log("Setting `o.bar` to", value);
    },
  },
});

console.log("打印对象o1的属性名和属性值");
for (prop in o1) {
  console.log(`${prop}:${o1[prop]}`);
}

// foo:hello  x:1 y:2 z:3

// 给对象o1新增a属性,属性可枚举配置为true 能够遍历得到
Object.defineProperty(o1, "a", {
  value: "4",
  enumerable: true,
});

// 给对象o1新增b属性,属性可枚举配置为false 不能能够遍历得到
Object.defineProperty(o1, "b", {
  value: "5",
  enumerable: false,
});

console.log("打印对象o1可枚举的属性名和属性值");
for (prop in o1) {
  console.log(`${prop}:${o1[prop]}`);
}

//foo:hello  x:1 y:2 z:3 a:4

Object.keys(obj)

该方法返回对象obj自身包含(不包括原型中)的所有可枚举属性的名称数组

const o = {
  x: 1,
  y: 2,
  z: 3,
};

// 对象o1继承o,
const o1 = Object.create(o);
o1.name = "thirteen";
o1.age = 18;

const resultArray = Object.keys(o1);
console.log(resultArray,'打印对象可枚举属性的名称数组');
// [name,age]

// 给对象o1新增a属性,属性可枚举配置为true 能够遍历得到
Object.defineProperty(o1, "a", {
  value: "4",
  enumerable: true,
});

// 给对象o1新增b属性,属性可枚举配置为false 不能能够遍历得到
Object.defineProperty(o1, "b", {
  value: "5",
  enumerable: false,
});

const resultArray1 = Object.keys(o1);
console.log(resultArray1,'打印对象可枚举属性的名称数组');
// [name,age,a]

Object.getOwnPropertyNames(obj)

该方法返回对象obj自身包含(不包括原型中)的所有属性(无论是否可枚举)的名称的数组。

const o = {
  x: 1,
  y: 2,
  z: 3,
};

const o1 = Object.create(o, {
  sex: {
    enumerable: false,
    value: "男",
  },
  height: {
    enumerable: true,
    value: 165,
  },
});
o1.name = "thirteen";
o1.age = 18;

const resultArray = Object.getOwnPropertyNames(o1);
console.log(resultArray, "打印对象可枚举属性的名称数组");
// [sex,height,name,age]

// 给对象o1新增a属性,属性可枚举配置为true 能够遍历得到
Object.defineProperty(o1, "a", {
  value: "4",
  enumerable: true,
});

// 给对象o1新增b属性,属性可枚举配置为false 不能能够遍历得到
Object.defineProperty(o1, "b", {
  value: "5",
  enumerable: false,
});

const resultArray1 = Object.getOwnPropertyNames(o1);
console.log(resultArray1, "打印对象可枚举属性的名称数组");
// [sex,height,name,age,a,b]

Object.getOwnPropertySymbols(obj)

返回一个数组,包含对象obj自身所有Symbol属性的键名

const mySymbol = Symbol("mySymbol");
const mySymbol1 = Symbol("mySymbol1");
const mySymbol2 = Symbol("mySymbol2");
const mySymbol3 = Symbol("mySymbol3");
const o = {
  [mySymbol]: 1,
  y: 2,
  z: 3,
};

const o1 = Object.create(o, {
  sex: {
    enumerable: false,
    value: "男",
  },
  height: {
    enumerable: true,
    value: 165,
  },
});
o1.name = "thirteen";
o1.age = 18;
o1[mySymbol1] = "mySymbol1";

const resultArray = Object.getOwnPropertySymbols(o1);
console.log(resultArray, "打印对象Symbol属性的键名数组");
// [symbol(mySymbol1)]

// 给对象o1新增a属性,属性可枚举配置为true 能够遍历得到
Object.defineProperty(o1, mySymbol2, {
  value: "4",
  enumerable: true,
});

// 给对象o1新增b属性,属性可枚举配置为false 不能能够遍历得到
Object.defineProperty(o1, mySymbol3, {
  value: "5",
  enumerable: false,
});

const resultArray1 = Object.getOwnPropertySymbols(o1);
console.log(resultArray1, "打印对象Symbol属性的键名数组");
// [symbol(mySymbol1),symbol(mySymbol2),symbol(mySymbol3)]

Reflect.ownKeys(obj)

返回一个数组,包含对象自身所有键名,不管键名是Symbol或字符串,也不管是否可枚举。

const mySymbol = Symbol("mySymbol");
const mySymbol1 = Symbol("mySymbol1");
const mySymbol2 = Symbol("mySymbol2");
const mySymbol3 = Symbol("mySymbol3");
const o = {
  [mySymbol]: 1,
  y: 2,
  z: 3,
};

const o1 = Object.create(o, {
  sex: {
    enumerable: false,
    value: "男",
  },
  height: {
    enumerable: true,
    value: 165,
  },
});
o1.name = "thirteen";
o1.age = 18;
o1[mySymbol1] = "mySymbol1";

const resultArray = Reflect.ownKeys(o1);
console.log(resultArray, "打印对象自身所有属性的键名数组");
// [sex,height,name,age,Symbol(mySymbol1)]

// 给对象o1新增a属性,属性可枚举配置为true 能够遍历得到
Object.defineProperty(o1, mySymbol2, {
  value: "4",
  enumerable: true,
});

// 给对象o1新增b属性,属性可枚举配置为false 不能能够遍历得到
Object.defineProperty(o1, "b", {
  value: "5",
  enumerable: false,
});

const resultArray1 = Reflect.ownKeys(o1);;
console.log(resultArray1, "打印对象Symbol属性的键名数组");
// [ "sex", "height", "name", "age", "b", Symbol(mySymbol1), Symbol(mySymbol2) ]

属性的特性

除了包含名字和值之外,属性还包含一些标记他们可写,可枚举和可配置的特性。在ES3中无法设置这些内容,ES5之后就可以了。属性可以分为『存取器属性』(getter和setter定义的属性)和『数据属性』,不同的属性有不同的特性。
存取器属性

  • get // 读取
  • set // 写入
  • enumerable // 是否可以被枚举(遍历读取)
  • configurable // 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。

数据属性

  • value // 属性值
  • writable // 属性是否可写(是否可以修改)
  • enumerable // 是否可以被枚举(遍历读取)
  • configurable

获取属性的属性描述

通过调用Object.getOwnPropertyDescriptor方法可以获得某个对象特定属性的属性描述,例如:

const o = {
  x: "我是X",
  y: 2,
  z: 3,
};

const o1 = Object.create(o);
o1.age = 18;

Object.defineProperty(o1, "name", {
  value: "thirteen",
  writable: true,
  configurable: true,
  enumerable: true,
});

const ageDesc = Object.getOwnPropertyDescriptor(o1, "age");
const nameDesc = Object.getOwnPropertyDescriptor(o1, "name");
const xDesc = Object.getOwnPropertyDescriptor(o1, "x");
//
console.log(ageDesc, "获取自有属性age默认的属性描述");
console.log(nameDesc, "获取自有属性name配置的属性描述");
console.log(xDesc, "获取继承属性X的属性描述");


// 如果想要获取继承的属性的属性描述,需要通过Object.getPrototypeOf方法先遍历原型链
const xdesc2 = Object.getOwnPropertyDescriptor(
  Object.getPrototypeOf(o1),
  "x"
);
console.log(xdesc2, "获取继承属性X的属性描述");

属性getter和setter

对象的属性是由名字、值和一组特性构成的。在ES5之后属性的值可以用一个或两个方法替代,这两个方法就是getter和setter。由getter和setter定义的属性称作『存取器属性』,不同于『数据属性』,数据属性只有一个简单的值。
当我们使用getter和setter定义属性的时候,我们就可以监听属性的读取和写入动作,比如VUE框架当中就使用了Object.defineProperty方法重写了data对象当中的所有属性,这样当data的值发生改变的时候,就可以更新vue当中的模板了;
存取器属性还可以智能检查属性的写入值,以及在属性每次读取的时候返回不同的值;

// 如何定义,第一种
const o1 = {
  name: "thirteen",
  $age: 18,
  get age() {
    return this.$age;
  },
  set age(v) {
    this.$age = v;
  },
};
console.log("第一种返回");
console.log(o1.age); // 18
o1.age = 20;
console.log(o1.age); // 20

// 第二种
const o2 = {
  $age: 18,
};

let ageValue = 18;
Object.defineProperty(o2, "age", {
  get: function () {
    return ageValue;
  },
  set: function (value) {
    ageValue = value;
  },
});
console.log("第二种返回");
console.log(o2.age); // 18
o2.age = 20;
console.log(o2.age); // 20

// 第三种:当使用Object.create 方法创建对象的时候
const o3 = Object.create(Object.prototype, {
  // foo会成为所创建对象的数据属性
  foo: {
    writable: true,
    configurable: true,
    value: "hello",
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function () {
      return 10;
    },
    set: function (value) {
      console.log("Setting `o.bar` to", value);
    },
  },
});
console.log("第三种返回");

console.log(o3.bar); // 10
o3.bar = 20; // 打印 『Setting o.bar to 20』

// 读取对象属性时,每次返回不同的值
const serialnum = {
  $n: 0,
  get next() {
    return this.$n++;
  },
  set next(n) {
    if (n >= this.$n) this.$n = n;
    else throw "序列号的值不能比当前值小";
  },
};
console.log("序列号属性返回")

console.log(serialnum.next); // 0
console.log(serialnum.next); // 1
console.log(serialnum.next); // 2
console.log(serialnum.next); // 3
console.log(serialnum.next); // 4
console.log(serialnum.next); // 5

// 每次读取属性都返回一个随机数
const random = {
  get octet() {
    // 返回0~255之间的数字
    return Math.floor(Math.random() * 256);
  },
  get uint16() {
    // 返回0~65535之间的数字
    return Math.floor(Math.random() * 65536);
  },
  get int16() {
    // 返回-32768~(65535-32768)之间的数字
    return Math.floor(Math.random() * 65536) - 32768;
  },
};

console.log("随机数打印结果")

console.log(random.octet);
console.log(random.octet);
console.log(random.octet);

console.log(random.uint16);
console.log(random.uint16);
console.log(random.uint16);

console.log(random.int16);
console.log(random.int16);
console.log(random.int16);

对象的方法

当我们通过对象直接量、new Object()和Object.create()不传入null的时候创建的对象,都会和另一个对象关联,『另一个』对象就是我们熟知的原型,每一个对象都从原型继承属性;
所以当我们新创建一个对象的同时,对象就有一些方法可以使用,这些方法就是从原型继承的。

toString

返回一个表示调用这个方法的对象值的字符串。

toLocaleString

同toString类似,不同的是针对不同类型的对象做了定制处理,比如Data和Number对象

valueOf

和toString类似,常常在JavaScript需要将对象转换为某种原始值而非字符串的时候才会调用它。

其它

new Object()

通过构造函数的形式创建对象,new Object()方法可接受一个参数;下面看一些例子:

const o = new Object();
console.log(o,'不传入任何参数');

const o1 = new Object(null);
console.log(o1,'传入null');

const o2 = new Object(undefined);
console.log(o2,'传入undefined');

// 相当于 const o3 = new Boolean(true);
const o3 = new Object(true); 
console.log(o3,'传入true');

// 相当于 const o4 = new String("3");
const o4 = new Object("3");
console.log(o4,'传入字符串');

const o5 = new Object(function(){});
console.log(o5,'传入函数');


const o6 = new Object(function(){
  return [1,2,3]
});
console.log(o6,'传入函数');

// 相当于 const o4 = new Boolean(false);
const o7 = new Object(Boolean());
console.log(o7,'传入对象');

打印结果:
截屏2021-05-27 16.00.49.png

super关键字

对象当中的this指向当前所在的对象,ES6中新增了一个super关键字,super关键字指向对象的原型对象;

const parent = {
  name: 'papa'
};

const child = {
  name: 'child',
  say() {
    return super.name;
  }
};
// 通过setProtoTypeOf 设置对象child的原型是parent
Object.setPrototypeOf(child, parent);
child.say() // "papa"

PS:注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
详情参考:ES6——对象的扩展
更多JavaScript对象的扩展内容,参考阮一峰的ES6对象的扩展一节


mmcai
126 声望10 粉丝

勿忘初心,方得始终