5.核心JavaScript笔记:面向对象(Object)

本系列内容由ZouStrong整理收录

整理自《JavaScript权威指南(第六版)》,《JavaScript高级程序设计(第三版)》

引用类型是一种数据结构,用于组织数据和功能,描述的是一类对象所具有的属性和方法

引用类型常被称为类,但他们并不相同,因为JavaScript不具备传统的面向对象语言所支持的类和接口等概念

引用类型的值(需要实例化)就是对象,对象是特定引用类型的一个实例

Object类型是最基础的类型,其他所有类型都继承自Object(都从Object类型继承了基本的数据和方法)

对象简介

大多数对象都是Object类型的实例,虽然Object的实例不具备多少功能,但多用于存储和传输数据

  • 对象是一种复合值,它将很多值(原始值或者对象)聚合在一起,可通过名字访问这些值
  • 对象是属性的无序集合,每个属性都是一个名/值对
  • 对象除了保持自身的属性,还可以从一个称之为原型的对象继承属性,这种“原型式继承”是JavaScript的核心特征
  • 对象是动态的——可以新增和删除属性

每个对象实例都具有继承自Object类型的以下属性或者方法

  • constructor
  • hasOwnProperty()
  • propertyIsEnumerable()
  • isPrototypeOf()
  • toString()
  • toLocaleString()
  • valueOf()

除了属性名和属性值之外,每个属性还有一些属性特性

  • 可写,表明属性值是否可以被重写
  • 可枚举,表明属性是否可以被for/in循环枚举
  • 可配置,表明属性是否可以被删除或修改

每个对象还拥有三个相关的对象特性

  • 对象的原型,指向另一个对象,本对象的属性继承自原型对象
  • 对象的类,是一个标识对象类型的字符串
  • 对象的扩展标记,(ECMAScript5)指明了是否可以向该对象添加新属性

最后,简单地说,可以将对象分为三类

  • 内置对象——Object、Array、function、RegExp、Date
  • 宿主对象——DOM对象、BOM对象
  • 自定义对象——JavaScript代码创建的

属性可以分为两类

  • 自有属性——直接在对象中定义的属性
  • 继承属性——在原型中定义的属性

一. 创建对象

创建对象有很多种方式

1. 对象直接量

创建对象最简单的方式就是使用对象直接量,它是由若干名/值对(属性名和属性值)组成的映射表

  • 属性名和属性值用冒号分开(:)
  • 属性之间用逗号分开(,)
  • 整个映射表由花括号括起来({})
  • 属性名可以是JavaScript标识符也可以是字符串直接量(包含空字符串)
  • 属性值可以是任意类型的JavaScript表达式(表达式的值就是属性的值)

看几个例子

var empty={};  //创建一个空对象,但包含默认属性和方法
var people={name:"strong",age:25};   //拥有两个属性的对象
var people2 ={name:people.name,age:people.gae};  //更复杂的值
var book={
	"main title" : "JavaScript",  //属性名包含空格,必须用字符串表示
	"subi-title" : "Java",  //属性名包含连字符,必须用字符串表示
	"for":"all people",  //属性名是保留字,必须用字符串表示
	auther:{             //属性值是对象
		name:"david",     
		age:56
	}
}

对象字面量简化了创建包含大量属性的对象的过程,使得代码量减少,并且有封装数据的感觉,而且对象字面量也是向函数传递大量可选参数的首选方式

function test(obj){
	if(typeof obj.name=="string"){
	}
	if(typeof obj.age=="number"){
	}
}

这种传递参数的模式最适合需要向函数传入大量可选参数的情形。一般来讲,命名参数虽然容易处理,但在有多个可选参数的情况下就会显示不够灵活(受顺序影响);最好的做法是对那些必需值使用命名参数,而使用对象字面量来封装多个可选参数

  1. 非法的标识符(例如包含空格,连字符,纯数字...)作为属性名,必须使用字符串形式
  2. 保留字作为属性名,ES3中必须使用字符串形式,ES5中则不必(都用字符串形式,总是没错的)
  3. ECMAScript5中对象直接量中最后一个属性后的逗号会被忽略,但在IE7及以下浏览器中不会忽略,会报错,所以永远不要书写最后一个逗号
  4. 对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新对象,并重新计算每个属性的值(如果在一个循环体内使用了对象直接量,将会创建很多对象,并且每次创建的对象的属性值也有可能不同)
  5. 使用对象字面量创建对象,不会调用Object构造函数

2. 通过new创建对象

new运算符创建并初始化一个新对象

关键字new后跟一个构造函数(通常都是首字母大写),构造函数用以初始化一个新创建的对象

JavaScript中的内置对象都包含内置构造函数

var obj= new Object();  //创建Object引用类型的一个实例
var obj = Object;  //new和括号()都可以同时省略,作用同上

除了内置构造函数,还可以使用自定义的构造函数来初始化一个对象

function Name(){
	this.name = "strong";
}
var obj = new Name();      //{name:"strong"}

注:构造函数通常都是首字母大写,以与普通函数区别,除此之外,与普通函数没有什么区别

3. 通过Object.create()创建对象

先了解一下原型(后续详解)

几乎每一个JavaScript对象都有原型,它们会从其原型继承属性

所有通过对象直接量创建的对象都具有同一个原型对象——Object.prototype

通过关键字new和构造函数创建的对象的原型就是——构造函数.prototype

prototype属性是函数的属性

没有原型的对象不多——null 和 Object.prototype就是其中的两个,他们不继承任何属性(使用Object.create()可以自定义创建没有原型的对象)

所有的内置构造函数以及大部分自定义构造函数都有一个继承自Object.prototype的原型,如Date.prototype的属性继承自Object.prototype,所以Date对象的属性同时继承自Date.prototype 和 Object.prototype,这一系列的原型对象就是所谓的——原型链

ECMAScript5 新增了创建对象的新方法Object.create(),可接受两个参数

  • 第一个参数,必须,表示要创建的对象的原型(要继承的对象)
  • 第二个参数,可选,对象属性的进一步描述(描述符对象)

继承指定对象

var  o = Object.create({name:"hello"});
//创建一个对象,继承了指定对象的name属性

可以传入null来创建一个没有原型的对象,他们不继承任何属性,甚至没有基础方法,比如toString(),这意味着它不能和"+"运算符一起正常工作

var  o = Object.create(null);    //创建一个没有原型的对象,不继承任何属性和方法

如果想创建一个普通的空对象(比如{}或者new创建的),则需要传入Object.prototype

var  o = Object.create(Object.prototype);    //创建一个对象,跟{}一样

可以通过任意原型创建新对象(也就是可以使任意对象可继承)

function inherit(prototype){
	if(prototype==null){
		throw TypeError();	
	}
	if(Object.create){
		return Object.create(prototype);
	}
	var type = typeof prototype;
	if(type!=="object" && type!=="function"){
		throw TypeError();
	}
	function F(){};
	F.prototype=prototype;
	return new F();
}

inherit()函数的其中一个用途就是防止对象被无意中修改,我们不是直接处理该对象,而是处理它的继承对象,当需要读取值时,读取的是继承来的值,而赋值或者修改值时,修改的继承对象,而不是原始对象

二. 属性访问

属性的查询和设置统称为属性访问

可以通过点(.)或者方括号([])运算符来获取属性的值

运算符左侧应当是一个表达式,返回一个对象,运算符右侧是属性名

  • 当属性名是正常标识符时,可以使用点号或者方括号语法来访问
  • 当属性名右侧是保留字或者包含非正常字符时,只能使用方括号语法(ECMAScript5中,当属姓名是保留字时,也可以使用点号来访问属性)

    var name = obj.name;
    var title = obj["main-title"];

同样通过点(.)或者方括号([])语法也可以创建属性或者给属性赋值

book.name="zsz";
book["main title"]="js";

方括号语法的主要优点是可以通过变量来访问属性

var propertyName = "name"; 
person[propertyName]

1. 作为关联数组的对象

当我们使用方括号语法时,看起来很像在操作数组,只不过这个数组的索引不是数字而是字符串,就像关联数组(也称散列、映射或字典)

JavaScript中的对象都是关联数组

在强类型语言中,对象必须拥有固定数目的属性,且这些属性名称必须提前定义好,JavaScript是弱类型语言,对象是动态的,可以拥有任意数量的属性,并且可以随时更改

当我们使用点(.)运算符访问对象属性时,属性名是一个标识符,标识符必须直接出现在JavaScript程序中,因为它不是数据类型,因此程序无法修改他们(也就是无法动态指定一个标识符)(标识符是静态的,必须写死在程序中)

但当使用方括号([])语法时,属性名是通过字符串表示的,字符串是数据类型,在程序运行过程中可以修改和创建它们(字符串是动态的,可以再运行时更改)

for(var i=0;i<5;i++){
	alert(obj["name"+i]);
}  //否则,就只能使用for in循环了

2. 继承

JavaScript对象的属性可能是“自有属性”也可能是从原型对象继承来的

当查询对象A的属性时,如果对象本身不存在该属性,那么将会继续在A的原型对象中查询属性,如果原型对象中也没有,但这个原型对象还有原型,那么继续在这个原型对象的原型中查询...直到找到属性或者返回undefined

对象的原型构成了原型链,通过这个“链”可以实现属性的继承

当给对象A的属性赋值时,如果A已有这个属性(自有属性),那么这个属性会被新值代替,如果这个属性是存在于原型中的,会给A创建并添加一个新属性(原型中的不受影响)

小结

属性查询操作,首先检查当前对象,不存在时,才去查找原型链

属性赋值操作,首先检查原型链,如果这个属性继承自原型对象中的只读属性,那么赋值操作是不允许的,因此属性赋值要么失败,要么覆盖当前对象的属性或者为当前对象创建一个新属性

在JavaScript中,只有查询属性时才会体会到继承的存在,而设置属性则和继承无关,因为它总是在原始对象上创建或者设置属性,而不会去修改原型链

3. 属性访问错误

属性访问并不总是返回或者设置一个值

查询一个不存在(自身和原型中都不存在)的属性不会报错——而是返回undefined

但是查询一个不存在的对象的属性就会报错。null和undefined都没有属性,因此查询它们的属性会报错

var a;
a.name;   //TypeError

给null和undefined设置属性时,也会报类型错误

给其它值设置属性时,也不总是成功,因为有些属性是只读的,不能重新赋值,而有一些对象不允许新增属性,但是这些设置属性的失败操作不会报错

//内置构造函数的原型是只读的
Object.prototype="zsz";  //赋值失败,不报错,仅仅是什么都没有发生

这其实是一个BUG,在ECMAScript5的严格模式下得以修复,任何失败的属性设置操作都会抛出一个类型异常错误

总结起来,属性设置时,会失败的几种情形

  • 对象的属性是自有的,但是只读的,不能给只读属性重新赋值(defineProperty()方法中有一个例外,可以对可配置的只读属性重新赋值)
  • 对象的属性是继承的,但是只读的,不能通过同名自有属性覆盖只读的继承属性
  • 对象中不存在自有属性P,对象没有使用setter方法继承属性P,并且对象的可扩展性是false,如果对象不存在属性P,而且没有setter方法可供调用,则P一定会添加至对象中,但如果对象是不可扩展的,那么在对象中不能定义新属性(没看懂)

三. 删除属性

delete运算符可以删除对象的属性

delete book.name;

delete只是断开对象和属性的联系,而不会去操作属性中的属性

a={b:{c:1}};
d=a.b;
delete a.b;
d.c       //仍然返回1

可能因为这种不严谨的代码而造成内存泄露,所以在销毁对象的时候,要遍历属性中的属性,依次删除

delete运算符只能删除自有属性,不能删除继承属性,要删除继承属性必须从定义这个属性的原型对象上删除它,而这会影响到所有继承自这个原型的对象

当delete表达式删除成功或者没有任何副作用(比如删除不存在的属性)时,它返回true,如果delete后不是一个属性访问表达式,delete同样返回true

o={x:1};
delete o.x;         //删除属性x,返回true
delete o.x;         //属性x不存在了,因此什么都没做,返回true
delete o.toString;    //不能删除继承属性,因此什么都没做,返回true
delete 1;         //无意义,返回true

delete不能删除那些可配置性为false的属性,某些内置对象的属性就是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性(也就是使用var声明的变量不可删除,省略var创建的变量可以删除)

注:在严格模式下,删除一个不可配置属性会报类型错误,在非严格模式下,会返回false

当在非严格模式中删除全局对象的可配置属性时,可以省略对全局对象的引用,直接在delete操作符后跟随要删除的属性即可,在严格模式下,必须显示指定对象及属性名

delete Object.prototype;    //不可删除
x=1;
delete window.x;(或delete x)         //删除成功
var x=1;
delete window.x;(或delete x)        //不能删除使用var声明的全局变量
function a(){}
delete window.a;(或delete a)       //不能删除全局函数

四. 检测属性

检测某个属性是否存在于某个对象中,可以使用in运算符、hasOwnProperty()方法和propertyIsEnumerable()方法,甚至通过属性查询也可以做到

1. in运算符

in运算符的左侧是属性名(必须是字符串形式),右侧是对象

如果对象的自有属性或者继承属性中包含这个属性则返回true

var o = {x:1};
"x" in o;              //true
"y" in o;             //false
"toString" in o;  //true

2. hasOwnProperty()方法

该方法用于检测指定的属性是不是对象的自有属性,只有属性是自有属性时才返回true

var o = {x:1};
o.hasOwnProperty("x");  //true
o.hasOwnProperty("y");  //false
o.hasOwnProperty("toString");  //false

3. propertyIsEnumerable()方法

该方法是hasOwnProperty()方法的增强版,只有属性是自有属性且属性是可枚举的时才返回true

var o = Object.create({y:1});
o.x=1;
o.propertyIsEnumerable("x");   //true
o.propertyIsEnumerable("y");   //false
Object.prototype.propertyIsEnumerable("toString");  //false

4. 属性查询

var o = {x:1};
if(o.x !==undefined){
	//x属性存在
}

这种方式不能代替in运算符,且不太可靠,因为这种方式不能区分不存在的属性和存在但是值为undefined的属性

五. 枚举(遍历)属性

遍历对象属性通常使用for/in循环,ECMAScript5提供了两个更好用的方法

1. for/in循环

for/in循环用来遍历对象的自有属性和继承的属性,但必须是可枚举的(例如继承的内置方法都是不可枚举的,自己添加的属性是可枚举的,除非使用ECMAScript5的方法自己设置为不可枚举)

var o={x:1,y:2,z:3};
for(var p in o){
	console.log(p);    //x,y,z
}

很多JavaScript库都给Object.prototype添加了新的属性或者方法,以便被所有对象继承,但是在ECMAScript5之前,这些新添加的方法是不能定义为不可枚举的,因此总会被for/in循环枚举出来,需要过滤

//方式1	
for(var p in o){
	if(!o.hasOwnProperty(p)){
		continue;  //跳过继承的属性
	}
	//正常操作在这
}

//方式2	
for(var p in o){
	if(typeof o[p]==="function"){
		continue;  //仅跳过方法
	}
}	

使用for/in循环实现的几个功能

/*将p的可枚举属性复制到o中,并返回o
 *如果o中有同名属性,则覆盖
 *这个函数不处理getter和setter以及复制属性
 */
function extend(o,p){
	for(var pro in p){
		o[pro]=p[pro];
	}
	return o;
}

.....

/*将p的可枚举属性复制到o中,并返回o
 *如果o中有同名属性,o中的属性将不受影响
 *这个函数不处理getter和setter以及复制属性
 */
function merge(o,p){
	for(var pro in p){
		if(o.hasOwnProperty(pro)){
			continue;			
		}
		o[pro]=p[pro];
	}
	return o;
}

.....

/*如果o中的属性在p中没有同名属性,则从o中删除该属性
 *返回o
 */
function restrict(o,p){
	for(var pro in o){
		if(!(pro in p)){
			delete o[pro];
		}
	}
	return o;
}

.....

/*如果o中的属性在p中存在同名属性,则从o中删除该属性
 *返回o
 */
function subtract(o,p){
	for(var pro in p){
		delete o[pro];  //这里有个点,直接删除就是,删除不存在的属性不会报错
	}
	return o;
}

.....

/*返回一个新对象,这个对象同时拥有o的属性和p的属性
 *重名的,则使用p中的属性值
 */
function union(o,p){
	return extend(extend({},o),p);
}

.....

/*返回一个新对象,这个对象拥有o和p的共有属性
 *p中属性的值被忽略
 */
function intersection(o,p){
	return restrict(extend({},o),p);
}

.....

/*返回一个数组,这个数组包含的是o中可枚举的自有属性*/
function keys(o){
	if(typeof o !=="object"){
		throw TypeError();
	}
	var result=[];
	for(var pro in o){
		if(o.hasOwnProperty(pro)){
			result.push(pro);
		}
	}
	return result;
}

2. Object.keys()——ECMAScript5

它接受一个对象,返回一个数组,数组由对象中的可枚举的自有属性的名称组成(与上面的keys()方法类似),如果没有,则返回空数组

var a={x:1,y:2};
Object.keys(a);    //["x","y"]

3. Object.getOwnPropertyNames()——ECMAScript5

它接受一个对象,返回一个数组,数组由对象中自有属性的名称组成(不管是不是可枚举的)

Object.getOwnPropertyNames(Object.prototype)
返回
["constructor", "toString", "toLocaleString", "valueOf", 
"hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
 "__defineGetter__", "__lookupGetter__", "__defineSetter__",
 "__lookupSetter__", "__proto__"]

这个方法就不能模拟了,因为ECMAScript3中没有办法获取不可枚举的属性

六. 属性getter和setter

对象的属性由属性名、属性值和一组特性构成

一般的属性都称为数据属性,数据属性就是一个值而已(包括了方法,函数也是一个值)

ECMAScript5中,属性值可以用一个或者两个方法代替,这两个方法是getter和setter,由getter和setter定义的属性称作——存取器属性(访问器属性),存取器属性不包含数据值

当查询存取器属性时,JavaScript调用getter方法(无参数),这个方法必须有返回值,返回的值就是属性存取表达式的值

当设置存取器属性时,JavaScript调用setter方法,将赋值表达式右侧的值当做参数传入setter,这个方法负责“设置”属性值,可以忽略setter方法的返回值

和数据属性不同,存取器属性不具有可写性

  • 如果属性同时具有getter和setter方法,那么它是一个读/写属性
  • 如果属性只有getter方法,那么它是一个只读属性
  • 如果属性只有setter方法,那么它是一个只写属性,读取只写属性总是返回undefined

定义存取器属性最简单的方法就是使用对象直接量的一种扩展写法

var o ={
	x:1,   //普通的数据属性
	//存取器属性,一般都是成对定义的函数
	get  a(){
		return  Math.random();
	},
	set a(value){
		this.x=value;    //this仍然表示当前对象
	}
}
o.a;    //返回一个随机数
o.a = 2 ;  //将对象的x属性重写为2

存取器属性定义为一个或者两个和属性同名的函数,这个函数定义没有使用function关键字,而是使用get或者set,并且这里没有使用冒号将属性名和函数体分开,但是函数体的结束和下一个属性之间仍用逗号分隔

注意,this指代当前函数,这说明JavaScript把这些函数当做对象方法来调用的

存取器属性本质上是方法,但是在对象的外部作为属性来访问

和数据属性一样,存取器属性也是可以继承的

有很多场景可以用到存取器属性,比如智能检测属性的写入值以及在每次读取属性时返回不同的值

也就是当属性是动态的时候,最好使用存取器属性

//这个对象产生严格自增的序列号
var o = {
	n:0,
	get next(){
		return this.n++;
	},
	set next(value){
		if(value>this.n){
			this.n=value;
		}else{
			throw "必须大于当前值";
		}
	}
};

还有一个例子

var o = {
	get random(){
		return Math.random();
	}
};	
o.random;  //返回随机数
o.random;  //返回不同的随机数

var o = {
	random:Math.random()
};	
o.random;  //返回随机数
o.random;  //返回相同的随机数

现在,我们讨论了给对象直接量定义存储器属性,下面会讲解给已经存在的对象添加存取器属性

七. 属性的特性

除了属性的名字和值以外,属性还包含一些标识它们可写、可枚举和可配置的特性

在ECMAScript3中,这些特性就已经存在,由ECMAScript3程序创建的对象的属性,始终都是可写、可枚举、可配置的

ECMAScript5新增了一些API,可以对属性的特性进行相关的修改或者设置

  • 可以给原型对象添加方法,并设置为不可枚举,使之看起来更像是内置方法
  • 可以给对象设置不可修改或不可删除的属性,借以“锁定”这个对象

我们将存取器属性的getter和setter方法看成是属性的特性,按照这个逻辑,数据属性的值同样也可以看成是属性的特性,因此,一个属性包含一个属性名和四个特性

数据属性的四个特性分别是

  • 值(value)
  • 可写性(writable)
  • 可枚举性(enumerable)
  • 可配置性(configurable)

存取器属性不具有值特性和可写性,它的可写性是由setter方法存在与否决定的,存取器属性的四个特性分别是

  • 读取(get)
  • 写入(set)
  • 可枚举性(enumerable)
  • 可配置性(configurable)

1. Object.getOwnPropertyDescriptor()方法——ECMAScript5

该方法返回一个属性描述符对象,该对象包含了四个属性特性

第一个参数,Object,必须,表示要检测的属性所在的对象

第一个参数,String,必须,表示要检测对象的属性

var a={x:1}
Object.getOwnPropertyDescriptor(a,"x");
返回{value: 1, writable: true, enumerable: true, configurable: true}

var a={get b(){return 1}};
Object.getOwnPropertyDescriptor(a,"b");
//返回{get: function, set: undefined, enumerable: true, configurable: true}

从名字就可以看出,该方法只能得到自有属性的描述符,对于继承属性和不存在的属性,该方法返回undefined

Object.getOwnPropertyDescriptor({},"x");      //undefined
Object.getOwnPropertyDescriptor({},"toString");     //undefined

2. Object.defineProperty()方法——ECMAScript5

该方法用于设置属性的特性(对于不存在的属性,则创建该属性并设置特性)

第一个参数,Object,必须,表示要设置属性所在的对象

第一个参数,String,必须,表示要设置的属性

第三个参数,Object,必须,表示属性描述符对象(不必全部包含四个特性)

var o ={};
Object.defineProperty(o,"x",{
	value:1,
	writable:true,
	enumerable:false,
	configurable:true
});
o.x;  //1
Object.keys[o];  //[]  不可枚举
Object.defineProperty(o,"x",{writable:false});
o.x=2;  //赋值失败
o.x;     //1
//但是属性仍然是可配置的,所以.....
Object.defineProperty(o,"x",{value:2});
o.x;    //2

也就是不可写属性的值也是可以修改的,只要属性特性还是可配置的,就可以通过Object.defineProperty()方法修改属性值

需要注意的是,当没有包含全部四个特性时,对于新创建的属性来说,不包含的特性默认取得false或者undefined(对于存取器属性),对于修改已有的属性来说,默认的特性值没有任何修改

并且该方法要么修改已有属性要么自建属性,但不能修改继承属性

此方法还可用于创建存取器属性

Object.defineProperty(o,"x",{get:function(){return 1;}});

3. Object.defineProperties()方法——ECMAScript5

该方法用于同时修改或创建多个属性

第一个参数,Object,必须,表示要设置属性所在的对象

第二个参数,Object,必须,这个对象的属性名表示要新建或修改的属性名,属性值表示它们的属性描述符对象

var o ={};
Object.defineProperties(o,{
	x:{value:2},
	y:{writable:false}
});

对于那些不允许创建或者修改的属性来说,如果用上面两个方法操作就会抛出异常,基本规则如下

  • 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性
  • 如果属性是不可配置的,则不能修改它的可配置性和可枚举性(以及存取器属性的setter和getter),并且不能从数据属性转换为存取器属性,反之也不行
  • 如果数据属性是不可配置的,但是可写的,则可以修改它的值,并且可以转换为不可写;但如果数据属性是不可配置的且不可写的,则不可以修改它的值,也不可以转换为可写的;但如果数据属性是可配置但不可写的,值也是可以修改的(通过ES5新增的两个方法,实际上是先将它标记为可写,然后修改值,最后再转换为不可写)

八. 对象的特性

每一个对象都有与之相关的三个特性

  • 原型(prototype)
  • 类(class)
  • 可扩展性(extensible)

1. 原型

对象的原型是用来继承属性的

原型是在对象创建之初就设置好的,通过对象直接量创建的对象使用Object.prototype作为它们的原型,通过new创建的对象使用构造函数.prototype作为它们的原型,通过Object.create()创建的对象使用第一个参数作为它们的原型(也可以传入null,则对象没有原型)

Object.getPrototypeOf()方法——ECMAScript5

使用该方法可以获得对象的原型

接收一个参数,要查询的对象,返回该对象的原型,或者在该对象没有原型时,返回null

Object.getPrototypeOf({})
//返回Object.prototype

在ECMAScript3中,使用o.constructor.prototype来检测一个对象的原型,通过new创建的对象,通常会继承一个constructor属性,这个属性指代创建这个对象的构造函数(后续详述)

通过对象直接量创建的对象也包含一个constructor属性,指代的是Object()构造函数,因此对象直接量的原型是Object.prototype

通过Object.create()创建的对象也包含一个constructor属性,指代的是Object()构造函数,但是当传入null时,则不包含constructor属性

小结

对象有一个非标准的__proto__属性(双下划线),用以直接获取或者设置对象的原型

**IE10及以下不支持__proto__**

isPrototypeOf()方法

该方法用于检测一个对象是否是传入对象的原型(或者位于原型链中)

var a = {};
Object.prototype.isPrototypeOf(a);   //true

2. 类特性

对象的类特性是一个字符串,表示的是对象的类型信息

但ECMAScript3和ECMAScript5都没有提供方法来获取或者设置对象的这个特性

只有一种间接的方法,toString()方法(继承自Object.prototype),返回如下字符串"[object class]"

var a = {}
a.toString();   // "[object object]"

字符串的第8个到倒数第二个位置之间的字符就是对象的类特性字符串

但是很多对象的toString()方法被重写了,因此必须调用call()方法

function classOf(o){
	if(o==null){
		return "Null";
	}
	if(o==undefined){
		return "Undefined";
	}
	return Object.prototype.toString.call(o).slice(8,-1);
}
classOf(null)            //"Null"
classOf(1)                //"Number"
classOf("")              //"String"
classOf(false)          //"Boolean"
classOf({})               //"Object"
classOf([])              //"Array"
classOf(/./)             //"Regexp"
classOf(new Date())     //"Date"
classOf(window)          //"global"
function f() {}; 
classOf(f);               //"Function"
classOf(new f());          //"Object"

小结

判断一个对象是不是数组除了使用ECMAScript5中的Array.isArray()之外,就可以使用类特性来判断了

3. 可扩展性

可扩展性表示是否可以给对象添加新属性

所有的内置对象和自定义对象默认都是可扩展的,宿主对象是否可扩展由JavaScript引擎决定

Object.preventExtensions()方法——ECMAScript5

该方法将对象转换为不可扩展,一旦转换,就无法再恢复成可扩展的了

一旦将对象转换成不可扩展,就不能添加新属性了,但是可以给对象的原型添加新属性,不可扩展的对象任然可以继承这些属性

可扩展性是对象的特性,将对象标记为不可扩展,仅表示不能给该对象添加新属性,但是可以修改、删除已有属性,所以要和属性的特性(可写行,可配置性)配合使用

Object.isExtensible()方法——ECMAScript5

该方法查询对象是否可可扩展的,返回布尔值

Object.seal()方法——ECMAScript5

该方法将封闭对象,除了将对象转换为不可扩展,还将对象的自有属性都设置为不可配置(但是可写的仍然是可写的),同样不能解封

Object.isSealed()方法——ECMAScript5

该方法检测对象是否是封闭的,返回布尔值

Object.freeze()方法——ECMAScript5

该方法将冻结对象,除了将对象转换为不可扩展,还将对象的自有属性都设置为不可配置,并将自有的数据属性设置为只读的

存取器属性如果具有setter方法,存取器属性将不受影响,仍然可以通过给属性赋值调用它们

Object.isFrozen()方法——ECMAScript5

该方法检测对象是否是冻结的,返回布尔值

九. 对象的序列化和还原

对象序列化,是指将对象的状态转换为字符串

对象还原是将字符串还原为对象

1. JSON.stringify()方法——ECMAScript5

该方法用于序列化对象,返回对象的...

var a = {x:1};
JSON.stringify(a);   //"{"x":1}"

JSON.stringify()只能序列化对象可枚举的自有属性,在序列化后输出的字符串中将会忽略不可枚举属性和继承属性

2. JSON.parse()方法——ECMAScript5

该方法用于还原对象,返回...

var a = {x:1};
var b = JSON.stringify(a);   //"{"x":1}"
var c = JSON.parse(b);   //{x:1}是a的深度拷贝

两个方法都是用JSON作为数据交换格式(后述)

函数,RegExp,Error对象和undefined不能序列化和还原

十. 其它对象方法

所有JavaScript对象都从Object.prototype继承属性(除了null和不是通过原型创建的对象,例如Object.create(null))

继承的主要是方法,前面讲过hasOwnProperty()、propertyIsEnumerable()、isPrototypeOf()三个方法,以及在Object构造函数里定义的静态函数Object.create()、Object.getPrototypeOf()等

下面再讲解几个定义在Object.prototype里的方法,不同的对象会将它们重写

1. toString()方法

该方法不接受参数,返回对象的字符串形式

在需要将对象转换为字符串的时候,都会隐式调用该方法,比如当使用"+"运算符连接一个字符串和对象时,或者在希望使用字符串的地方使用了对象时都会调用toString()方法

默认的toString()方法返回值包含的信息量很少(因为包含了对象的类型特性字符串,所以检测对象类型时很有用)

var a = {};
a.toString();   //"[object Object]"

很多类型都重写了toString()

对于数组来说,在有必要的情况下会对每个元素调用toString()方法,返回拼接而成的字符串

var a  =[1,2,3];
a.toString();   //"1,2,3"
var a  =[1,2,[3,4]];
a.toString();   //"1,2,3,4"
var a  =[1,2,{x:1}];
a.toString();   //"1,2,[object Object]"

对于函数来说,会返回函数的源码,对于正则表达式来说,会返回正则表达式对象(后面各个对象,详述)

2. toLocaleString()方法

该方法不接受参数,返回对象的本地化字符串形式

Object中默认的toLocaleString()方法并不做任何本地化操作,仅仅调用toString()方法

Date类型重写了toLocaleString()方法,对日期做本地化转换

Array类型的toLocaleString()方法和toString()方法类似,只不过对于每一项都是调用的toLocaleString()方法

3. valueOf()方法

valueOf()方法和toString()方法类似,但是只在需要将对象转换为某种原始数据而不是字符串的时候使用,尤其是转换为数字的时候

如果在希望使用原始值得地方使用了对象,就会隐式调用valueOf()方法

很多类型都重写了valueOf()方法(后面详述)

4. toJSON()方法

Object.prototype定没有定义toJSON()方法,但对于需要序列化的对象,JSON.stringify()方法会调用toJSON()方法

你可能感兴趣的:(JavaScript)