和其余面向对象的语言(如Java)不一样,Javascript语言对类的实现和继承的实现没有标准的定义,而是将这些交给了程序员,让程序员更加灵活地(固然刚开始也更加头疼)去定义类,实现继承。(如下不讨论ES6中利用class、extends关键字来实现类和继承;实质上,ES6中的class、extends关键字是利用语法糖实现的)php
首先,我先说说我对类的理解:类是包含了一系列【属性/方法】的集合,能够经过类的构造函数建立一个实例对象(例如人类是一个类,而每个人就是一个实例对象),而这个实例对象中会包含两方面内容:前端
a. 类的属性程序员
属性就是每个实例所特有的,属于个性。(例如每一个人的名字都不相同)web
b. 类的方法浏览器
方法就是每个实例所共享的,属于共性。(例如每一个人都要吃饭)闭包
a.利用函数建立类,利用new关键字生成实例对象(话很少说,先上代码,如下没有特别说明的话,我都会先上代码,而后进行解释说明)app
function Human() {异步
console.log('create human here')函数
}var fakeperson = Human() // undefinedvar person = new Human() // {}ui
这里Human既是一个普通函数,也是一个类的构造函数,当调用Human()的时候,它做为一个普通函数会被执行,会输出create human here,可是没有返回值(即返回undefined);而当调用new Human()时,也会输出create human here而且返回一个对象。由于咱们用Human这个函数来构造对象,因此咱们也把Human称做构造函数。因此经过定义构造函数,就至关于定义了一个类,经过new关键字,便可生成一个实例化的对象。
b.利用构造函数实现类的属性
function Human(name) {
this.name = name
}var person_1 = new Human('Jack')var person_2 = new Human('Rose')console.log(person_1.name) // Jackconsole.log(person_2.name) // Rose
这里的Human构造函数中多了一个参数而且函数体中多了一句this.name = name,这句话的中的this指针指向new关键字返回的实例化对象,因此根据构造函数参数的不一样,其生成的对象中的具备的属性name的值也会不一样。而这里的name就是这个类的属性
在Javascript中,每当咱们定义一个构造函数,Javascript引擎就会自动为这个类中添加一个prototype(也被称做原型)
在Javascript中,每当咱们使用new建立一个对象时,Javascript引擎就会自动为这个对象中添加一个__proto__属性,并让其指向其类的prototype
function Human(name) {
this.name = name
}console.log(Human.prototype)var person_test1 = new Human('Test1')var person_test2 = new Human('Test2')console.log(person_test1.__proto__)console.log(person_test2.__proto__)console.log(Human.prototype === person_test1.__proto__) // trueconsole.log(Human.prototype === person_test2.__proto__) // true
咱们会发现Human.prototype是一个对象,Human类的实例化对象person_test1、person_test2下都有一个属性__proto__也是对象,而且它们都等于Human.prototype,咱们知道在Javascript中引用类型的相等意味着他们所指向的是同一个对象。因此咱们能够获得结论,任何一个实例化对象的__proto__属性都指向其类的prototype。
var Pproto = {
name:'jack'
}var person = {
__proto__:Pproto
}console.log(person.name) // jackperson.name = 'joker'console.log(person.name) // joker
咱们发现最开始咱们并无给person定义name属性,为何console出来jack呢?这就是Javascript著名的原型链的结果啦。如图:
当咱们访问person.name时,发生了什么呢?首先它会访问person对象自己的属性,若是自己没有定义name属性的话,它会去寻找它的__proto__属性对象,在这个例子中person的__proto__属性对应的是Pproto对象,因此person的__proto__指向了Pproto,而后咱们发现Pproto对象是具备name属性的,那么person.name就到此为止,返回了jack,可是若是咱们又给person加上了一个自身的属性name呢?这时,再次person.name就不会再寻找__proto__了,由于person自己已经具备了name属性,并且其值为joker,因此这里会返回joker.
咱们注意到上图中Pproto的__proto__指向了Object,这是由于每个经过字面量的方式建立出来的对象它们都默认是Object类的对象,因此它们的__proto__天然指向Object.prototype。
function Human(name) {
this.name = name
}Human.prototype.eat = function () {
console.log('I eat!')
}var person_1 = new Human('Jack')var person_2 = new Human('Rose')person_1.eat() // I eat!person_2.eat() // I eat!console.log(person_1.eat === person_2.eat) // true
这里咱们在构造函数外多写了一句:Human.prototype.eat = function() {...} 这样之后每一个经过Human实例化的对象的__proto__都会指向Human.prototype,而且根据上述原型链知识,咱们能够知道只要构造函数中没有定义同名的方法,那么每一个对象访问say方法时,访问的其实都是Human.prototype.say方法,这样咱们就利用prototype实现了类的方法,全部的对象实现了共有的特性,那就是eat
假若有n(n>=2)个类,他们的一些【属性/方法】不同,可是也有一些【属性/方法】是相同的,因此咱们每次定义它们的时候都要重复的去定义这些相同的【属性/方法】,那样岂不是很烦?因此一些牛逼的程序员想到,能不能像儿子继承父亲的基因同样,让这些类也像“儿子们”同样去“继承”他们的“父亲”(而这里的父亲就是包含他们所具备的相同的【属性/方法】)。这样咱们就能够多定义一个类,把它叫作父类,在它的里面包含全部的这些子类所具备的相同的【属性/方法】,而后经过继承的方式,让全部的子类均可以访问这些【属性/方法】,而不用每次都在子类的定义中去定义这些【属性/方法】了。
function Father() {
}Father.prototype.say = function() {
console.log('I am talking...')
}function Son() {
}var sonObj_1 = new Son()console.log(sonObj_1.say) // undefined
// 原型链实现继承的关键代码Son.prototype = new Father()
var sonObj_2 = new Son()console.log(sonObj_2.say) // function() {...}
看到这句Son.prototype = new Father()你可能有点蒙圈
看下图
对着图咱们想想,首先,一开始Son、Father两个类没有什么关系,因此在访问say的时候确定是undefined,可是当咱们使用了Son.prototype = new Father()后,咱们知道经过new Son()生成的对象都会有__proto__属性,而这个属性指向Son.prototype,而这里咱们又让它等于了一个Father的对象,而Father类又定义了静态方法say,因此这里咱们的sonObj_2经过沿着原型链寻找,寻找到了say方法,因而就能够访问到Father类的静态方法say了。这样就实现了子类继承了父类的方法,那么如何让子类继承父类的属性呢?
function Father(name) {
this.name = name
}function Son() {
Father.apply(this, arguments)
this.sing = function() {
console.log(this.name + ' is singing...')
}
}var sonObj_1 = new Son('jack')var sonObj_2 = new Son('rose')sonObj_1.sing() // jack is singing...sonObj_2.sing() // rose is singing...
在这个例子中,经过在Son的构造函数中利用apply函数,执行了Father的构造函数,因此每个Son对象实例化的过程当中都会执行Father的构造函数,从而获得name属性,这样,每个Son实例化的Son对象都会有不一样的name属性值,因而就实现了子类继承了父类的属性
顾名思义,就是结合上述两种方法,而后同时实现对父类的【属性/方法】的继承,代码以下:
function Father(name) {
this.name = name
}Father.prototype.sayName = function() {
console.log('My name is ' + this.name)
}function Son() {
Father.apply(this, arguments)
}Son.prototype = new Father('father')var sonObj_1 = new Son('jack')var sonObj_2 = new Son('rose')sonObj_1.sayName() // My name is jacksonObj_2.sayName() // My name is rose
这里子类Son没有一个本身的方法,它的sayName方法继承自父类的静态方法sayName,构造函数中继承了父类的构造函数方法,因此获得了非静态的name属性,所以它的实例对象均可以调用静态方法sayName,可是由于它们各自的name不一样,因此打印出来的name的值也不一样。看到这里,你们可能认为这已是一种天衣无缝的Javascript的继承方式了,可是还差一丢丢,由于原型链继承不是一种纯粹的继承原型的方式,它有反作用,为何呢?由于在咱们调用Son.prototype = new Father()的时候,不只仅使Son的原型指向了一个Father的实例对象,并且还让Father的构造函数执行了一遍,这样就会执行this.name = name;因此这个Father对象就不纯粹了,它具备了name属性,而且值为father,那为何以后咱们访问的时候访问不到这个值呢?
这里父类的构造函数在进行原型链继承的时候也执行了一次,而且在原型链上生成了一个咱们永远也不须要访问的name属性,而这确定是占内存的(想象一下name不是一个字符串,而是一个对象)
为了让原型链继承的更纯粹,这里咱们引入一个Super函数,让Father的原型寄生在Super的原型上,而后让Son去继承Super,最后咱们把这个过程放到一个闭包内,这样Super就不会污染全局变量啦,话很少说上代码:
function Father(name) {
this.name = name
}Father.prototype.sayName = function() {
console.log('My name is ' + this.name)
}function Son() {
Father.apply(this, arguments)
}
(function () {
function Super(){}
Super.prototype = Father.prototype
Son.prototype = new Super()
}())var sonObj_1 = new Son('jack')
这个时候再去打印sonObj1就会发现,它的原型中已经没有name属性啦,以下所示:
总而言之,Javascript单线程的背后有浏览器的其余线程为其完成异步服务,这些异步任务为了和主线程通讯,经过将回调函数推入到任务队列等待执行。主线程所作的就是执行完同步任务后,经过事件循环,不断地检查并执行任务队列中回调函数。
(php开发,web前端,ui设计,vr开发专业培训机构技术分享,部分摘自Github开源社区!!!)