JS原型&原型链

JS原型&原型链

[TOC]

原型和原型链是js中的重点和难点。想要弄清楚原型和原型链

  • 首先,必须要搞清楚,__proto__、prototype、 constructor这几个属性
  • 其次,要知道js中对象和函数的关系,[函数其实是对象的一种]。
  • 最后,要知道函数、构造函数的区别,[ 任何函数都可以作为构造函数,但是并不能将任意函数叫做构造函数,只有当一个函数通过new关键字调用的时候才可以成为构造函数。] 如:
  • 1
    2
    var Parent = fvar Parent = function(){} //定义一个函数,那它只是一个普通的函数,
    var p1 = new Parent();//这时这个Parent就不是普通的函数了,它现在是一个构造函数。因为通过new关键字调用了它创建了一个Parent构造函数的实例

__proto__、prototype、 constructor

  • 属性__proto__、 constructor属性是对象所独有的;
  • 属性prototype属性是函数独有的;

js中函数也是对象的一种,那么函数同样也有属性__proto__、 constructor;

prototype属性

prototype属性可以看成是一块特殊的存储空间,存储了子类对象使用的公共方法和属性。

prototype设计之初就是为了实现继承,让由特定函数创建的所有实例共享属性和方法,也可以说是让某一个构造函数实例化的所有对象可以找到公共的方法和属性。有了prototype我们不需要为每一个实例创建重复的属性方法,而是将属性方法创建在构造函数的原型对象上(prototype)。那些不需要共享的才创建在构造函数中。

继续引用上面的代码,当我们想为通过Parent实例化的所有实例添加一个共享的属性时,

1
Parent.prototype.name = "我是原型属性,所有实例都可以读取到我";

__proto__属性

__proto__属性是对象(包括函数)独有的。

proto属性是从一个对象指向另一个对象,即从一个对象指向该对象的原型对象(也可以理解为父对象)。****它的含义就是告诉我们一个对象的原型对象是谁

Parent.prototype上添加的属性和方法叫做原型属性和原型方法该构造函数的实例都可以访问调用.

那这个构造函数的原型对象上的属性和方法,怎么能和构造函数的实例联系在一起呢?
就是通过__proto__属性。每个对象都有__proto__属性,该属性指向的就是该对象的原型对象。

代码块

1
2
JavaScript
console.log(s.__proto__ === Foo.prototype) //true

__proto__通常称为隐式原型,prototype通常称为显式原型,
那我们可以说一个对象的隐式原型指向了该对象的构造函数的显式原型。
那么我们在显式原型上定义的属性方法,通过隐式原型传递给了构造函数的实例。
这样一来实例就能很容易的访问到构造函数原型上的方法和属性了。

我们之前也说过__proto__属性是对象(包括函数)独有的,
那么Parent.prototype 也是对象,那它有隐式原型么?又指向谁****

代码块

1
2
JavaScript
Foo.prototype.__proto__ === Object.prototype; //true

可以看到,构造函数的原型对象上的隐式原型对象指向了Object的原型对象。
那么Parent的原型对象就继承了Object的原型对象。
由此我们可以验证一个结论,万物继承自Object.prototype
。这也就是为什么我们可以实例化一个对象,并且可以调用该对象上没有的属性和方法了。如:

1
Foo.prototype.__proto__ === Object.prototype; //true

我们可以调用很多我们没有定义的方法,这些方法是哪来的呢?现在引出原型链的概念,
当我们调用p1.toString()的时候,先在p1对象本身寻找,
没有找到则通过p1.__proto__找到了原型对象Parent.prototype ,
也没有找到,又通过Parent.prototype.__proto__找到了上一层原型对象Object.prototype。
在这一层找到了toString方法。返回该方法供p1使用。
当然如果找到Object.prototype上也没找到,就在Object.prototype.__proto__中寻找,
但是Object.prototype.proto === null所以就返回undefined。
这就是为什么当访问对象中一个不存在的属性时,返回undefined了。

constructor 属性

constructor是对象才有的属性,

1
2
constructor属性是让“徒弟”、“徒孙” 们知道是谁创造了自己,这里可不是“师父”啊
而是自己的父母,父母创造了自己,父母又是由上一辈人创造的,……追溯到头就是Function() 【女娲】。

constructor是对象才有的属性,它是从一个对象指向一个函数的。指向的函数就是该对象的构造函数。
每个对象都有构造函数,好比我们上面的代码p1就是一个对象,那p1的构造函数是谁呢?我们打印一下。

1
console.log(p1.constructor); // ƒ Parent(){}

通过输出结果看到,很显然是Parent函数。我们有说过函数也是对象,那Parent函数是不是也有构造函数呢?显然是有的。再次打印下。

1
console.log(Parent.constructor); // ƒ Function() { [native code] }

通过输出看到Parent函数的构造函数是Function(),
这点也不奇怪,因为我们每次定义函数其实都是调用了new Function(),下面两种效果是一样的。

那么我们再回来看下,再次打印Function.constructor

1
console.log(Function.constructor); // ƒ Function() { [native code] }

可以看到Function函数的构造函数就是本身了,那我们也就可以说Function是所有函数的根构造函数。

到这里我们已经对constructor属性有了一个初步的认识,它的作用是从一个对象指向一个函数,
这个函数就是该对象的构造函数。通过栗子我们可以看到,p1的constructor属性指向了Parent,
那么Parent就是p1的构造函数。同样Parent的constructor属性指向了Function,
那么Function就是Parent的构造函数,然后又验证了Function就是根构造函数。

1
2
dsffd

class A {
@Reflect.metadata(‘name’, ‘hello’)
hello() {}
}
}

const t1 = new A()
const t2 = new A()
// 给 t2.hello 新增元数据内容 otherName -> world
// 此时会新增到 t2.prototype(原型链) 上(注意不是 A.prototype 上)
Reflect.defineMetadata(‘otherName’, ‘world’, t2, ‘hello’);
// 首先,t1/t2 自己能获取原本的原型链 name 对应的元数据
Reflect.getMetadata(‘name’, t1, ‘hello’) // ‘hello’
Reflect.getMetadata(‘name’, t2, ‘hello’) // ‘hello’
// t1 自身上没定义 otherName 的元数据
Reflect.getMetadata(‘otherName’, t1, ‘hello’) // undefined
// t2 自身定义了 otherName 的元数据
Reflect.getMetadata(‘otherName’, t2, ‘hello’) // ‘world’
// t2 自身上只有 otherName 这个元数据,没有 name 元数据(该元数据来自父级)
Reflect.getOwnMetadata(‘name’, t2, ‘hello’) // undefined
Reflect.getOwnMetadata(‘otherName’, t2, ‘hello’) // ‘world’

1
2
3



// 类本身 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example);
// 类的静态属性 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example, “staticProperty”);
// 类的实例属性 上是否存在元数据,注意是在 prototype 上
result = Reflect.hasMetadata(“custom:annotation”, options, Example.prototype, “property”);
// 类的静态方法 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example, “staticMethod”);
// 类的实例方法 上是否存在元数据,注意是在 prototype 上
result = Reflect.hasMetadata(“custom:annotation”, options, Example.prototype, “method”);

// 类本身 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example);
// 类的静态属性 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example, “staticProperty”);
// 类的实例属性 上是否存在元数据,注意是在 prototype 上
result = Reflect.hasMetadata(“custom:annotation”, options, Example.prototype, “property”);
// 类的静态方法 上是否存在元数据
result = Reflect.hasMetadata(“custom:annotation”, options, Example, “staticMethod”);
// 类的实例方法 上是否存在元数据,注意是在 prototype 上
result = Reflect.hasMetadata(“custom:annotation”, options, Example.prototype, “method”);

const res1 = Reflect.deleteMetadata(type, DeleteMetadata)
const res2 = Reflect.deleteMetadata(type, DeleteMetadata, ‘staticMethod’)
const res3 = Reflect.deleteMetadata(type, DeleteMetadata)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

1. 在JavaScript中,每个**构造函数**都有一个`prototype`属性,这个属性指向一个对象,这个对象包含了所有实例共享的属性和方法。通过原型,可以实现方法的共享,从而节省内存空间。
2. 当使用`new`关键字调用构造函数时,新创建的对象会从构造函数的`prototype`对象继承属性和方法。这意味着所有通过同一个构造函数创建的对象共享同一个原型对象。例如,使用`new Person()`创建的两个对象会共享相同的原型对象中的方法和属性。
3. 每个对象都有一个内部属性`[[Prototype]]`(在代码中通过`__proto__`访问),它指向创建该对象的构造函数的`prototype`对象。如果当前对象的属性或方法不存在,则会沿着原型链向上查找,直到找到为止。原型链的终点是`Object.prototype`,所有对象最终都会继承自`Object.prototype`。



1,**原型是一个对象,为其他对象提供共享属性的对象,又称原型对象**。可以说原型不是一个固定的对象,它只是承担了某种职责。当某个对象,承担了为其他对象提供共享属性的职责时,它就成了该对象的原型。换而言之,不同对象的原型可能都是不一样的。

2,**几乎所有对象**在创建时都会被赋予一个非空的值作为原型对象的引用,来实现共享数据的效果。

3,在不同的对象上原型存放的方式也有所差别

- **函数**(function):函数是一种特殊的对象,函数的原型存放在**prototype**属性上
- **对象**(Object): 普通对象的原型是存放到内置属性**[[Prototype]]**上,可以通过对象的**__proto__**来访问对象的原型。
- **数组**(Array): 数组也是一种特殊的对象,但与函数不同的是它的原型和普通对象一样,也是存放到内置属性**[[Prototype]]**上,可以通过数组的**__proto__**来访问数组的原型。