Article 十一月 30, 2020

深入 javascript 之 原型 & 原型链

Words count 5.5k Reading time 5 mins. Read count 0

前言

js中有不少比较难以理解的概念,比如 js原型继承 。我曾经很早的时候就看过js原型方面的知识,并在当时写了一篇 博客 作为记录,很显然当时的我只是死记硬背。最近我利用空闲的时间将一些相对比较深入的js概念和用法重新学习,并新建了一个专栏 深入javascript 用于记录和分享。本篇来介绍 js原型和原型链 的基本概念:

概念

每个函数都有一个 prototype 属性,它指的是这个函数的原型对象。而每个 原型对象 都会有一个 constructor 属性,它会指向它的所有者函数。并且我们可以给 原型对象 添加任意属性。

function Person() {}

Person.prototype.name = 'foo'

console.log(Person.prototype)    // { name: "foo", constructor: ƒ Person(), ... }

在 javascript 中,对于生成 对象构造函数 (类) 通常约定为首字母大写。创建对象的方式有很多种,可以使用 new Object()Object.create() 或更简单的字面量的方式去创建一个对象。接下来我们使用构造函数的方式来创建一个对象。

创建对象

function Person(name) {
    this.name = name
    console.log(this)    // { name: "zhangsan" }
}

Person.prototype.name = 'foo'

const p = new Person('zhangsan')
console.log(p)    // { name: "zhangsan" }

从上面的代码中可以看出,构造函数中的 this 就是 实例对象 pthis 上增加的属性也会成为 实例对象 p 的基本属性。我们可以通过一张图更清晰的了解 实例对象 p

上图中 实例对象 p 除了有一个基本属性 name 外,还有一个颜色很淡的 __proto__ 属性,这个属性指向的就是 该对象的构造函数的原型属性 ,可以查看上面的代码 Person.prototype = { name: "foo", constructor: ƒ Person(), ... },也可以在浏览器中打印 p.__proto__ === Person.prototype 自行验证。

__proto__ 属性

关于 __proto__ 属性, MDN 中有这样的介绍:

警告:

Object.prototype.__proto__ 已被大多数浏览器厂商所支持的今天,其存在和确切行为仅在ECMAScript 2015规范中被标准化为传统功能,以确保Web浏览器的兼容性。为了更好的支持,建议只使用 Object.getPrototypeOf()

我们现在知道对象的 __proto__ 会指向构造函数的原型,但是这个属性在现在web标准中是已经被废弃的,官方推荐我们使用 Object.getPrototypeOf 来获取某个对象的原型。我们可以看下以下代码的打印:

p.__proto__ === Object.getPrototypeOf(p)    // true

关于上述中的为什么 __proto__ 属性颜色很淡,是因为谷歌浏览器对于对象不可枚举的属性会做一个特殊的颜色处理,我们可以使用 Object.defineProperty(obj, prop, { enumerable: false }) 给某个对象添加一个不可枚举的属性(ps:不能被 for in 遍历或被 Object.keys() 获取)。

原型链

关于原型链我也没有搜索到非常通俗的解释,也很难通过大白话将它描述的易于理解,还是通过以下的代码来解释为什么它会被称之为 原型链

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

Person.prototype.name = 'PersonName'

const p = new Person('pName')

console.log(p.name)    // pName
delete p.name
console.log(p.name)    // PersonName

从以上代码中我们可以看到,获取一个对象的属性的值,它会首先从对象的基本属性里面去找,所以第一次打印的是 "pName" 当我们删除 pname属性 后,再次打印 p.name 会发现它的值为 "PersonName",这一次它是从该对象的原型属性里去找的。这是最浅的一层原型链,下面再来一个🌰:

function Person() {}

Object.prototype.name = 'objName'
const p = new Person()
console.log(p.name)    // objName

在上面的代码中我们没有给 实例对象p 定义基本属性,也没有给它的原型对象 p.__proto__(Person.prototype) 定义属性。我们给 Object.prototype.name 赋了一个值,于是 p.name 打印出 "objName" 。这是因为 Person.prototype 本来就是一个对象,它是由 Object构造函数 创建的。

所以在获取 p.name 时会先在 实例对象 p 的基本属性中寻找,再从 Person.prototype 中去找,再从 Object.prototype 中去找,如果找到这里仍然没有找到 name属性 时,那么它会返回一个 undefined

js 规定 Object.prototype.__proto__ 的值为 null,它通常被作为原型链的终点,这里估计涉及到什么哲学问题😂,只需要记忆就好。

总结

以上,便是 js原型和原型链 了,感觉这篇博文篇幅挺多了,又感觉这么一点内容不足以将它概况😵。谈到原型和原型链就不得不扯出一个前端面试绕不开的话题——js中如何实现继承 。从上面的所有内容中大概也能看出,js是基于原型链来实现继承的。

PS:下方贴出的链接是一位大牛的博客,关于js原型这一块我是重点看他的博客的,他写的又详细又易读。关于原型链还有一条 Function 分支我这里没有介绍,推荐大家去他那里看看。

参考

王福朋 - 深入理解javascript原型和闭包

0%