- ecmascript有两种属性分别为数据属性和和访问器属性:
数据属性,包含以下4个特性:
①[[Configurable]]表示能否通过delete删除属性从而重新定义该属性
②[[Enumerable]]表示能否通过for-in循环返回属性
③[[Writable]]表示能否修改属性的值
④[[Value]]包含这个属性的属性值,默认为undefined
例如:
var person={};
Object.defineProperty(person,"name",{
configurable:false,//不可以配置
value:"tom"
});
alert(person.name);//tom
delete person.name;
alert(person.name);//tom
这里只是举个简单的例子,实际上这种高级配置属性应用的不是很多。
访问器属性:
访问器属性不包含数据值,只包含一对儿getter和setter函数,不过这两个函数都不是必须的。访问器属性不能直接定义,必须通过Object.defineProperty()来定义,它包含四个特性:
①[[Configurable]]表示能否通过delete删除属性从而重新定义该属性,能否修改属性的特性或者把属性修改为数据属性
②[[Enumerable]]表示能否通过for-in循环返回属性
③[[get]]在读取属性时调用的函数,默认为undefined
④[[set]]在写入属性时调用的函数,默认为undefined
例如:
var book={
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function()
{
return this._year;
},
set:function(newYear)
{
if(newYear>2004)
{
this._year=newYear;
this.edition+=newYear-2004;
}
}
});
book.year=2005;
alert(book.edition);//2
注意因为_year属性只能够通过对象访问,所以不能去指定_year 我们在这里只是指定了普通的year,通过year属性来重写了_year
可以通过使用Object.defineProperties()添加多个属性,该方法接收两个参数,一个是要在其上添加属性的对象,第二个是要添加的属性的一一对应。例如:
var book={};
Object.defineProperties(book,{
_year:{
value:4
},
edition:{
value:1
},//_year和edition是数据属性
year:{
get:function()
{
return this._year;
},
set:function(newYear)
{
if(newYear>2004)
{
this._year=newYear;
this.edition+=newYear-2004;
}
}
}//year是访问器属性,外界通过这个属性访问_year属性
});
实际上跟Object.defineproperty()是一样的,只不过把单一属性变成了属性和值的名值对列表。
Object.getownpropertydescriptor()方法,该方法接收两个参数,一个是属性所在的对象,另外一个是要读取的属性名称,使用方法如下:
var book={};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},//_year和edition是数据属性
year:{
get:function()
{
return this._year;
}
},//year是访问器属性,外界通过这个属性访问_year属性
set:function(newYear)
{
if(newYear>2004)
{
this._year=newYear;
this.edition+=newYear-2004;
}
}
});
var descriptor=Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value);//2004
alert(descriptor.configurable);//false
alert(descriptor.get);//undefined;
//var descriptor=Object.getOwnPropertyDescriptor(book,"year");
alert(descriptor.value);//undefined
alert(descriptor.enumerable);//false
alert(typeof descriptor.get);//function
- 创建对象
①工厂模式
发明一个函数,该函数可以返回一个对象例如:
function creatobject(name,age,job)
{
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function()
{
alert(this.name);
};
return o;
}
var person1=creatobject("zhangsan",23,"工程师");
var person2=creatobject("lisi",24,"保洁员");
person1.sayName();
②构造函数模式
像Object和Array这些属于原生的构造函数,在运行时会自动出现在执行环境中,另外我们还可以自定义构造函数,从而定义自定义对象的属性和方法,例如:
function Person(name,age,job)
{
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
}
}
var person1=new Person("zhangsan",23,"worker");
var person2=new Person("lisi",24,"enginerr");
person1.sayName();
Person1和person2都是Person的实例,同时也是Object的实例,因为所有对象都是Object对象的实例,运用intanceof操作符将都会返回true;
另外每个person的实例例如person1和person2都有一个constructor构造函数属性,该属性都指向Person即person1.constructor==Person将返回true
构造函数跟普通函数没有什么区别,因此可以像调用普通函数那样去调用构造函数
例如:
当做构造函数使用
var person1=new Person("zhangsan",23,"worker");
当做普通函数使用
Person("zhangsan",23,"worker");
因为该函数在全局环境中调用,因此该函数中所有的属性和方法都会附加在window对象上,可以通过window对象来调用
在另一个对象的作用域中使用:
var o=new Object();
Person.call(o,"lisi",24,"enginerr");
o.sayName();
构造函数的问题:
构造函数模式也不是没有缺点的,因为每当创建一个实例就会实例化一个对象,况且不同对象的同名函数时不相等的,也就是说没实例化一个对象都会重新创建该实例的函数,但这不是必要的,也可以把构造函数中的方法挪到外面去实现,但那样新问题又来了,我们不得不创建很多全局函数,既然这样的话就没有封装的意义可言了;这个过程如下:
function Person(name,age,job)
{
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName()
{
alert(this.name);
}
还好这个问题我们可以用原型模式来补救。
③原型模式
我们创建的每一个函数都有一个prototype原型属性,这个属性是一个指针,指向一个对象,因而可以不像构造函数模式那样一一指定属性和方法而是通过prototype直接添加到原型中,例如:
function Person(){}
Person.prototype.name="zhangsan";
Person.prototype.age=29;
Person.prototype.sayName=function()
{
alert(this.name);
}
var person1=new Person();
alert(person1.sayName());
var person2=new Person();
alert(person1.sayName()==person2.sayName())//true
理解什么事原型对象?
无论什么时候只要创建一个新函数就会根据一组特定的规则为该函数创建一个prototype属性,该属性指向函数的原型对象,在默认情况下,所有原型对象都会自动获得一个constructor构造函数属性,这个属性包含一个指向prototype属性所在函数的指针。他们的关系如下:
以上关系图通俗来讲就是每一个函数都具有XXX.prototype这个函数原型对象,函数的prototype属性就指向这个原型对象,当我们new一个函数对象时,就比如图中的person1和person2,这两个函数均具有prototype属性,该属性指向的都是person.prototype。
Person.prototype原型对象除了具有constructor属性外,还具有后来添加的其他属性,因为person1和person2都指向这个原型对象,所以person1和person2同样具有这些属性和方法。
我们可以通过isprototypeof()方法来测试person1或person2对象的原型属性指向哪里,例如:
Person.prototype.isprototypeof(person1)将返回true;
Person.prototype.isprototypeof(person2)将返回true;
也可以使用Object.getprototypeof()来返回对象内部原型属性指向的值,例如:
Object.getprototypeof(person1)将返回Person.prototype
虽然我们可以通过对象属性来访问原型对象中的属性,但是我们不能通过函数实例来修改原型对象中的值,如果我们给实例一个与原型对象重名的属性,那么在调用时该属性会覆盖掉原型对象的属性,因为执行属性搜索过程时先查找实例,发现实例具有这个属性则不再继续向上查找,而是返回这个属性,因此原型对象中的属性并没有发生任何改变,如果此时在创建一个函数实例,那么该实例拥有的属性仍是原型对象的属性。
即使我们给实例的重名属性赋值为null让然改变不了原型对象的值,但是如果我用如果我用delete操作符则可以完全删除该实例的重名属性,使其重新指向原型对象的属性
例如:
Person1.name=“grey”
Delete person1.name;
alert(person1.name)
将弹出原型对象的name属性。
通过hasownproperty()传入属性名称可以检测该属性是来自实例还是来自原型,来自实例的话会返回true,来自原型则会返回false。我们在新建函数实例时,如果不重写原型对象的属性那么所有属性就都来自原型对象,如果我们重写了原型对象的属性,那么重写后的属性就来自实例,例如下图:
3. 原型与in操作符
In的使用情况有两种,一种是单独使用,另外一种是for-in循环,当单独使用时,它在对象能够访问指定属性时返回true,而不论该属性是存在实例中还是原型中,例如:
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
person1.name = "Greg";
alert(person1.name); //"Greg" ?from instance
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true
alert(person2.name); //"Nicholas" ?from prototype
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true
delete person1.name;
alert(person1.name); //"Nicholas" - from the prototype
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
未完待续。。。