04.JavaScript基础系列:对象

269 阅读9分钟

JavaScript基础系列

上期知识点回顾

  • var: 使用 var 语句声明的全局变量是全局对象的属性,与其他全局对象属性不同,无法通过 delete 删除
  • switch: 语句的 case 判断执行的是 === 比较
  • throw: JavaScript 解释器会立即停止当前正在执行的逻辑,跳转到最近的异常处理程序;抛出异常采用的是 Error 类型或其子类型,错误对象有2个属性。
    • name: 表示错误类型
    • message: 存放传递给构函数的字符串

能够学到的知识点

  • 三种创建对象方法
  • 三个属性特性
  • 三个对象特性
  • 三类对象
  • 两类属性

对象

JavaScript 对象不仅仅是字符串到值的映射,还可以保持自有的属性(比如:可以从一个称为原型的对象继承属性)。

JavaScript 对象是动态的,可以新增属性或删除属性。

对象常见的用法是创建、设置、查找、删除、检测和枚举它的属性。

每个属性都有的三个属性特性

  • 可写(writbale): 表明是否可以设置该属性的值
  • 可枚举(enumerable): 表明是否可以通过 for/in 循环返回该属性
  • 可配置(configurable): 表明是否可以删除或修改该属性

每个对象都有的三个对象特性

  • 对象的原型(prototype): 指向另外一个对象,本对象的属性会继承自它的原型对象
  • 对象的类(class): 一个标识对象类型的字符串
  • 对象的扩展标记(extensible flag): 是否可以向该对象添加属性

三类对象和两类属性

  • 内置对象(native object): 由 ECMAScript 规范定义的对象或类,例如,数组、函数、日期和正则表达式
  • 宿主对象(host object): 是由 JavaScript 解释器所嵌入的宿主环境(比如 Web 浏览器)定义的
  • 自定义对象(user-defined object): 是由运行中的 JavaScript 代码所创建的对象
  • 自有属性(own-property): 直接在对象中定义的属性
  • 继承属性(inherited property): 在对象的原型中定义的属性

1.创建对象

对象直接量
var point = { x: 0, y: 0 }

对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象。每次计算对象直接量的时候,也都会计算它的每个属性的值。

通过 new 创建对象

new 运算符创建并初始化一个新对象,关键字 new 后跟随一个函数(构造函数)调用。

var obj = new Object({x: 1, y: 2})
console.log(obj); 	// {x: 1, y: 2}
Object.create()

ECMAScript 5 定义一个名为 Object.create() 方法,它创建一个新对象,其中第一个参数是这个对象的原型,第二个可选参数对对象的属性进行描述。

var obj = Object.create({x: 1, y: 2}, {
  x: {
    value: 42, 
    writable: true,
    enumerable: true,
    configurable: true 
  } 
});

# obj1 继承了属性 x 和 y
var obj1 = Object.create({x: 1, y: 2});
console.log(obj1.__proto__);        // {x: 1, y: 2}
console.log(obj1);                  // {}
console.log(obj1.x);                // 1

# obj2 不继承任何属性和方法,包括基础方法 toString()
var obj2 = Object.create(null);
console.log(obj2.toString());       // obj2.toString is not a function

# obj3 等同于 new Object()
var obj3 = Object.create(Object.prototype)

2.属性的查询和设置

可以通过点(.)和方括号([])运算符来获取或更新属性的值。对于点(.)来说,右侧必须是一个属性名称命名的简单标识符;对于方括号([])来说,方括号内可以是一个计算结果为字符串的表达式。

ECMAScript 3 约定点(.)运算符后的标识符不能是保留字(class、for等),而 ECMASCript 5放开了此限制。

属性赋值操作首先检查原型链,以判断是否允许赋值操作。在 JavaScript 中,只有在查询属性时才会体会到继承的存在,而设置属性则和继承无关。

3.删除属性

  • delete 只是断开属性和宿主对象的联系,不会去操作属性值
  • delete 只能删除自身属性,不能删除继承属性
  • delete 表达式删除成功与否没有任何副作用,返回 true
var obj = { x: 1 }
delete obj.x    // ture
delete 1        // 无意义

4.检测属性

JavaScript 对象可以看做属性的集合,可以通过 in 运算符、hasOwnProperty() 和 propertyIsEnumerable() 检测属性是否存在某个对象中。

  • in: 如果对象的自有属性或继承属性中含有这个属性,返回 true
  • hasOwnProperty(): 检测给定的名字是否是对象的自有属性,对于继承属性返回 false
  • propertyIsEnumerable(): 检测自有属性具有可枚举性,返回 true
var obj = { x:                                      
"x" in obj;                             // true,自有属性
"y" in obj;                             // false
"toString" in obj;                      // true,继承属性

# hasOwnProperty
obj.hasOwnProperty("x");                // true
obj.hasOwnProperty("y");                // false
obj.hasOwnProperty("toString");         // false

# propertyIsEnumerable
obj.propertyIsEnumerable("x")           // true
obj.propertyIsEnumerable("toString")    // false

5.枚举属性

for/in 循环可以在循环体重复遍历对象所有可枚举的属性(包括自有属性和继承的属性),对象继承的内置方式不可枚举的,在代码中给对象添加的属性默认是枚举的。

var point = {x: 1, y: 2, z: 3};
point.propertyIsEnumerable('toString'); // fasle

for (p in point) {
    if (!point.hasOwnProperty(p)) {
        console.log(p);		// x, y, z
    }
}
  • Object.keys() 返回一个数组,数组由对象中可枚举的自有属性的名称组成。
  • Object.getOwnPropertyNames() 返回对象的所有属性的名称,而不仅仅是可枚举的属性
var obj = Object.create({}, {
  x: {
    value: 42, 
    writable: true,
    enumerable: false,
    configurable: true 
  } 
});

Object.getOwnPropertyNames(obj);        // ['x']
Object.keys(obj);                       // []

6.属性getter 和 setter

在 ECMAScript 5 中,属性值可以通过 getter 和 setter 方法替代。由 getter 和 setter 定义的属性称做存取器属性。

当程序查询存取器属的值时,JavaScript 调用 getter 方法(无参数),这个方法的返回值就是属性存取表达式的值。

当程序设置一个存取器属性的值时,JavaScript 调用 setter 方法,将赋值表达式右侧的值当做参数传入 setter。

var point = {
  // 普通属性
  x: 1,
  y: 2,
  
  // 存取器属性
  get z() {
    return this.x + this.y
  },
  set z(val) {
    this.x =  this.x + val;
    this.y =  this.y + val * 2;
  },
}

console.log(p);                 // {x: 1, y: 2}
console.log(point.z)            // 3
point.z = 2
console.log(poin)               // {x: 3, y: 6}

console.log(Object.keys(point)) //["x", "y", "z"]

7.属性的特性

一个属性包含一个名字和4个特性,数据属性的4个特性分别是它的值(value)、可写性(writable)、可枚举性(enumerable)和可配置性(configurable)。

存取属性不具有值(value)特性和可写性,但仍具有4个特性是读取(get)、写入(set)、可枚举性(enumerable)和可配置性(configurable)。

ECMAScript 5 定义了一个名为 "属性描述符" 的对象,这个对象代表了那4个特性。可以通过 Object.getOwnPropertyDescriptor() 获得。

var point = {
  // 普通属性
  x: 1,
  y: 2,
  
  // 存取器属性
  get z() {
    return this.x + this.y
  },
  
  set z(val) {
    this.x =  this.x + val;
    this.y =  this.y + val * 2;
  },
}

// {value: 3, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(point, 'x')

// {enumerable: true, configurable: true, get: ƒ, set: ƒ}
Object.getOwnPropertyDescriptor(point, 'z')

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

var obj = {}
Object.defineProperty(obj, 'x', {
  value: 1,
  writeable: true,
  enumerable: false,
  configurable: true
});

obj.x                   // 1
Object.keys(obj)        // []

传入 Object.defineProperty() 的属性描述符对象不必包含所有4个特性,对于新创建的属性来说,默认的特性值是 false 或者 undefined。对于修改已有属性来说,默认的特性值没有做任何修改;这个方法无法修改继续属性。

Object.defineProperties() 可以同时修改或创建多个属性特性。

8.对象的三个属性

8.1原型属性(prototype)

对象的原型属性是用来继承属性的,我们经常把 o的原型属性 直接叫做 "o的原型"。

原型属性是在实例对象创建之初就设置好的。

对象原型的设置方法:

  • 通过对象直接量创建的对象使用 Object.prototype 作为它们的原型
var obj1 = {x: 1, y: 2};

# true
Object.getPrototypeOf(obj1) === Object.prototype  
# true
Object.getPrototypeOf(obj1) === Object.getPrototypeOf(new Object)
  • 通过 new 创建的对象使用构造函数的 prototype 属性作为它们的原型
var Person = function() {};
var p1 = new Person();

# true
Object.getPrototypeOf(p1) === Person.prototype
# true
Object.getPrototypeOf(p1) == p1.constructor.prototype
# false
p1 === Person.prototype

Object.getPrototypeOf(p1)               // {constructor: ƒ}
Person.prototype.constructor.name       // Person
p1.constructor.name                     // Person
p1.__proto__.constructor.name           // Person
  • 通过 Object.create() 创建的对象使用第一个参数(也可以为 null)作为它们的原型
var p2 = Object.create({x: 1, y: 2});
Object.getPrototypeOf(p2)                   // {x: 1, y: 2}
Object.getPrototypeOf(p2).constructor.name  // Object
p2.__proto__.constructor.name               // Object

查看对象原型的方法:

  • 在 ECMAScript 5 中可以通过 Object.getPrototypeOf() 可以查询它的原型。

  • 在 ECMAScript 3 中 没有与之等价的函数,可以使用表达式 o.constructor.prototype 来检测一个对象的原型。

    • 通过 new 表达式创建的对象,通常继承一个 constructor 属性,这个属性指代创建这个对象的构造函数
    • 通过对象直接量或 Object.create() 创建的对象包含一个名为 constructor 的属性,这个属性指代 Object() 构造函数。因此,constructor.prototype 才是对象直接量的真正的原型

检查对象原型的方法:

通过 isPrototypeOf() 方法可以检查一个对象是否是另一个对象的原型(或处于原型链中)。

8.2类(class)

对象的类属性是一个字符串,用以表示对象的类型信息。ECMAScript 3 和 ECMAScript 5 都未提供设置这个属性的方法,但可以通过 toString() 方法查询。

  • 通过内置构造函数(比如 Array 和 Date) 创建的对象包含类属性,它与构造函数名称相匹配
  • 通过对象直接量和 Object.create 创建的对象的类属性是 Object
  • 通过自定义构造函数创建的对象的类属性是 Object
function classof(o) {
  if (o === null) return 'Null';
  if (o === undefined) return 'Undefined';
  return Object.prototype.toString.call(o).slice(8, -1)
}

classof(null)           // "Null"
classof(undefined)      // "Undefined"
classof(1)              // "Number"	
classof('1')            // "String"
classof(false)          // "Boolean"
classof([])             // "Array"
classof(new Date)       // "Date"
classof(/\d+/)          // "RegExp"
var f = function() {}
classof(f)              // "Function"
classof(new f())        // "Object"
8.3可扩展性(extensible attribute)

对象的可扩展性用以表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显式可扩展的。宿主对象的可扩展性是由 JavaScript 引擎定义的。

ECMAScript 5 可以通过 Object.isExtensible(obj) 判断对象 obj 是否可扩展的。

ECMAScript 5 可以通过 Object.preventExtensions(obj) 将对象 obj 转换为不可扩展的,此过程是不可逆的。

preventExtensions() 只影响对象本身的可扩展性,如果给一个不可扩展的对象的原型添加属性,这个不可扩展的对象同样会集成这些属性。

var Person = function (name) {
  this.name = name;
}
var obj1 = Object.create(new Person('Tom'))

Object.isExtensible(obj1)           // true
obj1.age = '12'
obj1.age                            // '12'


Object.preventExtensions(obj1)
obj1.job = 'fe' 
obj1.job                            // undefined

Person.prototype.city = 'beijing'
obj1.city                           // "beijing"

Object.seal() 和 Object.isExtensible() 区别是,seal() 方法能够将对象的所有自有属性都设置为不可配置的。

Object.freezz() 将更严格地锁定对象。将对象设置为不可扩展、将其属性设置为不可配置、将自有的所有数据属性设置为只读(如果对象的存取器属性具有 setter 方法,存取器属性不受影响)。

9.对象方法

9.1 toString()

toString() 返回一个表示调用这个方法的对象值的字符串。在需要将对象转换为字符串的时候,JavaScript 会调用这方法,比如使用 "+" 运算符连接字符串时。

9.2 valueOf()

当 JavaScript 需要将对象转换为某种原始值而非字符串的时候才会调用,尤其是转换为数字的时候。