# 对象

# 在变量中存放对象

  • 通过变量读取对象中的某个属性

    • 当读取的属性不存在时,会得到 undefined
    • 当读取属性的对象不存在(undefined 或 null)时,程序报错
  • 删除一个属性:delete 变量名.属性名

  • 属性表达式

    • 给属性赋值时,或读取属性时,可以使用下面的格式:对象变量["属性名"]

# 属性访问错误

查询一个不存在的属性并不会报错,而是会返回 undefined,但是如果对象不存在就会报错,因为 null 和 undefined 没有属性

在下列场景下给对象 o 设置属性 p 会失败:

  1. o 中的属性 p 是只读的:不能给只读属性重新赋值
  2. o 中的属性 p 是只读继承属性:不同覆盖只读的继承属性
  3. o 中不存在自有属性 p:如果 o 中不存在 p,并且没有 setter 方法可供调用,则 p 一定会添加到 o 中;如果 o 不是可扩展的,那么 o 中就不能定义新属性

# 创建对象

创建对象有三种方式:

  • 对象字面量
  • new 操作符
  • 通过 Object.create() 函数来创建

# 工厂模式

function createPerson(name, age, sex) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.sex = sex;
  return o;
}
var person = createPerson("a", 18, "男");

# 构造函数模式

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}
var person = new Person("a", 18, "男");

以这种方式调用构造函数会经历 4 个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

# 原型模式

每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含属性和方法,这些属性和方法可以让所有的对象实例共享

function Person() {}
Person.prototype.name = "张三";
var person1 = new Person();
var person2 = new Person();
person1.name	// "张三"
person2.name // "张三"

&

利用原型可以提取函数的公有属性

简单的原型语法

Person.prototype = {
  // 显式创建 constructor
  constructor: Person,
  name: "张三",
  age: 18
}

通过这种方式创建的原型本质上是重写了默认的 prototype 属性,因此这个原型的 constructor 属性不指向 Person 了,而是指向了 Object。如果需要用到 constructor 属性,就必须显式创建出来

注意

通过此方法重设 constructor 属性会导致它的 [[Enumerable]] 特性为 true,默认情况下,原生的 constructor 是不可枚举的。

如果要使用兼容 ES5 的 JavaScript 引擎,可以试试 Object.defineProperty()

// 重设构造函数,只适用于兼容 ES5 的浏览器
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
})

# 组合使用构造函数模式和原型模式

在构造函数中定义实例属性,原型里面定义共享的属性和方法

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}
Person.prototype = {
  job: "Web 开发工程师",
  boss: "LQ",
  play: function() {
    console.log("打球");
  }
}

# 动态原型模式

把所有信息都封装到构造函数里面,通过构造函数来初始化原型(仅在必要的情况下)

function Person(name, age, sex) {
  // 属性
  this.name = name;
  this.age = age;
  this.sex = sex;
  // 方法
  // if 只用检查其中一个必要的方法或属性即可
  if (typeof this.play != "function") {
    Person.property.play = function() {
      console.log("打球");
    };
  }
}

# 对象的特性

除了包含属性之外,每个对象还包含三个相关的对象特性:

  1. 对象的原型:指向另一个对象,本对象的属性继承自它的原型对象
  2. 对象的类:是一个标识对象类型的字符串,通过调用对象的 toString() 方法,返回 [object class]
  3. 对象的可扩展性:标明是否可以向该对象添加新属性

# 对象的拷贝

浅拷贝:以赋值的形式拷贝对象,仍指向同一个地址,修改时原对象也会收到影响。

深拷贝:完全拷贝一个新对象,修改时不会影响原对象,有以下方法:

  • var newObj = JSON.parse(JSON.stringify(obj)) 性能最快
    • 当值为函数、undefined 或 symbol 时无法拷贝
  • 递归进行逐一赋值

# 原型

每一个 JavaScript 对象都和另一个对象(原型)相关联,这个原型是实例对象创建之初就设计好的:

  • 所有通过对象字面量都具有同一个原型对象(Object.prototype)
  • 通过 new 和构造函数调用创建的对象的原型就是构造函数的 prototype 属性的值
  • Object.prototype 不继承任何属性
  • 所有的内置构造函数(以及大部分自定义函数)都具有一个继承自 Object.prototype的原型
  • 通过 Object.create() 来创建的对象使用第一个参数作为它们的原型

在 ES5 中可以将对象当作参数传入 Object.getPrototypeOf() 查询它的原型

# 理解原型对象

无论什么时候,只要创建了一个新函数,就会为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。默认情况下,prototype 是一个 Object 对象,有一个属性是 constructor,它指向构造函数本身

Person.prototype => 原型对象 => constructor => Person

最终 Person.prototype.constructor => Person

  • 隐式原型 __proto__
    • 所有的对象都有一个属性 __proto__,称之为隐式原型
    • 默认情况下,隐式原型指向创建该对象的函数的原型(prototype)
    • 当访问一个对象的成员时:
      • 先看对象自身是否拥有该成员,如果有直接使用
      • 再看对象的隐式原型是否有该成员,如果有直接使用
      • 直到原型链的末端才会停下来
    • 不能重写隐式原型中的值
      • 如果在实例中添加了一个属性,该属性与隐式原型中的一个属性同名,那我们就在实例中创建该属性,自动屏蔽隐式原型中的属性,但是不会修改隐式原型中的值
    • 通过 hasOwnProperty 方法可以检测一个属性是存在与实例中(返回 true),还是存在与隐式原型中(返回 false

# 对象混合(mixin)

// 把 obj2 混合到 obj1,产生一个新的对象
function mixin(obj1, obj2) {
  var newObj = {};
  // 复制 obj2 属性
  for (var prop in obj2) {
    newObj[prop] = obj2[prop];
  }
  // 找到 obj1 中有,obj2 中没有的属性
  for (var prop in obj1) {
    if (!(prop in obj1)) {
      newObj[prop] = obj1[prop];
    }
  }
  return newObj;
}

JS 中提供了一个方法可以混合对象:var newObj = Object.assign({}, obj1, obj2);

把 obj2 赋值给 obj1,再把 obj1 赋值给一个空对象,这个空对象现在就是 obj2 和 obj1 混合后的内容,并把它返回。

# 对象的静态方法

Object.create()

  • 创建一个新的对象,使用现有对象来提供新创建的对象的 __proto__
  • 语法:Object.create(proto[, propertiesObject])
    • proto:新创建对象的原型对象
    • propertiesObject:可选,如果没有指定为 undefined,则是要添加到新创建对象的不可枚举属性(即其自身的属性,而不是原型对象的属性)
  • 返回一个新对象,带着指定的原型对象和属性

Object.defineProperty()

  • MDN 文档

  • 在对象上直接定义一个属性,或者修改已有属性

  • 语法:Object.defineProperty(obj, prop, descriptor)

    • obj:要定义属性的对象
    • prop:属性名
    • descriptor:属性描述符
      • configurable
      • enumerable
      • 数据描述符:一种具有值的属性
        • value
        • writable
      • 存取描述符:由 getter-setter 函数对描述的属性
        • get
        • set
  • 返回这个修改过的对象

  • 默认情况下,使用 Object.defineProperty() 定义的属性值是不可修改的

Object.keys()

  • 遍历对象,返回一个数组,包含对象中可枚举的自有属性
  • 实现:
function keys (obj) {
  if (typeof obj !== 'object') throw TypeError()
  var result = []
  for (var prop in obj) {
    if (obj.hasOwnProperty) result.push(prop)
  }
  return result
}
更新: 3/28/2020, 9:40:55 AM