前言
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
就是 实例对象 p ,this
上增加的属性也会成为 实例对象 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"
当我们删除 p
的 name属性
后,再次打印 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
分支我这里没有介绍,推荐大家去他那里看看。
参考
更新于2021-09-26
谷歌浏览器现在更新,__proto__
看不到了,但是仍然可以取值。