Javascript关于对象的理解

q-jlv / 2023-08-22 / 原文

对象的概念

  对象是一个包含数据和方法的集合。

  下面,我们通过实例探索对象。 首先,创建一个对象

var person = {};

  如果在控制台输入person,将会得到 

[object Object]

 这时,我们就创建了一个空的对象,接着,我们需要向空对象中添加数据或方法

var person = {
  name: ["Bob", "Smith"],
  age: 32,
  gender: "male",
  interests: ["music", "skiing"],
  bio: function () {
    alert(
      this.name[0] +
        " " +
        this.name[1] +
        " is " +
        this.age +
        " years old. He likes " +
        this.interests[0] +
        " and " +
        this.interests[1] +
        ".",
    );
  },
  greeting: function () {
    alert("Hi! I'm " + this.name[0] + ".");
  },
};

现在对象里有了数据和功能,可以对数据和方法进行访问,有点式和方括号两种方式

person.name[0];
person.age;
person.interests[1];
person.bio();
person.greeting();
person["eyes"] = "hazel";

这种方式创建的对象被称为对象的字面量---通过手动写出对象内容来创建,不同于从类实例化一个对象

 This的含义

指向了当前代码运行时的对象

对象原型

 原型链

浏览器中创建一个对象:

const myObject = {
  city: "Madrid",
  greet() {
    console.log(`来自 ${this.city} 的问候`);
  },
};
myObject.greet(); // 来自 Madrid 的问候

查看它的所有属性,你会得到

__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOf

  JavaScript 中所有的对象都有一个内置属性,称为它的 prototype(原型)。它本身是一个对象,故原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null 作为其原型的对象上。(备注: 指向对象原型的属性并不是 prototype。它的名字不是标准的,但实际上所有浏览器都使用 __proto__。访问对象原型的标准方法是 Object.getPrototypeOf()。)

  当你试图访问一个对象的属性时:如果在对象本身中找不到该属性,就会在原型中搜索该属性。如果仍然找不到该属性,那么就搜索原型的原型,以此类推,直到找到该属性,或者到达链的末端,在这种情况下,返回 undefined

例如,在调用 myObject.toString() 时,浏览器会做这些事情:

  • 在 myObject 中寻找 toString 属性
  • myObject 中找不到 toString 属性,故在 myObject 的原型对象中寻找 toString
  • 其原型对象拥有这个属性,然后调用它。

  有个对象叫 Object.prototype,它是最基础的原型,所有对象默认都拥有它。Object.prototype 的原型是 null,所以它位于原型链的终点:

 

const myDate = new Date();
let object = myDate;

do {
  object = Object.getPrototypeOf(object);
  console.log(object);
} while (object);

// Date.prototype
// Object { }
// null

  上面这段代码创建了Date对象,然后遍历原型链,记录并输出了原型。从中我们知道 myDate 的原型是 Date.prototype 对象,Date.prototype)的原型是 Object.prototype

 属性遮蔽

在上面的例子,当我们调用getYear()时, 浏览器首先会在myDate中找这个属性,如果找到就会直接调用,myDate里找不到才会到它的原型对象里去找,因此在子代对象定义了和父代相同的函数名,会直接调用子代函数,这个叫做“遮蔽”。

设置原型

设置对象原型的方法:Object.creat() 和构造函数。

Object.create 方法创建一个新的对象,并允许你指定一个将被用作新对象原型的对象。

const personPrototype = {
  greet() {
    console.log("hello!");
  },
};

const carl = Object.create(personPrototype);
carl.greet(); // hello!

  这里我们创建了一个 personPrototype 对象,它有一个 greet() 方法。然后我们使用 Object.create() 来创建一个以 personPrototype 为原型的新对象。现在我们可以在新对象上调用 greet(),而原型提供了它的实现。

  在 JavaScript 中,所有的函数都有一个名为 prototype 的属性。当你调用一个函数作为构造函数时,这个属性被设置为新构造对象的原型(按照惯例,在名为 __proto__ 的属性中)。

  如果我们设置一个构造函数的 prototype,我们可以确保所有用该构造函数创建的对象都被赋予该原型。

const personPrototype = {
  greet() {
    console.log(`你好,我的名字是 ${this.name}!`);
  },
};

function Person(name) {
  this.name = name;
}

Object.assign(Person.prototype, personPrototype);
//
// Person.prototype.greet = personPrototype.greet;

这里我们:

  • 创建了一个 personPrototype 对象,它具有 greet() 方法
  • 创建了一个 Person() 构造函数,它初始化了要创建人物对象的名字

然后我们使用 Object.assign 将 personPrototype 中定义的方法绑定到 Person 函数的 prototype 属性上。

在这段代码之后,使用 Person() 创建的对象将获得 Person.prototype 作为其原型,其中自动包含 greet 方法。

自有属性

我们使用上面的 Person 构造函数创建的对象有两个属性:

  • name 属性,在构造函数中设置,在 Person 对象中可以直接看到
  • greet() 方法,在原型中设置

我们经常看到这种模式,即方法是在原型上定义的,但数据属性是在构造函数中定义的。这是因为方法通常对我们创建的每个对象都是一样的,而我们通常希望每个对象的数据属性都有自己的值(就像这里每个人都有不同的名字)。

直接在对象中定义的属性,如这里的name,被称为自有属性,你可以使用静态方法 Object.hasOwn() 检查一个属性是否是自有属性。

const irma = new Person("Irma");

console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // false

  原型与继承

  原型使得重用代码和组合对象成为可能。

  特别是它们支持某种意义的继承。继承是面向对象的编程语言的一个特点,它让程序员表达这样的想法:系统中的一些对象是其他对象的更专门的版本。