对象是无序属性的集合,其属性可以包含基本值,对象或者函数
1、理解对象
创建对象最简单的方式就是创建一个Object实例,如:
var person= {
name:'anne',
age:22
}
- 属性类型:
属性有两种:数据属性和访问器属性
数据属性:
它有4个描述其行为的特性:
[[Configurable]]:表示能否通过delete删除属性,能够修改属性的特性,能够把属性改为访问器属性,默认为true
[[Enumerable]]:表示能否通过for in循环找到该属性,默认为true
[[Writable]]:表示能否修改属性的值,默认为true
[[Value]]:包含这个属性的数据值,读取的时候从这个位置读,写入的时候把新值保存到这个位置。默认为undefine
如果要修改属性特性的默认值,则要调用Object.defineProperty()方法,它接收三个参数,属性所在的对象、属性的名字、一个描述符对象。其中描述符必须是configurable,enumerable,writable,value。
var person= {};
Object.defineProperty(person,'name',{
writable:true,
value:'anne',
configurable:true,
enumerable:true
});
//也可以只设置其中一个或多个
访问器属性:
访问器属性不包含数据值,它们包含一对儿getter和setter函数,它有如下4个特性:
[[Configurable]]:与数据属性一样
[[Enumerable]]:与数据属性一样
[[Get]]:在读取属性时调用的函数,默认为undefine
[[Set]]:在设置属性时调用的函数,默认为undefine
var person= {
_name:'ly',
age:22
};
Object.defineProperty(person,'name',{
get:function(){ //当获取name时,调用get
return this._name;
},
set:function(newValue){ //当设置name时,调用set
this._name = newValue;
this.age += 1;
}
});
person.name = 'anne';
console.log(person.age) //23
可以使用Object.defineProperties()同时定义多个属性,第一个参数时要添加和修改其属性的对象,第二个参数是一个对象,表示要定义的属性
var person= {};
Object.defineProperties(person,{
name:{
value:'anne',
configurable:true
},
age:{
value:22
}
});
- 读取属性的特性:Object.getOwnPropertyDescriptor()
接收两个参数:属性所在的对象和要读取其描述符的属性名称
2、创建对象
2.1、工厂模式
function Person(name,age){
var o = {};
o.name = name;
o.age = age;
return o;
}
var one = Person('anne',22);
var two = Person('ly',30);
创建一个对象,并返回它,它没有解决对象识别的问题
2.2、构造函数模式
function Person(name,age){
this.name = name;
this.age = age;
this.getName = function(){
console.log(this.name);
}
}
var one = new Person('anne',22);
var two = new Person('ly',30);
必须使用new操作符,one和two都是Person的实例,他们都有一个constructor属性,指向Person
one.constructor == Person //true
也可以通过instanceof来检测:one instanceof Person //true
构造函数的问题是每个方法是要在实例上重新创建一遍,不同实例对象的同名函数是不相等的
2.3原型模式:
每个函数都有一个prototype属性,这个属性是一个指针,指向它的原型对象,原型对象包含所有实例共享的属性和方法。
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.getName = function(){
console.log(this.name);
}
var one = new Person('anne',22);
var two = new Person('ly',30);
one.getName(); //anne
two.getName(); //ly
只要创建一个函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象,所有的原型对象都会自动获得一个constructor属性(实例对象的constructor的属性就是继承自这个原型),指向prototype属性所在函数。
每个实例对象都包含一个内部属性,指向构造函数的原型对象,可以通过Object.getPrototypeOf()来获取原型对象,接收一个实例对象参数。
所以构造函数、原型、实例之间的关系就是:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性,搜索首先从对象实例本身开始,如果找到了,则返回该属性,如未找到,则继续搜索指针指向的原型对象。
如果一个实例属性与原型中的属性重名了,实例属性不会重写原型中的属性,它只会屏蔽掉原型中的属性。简言之,就是它会阻止我们访问原型中的属性,但不会修改那个属性
可以通过hasOwnProperty()方法来检测一个属性是存在于实例中还是原型中,在实例中返回true,在原型中返回false。
用in操作符可以检测一个对象实例是否有某个属性,不管是在实例中还是原型中。
通过以上两个方法可以检测一个属性是否只存在于原型中;
function hasPrototypeProperty(obj,name){
return !obj.hasOwnProperty(name) && (name in obj);
}
3、继承
原型链
js引入了原型链的概念,并将原型链作为实现继承的主要方法,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法,其基本模式的代码如下:
function Person(){
this.name = 'anne';
}
Person.prototype.sayName = function(){
console.log(this.name);
}
function Man(){
this.sex = 'man';
}
Man.prototype = new Person();
Man.prototype.getSex = function(){
console.log(this.sex);
}
var man1 = new Man();
man1.sayName(); //anne
man1.getSex(); //man
Man构造函数的原型对象是Person的实例对象,所以通过Man创建的实例拥有Person构造函数的方法
但这种方法存在两个问题:
a、因为原型链中属性和方法所有实例都会共享,而属性通常是不需要共享的,所以我们通常把属性放在构造函数中,但通过这种方法,Person的实例属性就成了Man的原型对象中的属性。
b、在创建子类型的实例时,不能像超类型的构造函数传递参数。
组合继承方式,能解决以上两个问题,代码如下:
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log(this.name);
}
function Man(name){
Person.call(this,name);
this.sex = 'man';
}
Man.prototype = new Person();
Man.prototype.getSex = function(){
console.log(this.sex);
}
var man1 = new Man('anne');
man1.sayName(); //anne
man1.getSex(); //man