谈谈JavaScript中的function constructor和prototype的建立

《谈谈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源码中,为DateStringNumberBoolean方法添加一个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

猜你喜欢

转载自blog.csdn.net/weixin_34124651/article/details/93183763
今日推荐