对象原型

对象原型

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。

在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是proto属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

注意

  • 理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的proto属性获得) 每个实例上都有的属性
  • 与构造函数的prototype属性之间的区别是很重要的。构造函数的属性
  • 也就是说,Object.getPrototypeOf(new Foobar())和Foobar.prototype指向着同一个对象。

理解原型对象

1
2
3
4
5
6
7
8
//定义一个构造器函数:
function Person(first, last, age, gender, interests) {

this.test=function(){console.log(1)}

};
//创建一个对象实例
let person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

实例图解

  • 在图中你可以看到定义在 person1 的原型对象、即 Person() 构造器中的成员—— name、age、gender、interests、bio、greeting。
  • 同时也有一些其他成员—— watch、valueOf 等等——这些成员定义在 Person() 构造器的原型对象、即 Object 之上。

注意:必须重申,原型链中的方法和属性没有被复制到其他对象——它们被访问需要通过前面所说的“原型链”的方式。
注意:没有官方的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 [[prototype]] 表示(参见 ECMAScript)。然而,大多数现代浏览器还是提供了一个名为 proto (前后各有2个下划线)的属性,其包含了对象的原型。你可以尝试输入 person1.proto 和 person1.proto.proto,看看代码中的原型链是什么样的!

prototype属性:继承成员被定义的地方

可继承的

  • 继承的属性和方法是定义在 prototype 属性之上的,prototype 属性的值是一个对象,我们希望被原型链下游的对象继承的属性和方法,都被储存在其中。
  • 于是 Object.prototype.watch()、Object.prototype.valueOf() 等等成员,适用于任何继承自 Object() 的对象类型,包括使用构造器创建的新的对象实例。

    不可继承的

  • Object.is()、Object.keys(),以及其他不在 prototype 对象内的成员,不会被“对象实例”或“继承自 Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。

    注意:这看起来很奇怪——构造器本身就是函数,你怎么可能在构造器这个函数中定义一个方法呢?其实函数也是一个对象类型,你可以查阅 Function()https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function 构造器的参考文档以确认这一点。

    重要:prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,this 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用 proto 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。

修改原型

1
2
3
4
5
6
7
8
9
10
11
12
function Person(first, last, age, gender, interests) {

// 属性与方法定义

};

let person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);

Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
//整条继承链动态地更新了,任何由此构造器创建的对象实例都自动获得了这个方法
}

上方代码说明

farewell()方法仍然可用于person1对象实例——旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

总结

事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 构造器及其属性定义

function Test(a,b,c,d) {
// 属性定义
}

// 定义第一个方法

Test.prototype.x = function () {}

// 定义第二个方法

Test.prototype.y = function () {}

// 等等……

参考文章https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes