JavaScript的对象详解(上)

167 阅读7分钟

什么是对象

ECMA-262(JavaScript的一种语言标准,262只是编号)将对象定义为一组属性的无序集合。换句话说在ECMAScript中“对象”可以看成是一张散列表,其内容是一组键值对,键可以是属性或方法,值可以是数据或函数。JavaScript与Java不同,在JavaScript中并不是“万物皆对象”的。

对象的定义

JavaScript中的对象可以通过两种形式定义:声明(文字)形式和构造形式。

声明形式(也叫‘对象字面量’)

var book={ name:'《算法》' //…… }

构造形式

var book=new Object(); book.name='《算法》'

对象类型

在JavaScript中一共有六种主要类型(语言类型):

  • String
  • number
  • boolean
  • null
  • underfined
  • object 这些基本类型本身并不是对象,而null有时会被当作对象原因在于在JavaScript中二进制前三位都为0的话就会被视为object类型,而null的二进制表示全部都是0,所以在执行typeof时会返回一个“object”.

在JavaScript中有许多对象子类型,也称之为复杂基本类型,如函数、数组。

内置对象

JavaScript中还有一些对象子类型,通常被称为内置对象:

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error 内置对象有点类似于Java中的类型(type)或者类(class),当然可能不仅仅是Java有这些。但是在JavaScript、中是没有类(class)的概念的,所以这些内置对象实际上是一些内置函数,编程时可以当作构造函数(new产生的函数调用)来使。举个 432180C5.png

var strObject=new String("I Love Cate Blanchett");

typeof strObject;//object

strObject instanceof String;//ture

在实例中strObject是由String构造函数创建的一个对象,但是原始值“I Love Cate Blanchett”并不是一个对象,只是一个字面量,且是一个不可变的值。若要在这个字面量上执行一些操作,如获取长度和访问某个字符等都需要将该字面量转换为String对象,而这个转换在JavaScript中引擎会自动完成,即你可以不必显示创建一个String对象。上面的代码中的

var strObject=new String("I Love Cate Blanchett");

可以写成:

var strObject="I Love Cate Blanchett";

类似的事也会在数值字面量上,如果使用类似42.359.toFixed()时,引擎会将42转换为new Number(42),对于布尔字面量也是如此。其中null和undefined没有对应的构造形式,只有声明形式;Date只有构造,没有声明形式。对于Object,Array,Function,RegExp无论是构造形式还是声明形式,都会是一个对象,不是字面量。Error对象使用较少,一般是抛出异常时自动被创建。

属性

对象的内容是由一些存储在特定命名位置的(任意类型)值组成,也称之为属性。

值得注意的是,说“属性”是“对象”的“内容”并不是说属性值被存储在对象内部,而是属性是对象的表现形式。在引擎内部,这些值的存储方式是多种多样的,存储在对象容器内部的是属性的名称,类似于指针(或者说引用),指向其值真实位置。

在对象中属性名永远都是字符串,如果使用一个非String(字面量)的值来作为属性名,其会自动被转换为一个字符串。

属性分两种:数据属性和访问器属性。

数据属性

数据属性包含保存数据值的位置,值的读取与写入都会在该位置执行。数据属性有四个特性描述它们的行为。

  • [[Configurable]]:属性是否可以通过delete删除并重新定义,是否可修改特性,是否可转换为访问器属性。默认ture.
  • [[Enumerable]]:属性是否可以枚举,即通过for-in循环输出。默认ture。
  • [[Writable]]:属性值是否可被修改。默认ture.
  • [[Value]]:包含属性的值,即属性值读取写入的位置。默认underfined.

即在我们定义一个对象并为其添加属性之后,这些特性都会被设定。如

let person={ name:"Jano"};

剖析来看上面这个person对象发生了以下过程:

设定一个名为name的属性,并为其赋值[[Value]]=“Jano”.其他三个特性也赋予默认值ture。

如果你想要修改这个对象属性的特性,就需要使用到:Object.defineProperty()这个方法。语法如下:

Object.defineProperty(属性对象,属性名,{要修改的特性});

例:

let person={};

Object.defineProperty(person,"name",{writable:false,value:"Tom"});

需要注意的是对于默认不可修改的属性值,在非严格模式下是可以通过Object.defineProperty()方法更改其属性值的,但是在严格模式下会报错。其他的特性也如此。

访问器属性

访问器属性不包含数据值,其包含一个getter和setter函数,当然这两个函数不是必须的。读取访问器属性时会调用getter返回一个有效值;写入访问器属性时会调用setter并决定对数据做出修改。访问器属性同样有四个特性来描述它们的行为:

  • [[Cnfigurable]]
  • [[Enumerable]]
  • [[Get]]:默认undefined;
  • [[Set]]:默认undefined;

访问器属性是不能直接被定义的,需使用Object.defineProperty()。而且访问器属性相较于数据属性最大的不同在于,访问器属性的属性值一个改变会引发另一个也变化。如:

let book={

year_:2021;

edition:1

};

Object.defineProperty(book,"year",{

get() {

return this.year_;

},

set(newValue){

this.year_=newValue;

this.edition+=newValue - 2021;

}

});

现假设将year的属性修改位2022,

book.year=2022;

则会有

console.log(book.edition);//2

说明当其中一个属性发生变化点的时候,其他的一些属性也会发生变化。

除此之外ECMAScript还提供了一些方法以适应不同的场景需求:

  • Object.defineProperties()//定义多个属性
  • Object.getOwnPropertyDescriptor()//读取属性的特性;(该方法只能一个一个的读取)

可计算属性

在引入可计算属性前如果使用变量的值作为属性,就必须先声明对象,然后使用中括号语法添加属性,而不能直接在字面量中直接动态命名属性。例如:

const nameKey='name';

const ageKey='aeg';

let person={};

person[nameKey]='Marks';

person[ageKey]='10';

console.log(person);//{name:'Mark',age:10}

而有了可计算属性,代码就会相对简便很多:

const nameKey='name';

const ageKey='10';

let person={[nameKey]:'Marks',[ageKey]:10};

console.log(person)//{name:'Marks',age:10}

值得注意的是,可计算属性表达式中出任何错误都会终止对象的创建,并抛出错误,且之前的计算不能回滚。

对象的合并(混入)

合并对象,有点类似于Java的“继承”,即将源对象所以的本地属性一起Ctrl+C到目标对象上。这种操作有时候也称之为“混入(mixin)”,由此目标对象的属性得到增强。ECMAScript中为合并对象提供了专门的方法:Object.assign()。

该方法接受一个目标对象和一个或者多个源对象作为参数,然后将每个源对象的可枚举(Object.propertyIsEnumerable())返回ture,将自有(Object.hasOwnProperty())返回ture,复制到目标对象上。

使用该方法时,以字符串和符号为键的属性会被复制,对每一个符合条件的属性,都会用源对象上的[[Get]]取得属性值,调用目标对象的[[Set]]方法设置属性值。

对象的深拷贝和浅拷贝

对象的深拷贝与浅拷贝的主要区别在于更改新的对象是否会对源对象产生影响。详细的我会专门写一篇笔记来探讨一下,在这里就不细说了。只要先明白:

浅拷贝:会重新在堆中创建内存,拷贝前后的对象基本数据类型不影响,而对象的引用类型因为是共享同一块内存,所以会相互影响。

深拷贝:从堆内存中开辟一个新的区域存放对象,对对象中的子对象进行递归拷贝,拷贝前后的对象互不影响。