JavaScript中的原型与原型链

JavaScript在某种程度上算是一门简单的语言,因为不包含复杂的继承机制,那么JavaScript是怎么实现共用属性的呢?答案就是原型链。

堆中的对象与垃圾回收

堆中的对象

众所周知,对象是存在堆中的,JavaScript中除基本类型以外都是对象,其中也包含了数组与函数,所以构造函数也是作为一个函数对象存储在堆中的,而构造函数的变量只是一个引用被存放在window或者global变量对象中,所以我们可以直接使用Object、Boolean、String等构造函数。我们自己new的对象也是存在于堆中的。

1
2
3
4
let a = {name: 'a'}
let b = a
b.name = 'b'
a.name // 'a'

修改b的name时a的name也会被修改,这个例子也可以充分的说明,当我们在操作对象时只是在操作一个对象的引用,如果想要复制一个变量就需要涉及到深拷贝了。

垃圾回收

垃圾回收(Garbage Collection),简称GC,直译为垃圾收集,很容易将其理解为将用不到的内存收集起来,而实际是相反的,他是将有用的内存做标记,因为当出现循环引用时会引用计数不会将其判断为垃圾,这将会导致内存泄漏,Python中就是使用的这种GC机制,需要弱引用的机制来解决此问题,而JavaScript类似Java中的GC机制,不存在此问题,我们只需要知道当一个堆内对象失去引用的时候,GC引擎就会在适当的时候清除掉。

原型(Prototype)

JavaScript中没有复杂的继承机制,然而当我们新建一个空对象时他是怎么存在对象自带的函数的呢?我们只需要在控制台中打印出来就看到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let a = {}
a
// {}
// __proto__:
// constructor: ƒ Object()
// hasOwnProperty: ƒ hasOwnProperty()
// isPrototypeOf: ƒ isPrototypeOf()
// propertyIsEnumerable: ƒ propertyIsEnumerable()
// toLocaleString: ƒ toLocaleString()
// toString: ƒ toString()
// valueOf: ƒ valueOf()
// __defineGetter__: ƒ __defineGetter__()
// __defineSetter__: ƒ __defineSetter__()
// __lookupGetter__: ƒ __lookupGetter__()
// __lookupSetter__: ƒ __lookupSetter__()
// get __proto__: ƒ __proto__()
// set __proto__: ƒ __proto__()

我们可以看到所有的函数都被存在了一个名为proto的引用中,这就是JavaScript中提取共用属性的方法,我们将他称为原型(Prototype),既然对象的原型是一个对象他就必然要存在堆内存中,如果没有对象引用就必然会被GC回收,为了防止被GC回收,对象的原型在引擎进行初始化时默认被添加在了构造函数的prototype属性上。

1
({}).__proto__ === Object.prototype // true

构造函数作为一个函数对象,它的proto指向Function.prototype

1
2
Obejct.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true

Object作为根节点比较特殊,它的prototype.proto为空

1
Object.prototype.__proto__ === null // true

原型链(Prototype Chain)

原型解决了属性共用的问题,我们在每个对象中都可以使用Object的属性,如果我们需要对共用属性进行扩展但又不想影响Object的原型怎么办呢?当我们打印出一个Boolean对象时就一切真相大白了。

1
2
3
4
5
6
7
8
9
new Boolean
// Boolean {false}
// __proto__: Boolean
// constructor: ƒ Boolean()
// toString: ƒ toString()
// valueOf: ƒ valueOf()
// __proto__: Object
// [[PrimitiveValue]]: false
// [[PrimitiveValue]]: false

可见一个Boolean对象的proto是Boolean原型,而Boolean原型的proto为Object原型,这就构成了一个链式结构,我们将其称为原型链。

1
2
false.__proto__ === Boolean.prototype
false.__proto__.__proto__ === Object.prototype

::: tip 提示
上例中我们直接调用了基本类型的属性,基本类型在JavaScript中虽然不是对象,但是当进行对象操作时,会在堆中申请一个临时对象,此时就可以使用对应的属性了,但此临时对象在调用完后就会被清除,所以我们对其进行赋值操作是不生效的。
:::
当我们调用对象的一个属性或方法时,如果该对象没有就会像上查找其原型,如果直到Object原型都没有就会返回undefined。

new操作发生了什么

当我们进行new一个对象的时候,发生了以下事情:

  1. 申请一个新的空对象
  2. 将新对象的proto指向构造函数的prototype
  3. 对新对象执行构造函数
  4. return 新对象

我们可以用一个函数来模拟这个过程,此方法可以构造对象,但是基础数据类型的实例使用此方法构造会出现数据类型不符的问题。

1
2
3
4
5
6
function _new (constructor,...arg) {
let res = {}
res.__proto__ = constructor.prototype
constructor.apply(res, arg)
return res
}


本文链接:JavaScript中的原型与原型链
版权声明:本文章采用CC BY-NC-SA 3.0 CN许可协议进行许可。转载请注明出处!