在《谈谈JavaScript中的function constructor和new关键字》这篇文章中我们说明了如何通过函数构造式(function constructor)搭配关键字new来建立对象,但其实这样只讲了一半,在这篇我们会补齐另一半,说明function constructor如何用来设定该对象的原型(prototype)。
在JavaScript中的函数也是一种对象,其中包含一些属性像是该函数的名称(Name
)和该函数的内容(Code
),但其实function这里面还有一个属性,这个属性就是prototype
,这个属性会以空对象的型式呈现。
除非你是把function当做function constructor来使用,否则这个属性就没有特别的用途;但如果你是把它当做function constructor,通过new
这个关键字来执行这个function的话,它就有特别的意义了。
要进入这个function的prototype
属性只要直接通过 .prototype
就可以了。
然而,有一点很容易令人困惑的地方,我们会以为如果我使用 .prototype
时,就可以直接进入该函数的原型,但实际上并不是这样的!
函数当中prototype
这个属性并不是这个函数的prototype
,它指的是所有通过这个function constructor所建立出来的对象的prototype
,听起来有点混乱吧...没关系,让我们来看一些代码来帮助我们理解这一概念。
说明函数中的prototype 属性
1.function 中的prototype 属性一开始是空对象
我们先执行上篇文章最后所写的代码:
function Person ( firstName , lastName ) {
this . firstName = firstName ;
this . lastName = lastName ;
}
var person1 = new Person ( 'Jay' , 'chou' ) ;
console . log ( person1 ) ;
var person2 = new Person ( 'Jane' , 'chou' ) ;
console . log ( person2 ) ;
复制代码
到Google Chrome的console视窗中,我们输入 Person.prototype
得到的结果会得到一个空对象,如下图:
2.通过function constructor 所建立的对象会继承该function 中prototype 的内容
接着,让我们在Person.prototype
里面增加一个getFullName
的函数:
function Person ( firstName , lastName ) {
this . firstName = firstName ;
this . lastName = lastName ;
}
Person . prototype . getFullName = function ( ) {
return this . firstName + ' ' + this . lastName ;
}
var person1 = new Person ( 'Jay' , 'chou' ) ;
console . log ( person1 ) ;
var person2 = new Person ( 'Jane' , 'chou' ) ;
console . log ( person2 ) ;
复制代码
在上面代码的第6 - 8行中,我们为Person.prototype
添加了一个函数,所以当我们在Google Chrome的console视窗中调用Person.prototype
时,会多了这个函数在内:
刚刚,我们有提到很重要的一句话,「函数当中prototype
这个属性并不是这个函数的prototype
,它指的是所有通过这个function constructor所建立出来的对象的prototype
」。
这句话的意思其实是说Person.prototype
并不是Person.__proto__
,但是所有通过Person
这个function constructor所建立的对象,在该实例对象的__proto__
中,会包含有Person.prototype
的内容。
也就是说,当我们使用new
这个运算符来执行function constructor时,它会先建立一个空对象,同时将该构造函数中prototype
这个属性的内容(Person.prototype
),设置到该实例对象的prototype
中,即 person1.__proto__ === Person.prototype
的结果为true
。
因此,当我们在Google Chrome的console中输入person1.__proto__
时,我们就可以看到刚刚在Person.prototype
所建立的函数getFullName
已经继承在里面了:
实际运用
由于Person.prototype
中的方法已经被继承到由Person
这个function constructor所建立的实例对象person1
中,所以这时侯,我们就可以顺利的使用 person1.getFullName
这个方法:
function Person ( firstName , lastName ) {
this . firstName = firstName ;
this . lastName = lastName ;
}
Person . prototype . getFullName = function ( ) {
return this . firstName + ' ' + this . lastName ;
}
var person1 = new Person ( 'Jay' , 'chou' ) ;
console . log ( person1 ) ;
console . log ( person1.getFullName() ) ;
复制代码
可以正确的执行getFullName
这个函数并得到如下的结果:
通过function constructor与Prototype 的实用处
通过这样的方法,我们可以让所有根据这个函数构造器(function constructor)所建立的对象都包含有某些我们想要使用的方法。如果我们有1000个对象是根据这个函数构造器所建立的,那么我们只需要使用 .prototype
这样的方法,就可以让这1000个物件都可以使用到我们想要执行的某个method
,这样减少了代码的复用。
有的人可能会好奇问,为什么我们不把getFullName
这个方法直接写在函数构造式当中呢?
function Person ( firstName , lastName ) {
this . firstName = firstName ;
this . lastName = lastName ;
this . getFullName = function ( ) {
return this . firstName + ' ' + this . lastName ;
}
}
/*
Person . prototype . getFullName = function ( ) {
return this . firstName + ' ' + this . lastName ;
}
*/
复制代码
注意!我们不该把方法放在function constructor 中。
把方法放在函数构造式中这么做虽然仍然可以正确执行并得到结果,但是这么做会有个问题,如果我们是把这个方法直接写在函数构造式中,那么每一个对象都会包含有这个方法,如果我们有1000 个对象根据这个函数构造式所建立,那么这1000 个对象都会包含这个方法在内,如此将会占据相当多的内存;但如果是建立在prototype
中,我们只会有一个这样的方法。
所以,为了性能上的考量,通常会把方法(method
)放在构造函数的prototype
中,因为它们可以是通用的;把属性(property
)放在构造函数当中,因为每一个对象可能都会有不同的属性内容,如此将能有效减少内存的问题。
最后,如果感觉当前缺少你要用的方法,可以自己通过这一方法去创建。
例如在json2.js源码中,为Date
、String
、Number
、Boolean
方法添加一个toJSON
的属性。
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
复制代码
如果你要添加内置方法的原型属性,最好做一步判断,如果该属性不存在,则添加。如果本来就存在,就没必要再添加了。
如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!
转载于:https://juejin.im/post/5cffb8f3f265da1ba84a87e0