原型(prototype和[[prototype]])

94 阅读5分钟

无论何时,只要你创建一个函数,就会默认生成一个属性,也就是原型(prototype)。

一、区分prototype和[[prototype]]

当创建一个函数时,会默认生成一个prototype属性,这里要注意是函数而不是对象,对象是没有默认的prototype属性的,但无论是函数还是对象都有[[prototype]]属性。

1.prototype和[[prototype]]的作用(这里简单介绍一下,会在下面详细讲解)

[[Prototype]]:是一个对象的内部属性,它指向该对象的原型。

prototype:该属性是一个对象,用于存储实例化对象所共享的属性和方法。

2.如何访问prototype和[[prototype]]

-注意:访问prototype属性时,我们可以通过函数.prototype就可以访问,在访问[[prototype]]属性时,Firefox、Safair和Chrome中可以使用__proto__访问,虽然得到了一些浏览器支持,目前都被广泛使用,但他并不是js规范的一部分(举例:obj.__proto__)。从 ECMAScript 6(ES2015)开始,Object.getPrototypeOf 方法被引入,作为一种更标准、更可靠的方式来获取对象的原型。(举例:Object.getPrototypeOf(obj)

以下是一个访问prototype和[[prototype]]的例子

function Person() {

}
let obj = {}

Person.prototype  // {constructor: ƒ} prototype下有一个默认属性constructor
obj.prototype   //undefined  obj下没有prototype这个属性

Object.getPrototypeOf(Person) //Function.prototype:functionƒ () { [native code] } 
Object.getPrototypeOf(obj)    //Object.prototype: {constructor: ƒ, __defineGetter__: ƒ, …} 

二、[[prototype]]

1.起因:

如果让你设计一个语言,当我想让所有的数组都有length这个方法时,你应该怎么实现呢?原理无非是写一个公共的方法,让我定义的数组变量可以默认访问这个方法,现在的困难是,我将如何让每一个定义的数组都带上这个方法呢,在js中就是通过[[prototype]]来实现的。

2.下面看一下调用a.length的过程:

2.1 首先定义一个数组变量a

let a = [] //此时如前面提到的,a是一个对象(数组是特殊的对象),所以a有一个默认的属性[[prototype]]。

2.2 [[prototype]]/Object.getPrototypeOf(a)

这里有人可能对Object.getPrototypeOf(a)指向哪里好奇,但由于接下来才讲解prototype,如果第一次了解原型,这里可以简单理解为指向了Array这个对象,但实际上指向的是Array.prototype这个属性上,这里的Array就是一个构造函数。当我们用字面量创建一个数组变量时(let a = [])其实就是在new Array。

提示:构造函数和普通函数没有区别,只要通过new来调用的函数就被称为构造函数,在js中构造函数既没有首字母大写约束,在使用new来调用时()也不是强制要求的(new Person和new Person()一个效果)

2.3 new 发生了什么(之前说了let a =[] 的字面量创建等同于 let a = new Array)
  1. let a 在内存中创建了一个对象a
  2. 将a内部的[[prototype]]指向构造函数的prototype 也就是Array.prototype (Object.getPrototypeOf(a)===Array.prototype)
  3. 更改this为这个对象a
  4. 执行构造函数代码(为a新增属性)
  5. 看构造函数有没有return,如果构造函数返回非空对象,则返回该对象,否则返回刚创建的新对象。
2.4 a.length

当调用a.length时,由于a是空的数组,他只有一个默认的属性也就是[[prototype]],引擎会在a中查找有没有length这个属性,没有的话会寻着[[prototype]]这个属性继续寻找,由于之前说过[[prototype]]指向了Array.prototype这个对象,在Array.prototype中我们定义了数组常用方法,所以在Array.prototype里找到了length这个方法,开始调用。

这里我们对[[prototype]]有了简单的了解,当我们定义变量时(如果该变量是基本类型,在调用方法时,JavaScript 会将该基本类型的值自动封装为相应的包装对象(也称为类)),这样我们就可以设置一些基本属性和方法,然后让变量通过[[prototype]]属性来访问了。

3.[[prototype]]尽头

[[prototype]]的尽头是Object.prototype,所有对象都源于Object.prototype,所以你可以定义任何变量来使用Object.prototype上的方法。

三、prototype

无论何时,只要你创建一个函数,就会默认生成一个属性,也就是原型(prototype
起因:函数是定义在堆里的,我们可以将函数名等效于地址名,如果功能一致,只是传参不同时,我们就没必要多次创建相同的方法。

看一下以下的例子:

function Person(name,age) {
    this.name = name 
    this.age = age
    this.walk = name => console.log(name+"学会走路了")
}

let Ann = new Person
let Joh = new Person
Ann.walk("Ann")
Joh.walk("Joh")

上面代码的实例都可以调用walk方法,但存在一个问题:构造函数的方法会在每一个实例上都创造一遍,但方法一致,只是传参不同,那就没必要多次开辟内存,完全可以共享walk方法,所以原型被创造了出来。

prototype示例:

这里我们将walk方法移动到了funciton外,并且把它添加到了prototype这个对象的属性上,之前说过new的过程,在第二步,我们将变量的[[prototype]]指向构造函数的prototype,也就是Person.prototype上,此时Ann.walk和Jon.walk都指向同一个构造函数的prototype上。

function Person(name,age) {
    this.name = name 
    this.age = age
}

Person.prototype.walk = name => console.log(name+"学会走路了")

let Ann = new Person
let Joh = new Person
Ann.walk("Ann")
Joh.walk("Joh")

应用:这里我们了解了prototype的基本用法,说一下开发可以用到的场景,例如我们需要将Date日期转化为(xxxx年-xx月-xx日),我们就可以自定义一个Date.prototype.format,这样所有的Date都有你自定义的方法了,但这样并不是最优解,最好不要直接改变原始包装类,我们可以继承Date,然后通过new你继承的类来达到同等效果。

欢迎大家留言讨论,如果这篇文章帮到你了,点赞支持一下,谢谢。