概述
对象可以看做是属性的无序集合,每个属性都是一个名值对。 属性名是字符串,可以把对象看成是从字符 串到值的映射。
除了可以保持自己的属性,JS对象还可以从一个称为原型的对象继承属性。这种‘’原型式继承‘’是JS的核心 特征。对象是可变的,我们通过引用而非值来操作对象。如果变量x指向一个对象的引用,那么执行代码var y = x
;变量y也是指向同一个对象的引用,而非这个对象的副本。通过变量y来修改这个对象亦会对变量x造成影 响。
属性特性:
可写:是否可以设置属性值
可枚举:是否可以通过for/in循环来返回该属性
可配置:是否可以删除或修改该属性
对象特性:
原型:指向另一个对象,本对象的属性继承自它的原型对象。
类:是一个标识对象类型的字符串。
扩展标记:指明是否可以向该对象添加新属性。
内置对象:是由ES规范定义的对象或类。例如数组、函数、日期和正则表达式。
宿主对象:是有JS解释器所嵌入的宿主环境(比如浏览器)定义的。客户端JS中表示网页结构的HTMLElement 对象均是宿主对象。
自定义对象:由运行中的JS代码创建的对象。
自有属性:直接在对象中定义的属性。
继承属性:在对象的原型对象中定义的属性。
创建对象
- 对象直接量
属性名可以是JS标识符也可以是字符串直接量。
属性的值可以是任意JS表达式或值。
var empty = {};
var point = {x: 0,y: 0};
var point1 = {x: point.x,y: point.y + 1};
var book = {
"main title": "JavaScript",//属性名中有空格必须用字符串表示
'sub-title': "The Definitive Guide",//属性名中有连字符必须用引号
"for": "all audience",
author:{
firstname:"David",
surname:"Flangan"
}
};
- 通过new创建对象
new运算符创建并初始化一个新对象。关键字new后跟随一个函数调用。这里的函数称作构造函数。构造函数用 以初始化一个新创建的对象。JS语言核心中的原始类型都包含内置构造函数。
var o = new Object();
var a = new Array();
var d = new Date();
var r = new RegExp('js');
- 原型
每一个对象(null除外)都和另一个对象相关联。
所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JS代码Object.prototype获得对原型对象的 引用。
通过关键字new和构造函数调用创建的对象的原型,就是对应构造函数的prototype属性的值(某一个对象)。
例如,通过new Array() 创建的对象的原型就是Array.prototype。
Object.prototype没有原型。
- Object.create(对象原型,)
这是一个静态函数,不提供给某个对象调用。
var o = Object.create({x:1,y:2}); // o继承了属性x和y
可以通过传入参数null创建一个没有原型的新对象,甚至没有基础方法。
var o = Object.create(null);//不继承任何属性和方法
想创建一个普通对象(通过{}或new Object()),需要传入Object.prototype:
var o = Object.create(Object.prototype);
属性查询与设置
var name = author.surname
var title = book["main title"]
(.)右侧必须是一个标识符。
([])右侧必须是一个计算结果为字符串(字符串本身或可以转换成字符串的值)的表达式。
book.edition = 6;
book["main title"] = "ECMAScript";
var person = {
name : 'nike',
age : 18
}
var property = 'age';
person['property'];
person[property];
// 没有任何意义,单纯的字符串拼接,像一个拼图,property就是 'age'
// 而两者的区别是,第一个访问失败,因为对象里没有'property'属性
// 而第二句,就是person['age']
作为关联数组的对象
JS对象都是关联数组:数组元素通过字符串索引而不是数字索引。
当通过(.)访问对象的属性时,属性名用一个标识符来表示。标识符必须直接出现在JS程序中,它们不是数据类型,因此程序无法修改它们。
当通过([])来访问对象的属性时,属性名通过字符串来表示。字符串是JS的数据类型,在程序运行时可以修改和创建它们。
var addr = " ";
for(i = 0, i < 4, i++){
addr += customer["address" + i] + '\n';
}
这段代码读取对象的address0~3的属性,并将它们连接起来。
当不知道属性名称时,可以使用([])运算符,因为它使用字符串值(字符串值是动态的,可以在运行时更改) 而不是标识符(标识符是静态的,必须写死在程序中)作为索引对属性进行访问。
function getvalue(portfolio){
var total = 0.0;
for(stock in portfolio){
var shares = portfolio[stock];
var price = getquote(stock);
total += shares * price;
}
return total;
}
继承
假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x。如果原型对象中也没 有x,但这个原型对象也有原型,直到找到属性或查找到一个原型是null的对象为止。
现在假设给对象o的属性x赋值,如果o中已经有属性x,那么这个赋值操作只改变这个已有属性x的值。如果o中 不存在属性x,那么赋值操作给o添加一个新的属性x。如果之前o继承自属性x,那么这个继承的属性就被新创建 的同名属性覆盖了。
// inherit()返回一个继承自原型对象p的属性的新对象
// 这里使用ES5的Object.create()函数
// 如果不存在的话,则退化使用其他方法
function inherit(p) {
if (p == null) throw TypeError(); // p是一个对象,不能是null
if (Object.create) { // 如果Object.create()存在的话
return Object.create(p); // 直接使用它
var t = typeof p;
if (t !== "object" && t !== "function") throw TypeError();
function f() {}; // 类似构造函数?
f.prototype = p;
return new f(); // 使用f()创建p的继承对象
}
}
var o = {} // o从Object.prototype 继承对象的方法
o.x = 1;
var p = inherit(o); // p继承o和Object.prototype
p.y = 2;
var q = inherit(p); // q继承p、o和Object.prototype
q.z = 3;
var s = q.toString(); // toString继承自Object.prototype
console.log(q.x + q.y); // 3
属性赋值操作会首先检查原型链,以此判定是否允许赋值操作。例如,如果o继承自一个只读属性x,那么赋值 操作是不允许的。如果允许属性赋值操作,它也总是在原始对象上创建属性或对已有的属性赋值,而不会去修 改原型链。
在JS中,只有查询属性时才会体会到继承的存在,而设置属性则和继承无关,这是JS的一个重要特性,可以有 选择性的覆盖继承的属性。
var unitcircle = {r: 1}; // 一个用来继承的对象
var c = inherit(unitcircle); // c继承属性r
c.x = 1;
c.y = 1; // c定义两个属性
c.r = 2; // c覆盖继承来的属性
unitcircle.r; // 1,原型对象没有修改
属性赋值要么失败,要么创建一个属性,要么在原始对象中设置属性。但有一个例外。如果o继承自属性x,而 这个属性是一个具有setter方法的accessor属性。那么这时将调用setter方法而不是给o创建一个属性x。需要注 意的是,setter方法是由对象o调用的,而不是定义这个属性的原型对象调用的。因此setter方法定义任何属性, 这个操作只是针对o本身,并不会修改原型链。
属性访问错误
查询一个不存在的属性不会报错。返回undefined。
如果对象不存在,查询这个不存在的对象的属性就会报错。
null和undefined都没有属性,因此查询这些值的属性会报错。
未确定变量是对象的情况下,避免出错的方法:
var len = book && book.subtitle && book.subtitle.length;
删除属性
delete运算符的操作数应该是一个属性访问表达式。
delete只是断开属性和宿主之间的联系,而不会去操作属性中的属性。
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
delete Object.prototype; // 不能删除,属性是不可配置的
var x = 1; // 声明一个全局变量
delete this.x; // 不能删除这个属性
function f(){} // 声明一个全局函数
delete this.f; // 也不能删除全局函数
- 当在非严格模式下中删除全局对象的可配置属性时,可以省略对全局对象的引用,直接在delete操作符后跟 随要删除的属性名即可:
this.x = 1; // 创建一个可配置的全局熟属性(没用var)
delete x; // 将它删除
- 在严格模式中,delete后跟随一个非法的操作数,则会报一个语法错误,因此必须显示指定对象及其属性:
delete x; //在严格模式下报语法错误
delete this.x; //正常工作
检测属性
检测集合中成员的所属关系,判断是否存在于某一个对象中。
- in运算符
属性名(字符串) in 对象
var o = {x:1}
"x" in o; // true
"y" in o; // false
"toString" in o; // true
in运算符可以区分不存在的属性和存在但值为undefined的属性。
var o = {x:undefined}
o.x !== undefined // false:属性存在,但值为undefined
o.y !== undefined // false: 属性不存在
"x" in o // true
"y" in o // false
delete o.x;
"x" in o //false
- hasOwnPreperty()
检测给定的名字是否是对象的自有属性。对于继承属性它将返回false。
var o = {x:1} o.hasOwnProperty("x"); // true
o.hasOwnProperty("y"); // false
o.hasOwnProperty("toString"); //false
- propertyIsEnumberable()
检测到是自有属性且这个属性可枚举才返回true。某些内置属性是不可枚举的。
- !==
判断一个属性是否是undefined。并且可以区分null和undefined。
var o = {x:1}
o.x !== undefined; // true
o.y !== undefined; // false
o.toString !== undefined; // true
- instanceof
A instanceof B
A的原型链上有B的原型
枚举属性
for/in 循环可以在循环体中遍历所有可枚举的属性(包括自有属性和继承属性),把属性名称赋值给循环变量。 对象继承的内置方法是不可枚举的,但在代码中给对象添加的属性都是可以枚举的。
var o = {x: 1, y: 2, z: 3};
o.propertyIsEnumerable('toString'); //false 不可枚举
for (p in o)
console.log(p,o[p],o.hasOwnProperty(p)); // 不会输出toString
ES5之前,一些新添加的方法不能定义为不可枚举,因此它们可以在for/in循环中枚举出来。为了避免这种情 况。需要过滤结果:
for (p in o){
if(!o.hasOwnProperty(p)) continue; // 跳过继承的属性
}
for (p in o){
if(typeof o[p] === "function")continue;// 跳过方法
}
属性getter和setter
属性的特性
对象的三个属性
原型属性
对象的原型属性是用来继承属性的,经常把“o的原型属性”直接叫做“o的原型”。
查询对象的原型方法:Object.getPrototypeOf(obj)
obj.constructor.prototype
检测一个对象是否是另一个对象的原型(或处于原型链中):
p.isPrototypeOf(o)
[_ proto_] 用以直接查询设置对象的原型。
类属性
对象的类属性是一个字符串,用以表示对象的类型信息。
只有一种间接的方式可以查询它:
默认的toString方法(继承自Object.prototype)返回如下格式的字符串:
[ object class ]
很多对象继承的toString方法重写了,为了能调用正确的toString()版本,必须间接的调用Function.call()方法。
//返回传递给它的任意对象的类:
function classof(o){
if(o === null) return "Null";
if(o === undefined) return "Undefined";
return Object.prototype.toString.call(o).slice(8,-1);
}
可扩展性
对象的可扩展性用以表示是否可以给对象添加新属性。
序列化对象
对象序列化是指将对象的状态转换为字符串,也可将字符串还原为对象。
ES5提供了 JSON.stringify() 和 JSON.parse() 用来序列化和还原JS对象。
JSON :”JavaScript Object Notation” JS对象表示法。
o = {x:1,y:{z:[false,null,""]}};
s = JSON.stringify(o);
// s = '{"x":1,"y":{"z":[false,null,""]}}'
p = JSON.parse(s); // p是o的深拷贝
JSON语法支持对象,数组,字符串,无穷大数字,true,false,null。
NaN,Infinity,序列化结果是 null。
对象方法
- toString()
该方法没有参数,它返回一个表示调用这个方法的对象值的字符串。 ’ [object Object] ‘