js对象再复习

376 阅读8分钟

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

语法

对象可以通过两种形式定义:

  • 对象字面量
  • 对象的构造形式
//对象字面量
var myObj = { 
 key: value 
 // ... 
};

//对象的构造形式
var myObj = new Object(); 
myObj.key = value;

构造形式和文字形式生成的对象是一样的。唯一的区别是,在文字声明中你可以添加多个键 / 值对,但是在构造形式中你必须逐个添加属性。

类型

对象是 JavaScript 的基础。在 JavaScript 中一共有六种主要类型(术语是“语言类型”):

  • string

内容

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

存储在对象容器内部的是这些属性的名称,它们就像指针(从技术角度来说就是引用)一样,指向这些值真正的存储位置。

属性的访问

要访问对象的属性值,可以通过. 操作符或者 [] 操作符。前者称作属性访问,后者称作键访问

这两种语法的主要区别在于 . 操作符要求属性名满足标识符的命名规范,而 [".."] 语法 可以接受任意 UTF-8/Unicode 字符串作为属性名。举例来说,如果要引用名称为 "Super-Fun!" 的属性,那就必须使用 ["Super-Fun!"] 语法访问,因为 Super-Fun! 并不是一个有效 的标识符属性名。

// 可以通过构造字符串的方式访问对象
var myObject = { 
 a:2 
}; 
var idx; 
if (wantA) { 
 idx = "a"; 
} 
// 之后
console.log( myObject[idx] ); // 2

在对象中,属性名永远都是字符串。如果你使用 string(字面量)以外的其他值作为属性 名,那它首先会被转换为一个字符串。即使是数字也不例外,虽然在数组下标中使用的的 确是数字,但是在对象属性名中数字会被转换成字符串。

var myObject = { }; 
myObject[true] = "foo"; 
myObject[3] = "bar"; 
myObject[myObject] = "baz"; 
myObject["true"]; // "foo"
myObject["3"]; // "bar"
myObject["[object Object]"]; // "baz"

可计算属性名

ES6 增加了可计算属性名,可以在文字形式中使用 [] 包裹一个表达式来当作属性名

var prefix = "foo"; 
var myObject = { 
 [prefix + "bar"]:"hello", 
 [prefix + "baz"]: "world" 
}; 
myObject["foobar"]; // hello 
myObject["foobaz"]; // world

数组对象

数组也是对象。

数组期望的是数值下标,也就是说值存储的位置(通 常被称为索引)是非负整数。

向数组添加一个属性,但是属性名“看起来”像一个数字,那它会变成 一个数值下标(因此会修改数组的内容而不是添加一个属性)

var myArray = [ "foo", 42, "bar" ]; 
myArray["3"] = "baz"; 
myArray.length; // 4 
myArray[3]; // "baz"

对象的克隆

对象的克隆分为浅克隆深克隆

浅度克隆对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于非基本数据类型的属性,仅仅复制一份引用给新产生的对象,即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。

在js中,对象的浅克隆是对基础数据类型进行值复制,对于复杂对象类型进行引用复制,即克隆对象与源对象中的复杂数据类型都指向一个内存地址,其中一个被修改了会体现在另一个上。

在浅度克隆的基础上,对于要克隆的对象中的非基本数据类型的属性对应的类,也实现克隆,这样对于非基本数据类型的属性,复制的不是一份引用,即新产生的对象和原始对象中的非基本数据类型的属性指向的不是同一个对象

深克隆在浅克隆的基础上对复杂对象类型进行深度克隆,即不止其本身,会递归地对其内部的引用对象进行克隆,这样克隆的对象与源对象在内存中的位置是独立的。

对于 JSON 安全(也就是说可以被序列化为一个 JSON 字符串并且可以根据这个字符串解 析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法:

// 通过json序列化进行深克隆。
//仅适用于json安全的对象。
var newObj = JSON.parse( JSON.stringify( someObj ) );

ES6 定义了 Object.assign(..) 方 法来实现浅复制。Object.assign(..) 方法的第一个参数是目标对象,之后还可以跟一个 或多个源对象。它会遍历一个或多个源对象的所有可枚举(enumerable,参见下面的代码) 的自有键(owned key)并把它们复制(使用 = 操作符赋值)到目标对象,最 后返回目标对象

var newObj = Object.assign( {}, myObject ); 
newObj.a; // 2 
newObj.b === anotherObject; // true 
newObj.c === anotherArray; // true 
newObj.d === anotherFunction; // true

属性描述符

在 ES5 之前,JavaScript 语言本身并没有提供可以直接检测属性特性的方法,比如判断属 性是否是只读。 但是从 ES5 开始,所有的属性都具备了属性描述符。

var myObject = { 
 a:2 
}; 
console.log(Object.getOwnPropertyDescriptor( myObject, "a" ))

// 输出
{ 
    value: 2,  //数据描述
    writable: true, //是否可写
    enumerable: true, //是否可枚举
    configurable: true //是否可配置
}

在创建普通属性时属性描述符会使用默认值,我们也可以使用Object.defineProperty(..) 来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置。

var myObject = {}; 
Object.defineProperty( myObject, "a", { 
 value: 2, 
 writable: true, 
 configurable: true, 
 enumerable: true
} ); 
myObject.a; // 2

writable 属性是否可修改

writable 决定是否可以修改属性的值。

设置为false则代表该属性定义后不可被改写,修改操作会无效化,严格模式下则报TypeError错误。

configurable 属性是否可配置

设置为false代表定义后不能再修改该属性的属性描述符,即对该属性再次使用defineProperty。

该配置是单向的,无法撤销。

var myObject = { 
 a:2 
}; 
myObject.a = 3; 
myObject.a; // 3 
Object.defineProperty( myObject, "a", { 
 value: 4, 
 writable: true, 
 configurable: false, // 不可配置!
  enumerable: true
} ); 
myObject.a; // 4 
myObject.a = 5; 
myObject.a; // 5 
Object.defineProperty( myObject, "a", { 
 value: 6, 
 writable: true, 
 configurable: true, 
 enumerable: true
} ); // TypeError

最后一个 defineProperty(..) 会产生一个 TypeError 错误,不管是不是处于严格模式,尝 试修改一个不可配置的属性描述符都会出错

除了无法修改,configurable:false 还会禁止删除这个属性

var myObject = { 
 a:2 
}; 
myObject.a; // 2 
delete myObject.a; 
myObject.a; // undefined 
Object.defineProperty( myObject, "a", { 
 value: 2, 
 writable: true, 
 configurable: false, 
 enumerable: true
} ); 
myObject.a; // 2 
delete myObject.a; //操作无效
myObject.a; // 2

Enumerable 是否可枚举

这个描述符控制的是属性是否会出现在对象的属性枚举中,比如说 for..in 循环。如果把 enumerable 设置成 false,这个属性就不会出现在枚举中,虽然仍 然可以正常访问它。相对地,设置成 true 就会让它出现在枚举中。

var myObject = {
    a: 1,
    b: 2,
    c: 3,
    d: 4
};

console.log("========= true =========")

for (let i in myObject){
    console.log(i)
}

console.log("========= false =========")
Object.defineProperty(myObject, 'c', {
    enumerable:false
})

for (let i in myObject){
    console.log(i)
}

// 输出

========= true =========
a
b
c
d
========= false =========
a
b
d

对象的不变性

对象默认是浅不变性,,也就是说,它们只会影响目标对象和 它的直接属性。如果目标对象引用了其他对象(数组、对象、函数,等),其他对象的内容不受影响,仍然是可变的

myImmutableObject.foo; // [1,2,3] 
myImmutableObject.foo.push( 4 ); 
myImmutableObject.foo; // [1,2,3,4]

要想创建自身和直接属性都是深不变性的对象,可通过以下方式

对象常量

结合 writable:false和configurable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除):

var myObject = {}; 
Object.defineProperty( myObject, "FAVORITE_NUMBER", { 
 value: 42, 
 writable: false, 
 configurable: false 
} );

禁止添加属性

如 果 你 想 禁 止 一 个 对 象 添 加 新 属 性 并 且 保 留 已 有 属 性, 可 以 使 用 Object.prevent Extensions(..):

var myObject = { 
 a:2 
}; 
Object.preventExtensions( myObject ); 
myObject.b = 3; 
myObject.b; // undefined

在非严格模式下,创建属性 b 会静默失败。在严格模式下,将会抛出 TypeError 错误。

密封

Object.seal(..) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用bject.preventExtensions(..)并把所有现有属性标记为 configurable:false。

密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。

冻结

Object.freeze(..)会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal(..)并把所有“数据访问”属性标记为writable:false,这样就无法修改它们的值。

[[Get]]

对象内部对属性实现了[[Get]]操作。

对象默认的内置 [[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。

找不到则会去对象的原型链中查找,查找失败后放回undefined

[[Put]]

既然有可以获取属性值的 [[Get]] 操作,就一定有对应的 [[Put]] 操作

如果已经存在这个属性,[[Put]] 算法大致会检查下面这些内容。

  1. 属性是否是访问描述符?如果是并且存在 setter 就调用 setter。
  2. 属性的数据描述符中 writable 是否是 false ?如果是,在非严格模式下静默失败,在 严格模式下抛出 TypeError 异常。
  3. 如果都不是,将该值设置为属性的值。

Getter和Setter

对象默认的 [[Put]]和[[Get]]操作分别可以控制属性值的设置和获取。

使用 getter 和 setter 属性描述符来改写默认操作。