JavaScript 从入门到屏弃 - 5 -对象

从最近的js入门类此外阅读量逐渐递减,观众老爷的兴味也不再可以经受部分细节性的地点深度挖掘,让我有了一部分思想。是应有继续看书挖掘细节,仍旧从易接受性来写一些稿子。不过自己觉得这一个连串本身就是让我们体会一下js的纵深与广度,不想与许多快餐式的入门教程来重合。对于想上学的人来说,挖掘文化的吃水,比知识的广度更为紧要,而且对于随后的读书都是可观的提高。本身在成千上万知识点下边已经省略了好多,将部分第一的学问呈现在在大家面前,可能需要观众老爷的一对苦口婆心,毕竟著作、代码都早就超出了10分钟阅读的量,许多学问细节,需要一整天来消化吸收。我愿意在我从不废弃在此之前,读者们也毫不丢弃,觉得食之无味。细细的去体会其中的每一段话,相信你会赢得颇丰!

对象

对象(object)是js的主导数据类型。是一种复合值:将许多值(原始值或者其他对象)聚合在联合,能够由此名字访问那么些值。

每个属性都是一个名/值对(key/value),属性名是字符串,由此大家得以把目的看成从字符串到值的映射。

不过对象不仅是字符串到值的炫耀,除了保障自有的习性,js对象还是可以从一个号称原型的目标继承属性。

目的的章程一般是继承的性质,这种原型式继承是js的着力特征。

js对象是动态的,能够激增属性,也得以去除属性。

除了字符串、数字、true、false、null和undefined之外,js中的值都是目的。

对象是可变的,大家经过引用而非值来操作对象。假诺变量x是指向一个对象的引用,那么执行代码

var y = x;

变量y也是指向同一个对象的引用,而非那多少个目的的副本。通过y来修改这多少个目的也会对变量x造成影响

js的属性,除了名字和值之外,每个属性还有一些与之相关的值,称为
性能特性

  • 可写,讲明是否足以设置该属性的值
  • 可枚举,阐明是否足以透过for/in循环重临该属性
  • 可安排,注脚是否能够去除或修改该属性

除此之外含有属性之外,每个对象还有所七个相关的对象特性:

  • 对象的原型(prototype)指向另一个目的,本对象的性质持续自它的原型对象
  • 目标的类是一个标识对象类型的字符串
  • 目的的扩大标记(extensible
    flag)指明了在ECMAScript5中是不是可以向该目标添加新的习性

三类js对象和两类特性的区分:

  • 嵌入对象(native
    object)是由ECMAScript规范定义的靶子或类。例如,数组、函数、日期和正则表明式
  • 宿主对象(host
    object)是由js解释器所安放的宿主环境(比如WEB浏览器)定义的。客户端js中代表网页结构的HTMLElement对象均是宿主对象。既然宿主环境定义的章程可以算作普通的js函数对象,那么宿主对象也可以算作内置对象
  • 自定义对象(user-defined Object)是由运行中的js代码创制的目的
  • 自有总体性(own property)是一直在对象中定义的特性
  • 继续属性(inherited property)是在对象的原型对象中定义的性质

创立对象

创设对象重要有二种模式:

  • 对象直接量
  • 关键字new创建
  • Object.create()函数

目标直接量

对象直接量是由若干名值对构成的映射表,名值对中级用冒号分割,名值对中间用逗号分割,整个映射表用花括号括起来

属性名可以是js标识符也得以是字符串直接量

特性的值可以是轻易档次js表达式、表达式的值就是其一特性的值

来个例子:

var empty = {}; // 没有任何属性的对象
var point = { x:0, y:1 }; // 两个属性
var point2 = { x: point.x; y: point.y }; // 更复杂的属性值
var book = {                
  "main title": "javascript", // 属性名字有空格,必须用字符串表示
  "sub-titile": "book", // 属性名字有连字符,必须用字符串表示
  "for": "all audience", // 属性名字是保留字,必须用引号
  author: {               // 允许的名字可以没有引号
    firstname: "zhao",
    subname: "lion"
  }
}

目的直接量是一个表明式,这么些表明式的每一回运算都创制并伊始化一个新的靶子。每便总结对象直接量时候,也都会盘算它的每一个属性的值。

因此new创立对象

new
运算符成立并先河化一个新对象。关键字new后跟随一个函数调用。这么些函数被喻为
构造函数(constructor)
构造函数用来先导化一个新成立的靶子。js语言要旨中原始类型都富含内置构造函数.比如:

var o = new Object(); // 创建一个空对象
var a = new Array(); // 创建一个空数组
var d = new Date(); // 创建一个当前时间对象
var r = new RegExp("^js"); // 创建一个可以进行模式匹配的RegExp对象

原型

每一个js对象(null除外)都和另一个对象相关联。“另一个目标”就是大家通常听到的原型,每一个对象都从原型继承属性。

所有通过对象间接量成立的靶子都怀有同一个原型对象,并且能够通过Object.prototype来拿到原型对象的引用。

通过new创设和构造函数调用成立的靶子原型就是构造函数的prototype属性的值。因而,同使用{}成立对象一样,通过new
Object()创制的目标也连续自Object.prototype。
无异于,通过new Array()创造的目标原型就是Array.prototype,通过new
Date()创立的靶子的原型就是Date.prototype。

从不原型的靶子为数不多,Object.prototype就是一个。它不继续任何性质。

另外原型对象都是惯常对象,普通对象都有原型。所有的嵌入构造函数(以及大部分自定义构造函数)都抱有一个无冕自Object.prototype的原型。而这一多重的原型对象就是所谓的“原型链”

Object.create()

ECMAScript5概念了一个Object.create()的法门,创制一个新对象,其中一个参数就是那个目的的原型。Object.create()提供第二个可选参数,用以对新对象的特性举办进一步描述.

语法如下:
Object.create(proto[, propertiesObject])

Object.create()是一个静态函数,不是提供给某个对象调用的法子。使用很简短,传入所需的原型对象即可:

var o1 = Object.create({ x:1, y:2}); // o1继承属性x和y

可以透过传播参数null来创设一个尚未原型的新目的。通过这种艺术创制的目的不会继续任何事物,包括toString()

var o2 = Object.create(null); // o2不继承任何属性和方法

假使想创设一个一般的空对象(类似通过{}或new Object()创设的对象),需要
Object.prototype:

var o3 = Object.create(Object.prototype); // o3和{}和new Object()创建对象一样

可以通过其他原型创立新对象。不过一旦碰着js实现不协助Object.create()时,如何模拟原型继承呢?看下代码:

function inheritPrototype(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; // 将原型指向p
  return new f(); // 使用f()创建p的继承对象
}

地点这些函数模拟了一部分Object.create一部分,但依然相当实惠的。比如防范库函数无意间修改不受你说了算的靶子。
不是将对象直接当做参数传入函数,而是将它的连续对象传入函数。当函数读取继承对象的习性时,实际上读取的事持续来的值。
假诺给后续对象的性质赋值,则那一个属性之后影响那么些延续对象自我,不会潜移默化原本对象:

var o = {x: "don't change this value"}; //  创建一个对象
var inherit_o = inheritPrototype(o); // 创建一个原型是o的继承对象
inherit_o.x = "change it"; // 修改继承对象本身的属性x
o.x; // => "don't change this value" 原型o的属性并没改变

特性的询问和装置

查询属性能够因而.[]来赢得属性的值。

  • .的右手必须是一个以属性名称命名的大概标示符
  • []的方括号内必须是一个乘除结果为字符串的表达式

var author = book.author;
var name = author.subname;
var title = book["main title"]

和查询属性一样,可以由此.[]来博取成立属性或给属性赋值。

book.edition = 1;
book["main title"] = "Js";

当通过[]做客对象属性时,可以在程序运行时修改和创立。举个例子:
经过读取customer对象的address0/address1/address2的性质,并且连接起来

var addr = "";
for(var i=0; i<3; i++){
  addr += customer["address"+i] + '\n';
}

继承

js对象拥有自己的特性,也有一部分特性从原型对象继承而来。

若果要查询对象o的属性x,假若o中不存在x,那么将会在o的原型中询问属性x。假诺原型对象也尚未x,但是这多少个原型对象也有原型,
那就是说继续在这一个原型对象的原型上查询,直到找到x或者查找到一个目的的原型是null为止。

这般对象的原型属性构成一个“链”,通过这么些”链实现属性的接轨。

探望上面那么些栗子,加强一下明亮:

var o = {}; // 创建一个空对象
o.x = 1; // o对象新增一个属性x
var p = inheritPrototype(o); // 上面提到的继承函数,p继承自o
p.y = 2; // p新增一个属性y
var q = inheritPrototype(p); // q继承自p
q.z = 3; // q新增属性z
var s = q.toString(); // toString继承自Object.prototype
q.x + q.y; // x继承自o,y继承自p

在性能赋值上,总是在原本对象上去创制属性或对已有些属性赋值,不会修改原型链

只有查询属性,才会体会继承的存在,而设置属性与后续无关,这是js的显要特征,务必牢记

这种特性,让程序员有采取的遮盖继承的习性:

var raw_circle = { r:1 };
var new_circle = inheritPrototype(raw_circle); // new_circle继承自raw_circle
new_circle.r = 2; // 修改new_circle继承来的属性的值,只修改自身
raw_circle.r; // => 1 ,原型的值并没有改变

属性访问错误

查询一个不设有的性质并不会报错,假若目的自我的习性和延续的特性中均没有,则会再次来到undefined。

只是倘诺目的不设有,试图询问这一个不存在的靶子的特性就会报错。null和undefined都尚未性能,因此查询这个值的性能会报错

下边提供2种避免失误的法子:

// 简单的方法
var len = undefined;
if(book){
  if(book.title) len = book.title.length;
}
// 更为灵活的方法
var len = book && book.title && book.title.length; // &&短路行为

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

给另外值设置属性不会整整得逞,有一部分属性是只读的,无法重新赋值,有一对目的无法新增属性,可是那多少个设置属性的败诉操作不会报错。但在ECMAScript5的严厉情势下会报那一个

Object.prototype = o; // 失败,Object.prototype没有被修改

由此可见,下边场景中给目的o设置属性p会失利:

  • o的习性是只读的,不可以给只读的习性重新赋值
  • o中的属性p是继续属性,且它是只读的,不可以因此同名自有总体性覆盖只读的连续属性
  • o中不设有自有属性p:o没有运用setter方法继承属性p,并且o的可扩大性是false。假设o中不存在p,且从未setter方法可以调用,则p一定会添加到o中,但只要o不是可扩张的,那么在o中不可能定义新属性

删除属性

delete运算符可以去除对象的属性。但是,delete只是断开属性和宿主对象的沟通,不是删除这一个特性。由此,在销毁对象的时候,要遍历属性中的属性,依次删除

a = { p: {x:1}}; //
b = a.p; // b是对a的属性p的一个引用
delete a.p; // 删除a的属性p
b.x; // => 1,已经删除的属性p的引用仍然存在

delete运算符只好删除自有总体性,不能够去除继承属性。

当delete表明式删除成功后还要没有副功用,再次回到true。如果delete前边不是一个属性访问表达式,同样重临true

o = {x:1};
delete o.x; // 删除x,true
delete o.x; // 删除失败,什么都不做,返回true
delete o.toString(); // 无法删除,返回true
delete 1; // 无意义,返回true

delete
可以去除不可扩张对象的可配备属性,但无法去除可配置性为false的属性。在严厉形式中,删除一个不得配置的性质会报一个项目错误。
在非严刻格局下,这多少个情形delete操作会退步并赶回false:

delete Object.prototype; // 不能删除,属性不可配置
var x=1; // 声明一个全局变量
delete this.x; // 不能删除这个属性
function f(){}; // 声明一个全局函数
delete this.f; // 也不能删除全局函数

当在非严厉形式中剔除全局对象的可部署属性时,可以概括对全局对象的引用,直接delete前面跟要删除的属性名即可:

this.x = 1;
delete x;

在严刻情势中,delete后跟随一个野鸡的操作数,将会报一个语法错误。必须显式的指定对象及其性质。

delete x; // 严格模式下报语法错误
delete this.x; // 正常

检测属性

我们平常索要看清一个属性是否存在某个对象中。

  • in运算符
  • hasOwnPreperty()
  • propertyIsEnumerable()

in运算符左边是属性名,右边是目标,即使目的的自有总体性或接续属性包含那么些特性,重临true

var o = {x:1};
"x" in o; // true,“x”是o的属性
"y" in o; // false, 'y'不是o的属性
"toString" in o; // true,o继承toString属性

对象的hasOwnPreperty()方法用来检测给的的名字是否是对象的自有总体性。对于持续属性重临false

var o = {x:1};
o.hasOwnPreperty("x"); // true,o有一个自有属性x
o.hasOwnPreperty("y"); // false,o没有属性y
o.hasOwnPreperty("toString"); // false,toString是继承的属性

propertyIsEnumerable()是hasOwnPreperty()的增强版,只有检测到是自有属性且这些特性的可枚举性为true时,才会回去true。
一些内置属性是不可枚举的。日常js代码创立的性质都是可枚举的,除非在ECMAScript5中使用一个例外的点子来改变属性的可枚举性。

var o =inheritPrototype({y:2});
o.x = 1;
o.propertyIsEnumerable("x"); // true,o有一个可枚举的属性x
o.propertyIsEnumerable("y"); // false,y属性是继承来的
object.prototype.propertyIsEnumerable("toString"); // false, 不可枚举的

而外采用in运算符之外,另一种更便捷的措施是使用!==认清一个属性是否是undefined:

var o = {x:1};
o.x !== undefined; // true,o中有属性x
o.y !== undefined; // false,y属性没有
o.toString !== undefined; // true,o继承了toString

接下来有一种情形,只好用in运算符,无法动用上述特性访问表明式。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
"x" in o; // 属性x不存在

专注,上边使用的是!==而不是!=,!==可以区分undefined和null。有时不需要展开区分:

// 如果o中含有属性x,且x值不是null或undefined
if (o.x != null) o.x += 2;
// 如果o中含有属性x,且x的值不能转换成false,乘以2
// 如果x是undefined、null、false、" "、0 或者NaN,则保持不变
if (o.x) o.x *= 2;

枚举属性

除了检测对象的习性是否存在,还每每需要遍历对象的特性。通常采取for/in循环遍历

for/in循环能够遍历对象中所有可枚举的性能(包括自己和持续的性质)

一般的话,对象继承的停放方法是不可枚举的,还有通过Object.defineProperty(obj,property,enumerable:false)来定义不可枚举的性质

var o = { x:1, y:2}; // 2个可枚举的属性
o.propertyIsEnumerable("toString"); // false 继承的方法,不可枚举
for(p in o){
  console.log(p)  // 输出x,y,不会输出toString
}

重重时候给目的添加新的法子或性质,但是会被for/in枚举,由此需要跳过连续的性质和跳过方法,来避免在for/in中被循环枚举出来

for(p in o){
  if(!o.hasOwnPreperty(p)) continue;
}
for(p in o){
  if(typeof o[p] === "function") continue;
}

除外for/in循环之外,ECMAScript5概念了2个用来枚举属性名称的函数

  • Object.keys(),再次来到一个数组,数组由对象中可枚举的自有总体性的称号组成
  • Object.getOwnPropertyNames(),与keys相似,可是它回到所有自有性能的名称,而不仅是可枚举的习性

属性getter和setter

对象属性是由名字、值和一组特性(attribute) 构成的。

在ECMAScript5中,属性可以用1-2个措施替代,这三个艺术就是getter和setter。

由getter和setter定义的性能称作“存取器属性”(access
property),与“数据属性”不同,数据属性唯有一个简约的值。

当程序查询存取器属性的值时,js调用getter方法(无参数),这一个点子重返值就是性质存取表明式的值。
当程序设置一个存取器属性的值时,js调用setter方法,将赋值表明式右边的值当作参数传入setter。可以忽略setter方法的重返值。

和数码属性不同,存取器属性不享有可写性(writable
attribute)。假使属性同时兼有getter和setter方法,那么是一个读/写属性,
假诺它惟有getter方法,那么它是一个只读属性;假若它只有setter方法,那么它是一个只写属性,读取只写属性,只会再次回到undefined

概念存取器最简单易行的方法如下:

var o = {
  // 普通的数据属性
  data_prop: value;

  // 存取器都是成对定义的函数
get accessor_prop() { /* function body */ }
set accessor_prop(value) { /* function body */ }
}

瞩目,存取器属性定义为1个或者2个和总体性同名的函数,可是并未接纳function,而是采取get和set。

来看个例证:表示2D笛Carl坐标系的靶子

var p = {
  // 2个普通的读写属性
  x: 1.0,
  y: 1.0,

  // 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 = newvalue/oldvalue;
    this.x *= ratio;
    this.y *= ratio;
  }

  // theta是只读存取器,只有getter
  get theta() { return Math.atan2(this.y, this.x); }
}

留神,在这段代码中,getter和setter中this关键字的用法。js把这多少个函数当作对象的方法来调用,也就说,在函数体内的this指向代表这么些点的靶子,
故而,r属性的getter方法可以透过this.x和this.y引用x和y的特性。

和数据属性一样,存取起属性是可以继续的,由此得以将上述代码的对象p当在另一个点的原型。可以给新对象定义它的x和y属性,但r和theta属性是后续的:

var q = inheritPrototype(p); // 创建一个继承getter和setter的新对象
q.x = 1, q.y = 2; // 给q添加2个属性
console.log(q.r); // 可以使用继承的存取器属性
console.log(q.theta);

特性的特色

性能除了名字和值之外,属性还隐含部分标识:可写、可枚举和可配备的性状。

ECMAScript5询问和安装属性的API:

  • 原型对象添加方法,设置成不可枚举的,更像内置方法
  • 给目标定义不能够修改或删除的性能,锁定这么些目的

一个属性包含一个名字和四个特性,分别是:

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

存取器属性不富有值(value)特性和可写性,它们的可写性是由setter方法是否留存控制。因而存取器属性的4个特色是读取(get)、写入(set)、可举性和可配置性。

为了贯彻属性特性的询问和安装操作,ECMAScript5定义了一个“属性描述符”的目的,那个目的表示了4个特性。

数码属性的叙说符对象的特性有:

  • value
  • writable
  • enumerable
  • configurable

存取器属性的叙述符对象用set和get属性代替value和writable。

透过调用Object,getOwnPropertyDescriptor()能够拿到某个对象特定属性的特性描述符:

// 返回 { value:1, writable:true, enumerable:true, configurable:true }
Object.getOwnPropertyDescriptor({ x:1 },"x");
// 查询上文中定义的random对象的octet属性
// 返回 { get: /*func*/, set:undefined, enumerable:true, configurable:true }
Object.getOwnPropertyDescriptor(random, 'octet');
// 对于继承属性和不存在的属性,返回undefined
Object.getOwnPropertyDescriptor({}, 'x');
Object.getOwnPropertyDescriptor({}, 'toString');

从名字就足以看到,Object,getOwnPropertyDescriptor()只好拿到自有性能的描述符。
要想赢得持续属性的性状,需要遍历原型链(使用Object.getPrototypeOf())

要想设置属性的特色,或者想让新建属性具有某种特性,可以调用Object.defineProperty(),传入修改的靶子、要修改或创建的性能名称以及性能描述符对象:

var o={};
// 添加一个不可枚举的数据属性x,赋值为1
Object.defineProperty(o, "x",
  { value: 1,
    writable: true,
    enumerable: false,
    configurable: true
    });
// 属性是存在的,但不可以枚举
o.x; // =>1
Object.keys(o); // [],不可枚举
// 现在对属性x修改,变成制度属性
Object.defineProperty(o, "x",
  {
    writable: false
    });
// 试图更改这个属性的值
o.x = 2; // 操作失败,但是不报错,但是在严格模式中抛出类型错误异常
o.x; // => 1
// 属性依然可以配置,因此可以通过这种方式对它修改:
Object.defineProperty(o, "x",
  {
    value: 2
    });
// 返回的值变为2
o.x // =>2
Object.defineProperty(o, "x",
  {
    get:function() { return 0;}
    });
o.x; // => 0 现在将x从数据属性修改为存取器属性

传扬Object.defineProperty()的特性描述符对象无须包含所有4个特色。
对于新创制的习性,默认的性状值是false或undefined。对于修改的已有性能来说,默认特性值没有此外修改。其它,那多少个法子仍旧修改已有总体性,要么新建自有性能,但不会修改继承属性

假定同时修改或创办四个属性,则需要利用Object.defineProperties()。第一个参数是要修改的靶子,第二个参数是一个映射表,要含有新建的活修改的属性的称呼,以及它们的属性描述符:

var p = Object.defineProperties({}, {
    x: { value: 1, writable: true, enumerable: true, configurable: true},
    y: { value: 1,writable: true, enumerable: true, configurable: true},
    r: {
        get: function() { return Math.sqrt(this.x*this.x + this.y*this.y)},
        enumerable: true,
        configurable true
    }
  });

这段代码从一个空对象起先,给它添加六个数据属性和一个只读存取器属性。最后Object.defineProperties()再次来到修改的目的
和Object.defineProperty()一样

对于这个不允许创设或改动的特性,假使用Object.defineProperty()和Object.defineProperties()对其操作(新建或涂改)
就会抛出档次错误很是。比如,给一个不足扩大的对象新增属性就会抛出档次错误至极。

可写性控制着对值特性的改动,可配置性控制着对其他值特性的修改。

一经属性是可配备的,则足以修改不可写属性的值。同样的,借使属性是不足配置的,仍旧可以将可写属性修改为不可写属性。

上面是修改的条条框框,违反规则的施用都会抛出档次错误分外:

  • 即使目的是不可扩大的,则足以编写已有性能,但无法添加新属性(preventExtensions()阻止扩大)
  • 假使属性是不行配置的,则不可能改改它的可配置性和可枚举性
  • 一旦存取器属性是不行配置,则无法改改getter和setter的点子,也不可以转换成数据属性
  • 只要数额属性是不足配置的,则不可能将它转换成存取器属性
  • 假诺数额属性是不足配置的,则不可以将它的可写性从false修改为true,但能够从true修改成false
  • 假定数量属性是不行配置且不可写的,则不可能改改它的值,然则可安排但不足写属性是可以修改的(实际上先将它标志为可写的,然后修改它的值,最后转换为不可写)

下边这个方法,不仅将一个目的的属性复制到另一个目的中,而且复制了性能的特色。

/* 给Object.prototype添加一个不可枚举的extend()方法
 * 这个方法继承自调用它的对象
 * 将作为参数传入的对象的属性一一复制
 * 除了值之外,也复制属性的所有特性,除非在目标对象中存在同名属性
 * 参数对象的所有自有对象(包括不可枚举的属性)也会一一复制

Object.defineProperty(Object.prototype,
  "extend", // 定义 Object.prototype.extend
  {
    writable: true,
    enumerable: false, // false,定义为不可枚举的
    configurable: true,
      value: function(o){ // 值就是这个函数
        // 得到所有的自有属性,包括不可枚举属性
        var names = Object.getOwnPropertyNames(o);
        // 遍历它们
        for(var i=0; i<names.length; i++){
          // 如果属性已经存在,则跳过
          if(names[i] in this) continue;
          // 获得o中属性的描述符
          var desc = Object.getOwnPropertyDescriptor(o, names[i]]);
          // 用它给this创建一个属性
          Object.defineProperty(this, names[i], desc)
        }
      }
    });

目标的多个特性

每一个对象都有与之有关的原型(prototype)、类(class)和可扩大性(extensible)

原型属性

目标的原型属性是用来延续属性。

原型属性是在实例对象创设在此之前就安装好的,通过对象直接量创立的靶子使用Object.prototype作为它们的原型。
通过new创造的对象使用构造函数的prototype属性作为它的原型。通过Object.create()创设的目的使用首个参数(也得以是null)作为它们的原型。

在ECMAScript5中,将目的作为参数传入Object.getPrototypeOf()能够查询它的原型。

通过new表明式创设的靶子,平时持续一个constructor属性,这几个特性指代创建那个目的的构造函数。

通过对象直接量或Object.create()创建的对象涵盖一个名为constructor的特性,这些特性指代Object()构造函数。

之所以,constructor.prototype才是目标直接量的着实的原型,不过经过Object.create()创制的靶子往往不是如此

要想检测一个目的是否是另一个目的的原型(或地处原型链中),可以动用isPrototypeOf()方法。

var p = { x:1 }; // 定义一个原型对象
var o = Object.create(p); // 使用这个原型创建一个对象
p.isPrototypeOf(o); // true,o继承自p
Object.prototype.isPrototypeOf(p); // true,p继承自Object.prototype

类属性

对象的类属性是一个字符串,用以标识对象的类型消息,有一个直接的toString()方法,再次来到如下这种格式的字符串[object class]

要想博得对象的类,可以调用对的toString()方法,然后提取重返字符串串的第8个到倒数第2个职务之间的字符。为了可以调用正确的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)
}

classof()函数可以流传任何类型的参数。数字、字符串和布尔值可以从来调用toString(),就和目的toString()方法同样。

通过对象直接量和Object.create创设的靶子的类属性是“Object”,这个自定义的构造函数创造的对象也是同等,类性质也是”Object”,
于是没办法通过类属性来区分对象的类

classof(null) // => "NULL"
classof(1) // => "Number"
classof("") // => "String"
classof(false) // => "Boolean"
classof({}) // => "Object"
classof([]) // => "Array"
classof(/./) // => "Regexp"
classof(new Date()) // => "Date"
classof(window) // => "Window"
function f() {}; // => "定义一个自定义构造函数"
classof(new f()); // => "Object"

可扩张性

目的的可扩充性用以代表是否给剋有给目标添加新的习性。

具有的放到对象和自定义对象都是显式可扩张的,宿主对象的可扩张性是由js引擎定义的。

在ECMAScript5中,所有的嵌入对象和自定义对象都是可扩展的,除非呗转换成不可扩充的。

可增添性的目标是将目的“锁定”,以防止外界的打扰。对象的可扩充性通车和属性的可配值性和可写性配合使用

ECMAScript5概念了用来询问和安装对象可扩张性的函数。

  • 经过将目的传入Object.esExtensible(),来判断目标是否是可扩张的。
  • 透过将preventExtensions()只影响对象自我的可扩张性。将目的作为参数传进去,一旦将对象转换为不可扩充的,就不能转换成可增加的。如若给一个不得扩充的靶子的原型添加属性,那个不可扩张的对象同样会持续这多少个新属性
  • Object.seal()和Object.preventExtensions()类似,除了可以将对象设置为不可扩展性,仍可以够将目的的兼具自有总体性都设置为不可配置的,也就是说,不可能给这一个目的添加新属性,而且它已有些属性也无法去除或安排,可是已有的可写属性依旧得以是设置。对于那多少个已经封闭的(sealed)起来的目的是不可以解封的。可以行使Object.isSealed()来检测对象是否封闭
  • Object.freeze()将更严厉的锁定目标,将目标设置成不可扩充的和属性设置成不可配置的之外,仍可以将它自有的拥有数据属性设置成只读的(假使目标的存取器属性有setter方法,存取器属性不受影响,仍能够透过性能赋值调用)。使用Object.isFrozen()来检测对象是不是冻结。

Object.preventExtensions()、Object.seal()和Object.freeze()都回去传入的目的,也就是说可以通过函数嵌套的法子调用他们:

// 创建一个封闭对象,包括一个冻结的原型和一个不可枚举的属性
var o = Object.seal(Object.create(Object.freeze({x:1}),{y:{value:2, writable: true}}));

连串化对象

目标系列化是指将对象的动静转换为字符串,也可以将字符串还原为对象。

ECMAScript5提供内置函数JSON.stringfy()和JSON.parese()用来体系化和死灰复燃js对象。

这么些办法应用JSON作为数据互换格式,JSON的语法和js对象和数组直接量的语法万分接近

o = { x:1, y: { z: [false,null,""]}}; // 定义一个测试对象
s = JSON.stringfy(o); // s是 '{"x":1, "y":{"z":[false,null,""]}}'
p = JSON.parse(s); // p 是 o 的深拷贝

JSON的语法是js语法的子集,并不可能表示js里的装有值。

援助对象、数组、数组、字符串、无穷大数字、true、false和null,并且他们可以体系化和回复。

NaN、Infinity和-Infinity连串化的结果是null,日期对象序列化的结果是ISO格式的日期字符串,但JSON.parse()如故保留它们的字符串形态,
而不会将它们还原成原始日期对象。函数、RegExp、Error对象和undefined值不可能体系化和欢原

JSON.stringfy()只可以体系化对象可枚举的自有性能,对于一个无法体系化的习性来说,在连串化后的出口字符串中会将以此特性省略掉。

目标方法

抱有的js对象都从Object.prototype继承属性。

下面说多少个最常用的主意

toString()方法

toString()方法没有参数,将回到一个调用这些主意的对象对象值的字符串。在需要将目标转换为字符串的时候,js都会调用那一个艺术。

默认的toString()方法的重回值带有的消息量很少,例如上边这行代码的臆想结果为字符串:

var s = { x:1, y:1}.toString(); // => [object object]

由于默认的toString()方法并不会输出很多灵光的信息,因而不少类都富含自定义的toString()。

toLocaleString()方法

除开主导的toString()方法之外,对象都蕴涵toLocaleString()方法,这么些办法再次回到一个意味那一个目的的地方化字符串。

Object中默认的toLocaleString()方法并不做其他本地化自身的操作,仅调用toString()方法并赶回对应值。

Date和Number类对toLocaleString()方法做了定制,可以用它对数字、日期和骨子里本地化的转移

Array类的toLocaleString方法和toString方法很像,唯一的不比是各种数组元素会调用toLocaleString()方法转换为字符串,而不是调用给各自的toString

toJSON()方法

Object.prototype实际上没有定义toJSON()方法,但对此急需执行系列化的目的的话,JSON.stringfy()方法会调用toJSON()方法,
假若在待序列化的靶子中设有这些格局,则调用它,再次来到值是连串化的结果,而不是土生土长对象。

valueOf()方法

valueOf()方法和toString()方法非常接近,但屡屡当js需要将目的转换成某种原始值而非字符串对象的时候才会调用,尤其是转换成数字的时候。
假若在需要拔取原始值的上下文中使用了目的,js会自动调用这些办法