JS中的克隆与数据属性和访问器属性

一、数据属性和访问器属性

(1)在ECMAScript5中用Object.keys可以获取到对象的所有的可枚举属性的字符串数组,如果用于原型,那么就获取原型上面的所有的属性,如果用于实例,那么就返回实例属性。如果你想得到所有的实例属性,不管他是否可以枚举就可以用Object.getOwnPropertyNames方法,这个方法连constructor也会迭代出来!
(2)Object.keys和Object.getOwnPropertyNames可以用于替代for..in循环!(for..in既可以获取实例属性也可以获取原型属性,但是必须是可以枚举的属性)

(3)如果是访问器属性(对象)那么有configurable,enumerable,get,set,如果是数据属性那么有configurable,enumberable,writable,value属性!

    数据属性

 [[configurable]]表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性(defineProperty),能否把属性修改为访问器属性,如直接定义默认为true

 [[enumerable]]表示能够通过for..in循环返回属性,直接在对象上定义为true,如果调用defineProperty未指定就是false

 [[writtable]]表示是否能够修改属性的值,直接在对象上定义为true,如果调用defineProperty未指定就是false

  [[value]]包含这个属性的值,读取属性的时候从这个位置开始读,写入属性的时候把新值保存在这个位置中,默认为undefined

 这些特性是为了JS引擎用的,因此在JS中不能直接访问他们!要修改属性默认的特性必须使用ECMAScript5提供的Object.defineProperty方法,接受的参数为三个:属性所在的对象,属性的名字,一个描述符对象!描述符对象的属性必须是configurable,enumerable,writtable,value!设置其中的一个或者多个可以修改对于的特征值!

例1:

var person={}
Object.defineProperty(person,"name",{writable:false,value:"Nicholas"});
//打印"Nicholas"
alert(person.name);
//设置新的值,但是因为writable是false表示不能够修改name的值,所以不变,如果将writable改为true那么name就是新的值!
person.name="xxxx";
alert(person.name);
注意:因为name的writable是false表示不能修改,如果在非严格模式下赋值会被操作,在严格模式下直接抛出错误!
例2:

var person={}
Object.defineProperty(person,"name",{configurable:false,value:"Nicholas"});
//打印"Nicholas"
alert(person.name);
//因为name属性的configurable为false表示不能直接删除属性,于是如果在非严格模式下直接忽略,在严格模式下抛出异常
delete person.name
alert(person.name);
注意:一旦把属性定义为不可配置的,那么就不能把它变回可配置的了,这时候使用Object.defineProperty方法修改 除了writtable以外的任何特效都会报错!也就是只能修改writable属性! 同时如果在调用defineProperty时候不指定configurable,enumerable,writable默认都是false

访问器属性:

不包含数据值,他们包含一对儿getter和setter方法,但两种不是必须的!读取时候调用getter,设置时候调用setter,访问器属性有4个特效

     [[configurable]]对于直接在对象上定义的属性这个默认是是true,如果调用defineProperty未指定就是false

     [[Enumerable]]对于直接在对象上定义的属性这个默认是是true,如果调用defineProperty未指定就是false

    [[GET]]读取属性时候调用的方法,默认是undefined

    [[Set]]设置属性是调用的方法,默认为undefined!

访问器属性不能直接定义,必须使用definePeroperty定义!

  例1:

var book={
	_year:2004,//下划线表示只能通过对象方法访问的属性,也就是是通过调用对象的方法修改的!这里是调用book对象的set方法完成的!
	edition:1
};
Object.defineProperty(book,"year",//通过defineProperty方法创建一个year属性,是一个访问器属性!对该属性的设置会调用set方法!
	{
	get:function()
	{
		return this._year;//这里的this就是book对象,这一点要弄清楚!
	},
	set:function(newValue)
	{
		if(newValue>2004)
			{
				this._year=newValue;//this指的是book对象
				this.edition+=newValue-2004;
			}
	}
});
//他会调用book的year的set方法进行设置!
book.year=2006;
//打印3
alert(book.edition);
//打印2006
alert(book._year);
总结:要设置访问器属性必须通过defineProperty方法来完成!虽然可以直接对_year进行赋值,但是这么做没有任何意义,因为他不会导致edition的改变,所以这里用了下划线表示不要对它进行直接赋值,他的改变是通过其它代码来完成的,相当于私有属性一样!在这个方法之前要创建访问器属性是通过_defineGetter_和_defineSetter_方法,如book._defineGetter_("year",function(){return this._year}),在不支持defineProperty的方法的浏览器中configurable和enumerable是不可以改变的!

二、通过defineProperties定义多个属性:

var book={
};
//通过defineProperties定义多个属性!
Object.defineProperties(book,
	{
	_year:{value:2004},
	edition:{value:2},
	year:{
		get:function()
		{
			return this._year;//这里的this就是book对象,这一点要弄清楚!
		},
		set:function(newValue)
		{
			if(newValue>2004)
				{
					this._year=newValue;//this指的是book对象
					this.edition+=newValue-2004;
				}
		}
	}
	
});
总结:通过defineProperties定义多个属性,两个数据属性还有一个访问器属性!

三、通过ECMAScript5的Object.getOwnPropertyDescriptor获取属性描述符

    该方法接受两个参数,属性所在的对象和要读取的其描述符的属性名称,返回值是一个对象,如果是访问器属性那么有configurable,enumerable,get和set,如果是数据属性那么有configurable,enumerable,writable,value值!该方法只能用于实例属性,要想取得原型属性描述符必须在原型对象上面调用!

  在js中可以针对任何对象包括DOM和BOM对象使用object.getOwnDescriptor方法!

四、通过isPrototypeOf确定对象之间的关系

     如person.prototype.isPrototypeOf(person1);

五、通过Object.getPrototypeOf获取对象原型

    如Object.getPrototypeOf(person1)===Person.Prototype,获取该原型可以用于实现原型的继承特别有用!当原型和实例属性同名的时候,实例属性会屏蔽原型属性的值,这时候一个较好的方法就是用delete方法删除实例属性进而恢复到原型属性的引用!

六、通过hasOwnProperty检测一个属性是存在于实例中还是原型中

   这个方法继承于object,所以任何对象都可以调用,只有给定属性存在于对象实例中才会返回true.见下例:

function hasPrototypeProperty(obj,name)
{
	//不能存在于对象本身上面,但是必须存在于原型上面,最终结果就是判断obj的原型是否有某个属性!
	return !obj.hasOwnProperty(name)&&(name in obj)
}
七、通过Object.keys,getOwnPropertyNames获取所有可枚举的字符串数组

     keys方法用于原型对象就获取原型上面的所有的可以枚举的属性集合,用于实例对象那么就只是获取实例上面的所有的可枚举的属性集合(不包括原型上面的属性)。如果要获取所有的属性,不管他是否可以枚举,那么就用getOwnPropertyNames方法

function Person()
{}
Person.prototype.name="xxx";
Person.prototype.sex="female";
var p=new Person();
Object.defineProperty(p,"age",
{
	enumerable:false,
	value:120//这里没有分号!
})
//打印[age],用于实例对象只是获取实例上面的所有的可以枚举以及不可以枚举的属性,不包括原型上面的属性
//例如这里的enumerable是false表示不可以枚举,但是依然被打印出来!
alert(Object.getOwnPropertyNames(p));
//打印[constructor,name,sex]虽然constructor不能被枚举,但是依然被打印出来!
alert(Object.getOwnPropertyNames(Person.prototype));
对于keys方法只能打印enumerable是true的属性,但是getOwnPropertyNames连enumerable为false也一起打印:

function Person()
{	
}
Person.prototype.name="xxx";
Person.prototype.sex="female";
var p=new Person();
Object.defineProperty(p,"age",
{
	enumerable:false,
	value:120//这里没有分号!
})
//打印[]空数组,因为age的enumerable是false所以keys方法不会将他打印出来!
alert(Object.keys(p));
//打印[name,sex],keys方法也不会打印enumerable为false的属性
alert(Object.keys(Person.prototype));
八、重写prototype后导致的问题

例1:

function Person()
{
	
}
//这相当于完全重写原型了,于是原型的constructor不再指向上面的构造函数了,而是指向了object函数了!所以p的constructor指向了Object函数!
//但是instanceof操作符还是正确的!因为Person.prototype仍然在p对象的原型链中!
Person.prototype={
	name:"xxx",
	sex:"female"
}
var p=new Person();
//打印true,因为p的原型是Person.prototype,但是Person.prototype的原型是Object.prototype了
//所以Person.prototype仍然在p对象的原型链中!
alert(p instanceof Person);
//打印function Object(){[native code]}
alert(p.constructor);
//打印function Object(){[native code]}
alert(Person.prototype.constructor);
//打印function Function(){[native code]}
alert(Person.constructor);

注意:如果确实需要constructor返回正确的值,那么我们应该在原型中添加一句constructor:Person,但是这样做也是有负作用的,这回导致constructor的enumerable属性变成true,而原生的constructor是不可以枚举的,于是我们还是建议在defineProperty上修改,而不是在原型上面直接修改!

我们要知道constructor属性来自于原型对象,实例对象之所以可以访问是通过原型链来实现的

function Person()
{
	
}
Person.prototype={
	name:"xxx",
	sex:"female",
	constructor:"Person"
}
var p=new Person();
//constructor来自于prototype对象,实例对象没有constructor属性
console.log(Person.prototype.hasOwnProperty("constructor"));
console.log(p.hasOwnProperty("constructor"));
//对象本身的constructor来自于原型对象,自己没有这个属性!
console.log(p.constructor===Person.prototype.constructor);

例2:

function Person()
{}
//下面这一句new代码已经建立了从p到Person.prototype的指针了
var p=new Person();
//这相当于重写整个原型对象,但是前面的p仍然指向原来的原型对象,这就是重写整个原型对象的结果!
Person.prototype={
	constructor:Person,
	name:"xxx",
	sex:"female",
	sayName:function()
	{
		alert("name");
	}
}
//打印p.sayName不是一个函数的错误!因为原来的p从实例属性到原型属性都没有sayName函数!
p.sayName();
注意:虽然对原型的任何修改可以马上反映到所有的实例对象上面,但是, 这中情况不包括重写整个实例!
八、Object.create方法(原型式继承)
  ECMAScript引入该方法是为了规范原型继承,接受两个参数,第一个是用作新对象的原型的对象(可选的),第二个是为新对象定义额外属性的对象。传入一个参数情况下,与下面object方法相同(注意object返回的对象 继承了传入的参数的原型属性和实例属性):

例1:

function Person()
{	
}
Person.prototype.sex="female";
function object(o)
{	function F(){}
	//把参数作为返回的对象的原型,也就是返回的对象会具有该参数对象的所有的属性和方法!
	F.prototype=o;
	return new F();
}
var p=new Person();
p.age=120;
//打印120
alert(object(p).age);
//打印female
alert(object(p).sex);

我们要注意,获取到的新的对象的constructor属性来自于Person.prototype,然而instanceof还是能够返回正确的值,也就是依然还是Person对象

var result=object(p);
console.log(result.constructor);
//返回的对象的constructor是function Person(){},这也是原型式继承的原理!
//因为F函数的prototype被重写,constructor丢失,所以从原型链中获取到person
//的constructor属性!
console.log(result instanceof Person);
//打印true,所以还是Person类型,instanceof返回正确的值
console.log(result instanceof p);
//这里会报错,因为instanceof第二个参数必须是函数!

例2:

//这种方式的继承会导致继承对象的所有属性,但是这种方式会导致数组等引用对象的继承导致共享
//导致牵一发而动全身的情况!这是原生继承的弊端!
var person={
	name:"xx",
	friends:["a","b"]
}
var p1=Object.create(person);
p1.name="yyy"
p1.friends.push("c");
var p2=Object.create(person);
p2.name="zzz"
p2.friends.push("d");
//打印[a,b,c,d]
alert(person.friends);
//打印[a,b,c,d]
alert(p1.friends);
//打印[a,b,c,d]
alert(p2.friends);
//打印xx
alert(person.name);
//打印yyy
alert(p1.name);
//打印zzz
alert(p2.name);
//我们给create方法传入第二个参数,要注意这个第二个参数的格式问题!
var result=Object.create(person,{sex:{value:"female"}})
//打印female,也就是create方法的第二个参数是为新对象定义额外的属性!
alert(result.sex);

九、for..in和in的区别
例1:

//for..in语句用于对数组或者对象进行遍历!返回的是所有能够通过对象访问并且可以枚举的属性
//其中既包括存在于实例中的属性也包括存在于原型中的属性
var obj=["zhao","liang","zl","an"];
for(var j in obj)
{
document.write(j+"="+obj[j]+"\n");
}
var obj2={"name":"zl","age":"24","sex":"男"};
for(var j in obj2)
{
document.write(j+"="+obj2[j]+"\n");
}
例2:

var o={
	toString:function()
	{
		return "My Object"
	}
};
for(var prop in o)
	{
//对象o定义了toString方法该方法屏蔽了原型中不可枚举的toString方法,所以也会被其它浏览器枚举
//但是在IE早期版本,不会有弹出框,在IE中屏蔽不可枚举属性的实例属性不会出现在for.in循环中!
		if(prop==="toString")
			{
				alert("Found toString!");
			}
	}
注意:屏蔽原型属性不能枚举的属性的实例属性在其它浏览器中会被枚举,早期IE浏览器除外,所以hasOwnProperty,toLocalString,toString,valueOf,propertyIsEnumerable等在其它浏览器中都能够被屏蔽!同时constructor,prototype的enumerable也是false!in方法用于判断一个对象的实例属性或原型属性是否有特定的属性,不管是在实例属性还是原型属性中都返回true!

十,通过前面的方法实现克隆对象

function copy(o){
  var copy = Object.create( Object.getPrototypeOf(o) );
	//getOwnPropertyNames如果用于实例那么获取所有的实例属性,如果用于prototype那么就是获取所有的原型属性!和Object.keys一样!
  var propNames = Object.getOwnPropertyNames(o);
 //迭代属性和方法
  propNames.forEach(function(name){
	 //可以获取给定属性的描述符,返回值是一个对象,如果是访问器属性(对象)那么有configurable,enumerable,get,set,如果是数据属性那么有
	  //configurable,enumberable,writable,value属性!
    var desc = Object.getOwnPropertyDescriptor(o, name);
	  //把这些方法放在copy上面!
    Object.defineProperty(copy, name, desc);
  });

  return copy;
}
var o1 = {a:1, b:2,c:{name:"xxx",sex:"female"}};
var o2 = copy(o1); // o2 looks like o1 now
alert(o2.c.sex);

注意:该方法的思路是首先继承o对象原型上面的所有的属性,然后通过getOwnPropertyNames获取所有实例属性,进而通过getOwnPropertyDescriptor获取该属性的签名,最后赋值到对象上面返回!

Object中的其它方法:

(1)preventExtensions方法,可以定义防止串改的对象,调用该方法以后就不能添加任何新属性和方法了,在严格模式下会报错,在非严格模式下会失败。但是现有的对象所具有的属性还是可以修改和删除的!

(2)isExtensible方法用于判断对象是否可以拓展

(3)seal是第二个保护级别的密封对象,该对象不能拓展,而且已有成员的[[Configurable]]属性设置为false,这意味着不能删除属性和方法。因为不能通过Obejct.defineProperty把数据属性修改为访问器属性,或者相反。但是属性值可以修改!(可以用sealed判断)

var benjamin = {
    name: "Benjamin"
};
sealed = Object.seal(benjamin),
benjamin.name="xxx";
alert(benjamin.name);//打印"xxx",不能删除,不能修改属性的特性,但是依然可以修改值!

(4)最严格的防篡改级别就是冻结对象,该对象不可扩展,同时又是密封的,而且属性[[writtable]]也是false,如果定义[[set]]方法那么访问器属性仍然是可写的!可以用isFrozen()方法来判断!与密封对象和可扩展对象一样,在非严格模式下操作会被忽略,在严格模式下抛出异常!

note:对javascript库的作者来说,冻结是很有用的,因为js库最怕有人意外修改库中的核心对象,冻结主要的库对象可以防止这种问题的产生!用法参见博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值