JavaScript中的对象原型介绍。

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

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

在此感谢https://www.cnblogs.com/gulei/p/6733707.html这篇文章给予我的启迪

(这篇文章解释的很棒_(:з」∠)_)。

在JavaScript中,每个函数都有一个特殊的属性叫做原型(prototype),如下图示例:

function doSomething(){}
console.log( doSomething.prototype );
// It does not matter how you declare the function, a
//  function in javascript will always have a default
//  prototype property.
var doSomething = function(){}; 
console.log( doSomething.prototype );//原型为:Object()

//会出现以下结果:

{
    constructor: ƒ doSomething(),
    __proto__: {                             
        constructor: ƒ Object(),
        hasOwnProperty: ƒ hasOwnProperty(),
        isPrototypeOf: ƒ isPrototypeOf(),
        propertyIsEnumerable: ƒ propertyIsEnumerable(),
        toLocaleString: ƒ toLocaleString(),
        toString: ƒ toString(),
        valueOf: ƒ valueOf()
    }
}

//也可以添加一些属性
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );

//结果如下:

{
    foo: "bar",
    constructor: ƒ doSomething(),
    __proto__: {……}
}

如果你new了一个对象,创建一个doSomething的实例,也就是如下图所示:

var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log(doSomeInstancing);

//会出现以下结果:

{
    prop: "some value",
    __proto__: {
        foo: "bar",
        constructor: ƒ doSomething(),
        __proto__: {
            constructor: ƒ Object(),
            hasOwnProperty: ƒ hasOwnProperty(),
            isPrototypeOf: ƒ isPrototypeOf(),
            propertyIsEnumerable: ƒ propertyIsEnumerable(),
            toLocaleString: ƒ toLocaleString(),
            toString: ƒ toString(),
            valueOf: ƒ valueOf()
        }
    }
}

注释:当你访问doSomeInstancing的一个属性的时候,浏览器会先在doSomeInstancing中是否有这个属性。如果没有,就会在doSomething的__proto__中(即:doSomething.prototype)寻找这个属性。如果没有,就会继续再往上找,直到找到或者没找到返回undefined。

//一个很不正经的理解:我个人是觉得原型对象和构造器函数应该是:原型对象拥有一个构造函数,构造函数用来创建对象并且赋予对象私有的能力,而原型对象给这些对象提供了一些共有的能力(这个能力其实还是由原型对象来代劳)。

prototype 属性

我们创建的对象并不能完全继承Object();因为继承的属性和方法是定义在prototype属性上的,即那些以 Object.prototype. 开头的属性,而非仅仅以 Object. 开头的属性。 于是 Object.prototype.watch()、Object.prototype.valueOf() 等等成员,适用于任何继承自 Object() 的对象类型。而Object.is()Object.keys(),以及其他不在 prototype 对象内的成员,不会被“对象实例”或“继承自 Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。

var person2 = Object.create(person1);
//这个person2是以person1为原型对象创建的,所以person2.__proto__会返回person1
constructor 属性

每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。

person1.constructor   //Person()

可以在 constructor 属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new 关键字,便能将此函数作为构造器使用。

//如:
var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);

//和以下代码一样:
var person3 = new Person('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);

此外,constructor 属性还有其他用途。想要获得某个对象实例的构造器的名字可以这么用:

person1.constructor.name
修改原型

我们可以通过构造函数来修改构造器的 prototype 属性 。

比如:

//我们用Person()构造函数为prototype添加了一个farewell方法
Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');
}
//用我们创建的对象来调用,会发现可以运行
person1.farewell();
//整条继承链动态地更新了,任何由此构造器创建的对象实例都自动获得了这个方法。

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

Note:你可能会在 prototype 上定义常属性 (constant property) (指那些你永远无需改变的属性),但一般来说,在构造器内定义属性更好。

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

即如下代码所示:

// 构造器及其属性定义

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

// 定义第一个方法

Test.prototype.x = function () { ... }

// 定义第二个方法

Test.prototype.y = function () { ... }

// 等等……

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

js学习笔记-继承 上一篇
js学习笔记-对象 下一篇