javaScript-对象

javaScript对象

对象的定义:无序属性的集合,其属性可以包含基本类型值、对象或者函数等。通俗来讲,对象是一组键值对的集合,键表示的是属性的名称,值表示的是属性的值。

对象的属性

对象的属性可以分为数据属性和访问器属性。数据属性一般用于存储数据数值,而访问器属性一般进行get/set操作,不能直接存储数据数值。

数据属性

数据属性具有4个描述其行为的特征

  • [[configurable]]:是否可以通过delete删除属性默认true;
  • [[enumerable]]:是否可以通过for…in循环返回,默认true;
  • [[writable]]:是否可以修改属性的值,默认true;
  • [[value]]:设置属性的值,默认undefined。

修改数据属性默认的特征,必须使用Object.defineProperty()方法,这个方法接收三个参数:属性所在对象、属性的名字和一个描述符对象。

1
2
3
4
5
6
Object.defineProperty(target, property, {
configurable: false,
enumerable: false,
writable: false,
value: 'node'
})

其中target为目标对象,property表示将要更改特征的属性,第三个参数是一个描述对象,描述对象的属性必须为configurable、enumerable、writable、value,以分别设置对应的特征值。

1
2
3
4
5
6
7
8
9
let person = {
name: "node"
}
Object.defineProperty(person, 'name', {
writable: false,
value: "Node"
})
person.name = "NODE"
console.log(person.name); // Node

上面代码中通过Object.defineProperty()函数设置person对象的name的writable值为false,表示name的属性值不可更改,并设置name的value为Node。由于writable值为false使的在后面设置name的属性值为Node不生效,而设置name的value为Node使的name的属性值为Node。

访问器属性

访问器属性不包含数据值。它包含一对getter和setter函数。当读取访问器属性时,会调用getter函数并返回有效值;当写入访问器属性时,会调用setter函数并传入新值,setter函数负责处理数据。该属性有四个特性:

  • [[Configurable]]:默认为true。表示能否通过delete删除属性从而重新定义属性,能否修改属性特性,或者能否把属性修改为访问器属性;
  • [[Enumerable]]:默认为true。表示能否通过for-in循环返回属性;
  • [[Get]]:读取属性时调用的函数,默认为undefined;
  • [[Set]]:写入属性时调用的函数,默认为undefined。

访问器属性不能直接定义,必须通过Object.defineProperty()函数定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let person = {
_age: 16
}
Object.defineProperty(person, 'age', {
get: function(){
return this._age
},
set: function(newAge){
if(newAge > 16){
this._age = newAge
}
}
})

person.age = 9;
console.log(person.age); // 16
person.age = 18;
console.log(person.age); // 18

上面代码中定义的person对象包含一个_age属性,一般下划线开头的属性将理解为私有属性。通过Object.defineProperty()函数为person对象定义一个age属性,用来控制对_age属性的读取和写入。

当读取age属性时直接返回对象的_age属性,当写入age属性时通过setter()函数对写入的值进行控制,当值大于16时,才会允许写入,所以第一个输出为16(9小于16不写入),第二个输出为18(18大于16写入)。

属性的访问

属性的访问有两种,一种是使用点操作符(.),一种是使用括号操作符([])

使用点(“.”)来访问

语法:objectName.propertyName

1
2
3
4
let person = {
name: 'node'
}
person.name // node

使用括号(“[]”)访问属性

语法:objectName[propertyName]

1
person['name']    // node

点和括号访问的不同

  • 点操作符是静态的,只能是一个以属性名称的简单描述符,而是无法修改;而中括号操作符是动态的,可以传递字符串或变量,并且支持在运行时修改。
1
2
3
let myName = 'name'
console.log(person.myName); // undefined
console.log(person[myName]); // node
  • 点操作符不能使用数字作为属性名,而括号可以
1
2
3
4
// person.1 = 1   // 异常:Unexpected number
person[1] = 1
// console.log(person.1); // 异常:missing ) after argument list
console.log(person[1]); // 1
  • 如果点操作符中包含会导致语法错误的字符,或者属性名中含有关键字或者保留字,可以使用方括号操作符,而不能使用点操作符
1
2
3
person['first name'] = "NodeJs"
console.log(person['first name']); // NodeJs
console.log(person.first name); // 异常:missing ) after argument list

创建对象

1. 基于Object()构造函数

通过Object对象的构造函数生成一个实例,然后给他增加需要的各种属性

1
2
3
4
5
6
7
8
9
10
let person = new Object();
// 为实例添加各种属性
person.name = 'node'
person.getName = function(){
return this.name
}
person.address = {
name: "广东",
code: "100000"
}

2.基于对象字面量

对象字面量本身就是一系列键值对的组合,每个属性用逗号分隔。

1
2
3
4
5
6
7
8
9
10
let person = {
name: "node",
getName: function(){
reurn this.name
},
address: {
name: "北京",
code: "100000"
}
}

方法1与方法2在创建对象时都具有相同的优点,即简单、容易理解。但是对象的属性值是通过对象自身进行设置的,如果需要同时创建若干个属性名相同,而只是属性值不同的对象时,则会产生很多的重复代码。

3.基于工厂方法模式

工厂方法模式是一种比较重要的设计模式,用于创建对象,旨在抽象出对象和属性赋值的过程,只对外暴露出需要设置的属性值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createPerson(name, age, address){
var o = Object();
o.name = name;
o.age = age;
o.address = address;
o.getName = function(){
return this.name;
}
return o;
}

let person = createPerson('node', 18, {
name: "北京",
code: "100000"
})

使用工厂方法可以减少很多重复的代码,但是创建的所有实例都是Object类型,无法进一步区分具体的类型。

4.基于构造函数模式

构造函数是通过this为对象添加属性的,属性值类型可以为基本类型、对象类型或者函数,然后通过new操作符创建对象实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name, age, address){
this.name = name;
this.age = age;
this.address = address;
this.getName = function(){
return this.name
};
}

let person = new Person('node', 18, {
name: "北京",
code: "100000"
});
let person1 = new Person('node', 18, {
name: "北京",
code: "100000"
});
console.log(person instanceof Person); // true
console.log(person1.getName === person.getName); // false

使用构造函数创建的对象可以确定所属类型,但是相同实例的函数是不一样的。每个实例都会占据一定的内存空间。

5.基于原型对象的模式

原型对象:在每一个函数创建时都会赋予一个prototype属性,它指向函数的原型对象,这个对象包含所有的实例共享的属性和函数

基于原型对象的模式是将所有的函数和属性都封装在对象的pertotype属性上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){}
Person.prototype.name = 'node';
Person.prototype.age = 18;
Person.prototype.address = {
name: "北京",
code: "100000"
};
Person.prototype.getName = function(){
return this.name
};

let person = Person();
let person2 = Person()
console.log(person.name === person2.name); // true
console.log(person.getName === person2.getName); // true

使用基于原型对象的模式创建的实例,其属性和函数都是相等的,不同的实例会共享原型上的属性和函数,改变其中的一个会引起其他实例的改变。

6.构造函数和原型混合的模式

构造模式和原型混合的模式是目前最常见的创建自定义类型对象的方式。

构造函数中对于实例的属性,原型函数中用于定义实例共享的属性和函数。通过构造函数传递参数,这样每个实例都能拥有自己的属性值,同属实例还能共享函数的引用,最大限度地节省了内存空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Person(name, age, address){
this.name = name;
this.age = age;
this.address = address;
}

Person.prototype.getName = function(){
return this.name;
};

let person = new Person('node1', 11, {
name: "北京",
code: "100000"
});

let person1 = new Person('node', 18, {
name: "北京",
code: "100000"
});
console.log(person.getName === person1.getName); // true

7.基于动态原型模式

将原型对象放在构造函数内部,通过变量控制,只在第一次生成实例的时候进行原型的设置。

相当于懒汉模式,只在生成实例是设置原型对象,但是功能与构造函数和原型混合模式相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(name, age, address){
this.name = name;
this.age = age;
this.address = address;
// 如果Person对象中_initalized为undefined,则表明还没有为Person的原型添加函数
if(typeof Person._initalized === "undefined"){
Person.prototype.getName = function(){
return this.name
}
Person._initalized = true
}
}

let person = new Person('node1', 11, {
name: "北京",
code: "100000"
});

let person1 = new Person('node', 18, {
name: "北京",
code: "100000"
});
console.log(person.getName === person1.getName); // true