javascript基础知识梳理-对象

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>对象</title>
<script type="text/javascript">
	//javascript中对象操作包括,对象的创建,属性设置(包括新增和修改),属性查找,属性删除,属性检测(in),属性枚举(for..in)
	//对象的每个属性除了名字和值之外,还有一个与之相关的值,称为属性特性(property attribute)
	//有三种属性特性,可写(表明是否可以设置该属性的值),可枚举(表明是否可以通过for.in循环返回该属性),可配置(表明是否可以删除或修改该属性)。
	//对象除了包含属性之外,还有三个相关的对象特性(object attribute),对象的原型对象,创建对象的构造函数(标识对象类型),对象扩展标记(表明是否可以向对象添加新属性)
	//对象可以分为三种:
	//一种是native objetc,就是ECMAScript中定义的标准对象,包括函数,数组,日期,正则表达式等
	//一种是宿主对象,在浏览器环境下就是浏览器自定义的一些对象,想DOM对象就是浏览器定义的,各个浏览器可能存在差异性
	//还有一种就是自定义的对象,即程序员代码定义的对象
	//属性可以分为两种:一种是对象的自有属性,一种是对象的原型对象中的属性(继承属性)
	//创建对象的方法有三种
	//一种是通过对象直接量直接创建对象,将属性名值对直接写在一个大括号中即可,属性名可以使合法的标识符或者字符串(常量变量都可以)
	//对象直接量每次计算的时候都会创建一个新的对象
	var obj = {
		name : "duchao",
		age : 25
	};
	//另外一种方式是通过new操作符来创建对象,new后面是一个函数对象,一般称作构造函数
	var obj = new Date();//创建一个日期对象
	var obj = new String();//创建一个字符串对象
	//最后一种方式是通过Object对象的create静态方法,第一个参数就是对象的原型对象,第二个参数可选,可以用来设置对象属性的一些特性,后面会讲到
	var obj = Object.create(Object.prototype);//等价于var obj = new Object();
	var obj = Object.create(Array.prototype);//等价于var obj = new Array();
	var obj = Object.create(null);//通过new操作符永远不可能创建出原型对象是空的对象,即使将构造函数的prototype设为null也没用,Object.create()方法就可以,对象直接量可以设置__proto__属性为null实现,但是不同浏览对象的原型属性可能不同
	console.log(obj instanceof Object);//如果通过Object.create(null)创建对象,那么obj instanceof Object将返回false
	//了解了创建对象的几种方式之后,下面看看对象的属性访问相关知识。
	//常见的一些错误,如果尝试给null或者undefined设置属性或者查找属性会报错。
	//var obj = null;
	//obj.name = "duchao";
	//console.log(obj.name);
	//如果尝试修改一个只读属性的值,chrome和firefox均不会报错,但是其实操作时失败的
	Object.prototype = 0;
	console.log(Object.prototype);//这里不会输出0,因为Object.prototype的值根本没变
	//下面有几种情况给对象o属性设值会失败
	//1、o中的属性p是只读的,不能给只读属性重新赋值,是不是类似java中的final(defineProperty()方法中有一个例外,可以对可配置的只读属性重新赋值)
	//2、o中的属性p是继承属性,且它是只读的,不能通过同名自有属性覆盖只读的继承属性。我觉得类似于java中父类定义了final字段,子类不能重写。
	//3、如果o中不存在属性p,而且没有setter方法可供调用,则p一定会添加至o中,但如果o不是可扩展的,那么在o中不能定义新属性。
	//下面看看删除属性,删除属性使用的是delete运算符,他的操作数应该是一个属性访问表达式,删除操作只是断开属性和宿主对象的关系,而不会去操作属性中的属性,看例子
	var p = 1;
	var obj = {};
	obj.p = p;
	delete obj.p;
	console.log("obj.p = " + obj.p);//输出undefined
	console.log("p = " + p);//输出1
	//delete运算符只能删除对象的自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除,而且会影响到所以继承这个原型的对象)
	//当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时返回true。如果delete后面不是一个属性访问表达式,同样返回true。
	//delete不能删除那些可配置性为false的属性(但是可以删除不可扩展对象的可配置属性,但是我觉得不可扩展对象属性越删越少啊,只能删不能增加也很蛋疼)
	//某些内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性
	//检测属性
	//判断某个属性是否存在于某个对象中,可以通过in运算符、hasOwnProperty()和propertyIsEnumerable()方法来完成这个工作。
	var obj = Object.create({
		x : 1,
		y : 2
	});
	obj.name = "duchao";
	//in运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回true。
	console.log("'x' in obj = " + ("x" in obj));
	console.log("'name' in obj = " + ("name" in obj));
	//对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承原型对象的属性将返回false。
	console.log("obj.hasOwnProperty('x') = " + obj.hasOwnProperty("x"));//这里会返回false,因为x是原型对象的属性
	console.log("obj.hasOwnProperty('name') = " + obj.hasOwnProperty("name"));//这里会返回true,因为name是obj的自有属性
	//propertyIsEnumerable()方法是hasOwnProperty()方法的增强版,只有在该属性为自有属性并且属性的可枚举性为true时才返回true
	console.log("obj.propertyIsEnumerable('x') = " + obj.propertyIsEnumerable("x"));//这个会返回false,因为x不是对象的自有属性
	//某些内置对象的属性是不可枚举的,比如Object.property对象中的toString方法
	console.log("Object.prototype.propertyIsEnumerable('toString') = "
			+ Object.prototype.propertyIsEnumerable("toString"));//toString是Object.prototype对象的自有属性,但是不可枚举
	//除了in操作符之外,还可以使用!==undefined方法判断一个对象以及其原型对象中是否包含某个属性,但是有一种场景只能使用in来判断,就是对象中的某个属性值就是undefined的情况
	//总结一下有几种判断对象中属性是否存在的方法:
	//o.x!==undefined(当o中或原型中有x属性并且x属性值不为undefined时表达式为真),可以用于判断属性存在并且不是undefined值
	var o = {
		x : null
	};
	console.log("o.x!==undefined = " + (o.x !== undefined));
	//o.x!=undefined与o.x!=null等价(当o中或原型中有x属性并且x属性值不为undefined并且x属性值不为null时表达式为真),可以用与判断属性存在并且不是null不是undefined
	console.log("o.x!=undefined = " + (o.x != undefined));
	//直接判断o.x,可以判断x属性存在,并且值不为null,undefined,0,-0,"",false

	//下面看看枚举属性
	//可以使用for/in循环,for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。
	//对象继承的内置方法不可枚举的,但是在代码中给对象添加的属性都是可枚举的(除非用下文中提到的一个方法将他们转换为不可枚举的)。
	//除了for/in循环之外,还有两个用以枚举属性名称的函数。一个是Object.keys(),它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。参数是需要枚举的对象。
	var obj = Object.create({
		x : 1,
		y : 2
	});
	obj.name = "duchao";
	obj.age = 20;
	var keys = Object.keys(Object.prototype);
	console.log(keys.toString());//返回空,说明Object.prototype对象没有可以枚举的属性
	//第二个是Object.getOwnPropertyNames(),它和Object.keys()类似,只是它返回的是对象的所有自有属性的名称,而不仅仅是可枚举属性。
	var keys = Object.getOwnPropertyNames(Object.prototype);//返回很多属性
	console.log(keys.toString());
	//下面看看属性的getter和setter
	//在ECMAScript5中属性值可以由一个或两个方法代替,这两个方法就是getter和setter
	//由getter和setter定义的属性称作"存取器属性",不同与数据属性,数据属性只是一个简单的值而已。
	//当程序查询存储器属性的值时,javascript调用getter方法(无参数),这个方法的返回值就是属性存取表达式的值。
	//当程序设置一个存取器属性的值时,javascript调用setter方法,将赋值表达式右侧的值当做参数传入setter方法
	//和数据属性不同,存储器属性不具有可写性。如果属性同时具有getter和setter方法,那么它是一个读/写属性,如果它只有getter方法,那么它是一个只读属性。如果它只有一个setter方法,那么它是一个只写属性,读取只写属性总是返回undefined。
	//定义存取器属性最简单的方法是使用对象直接量语法的一种扩展写法
	//存储器属性定义为一个或两个和属性同名的函数,这个函数定义没有使用function关键字,而是使用get和set,属性名和函数体之间也不需要冒号,但是需要逗号。
	var p = {
		//x和y是普通的可读写的数据属性
		x:3,
		y:4,
		//r是可读写的存储器属性,它有getter和setter
		get r(){return Math.sqrt(this.x*this.x+this.y*this.y)},
		set r(newValue){
			var oldValue = Math.sqrt(this.x*this.x+this.y*this.y);
			var ratio = newVlaue/oldValue;
			this.x*=ratio;
			this.y*=ratio;
		},
		//theta是只读存储器属性,它只有getter方法
		get theta(){return Math.atan2(this.y,this.x);}
	};
	console.log("p.r = " + p.r);
	console.log("p.theta = " + p.theta);
	//和数据属性一样,存储器属性也是可以继承的。
	var q = Object.create(p);
	console.log("q.x = " + q.x);
	//有很多场景可以用到存取器属性,比如智能检测属性的写入值以及在每次属性读取时返回不同值。
	//这个对象产生严格自增的序列号
	var serialnum = {
			//这个数据属性包含下一个序列号
			//$符号暗示这个属性是一个私有属性
			$n:0,
			//返回当前值,然后自增
			get next(){return this.$n++},
			//给n设置新的值,但只有当它比当前值大时才设置成功
			set next(n){
				if(n>=this.$n){
					this.$n = n;
				}
				else{
					throw "序列号的值不能比当前值小";
				}
			}
	};
	console.log(serialnum.next);
	console.log(serialnum.next);
	console.log(serialnum.next);
	//serialnum.next = 1;//这里会报错
	console.log(serialnum.next);
	//上面已经通过对象直接量定义了存储器属性,下面再看看如何给一个已经存在的对象添加一个存储器属性
	//属性的特性,属性除了包含名字和值之外,还包含一些标识它们可写,可枚举,可配置的特性,ECMAScript5中已经有查询和设置这些特性的API,在开发库函数的时候很有用。
	//可以通过这些API给原型对象添加方法,并将他们设置成不可枚举的,这让他们看起来更像内置方法。
	//可以通过这些API给对象定义不能修改或删除的属性,借此锁定这个对象。
	//数据属性包含四个特性:值,可写,可枚举,可配置。
	//存储器属性不具有值和可写性,它们的可写性是setter方法存在与否决定的。因此存储器的四个特性是读取,写入,可枚举,可配置。
	//数据属性的描述符对象的属性value,writable,enumerable,configurable,除了value,其他三个是bool值
	//存储器属性则用get,set属性代替value和writable属性,get和set是函数值
	//通过调用Object.getOwnPropertyDescriptor()可以获得某个对象特定属性的属性描述符。如果查询继承属性或者不存在的属性,返回undefined
	var obj = {x:1};
	var descriptor = Object.getOwnPropertyDescriptor(obj,"x");
	for(var p in descriptor){
		console.log(p + ":" + descriptor[p]);
	}
	//如果想要获取原型对象中属性的描述符对象,需要使用Object.getPrototypeOf()方法
	var obj = Object.create({y:2});
	var descriptor = Object.getPrototypeOf(obj,"y");
	for(var p in descriptor){
		console.log(p + ":" + descriptor[p]);//这里获取不到,后面再看吧
	}
	//要想设置属性的特性,或者想让新建属性具有某种特性,则需要调用Object.defineProperty(),传入要修改的对象,要创建或要修改的属性名,以及属性描述符对象。
	var obj = {};//创建一个空对象
	Object.defineProperty(obj,"x",{writable:false,enumerable:true,configurable:true});//将obj的x属性定义为不可写,可枚举的
	obj.x = 1;//这里给x属性赋值,但是由于x不可写,所以x值仍然为undefined
	console.log("obj.x = " + obj.x);
	//虽然通过直接赋值的方式无法修改可写性为false的属性,但是如果在定义的时候设置了configurable:true,就可以通过再次defineProperty方法给x属性重新赋值,虽然可写性为false,但是如果configurable为false的话再次对x属性调用defineProperty方法会报错
	Object.defineProperty(obj,"x",{value:1});
	console.log("obj.x = " + obj.x);
	console.log("Object.keys(obj) = " + Object.keys(obj).toString());//这里可以枚举出x,因为x是可枚举的
	//在新建属性并定义属性特性的时候,不用写出所有的特性,未配置的特性默认为false或者undefined,要注意的是这个方法要么修改对象的自有属性,要么新建属性,不会去修改原型对象的属性。
	//如果要同时定义多个属性,可以使用Object.defineProperties()方法,第一个参数要修改或者添加属性的对象,第二个参数是属性和描述符对象映射表
	var obj = {};
	Object.defineProperties(obj,{
		x:{value:2},
		y:{value:3}
	});
	console.log("obj.x = " + obj.x);
	//下面是属性特性配置的完整规则
	//1.如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性。
	//2.如果属性是不可配置的,则不能修改它的可配置性和可枚举性。(为啥测试出来值和可写性都TMD不能配置啊,下面有说明。。)
	var obj = {};
	Object.defineProperty(obj,"x",{writable:true});
	Object.defineProperty(obj,"x",{writable:false});
	//3.如果存取器属性是不可配置的,那么不能修改其getter和setter方法,也不能将它转换为数据属性。
	//4.如果数据属性是不可配置的,那么不能将其转换为存取器属性。
	//5.如果数据属性是不可配置的,那么不能将它的可写性从false改为true,但是可以从true改为false,配置性和枚举性是绝对不可修改的。
	//6.如果数据属性是不可配置且不可写的,则不能修改它的值,然而可配置但不可写属性的值是可以修改的(实际上是先将它标记位可写的,修改值之后再改为false)
	//用var定义一个全局变量,看看它的描述符对象,但是无论是否用var定义,查询出来的都是undefined。。。
	global = {};
	var descriptor = Object.getOwnPropertyDescriptor(window,global);
	for(var p in descriptor){
		console.log(p + ":" + descriptor[p]);//这里获取不到,后面再看吧
	}
	//普通对象直接定义的属性就可以查询到描述符对象,window对象直接定义的属性就无法查询描述符对象。。
	var obj = {x:1};
	var descriptor = Object.getOwnPropertyDescriptor(obj,"x");
	for(var p in descriptor){
		console.log(p + ":" + descriptor[p]);
	}
	//用Object.defineProperty()方法给window对象定义属性试试,果然TMD可以啊
	Object.defineProperty(window,"x",{});
	var descriptor = Object.getOwnPropertyDescriptor(window,"x");
	for(var p in descriptor){
		console.log(p + ":" + descriptor[p]);
	}
	//给Object.prototype对象增加一个extend方法,则所有的对象都可以继承这个方法,该方法可以将参数对象的所有属性包括属性的描述符对象全部复制到自有属性中,并且将该方法设置为不可枚举的
	Object.defineProperty(Object.prototype,"extend",{
		writable:false,//设置为不可写
		enumerable:false,//不可枚举
		configurable:false,//不可配置
		value:function(o){
			//遍历所有属性,包括不可枚举属性
			if(typeof o == 'object'){
				//获取所有自有属性名称数组
				var names = Object.getOwnPropertyNames(o);
				for(var i=0;i<names.length;i++){
					//对象中原来有的属性不会覆盖
					if(names[i] in this)continue;
					//获取属性p的描述符对象
					var descriptor = Object.getOwnPropertyDescriptor(o,names[i]);
					//如果存在描述符对象,则进行复制
					if(descriptor){
						Object.defineProperty(this,names[i],descriptor);
					}
				}
			}
		}
	});
	var o1 = {};
	Object.defineProperty(o1,"x",{value:2,configurable:true});
	var o2 = {};
	o2.extend(o1);
	console.log("o2.x = " + o2.x);
	//上面讲的是对象的属性特性,下面再讲讲对象的三个属性:原型,类,可扩展性
	//首先看看对象的原型属性,对象的原型属性是在创建对象之初就设置好的,通过对象直接量创建的对象使用Object.prototype作为他们的原型,通过new创建的对象使用构造函数的prototype属性作为他们的原型,通过Object.create()创建的对象以第一个参数作为原型
	//在ECMAScript5中,将对象作为参数传入Object.getPrototypeOf()可以查询他的原型,在ECMAScript3中没有与之等价的函数,但经常使用o.constructor.prototype来检测对象的原型
	var obj = {};
	var proto = Object.getPrototypeOf(Object.getPrototypeOf(obj));
	var proto = obj.constructor.prototype;
	console.log(proto);//对象直接量的原型没有争议
	//我们看看使用Object.create()方法创建的对象的原型
	var proto = {x:1};
	var obj = Object.create(proto);
	console.log(proto === Object.getPrototypeOf(obj));
	console.log(proto === obj.constructor.prototype);//这里使用obj.constructor.prototype方法获取原型对象就不对了
	//所以使用obj.constructor.prototype获取原型的方法是不靠谱的,只有使用直接量和构造函数创建的对象可以用该方式获取原型
	//要想检测一个对象是否是另一个对象的原型(或处于原型链中),请使用isPrototypeOf()方法,该方法是Object.prototype对象定义的方法,所有对象共享该方法。
	var proto = {x:1};
	var obj = Object.create(proto);
	console.log("proto.isPrototypeOf(obj) = " + proto.isPrototypeOf(obj));
	//除了原型属性,对象都有一个类属性,类属性是一个字符串,用以表示对象的类型信息,只有一种间接的方法可以访问他,即默认的toString()方法
	//由于很多继承的类覆写了toString()方法,则必须使用func.call()才能正确调用toString()方法,比如Array的toString,Function的toString,Date的toString,Regex的toString都被覆盖过了
	var obj = new Array();
	console.log("obj.toString() = " + obj.toString());//将返回空字符串
	console.log("Object.prototype.toString.call(obj) = " + Object.prototype.toString.call(obj));
	//但是这种判断类属性的方法只能适用于内置类创建的对象,注意下使用对象直接量和Object.create()创建出来的对象的类属性都是Object
	//使用自定义的构造函数new出来的对象使用toString()方法获取的类型仍然是Object。。。
	function f(){}
	var obj = new f();
	console.log("Object.prototype.toString.call(obj) = " + Object.prototype.toString.call(obj));
	//最后看看对象的可扩展性,对象的可扩展性用于表示是否可以给对象添加新属性,所有内置对象和自定义对象都是显示可扩展的,宿主对象的可扩展性是由js引擎决定的。
	//ECMAScript5定义了用来查询和设置对象可扩展性的函数,通过将对象传入Object.isExtensible获取对象的可扩展性
	var obj = {};
	console.log(Object.isExtensible(obj));
	//如果要将对象转换为不可扩展的,需要调用Object.preventExtensions()方法,传入需要转换的对象
	var obj = {};
	Object.preventExtensions(obj);
	obj.x = 1;
	console.log("obj.x = " + obj.x);
	console.log("Object.isExtensible(obj) = " + Object.isExtensible(obj));
	//需要注意的是,如果将对象转换成不可扩展的,就无法再让对象可扩展了。
	//还可以使用Object.seal()方法将对象彻底锁闭,使对象不可扩展,所有自有属性不可配置。可以用Object.isSealed()判断对象是否封闭。还有更牛逼的Object.freeze()方法,将对象彻底封闭,所有属性特性false,唯有getter和setter可以用,使用Object.isFrozen()判断对象是否被冻结。
	//最后看看序列化对象,对象序列化是指将对象的状态转换为字符串,也可将字符串还原为对象。ECMAScript5提供了内置函数JSON.stringify()和JSON.parse()来序列化和还原javascript对象。
	console.log("JSON.stringify([1,2,3]) = " + JSON.stringify([1,2,3]));
	console.log("JSON.parse('[1,2,3]').toString() = " + JSON.parse("[1,2,3]").toString());
	//JSON的语法是javscript语法的子集,它并不能表示javascript里的所有值,支持对象,数组,字符串,无穷大数字,true,false,null。NaN,Infinity,-Infinity序列化结果都是null。
	console.log("JSON.stringify(undefined) = " + JSON.stringify(undefined));
	console.log("JSON.stringify(NaN) = " + JSON.stringify(NaN));
	console.log("JSON.stringify(Infinity) = " + JSON.stringify(Infinity));
	console.log("JSON.stringify(-Infinity) = " + JSON.stringify(-Infinity));
	//日期对象序列化会变成日期字符串,并且还原也是相同的字符串
	var date = new Date();
	var dateToJson = JSON.stringify(date);
	var jsonToDate = JSON.parse(dateToJson);
	console.log("dateToJson = " + dateToJson);
	console.log("jsonToDate = " + jsonToDate);
	console.log("date.toJSON() = " + date.toJSON());
	//函数,正则对象,ERROR对象都不能序列化和还原,并且JSON.stringify函数只能序列化对象中的自有可枚举属性,对于一个不能序列化的属性来说,序列化的时候会忽略掉。
	console.log("JSON.stringify(function f(){}) = " + JSON.stringify(function f(){}));
	console.log("JSON.stringify(function f(){}) = " + JSON.stringify(function f(){}));
</script>
</head>
<body>
</body>
</html>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值