先看以下代码
function Test() {}
var test = new Test()
console.log(test.name) // undefined
Object.prototype.name = 'hello'
console.log(test.name) // hello
在这个例子中,先定义Test函数,再创建一个test实例,然后打印test.name,意料之中的打印undefined,但在赋值给Object.prototype.name后,再次打印test.name,显示为hello。
这是为什么呢?要想知道其内部机制,我们要深入理解原型和原型链等知识,下面进入正题:
prototype原型
函数可以有属性,每个函数都有一个特殊的属性叫做原型(prototype),如下所示
function Test() {}
console.log(Test.prototype)
放在控制台打印,会出现以下内容
可以看出prototype有两个属性:contructor和__proto__ . 这两个属性后续会继续说,那我们尝试给prototype加一下属性
function Test() {}
Test.prototype.name = "Test"
console.log(Test.prototype)
放在控制台打印,会出现以下内容
可以看出,prototype已经有了一个名为name的属性。
那么这个prototype有什么作用呢?
实际上,我们把prototype称为原型对象,对象以原型对象为模板,从原型对象那继承方法和属性,这样说有点抽象,我们看以下例子
function Test() {}
var test = new Test()
Test.prototype.name = "Test"
console.log(test.name)
放在控制台打印,会出现以下内容
可以看出,在原型对象定义的属性,通过new生成的实例对象test也拥有,我们来打印一下实例对象test,得到
可以看出,实例对象test有一个名为__proto__的属性,而里面保存着我们给原型对象定义的name属性。
那么__proto__是什么?和prototype的关系是什么?
__proto__
我们发现,test.__proto__
和 Test.prototype 的内容很相似,我们来验证一下
显而易见,test.__proto__
和 Test.prototype 都指向同一个对象,他们是相同的,这也解析了,在我们访问test.name时,会先判断test是否有name属性,如果没有,就去test.__proto__
去寻找name属性。
除了__proto__属性,我们发现test对象还有一个constructor属性,这是什么来的呢?
constructor属性
我们来看看constructor属性都有哪些内容
可以发现,constructor属性的内容很像Test函数,实际上,每个实例对象test都会从原型中继承一个constructor属性,该属性指向了用于构造此实例对象的构造函数,也就Test。
既然test.constructor是指向构造函数Test,那意味这我们也可通过constructor属性来实例化一个新的对象。
var test2 = new test.constructor()
正常情况下我们不会这样使用,除非因为某些原因没有原始构造器的引用,也就是没有了Test的引用。
原型链
以上我们理解了prototype、__proto__
和 constructor, 那么他们的关系是如何的,我画了一个图便于理解:
打印实例test同样可以验证图上显示的流程
可以知道的是,实例test从原型对象Test.prototype继承方法和属性,原型对象同样可能有原型,Test.prototype从Object.prototype继承方法和属性。这样一层一层类推,我们把这种关系称为原型链。
原型继承的范围
我们知道Object对象拥有许多方法,比如Object.key(),Object.is() 等等,但是我们发现实例对象test继承的方法并没有这些,这是为什么呢?
原因在于,定义在prototype属性上的方法或属性才会被继承,也就是说test实例只会继承Object.prototype上的方法,而不会继承Object上的方法。
这看起来有点费解,构造器本身是一个函数,怎么可以给函数添加方法呢,实际上函数也是一个对象类型,可参考 function
因此我们给Test函数添加属性或方法,test实例是不会继承的,如
function Test() {}
var test = new Test()
Test.attr = '属性'
console.log(Test.attr) // 属性
console.log(test.attr) // undefined
总结
以上就是我对js原型和原型链的理解,深入分析了prototype、__proto__
、constructor之间的关系,也分析了原型继承的范围。如果觉得对你有帮助,请帮忙点个赞+评论+收藏。
最后留下一道题给大家:
// 希望实现一个Number类型的加法,期待num为3
var num = 1
num.add(2)
console.log(num)