概述
对象中包含一系列属性,这些属性是无序的。每个属性都有一个字符串key和对应的value。属性名可以是包含空字符串在内的任意字符串,对象中不能存在两个同名的属性。值可以是任意javascript值,或者可以是一个getter或setter函数。除了名字和值之外,每个属性还有一些与之相关的值,称为“属性特性”;除了包含属性之外,每个对象还拥有3个相关的对象特性,如下图所示:
创建对象
可以通过对象直接量、关键字new和Object.create()函数来创建对象。
对象直接量
let obj1 = {x:1, y:2};
let obj2 = {
x:1,
y:2,
z: {
m:'a',
n:'b'
}
};
复制代码
关键字new创建对象(原型链)
new运算符创建并初始化一个新对象。关键字new后面跟随一个函数调用。这里的函数称做构造函数,构造函数用以初始化一个新创建的对象。
function foo(){}
foo.prototype.z = 3;
var obj = new foo();
obj.y = 2;
obj.x = 1;
obj.x; // 1
obj.y; // 2
obj.z; // 3
typeof obj.toString; // ‘function'
'z' in obj; // true
obj.hasOwnProperty('z'); // false
obj.z = 5;
obj.hasOwnProperty('z'); // true
foo.prototype.z; // still 3
obj.z; // 5
obj.z = undefined;
obj.z; // undefined
delete obj.z; // true
obj.z; // 3
delete obj.z; // true
obj.z; // still 3!!!
复制代码
Object.create()
Object.create()创建一个新对象,其中第一个参数是这个对象的原型,第二个参数可选,用以对对象的属性进行进一步描述。
var obj = Object.create({x:1, y:2}); // obj继承了属性x和y
obj.x // 1
typeof obj.toString // "function"
obj.hasOwnProperty('x') // false
复制代码
可以通过传入参数null来创建一个没有原型的新对象,但是通过这种方式创建的对象不会集成任何东西,甚至不包括基础方法,比如toString(),也就是说,它将不能和’+’运算符一起正常工作。
var obj = Object.create(null);
obj.toString // undefined
复制代码
如果想创建一个普通的空对象(比如通过{}或new Object()创建的对象),需要传入Object.prototype:
var obj = Object.create(Object.prototype); // obj和{}和new Object()一样
复制代码
属性操作
属性的查询与设置(读写属性)
- 可以通过(.)或方括号([])运算符来获取属性的值或者给属性赋值。
var obj = {x: 1, y: 2};
obj.x; // 1
obj["y"]; // 2
obj["x"] = 3;
obj.y = 4;
复制代码
- javascript对象具有“自有属性”,也有一些属性是从原型对象继承而来。假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x。如果原型对象中也没有x,但这个原型对象也有原型,那么继续在这个原型对象的原型上执行查询,直到找到x或者找到一个原型是null的对象为止。可以看到,对象的原型构成了一个“链”,通过这个“链”可以实现属性的继承。
- 现在假设给对象o的属性x赋值,如果o中已经有属性x(这个属性不是继承而来的),那么这个赋值操作只改变这个已有属性的值。如果o中不存在属性x,那么赋值操作给o添加一个新属性x。如果之前o继承自属性x,那么这个继承的属性就被新创建的同名属性覆盖。
- 属性赋值操作首先检查原型链,以此判定是否允许赋值操作。例如,如果o继承自一个只读属性x,那么赋值操作是不允许的。如果允许属性赋值操作,它也总是在原始对象上创建属性或对已有的属性赋值,而不会去修改原型链。
- 在javascript中,只有查询属性才会体会到继承的存在,而设置属性则和继承无关。
- 如果o继承自属性x,而这个属性是一个具有setter方法的accessor属性,那么这时将调用setter方法而不是给o创建一个属性x。需要注意的是,setter方法是由对象o调用的,而不是定义这个属性的原型对象调用的。因为如果setter方法定义任意属性,这个操作只针对o本身,并不会修改原型链。
function foo() {}
Object.defineProperty(foo.prototype, 'z', {
get: function(){
return 1;
}
});
var obj = new foo();
obj.z; // 1
obj.z = 10; // obj继承自属性z,而这个属性如果存在get/set方法,给当前对象赋值会失败。
obj.z; // still 1
Object.defineProperty(obj, 'z', {value : 100, configurable: true});
// 此时如果想给当前对象添加新属性z,只能通过defineProperty方式
obj.z; // 100;
delete obj.z;
obj.z; // back to 1
复制代码
var o = {};
Object.defineProperty(o, 'x', {value : 1}); // writable=false, configurable=false
var obj = Object.create(o);
obj.x; // 1
obj.x = 200; // writable为false不可写,赋值失败
obj.x; // still 1, can't change it
Object.defineProperty(obj, 'x', {writable:true, configurable:true, value : 100});
obj.x; // 100
obj.x = 500;
obj.x; // 500
复制代码
删除属性
- delete运算只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)。
- 当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。如果delete后不是一个属性访问表达式,delete同样返回true。
- delete不能删除那些可配置性为false的属性。
var person = {age : 28, title : 'fe'};
delete person.age; // 删除属性age,返回true
delete person['title']; // 删除属性title,返回true
person.age; // undefined
delete person.age; // 什么都没做(age属性已经不存在了),返回true
delete person.gender; // 什么都没做(gender属性不存在),返回true
delete person.toString; // 什么都没做(toString是继承而来的),返回true
delete 1; // 无意义,返回true
delete Object.prototype; // false, 属性是不可配置
var descriptor = Object.getOwnPropertyDescriptor(Object, 'prototype');
descriptor.configurable; // false
复制代码
检测属性
javascript对象可以看做属性的集合,我们经常会检测集合中成员的所属关系——判断某个属性是否存在于摸个对象中。可以通过in运算符、hasOwnProperty()和propertyIsEnumerable()方法来完成这个工作。
- in运算符的左侧是属性名,右侧是对象。如果对象自有属性或继承属性中包含这个属性则返回true;
- 对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性,对于继承属性返回fasle。
- propertyIsEnumerable是hasOwnProperty()的增强版,只有检测是自有属性且这个属性的可枚举性为true时它才返回true。
let cat = new Object;
cat.legs = 4;
cat.name = "Kitty";
'legs' in cat; // true
'abc' in cat; // false
"toString" in cat; // true, inherited property!!!
cat.hasOwnProperty('legs'); // true
cat.hasOwnProperty('toString'); // false
cat.propertyIsEnumerable('legs'); // true
cat.propertyIsEnumerable('toString'); // false
Object.defineProperty(cat, 'price', {enumerable : false, value : 1000});
cat.propertyIsEnumerable('price'); // false
cat.hasOwnProperty('price'); // true
复制代码
枚举属性
除了检测对象的属性是否存在,我们还会经常遍历对象的属性。
类型 | 特点 |
---|---|
Object.keys(obj) | 返回对象本身可直接枚举的属性(不含Symbol属性) |
Object.values(obj) | 返回对象本身可直接枚举的属性值(不含Symbol属性) |
Object.entries(obj) | 返回对象本身可枚举属性键值对相对应的数组(不含Symbol属性) |
Object.getOwnPropertyNames(obj) | 返回对象所有自身属性的属性名(不包括Symbol值作为名称的属性) |
Object.getOwnPropertySymbols(obj) | 返回一个给定对象自身的所有 Symbol 属性的数组 |
for……in | 所有可枚举的属性(包括原型上的) |
for……of | 必须部署了Iterator接口后才能使用,例如数组、Set和Map结构、类数组对象、Generator对象以及字符串 |
forEach | break不能中断循环 |
Reflect.ownKeys(obj) | 对象自身所有属性 |
- 首先遍历所有属性名为数值的属性,按照数字排序
- 其次遍历所有属性名为字符串的属性,按照生成时间排序
- 最后遍历所有属性名为Symbol值的属性,按照生成时间排序
const Obj = {
[Symbol(0)]: 'symbol',
1 : '1',
'c': 'c',
'1a1': '11',
22223333: '2',
'd': 'd'
};
console.log(Reflect.ownKeys(Obj)); // [ '1', '22223333', 'c', '1a1', 'd', Symbol(0) ]
复制代码
getter、setter
另一种读写属性的方式。
var man = {
name : 'Bosn',
weibo : '@Bosn',
get age() {
return new Date().getFullYear() - 1988;
},
set age(val) {
console.log('Age can\'t be set to ' + val);
}
}
console.log(man.age); // 27
man.age = 100; // Age can't be set to 100
console.log(man.age); // still 27
复制代码
属性标签
- 可写(writable):表明是否可以设置该属性的值。
- 可枚举(enumerable):表明是否可以通过for/in循环返回该属性。
- 可配置(configurable):表明是否可以删除或修改该属性。
- 数据属性:value、writable、enumerable、configurable
Object.getOwnPropertyDescriptor({pro : true}, 'pro');
// Object {value: true, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor({pro : true}, 'a'); // undefined
var person = {};
Object.defineProperty(person, 'name', {
configurable : false,
writable : false,
enumerable : true,
value : "Bosn Ma"
});
person.name; // Bosn Ma
person.name = 1; // writable为fasle,赋值失败
person.name; // still Bosn Ma
delete person.name; // false,// configurable为fasle,删除失败
Object.defineProperty(person, 'type', {
configurable : true,
writable : true,
enumerable : false,
value : "Object"
});
Object.keys(person); // ["name"],name的enumerable为true, type的enumerable为false
Object.defineProperties(person, {
title : {value : 'fe', enumerable : true},
corp : {value : 'BABA', enumerable : true},
salary : {value : 50000, enumerable : true, writable : true}
});
Object.getOwnPropertyDescriptor(person, 'salary');
// Object {value: 50000, writable: true, enumerable: true, configurable: false}
Object.getOwnPropertyDescriptor(person, 'corp');
// Object {value: "BABA", writable: false, enumerable: true, configurable: false}
复制代码
- 存取器属性:get、set、enumerable、configurable
Object.defineProperties(person, {
title: {value : 'fe', enumerable : true},
corp: {value : 'BABA', enumerable : true},
salary: {value : 50000, enumerable : true, writable : true},
luck: {
get : function() {
return Math.random() > 0.5 ? 'good' : 'bad';
}
},
promote: {
set : function (level) {
this.salary *= 1 + level * 0.1;
}
}
});
Object.getOwnPropertyDescriptor(person, 'salary');
// Object {value: 50000, writable: true, enumerable: true, configurable: false}
Object.getOwnPropertyDescriptor(person, 'corp');
// Object {value: "BABA", writable: false, enumerable: true, configurable: false}
person.salary; // 50000
person.promote = 2;
person.salary; // 60000
复制代码
可写控制着对值特性的修改。可配置性控制着对其他特性(包括属性是否可以删除)的修改。然而规则远不止这么简单,例如,如果属性是可配置的话,则可以修改不可写属性的值。同样,如果属性是不可配置的,仍然可以将属性修改为不可写属性。以下是完整的规则:
- 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性。
- 如果属性是不可配置的,则不能修改他的可配置性和可枚举性。
- 如果存取器属性是不可配置的,则不能修改其getter和setter方法,也不能将它转换为数据属性。
- 如果数据属性是不可配置的,则不能将它转换为存取器属性。
- 如果数据属性是不可配置的,则不能将它的可写性从false修改为true,但可以从true修改为false。
- 如果数据属性是不可配置且不可写的,则不能修改它的值。然而可配置但不可写属性的值是可以修改的(实际上是先将它标记为可写,然后修改它的值,最后转化为不可写)。
对象标签
- 对象的原型([[proto]]):指向另外一个对象,本对象的属性继承自它的原型对象。
- 对象的类([[class]]):是一个标识对象类型的字符串。
var toString = Object.prototype.toString;
function getType(o){
return toString.call(o).slice(8,-1);
};
toString.call(null); // "[object Null]"
getType(null); // "Null"
getType(undefined); // "Undefined"
getType(1); // "Number"
getType(new Number(1)); // "Number"
typeof new Number(1); // "object"
getType(true); // "Boolean"
getType(new Boolean(true)); // "Boolean"
复制代码
- 对象的扩展标记([[extensible]]):指明了是否可以向该对象添加新属性。
var obj = {x : 1, y : 2};
Object.isExtensible(obj); // true
Object.preventExtensions(obj);
Object.isExtensible(obj); // false
obj.z = 1;
obj.z; // undefined, add new property failed
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: true, enumerable: true, configurable: true}
Object.seal(obj);
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: true, enumerable: true, configurable: false}
Object.isSealed(obj); // true
Object.freeze(obj);
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: false, enumerable: true, configurable: false}
Object.isFrozen(obj); // true
// [caution] not affects prototype chain!!!
复制代码
序列化对象
对象序列化是指将对象的状态转换为字符串,也可以将字符串还原为对象,可通过JSON.stringify()和JSON.parse()操作。这些方法都使用JSON作为数据交换格式。JSON的语法是javascript语法的子集,它并不能表示javascript里的所有值:
- 支持对象、数组、字符串、无穷大数字、true、false、null,并且他们可以序列化和还原。
- NaN、Infinity和-Inifity序列化的结果是null,日期对象序列化的结果是ISO格式的日期字符串。
- 函数、RegExp、Error对象和undefined值不能序列化和还原。
- JSON.stringify()只能序列化对象可枚举的自有属性。
let obj = {x : 1, y : true, z : [1, 2, 3], nullVal : null};
JSON.stringify(obj); // "{"x":1,"y":true,"z":[1,2,3],"nullVal":null}"
obj = {val : undefined, a : NaN, b : Infinity, c : new Date()};
JSON.stringify(obj); // "{"a":null,"b":null,"c":"2015-01-20T14:15:43.910Z"}"
obj = JSON.parse('{"x" : 1}');
obj.x; // 1
复制代码
对象方法
let obj = {x : 1, y : 2};
obj.toString(); // "[object Object]"
obj.toString = function() {return this.x + this.y};
"Result " + obj; // "Result 3", by toString
+obj; // 3, from toString
obj.valueOf = function() {return this.x + this.y + 100;};
+obj; // 103, from valueOf
"Result " + obj; // still "Result 3"
复制代码