最近面试了不少前端同窗,发现有很多同窗的前端基础很薄弱,会使用react/vue等库或框架,但对一些前端最核心的概念,如原型,继承,做用域,事件循环等却掌握的模棱两可。因此不少时候你问深刻点的问题,或者涉及到原理时,就支支吾吾答不出来。javascript
因此呢,打算更新一个新的系列,专门讲前端的核心基础知识,让你们不论是在前端技术的成长路上,仍是面试过程当中都能乘风破浪!前端
今天咱们讲javascript里最核心的一个概念:原型。其余文章会陆陆续续更新。vue
虽然今天咱们是要讲javascript的原型,但为了让你们知道为啥要设计这么个东西,我打算从如何生成一个对象讲起。java
最简单的生成对象的方法:react
let user = {} user.name = 'zac' user.age = 28 user.grow = function(years) { this.age += years console.log(`${this.name} is now ${this.age}`) }
这样生成一个user对象是很简单,假如须要生成一堆user对象该怎么办呢?咱们能够建立一个函数专门来生成user:es6
function User(name, age) { let user = {} user.name = name user.age = age user.grow = function(years) { this.age += years console.log(`${this.name} is now ${this.age}`) } return user } const zac = User('zac', 28) const ivan = User('ivan', 28)
如今咱们这个函数有个问题,每一次咱们实例化一个User时,就得从新分配内存建立一遍grow方法,怎么优化呢?咱们能够把User对象里的方法都移出去:面试
const userMethods = { grow(years) { this.age += years console.log(`${this.name} is now ${this.age}`) } } function User(name, age) { let user = {} user.name = name user.age = age user.grow = userMethods.grow return user } const zac = User('zac', 28) const ivan = User('ivan', 28)
移出去后又遇到一个麻烦的问题,假如咱们须要给User新增一个方法,好比sing,数组
const userMethods = { grow(years) { this.age += years console.log(`${this.name} is now ${this.age}`) } sing(song) { console.log(`${this.name} is now singing ${song}`) } }
这时候咱们还须要去User里去增长相应的方法:app
function User(name, age) { let user = {} user.name = name user.age = age user.grow = userMethods.grow user.sing = userMethods.sing return user }
这就给后期的维护带来里无穷的麻烦,有没有什么办法可让咱们避免呢?如今咱们的User函数每次都是先去生成一个空对象{},咱们是否是能够直接用userMethods这个对象为蓝图来生成一个对象呢?这样就能够直接使用userMethods里面的方法了。框架
javascript为咱们提供了这个方法:Object.create(proto)
,这个方法生成一个空对象,并将proto设置为本身的原型[[Prototype]]
。原型有什么用呢?简单来讲,假如咱们在一个对象里找某个属性或方法,没找到,那javascript引擎就会继续往这个对象的原型里找,再找不到就继续往这个对象原型的原型里找,直到找到或者遇到null,这个过程就是原型链啦。ok,咱们再来改写User:
function User(name, age) { let user = Object.create(userMethods) user.name = name user.age = age return user }
不知道大家有没有注意到,个人User函数首字母是大写的,这样的函数在javascript里叫什么呢?构造函数,也就是conscrutor
,它就是专门用来构造对象的!
如今还有一个问题,咱们这User构造函数,还得配合着userMethods使用,看上去就很麻烦,javascript里有没有什么方法可让咱们省去写这个userMethods对象呢?
有的!下面我要讲一个很重要的概念————什么是原型prototype
?敲黑板了!javascript里建立的每一个函数都带有prototype这个属性,它指向一个对象(这个对象里包含一个constructor属性指向原来的这个函数)
看起来好像很绕口,其实很好理解,咱们看个例子,咱们建立里一个叫a的函数,它自然包含里prototype属性,打印出来能够看出它是一个对象,这个对象里自然有一个属性叫constructor,它指向的f函数就是咱们的a函数自己。
function a() {} console.log(a.prototype) // {constructor: ƒ}
这里我顺带要讲一个咱们刚刚的Object.create(proto)
,我不是也提到了原型[[Prototype]]
吗?敲黑板了!这里千万要注意,以下所示,对象的原型能够经过Object.getPrototypeOf(obj)
或者远古写法__proto__
取到;而函数的自己有一个叫作原型prototype的属性,它是能够直接在函数上找到的f.prototype
。这二者并非同一个东西。
const b = {} const c = Object.create(b) console.log(Object.getPrototypeOf(c) === b) //true console.log(c.__proto__ === b) // true
好,如今咱们在扯回原来的话题,已知每一个函数都自带prototype属性,咱们是否是能够好好利用这一点,咱们根本不须要把user对象须要公用的方法放在userMethods里了,直接放在User函数的prototype里就好啊喂!
function User(name, age) { let user = Object.create(User.prototype) user.name = name user.age = age return user } User.prototype.grow = function(years) { this.age += years console.log(`${this.name} is now ${this.age}`) } User.prototype.sing = function(song) { console.log(`${this.name} is now singing ${song}`) } const zac = User('zac', 28)
个人天,简直简洁优雅大方!如此简洁优雅大方以致于javascript决定把这个融入到javascript语言当中去,因而就正式产生了构造函数constructor
,专门用来构造对象的,使用方法就是在构造函数前使用new
指令。咱们看下若是直接用javascript的构造函数怎么写:
function UserWithNew(name, age) { // let this = Object.create(User.prototype) this.name = name this.age = age // return this } User.prototype.grow = function(years) { this.age += years console.log(`${this.name} is now ${this.age}`) } User.prototype.sing = function(song) { console.log(`${this.name} is now singing ${song}`) } const zac = new UserWithNew('zac', 28)
对比上面咱们本身写的User函数,是否是发现并无太大的差异?那些差异其实就是new
指令作的事情!
这里咱们再稍微拓展下,假若有人要你手写一个new
指令,是否是手到擒来了?总结起来,就是4件事:
你们如今立刻去本身写一个!写不出再来看个人:
function myNew(constructor, args) { const obj = {} Object.setPrototypeOf(obj, constructor.prototype) constructor.apply(obj, args) return obj }
固然,如今这个myNew在生产环境确定是有问题的:
myNew(constructor, 1, 2, 3)
因此改写下:
function myNew(constructor, args) { const obj = Object.create(constructor.prototype) const argsArray = Array.prototype.slice.apply(arguments) const result = constructor.apply(obj, argsArray.slice(1)) if(typeof result === 'object' && result !== null) { return result } return obj }
注意这里第二句,因为arguments是一个类数组的东西,它自己其实并无slice这个方法,因此咱们向Array.prototype借用来这个方法。
这里我仍是要继续展开讲一下,我举个例子:
const a = [1, 2, 3] a.toString()
你们想一下,为何a这个数组会有一个叫toString的方法?
new Array(1, 2, 3)
帮你建立的数组aa.__proto__ === Array.prototype
)a.__proto__.__proto__ === Object.prototype
)讲到这里就差很少了,原型,原型链,构造函数,new我通通给你们讲了一遍,但愿我讲清楚了。对了es6不是带来了class的写法吗?明天我再跟你们用class改写下咱们的User构造函数,还有extend继承等概念都会相继讲到,你们期待下吧。
这篇号称15分钟读完的文章,花了我3个小时才写完,以为对本身有用的话,记得收藏点赞哦,另外深圳阿里持续招人,欢迎私信勾搭