JavaScript数据类型分为基本类型和引用类型,对象则是某个特定引用类型(如原生引用类型:Object、Array、Date、RegExp、Function以及三个基本包装类型Boolean、Number、String)的实例。
对象是一组无序键值对的集合,即key和value的集合。
按照表现形式的不同,对象包含的内容可以分为属性和方法。
方法本质上也是属性的一种,所以有时候提到对象属性时也包含了方法。
对象创建的方式
单个创建对象
- 使用new操作符后跟构造函数
var person = new Object();
person.name = 'Lily';
person.age = 20;
- 对象字面量表示法(常用)
var persong = {
name: 'Lily',
age: 20
};
批量创建对象
- 工厂模式
将创建对象的过程封装在一个函数中,返回一个对象,缺点在于无法判断对象的类型。
function creatP(name, age) {
var o = new Object();
person.name = 'Lily';
person.age = 20;
return o;
}
var p1 = creatP('Tom', 21);
p1 instanceof creatP; // false
- 构造函数模式
将属性值直接赋值给this,用new操作符实例化,可以判断对象的类型,缺点在于会创建很多执行相同任务的方法(函数)。
function Person(name, age) {
this.name = 'Lily';
this.age = 20;
this.sayName = function() {
console.log(this.name);
}
}
var p1 = new Person('Tom', 21);
p1 instanceof Person; // true
p1 instanceof Object; // true
Person instanceof Object; // true
var p2 = new Person('Jerry', 20);
p1.sayName === p2.sayName; // false
构造函数既可以当做构造函数使用,也可以当做普通函数使用。
所有对象都是Object的实例。
- 原型模式
和构造函数的区别在于,不再在构造函数中定义属性和方法,而是将属性和方法直接添加到原型对象中。
function Person() {
}
Person.prototype.name = 'Lily';
Person.prototype.age = 20;
Person.prototype.sayName = function() {
console.log(this.name);
}
var p1 = new Person();
var p2 = new Person();
p1.sayName === p2.sayName; // true
每个函数都有一个prototype属性,指向原型对象,这个对象包含了实例对象要共享的所有属性和方法。
原型对象有一个constructor属性,指向prototype属性所在的函数。
即:
构造函数:Person
原型对象:Person.prototype
他们具有以下关系:
Person === Person.prototype.constructor
实例对象的内部也包含一个指向原型对象的指针,在ES5中,这个指针被定义为[[Prototype]],很遗憾,JavaScript中没有标准的方式访问[[Prototype]]。
但是有一个非标准的方法可以访问:Firefox、Safari、Chrome浏览器中为每个实例对象定义了一个__proto__属性,指向原型对象。
简而言之,构造函数、实例对象、原型对象三者之间的关系可以表示为:

原型对象还包含一个方法isPrototypeOf()用来判断一个对象是不是这个原型对象的实例。
Person.prototype.isPrototypeOf(p1); // true
Object.getPrototypeOf()方法可以用来访问实例的原型对象。
Object.getPrototypeOf(p1) === Person.prototype; // true
实例对象拥有原型对象的所有属性和方法,实例对象自身的同名属性会覆盖原型对象的属性。
实例对象无法更改原型对象的属性。
实例对象的hasOwnProperty()方法可以判断一个属性是否属于自身。
p1.hasOwnProperty('name'); // false
in操作符可以判断对象是否有某个属性(无论自身属性还是原型对象上的属性)
'name' in p1; // true;
遍历对象的属性
- for...in 遍历对象的可枚举属性(key)包含实例和原型中的属性
for (prop in p1) {
// ...
}
- Object.keys() 遍历对象的可枚举属性,不包括原型的属性
Object.keys(p1); // name, age, sayName
- Object.getOwnPropertyNames() 遍历对象的所有属性,不包括原型的属性
Object.getOwnPropertyNames(Person.prototype); // name, age, sayName, constructor
原型模式的缺点在于,如果原型对象的某个属性值为引用类型,那么所有的实例的该属性都会指向同一个引用类型,对这个引用类型所做的修改将会应用到所有的实例上。
- 组合使用构造函数模式和原型模式(推荐)
结合构造函数模式和原型模式的优点,既能保证实例共用方法,又能保证不共用引用类型,同时初始化实例对象时还能传参。
即:将值为引用类型的属性写在构造函数中,将方法写在原型对象中。
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Lucy', 'Jack'];
}
Person.prototype = {
constructor: Person, // 这种写法相当于重写了原型对象,因此要将consructor指回Person,但此时constructor属性变为可枚举属性了
sayName: function() {
console.log(this.name);
}
};
var p1 = new Person('Tom', 21);
var p2 = new Person('Jerry', 20);
p1.friends.push('Van');
p1.friends === p2.friends; // false
p1.sayName === p2.sayName; // true
实例对象的属性仅包含构造函数中定义的属性;原型对象中的属性仅包含直接对原型对象添加的属性。
- 动态原型模式
- 寄生构造函数模式
- 稳妥构造函数模式
继承
原型链继承
原型链是实现继承的主要方法。
什么是原型链?每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针;如果让原型对象A等于实例B,此时原型对象A将包含一个指向原型对象B的指针,原型对象B又包含一个指向构造函数B的指针;假如再让原型对象B等于实例C,上述关系依然成立,如此层层递进,就形成了一条实例与原型的链条。
需要注意,如果要给原型对象A添加属性,一定要在 原型对象A=实例B 这一步之后操作,因为这一步相当于重写了原型对象A,如果在此之前为原型对象A添加属性,将会无效。
constructor属性也会重写,会指向构造函数B而非构造函数A;如果原型对象B也被重写了,那么原型对象A也会继承新的原型对象B继承的原型对象C的constructor属性,指向构造函数C。
原型链继承方式的问题是什么呢?已经知道,原型对象A等于实例B,如果实例B中包含引用类型,那么原型对象A的所有实例对象都会共享上述引用类型,其中一个实例如果修改了引用类型,那么其他实例都会受到影响。
改进方法是在构造函数A中用call/apply调用构造函数B,获得B中定义的属性,依旧让原型对象A等于实例B,并且再将constructor属性指向构造函数A。
function B(name) {
this.name = name;
this.friends = ['Jason', 'Jack'];
}
B.prototype.sayName = function() {
console.log(this.name);
}
function A(name, age) {
B.call(this, name);
this.age = age;
}
A.prototype = new B();
A.prototype.constructor = A;
A.prototype.sayAge = function() {
console.log(this.age);
}
var a = new A('Abby', 20);
a.friends.push('Jimmy');
a.friends; // ["Jason", "Jack", "Jimmy"]
var b = new B('Baby', 23);
b.friends; // ["Jason", "Jack"]
原型式继承
如果只想让一个对象与另一个对象保持类似,可以使用Object.create()方法实现继承。
Object.create()接受两个参数,参数1是想要(浅)复制的原型对象,参数2(可选)是为新对象定义额外属性的对象。
var a = {
name: 'Abby',
friends: ['Jack', 'Jason']
}
var b = Object.create(a, {
name: {
value: 'Baby'
}
})
b.name; // 'Baby'
b.friends.push('Jimmy');
a.friends; // ["Jack", "Jason", "Jimmy"]
注意第二个参数的格式。
访问对象
- 点操作符(推荐)
console.log(person.name);
- 方括号语法
console.log(person['name']);
检测对象类型
- 使用对象的constructor属性
var arr = [];
arr.constructor === Array; // true
- instanceof操作符(推荐)
var arr = [];
arr instanceof Array; // true
实际上instanceof检测的是一个构造函数是否出现在实例的原型链中。
isPrototypeOf()则是检测一个原型对象是否出现在实例的原型链中:
var arr = [];
Array.prototype.isPrototypeOf(arr); // true
网友评论