一、序言

  和其他面向对象的语言(如Java)不同,Javascript语言对类的实现和继承的实现没有标准的定义,而是将这些交给了程序员,让程序员更加灵活地(当然刚开始也更加头疼)去定义类,实现继承。(以下不讨论ES6中利用class、extends关键字来实现类和继承;实质上,ES6中的class、extends关键字是利用语法糖实现的)

Javascript灵活到甚至可以实现接口的封装(类似Java中的Interface和implements)。

二、类的实现

1.我对类的理解

  首先,我先说说我对类的理解:类是包含了一系列【属性/方法】的集合,可以通过类的构造函数创建一个实例对象(例如人类是一个类,而每一个人就是一个实例对象),而这个实例对象中会包含两方面内容:

a.类的所有非静态【属性/方法】

   非静态【属性/方法】就是每一个实例所特有的,属于个性。(例如每个人的名字都不相同,而名字这个属性就是一个非静态属性)

b.类的所有静态【属性/方法】

   静态【属性/方法】就是每一个实例所共享的,属于共性。(例如每个人都要吃饭,而吃饭这个方法就是一个非静态方法)

2.Javascript对类的实现

a.利用函数创建类,利用new关键字生成实例对象

 (话不多说,先上代码,以下没有特别说明的话,我都会先上代码,然后进行解释说明)

  1. // 代码2.2.a
  2. function Human() {
  3. console.log('create human here')
  4. }
  5. var fakeperson = Human() // undefined
  6. var person = new Human() // {}

这里Human既是一个普通函数,也是一个类的构造函数,当调用Human()的时候,它作为一个普通函数会被执行,会输出create human here,但是没有返回值(即返回undefined);而当调用new Human()时,也会输出create human here并且返回一个对象。因为我们用Human这个函数来构造对象,所以我们也把Human称作构造函数。所以通过定义构造函数,就相当于定义了一个类,通过new关键字,即可生成一个实例化的对象。

b.利用构造函数实现非静态【属性/方法】

  1. // 代码2.2.b
  2. function Human(name) {
  3. this.name = name
  4. }
  5. var person_1 = new Human('Jack')
  6. var person_2 = new Human('Rose')
  7. console.log(person_1.name) // Jack
  8. console.log(person_2.name) // Rose

这里的Human构造函数中多了一个参数并且函数体中多了一句this.name = name,这句话的中的this指针指向new关键字返回的实例化对象,所以根据构造函数参数的不同,其生成的对象中的具有的属性name的值也会不同。而这里的name就是这个类的非静态【属性/方法】

c.利用prototype实现静态【属性\方法】

这里因为要用到原型链的知识,所以放到原型链后面说。

三、原型链

1.类的prototype是什么?

   在Javascript中,每当我们定义一个构造函数,Javascript引擎就会自动为这个类中添加一个prototype(也被称作原型)

2.对象的 proto 是什么?

   在Javascript中,每当我们使用new创建一个对象时,Javascript引擎就会自动为这个对象中添加一个__proto__属性,并让其指向其类的prototype

  1. // 代码3.2
  2. function Human(name) {
  3. this.name = name
  4. }
  5. console.log(Human.prototype)
  6. var person_test1 = new Human('Test1')
  7. var person_test2 = new Human('Test2')
  8. console.log(person_test1.__proto__)
  9. console.log(person_test2.__proto__)
  10. console.log(Human.prototype === person_test1.__proto__) // true
  11. console.log(Human.prototype === person_test2.__proto__) // true

我们会发现Human.prototype是一个对象,Human类的实例化对象person_test1、person_test2下都有一个属性__proto__也是对象,并且它们都等于Human.prototype,我们知道在Javascript中引用类型的相等意味着他们所指向的是同一个对象。所以我们可以得到结论,任何一个实例化对象的__proto__属性都指向其类的prototype。

3.对象的 proto 有什么作用?

  1. // 代码3.3
  2. var Pproto = {
  3. name:'jack'
  4. }
  5. var person = {
  6. __proto__:Pproto
  7. }
  8. console.log(person.name) // jack
  9. person.name = 'joker'
  10. 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。

4.利用prototype实现静态【属性/方法】

  1. // 代码3.4
  2. function Human(name) {
  3. this.name = name
  4. }
  5. Human.prototype.eat = function () {
  6. console.log('I eat!')
  7. }
  8. var person_1 = new Human('Jack')
  9. var person_2 = new Human('Rose')
  10. person_1.eat() // I eat!
  11. person_2.eat() // I eat!
  12. console.log(person_1.eat === person_2.eat) // true

这里我们在构造函数外多写了一句:Human.prototype.eat = function() {...} 这样以后每个通过Human实例化的对象的__proto__都会指向Human.prototype,并且根据上述原型链知识,我们可以知道只要构造函数中没有定义同名的非静态【属性/方法】,那么每个对象访问say方法时,访问的其实都是Human.prototype.say方法,这样我们就利用prototype实现了类的静态【属性/方法】,所有的对象实现了共有的特性,那就是eat

四、继承的实现

1.我对继承的理解

  假如有n(n>=2)个类,他们的一些【属性/方法】不一样,但是也有一些【属性/方法】是相同的,所以我们每次定义它们的时候都要重复的去定义这些相同的【属性/方法】,那样岂不是很烦?所以一些牛逼的程序员想到,能不能像儿子继承父亲的基因一样,让这些类也像“儿子们”一样去“继承”他们的“父亲”(而这里的父亲就是包含他们所具有的相同的【属性/方法】)。这样我们就可以多定义一个类,把它叫做父类,在它的里面包含所有的这些子类所具有的相同的【属性/方法】,然后通过继承的方式,让所有的子类都可以访问这些【属性/方法】,而不用每次都在子类的定义中去定义这些【属性/方法】了。

2.原型链实现继承(让子类继承了父类的静态【属性/方法】)

  1. // 代码4.1
  2. function Father() {
  3. }
  4. Father.prototype.say = function() {
  5. console.log('I am talking...')
  6. }
  7. function Son() {
  8. }
  9. var sonObj_1 = new Son()
  10. console.log(sonObj_1.say) // undefined
  11. // 原型链实现继承的关键代码
  12. Son.prototype = new Father()
  13. var sonObj_2 = new Son()
  14. 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了。这样就实现了子类继承了父类的静态【属性/方法】,那么如何让子类继承父类的非静态【属性/方法】呢?

3.构造函数实现继承(让子类继承了父类的非静态【属性/方法】)

  1. // 代码4.3
  2. function Father(name) {
  3. this.name = name
  4. }
  5. function Son() {
  6. Father.apply(this, arguments)
  7. this.sing = function() {
  8. console.log(this.name + ' is singing...')
  9. }
  10. }
  11. var sonObj_1 = new Son('jack')
  12. var sonObj_2 = new Son('rose')
  13. sonObj_1.sing() // jack is singing...
  14. sonObj_2.sing() // rose is singing...

在这个例子中,通过在Son的构造函数中利用apply函数,执行了Father的构造函数,所以每一个Son对象实例化的过程中都会执行Father的构造函数,从而得到name属性,这样,每一个Son实例化的Son对象都会有不同的name属性值,于是就实现了子类继承了父类的非静态【属性/方法】

4.组合方式实现继承(组合 原型链继承 + 构造函数继承)

顾名思义,就是结合上述两种方法,然后同时实现对父类的静态及非静态【属性/方法】的继承,代码如下:

  1. // 代码4.4
  2. function Father(name) {
  3. this.name = name
  4. }
  5. Father.prototype.sayName = function() {
  6. console.log('My name is ' + this.name)
  7. }
  8. function Son() {
  9. Father.apply(this, arguments)
  10. }
  11. Son.prototype = new Father()
  12. var sonObj_1 = new Son('jack')
  13. var sonObj_2 = new Son('rose')
  14. sonObj_1.sayName() // My name is jack
  15. sonObj_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不是一个字符串,而是一个对象),呢么我们怎么能让原型链继承更纯粹一点呢?让它只继承原型(静态【属性/方法】)呢?

5.寄生组合方式实现继承

为了让原型链继承的更纯粹,这里我们引入一个Super函数,让Father的原型寄生在Super的原型上,然后让Son去继承Super,最后我们把这个过程放到一个闭包内,这样Super就不会污染全局变量啦,话不多说上代码:

  1. // 代码4.4
  2. function Father(name) {
  3. this.name = name
  4. }
  5. Father.prototype.sayName = function() {
  6. console.log('My name is ' + this.name)
  7. }
  8. function Son() {
  9. Father.apply(this, arguments)
  10. }
  11. (function () {
  12. function Super(){}
  13. Super.prototype = Father.prototype
  14. Son.prototype = new Super()
  15. }())
  16. var sonObj_1 = new Son('jack')

这个时候再去打印sonObj1就会发现,它的原型中已经没有name属性啦,如下所示:

对Javascript 类、原型链、继承的理解的更多相关文章

  1. JavaScript的原型链继承__propt__、prototype、constructor的理解、以及他们之间相互的关系。

    回想自己已经工作了有一段时间了,但是自己对JavaScript的原型链.和继承的理解能力没有到位,最近他们彻底的整理并且复习了一遍. 本案例中部分文案来自网络和书籍,如有侵权请联系我,我只是把我的理解 ...

  2. JS原型与原型链继承的理解

    一.原型 先从构造函数开始吧! 构造函数是什么?构造函数与其他函数唯一的区别在于调用方式不同.任何函数只要通过new来调用就可以作为构造函数,它是用来创建特定类型的对象. 下面定义一个构造函数 Fem ...

  3. 深入浅出JavaScript之原型链&继承

    Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instanc ...

  4. 🍓JavaScript 对象原型链继承的弊端 🍓

  5. javascript中继承(一)-----原型链继承的个人理解

    [寒暄]好久没有更新博客了,说来话长,因为我下定决心要从一个后台程序员转为Front End,其间走过了一段漫长而艰辛的时光,今天跟大家分享下自己对javascript中原型链继承的理解. 总的说来, ...

  6. 三张图搞懂JavaScript的原型对象与原型链 / js继承,各种继承的优缺点(原型链继承,组合继承,寄生组合继承)

    摘自:https://www.cnblogs.com/shuiyi/p/5305435.html 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__pro ...

  7. Javascript 对象继承 原型链继承 对象冒充 call 混合方式

    一.原型链继承 function ClassA() {} ClassA.prototype.color = "blue"; ClassA.prototype.sayColor = ...

  8. 8条规则图解JavaScript原型链继承原理

    原形链是JS难点之一,而且很多书都喜欢用一大堆的文字解释给你听什么什么是原型链,就算有图配上讲解,有的图也是点到为止,很难让人不产生疑惑. 我们先来看一段程序,友情提示sublimeText看更爽: ...

  9. 理解JavaScript的原型链

    1. 什么是对象 在JavaScript中,对象是属性的无序集合,每个属性存放一个原始值.对象或函数. 1.1 创建对象 在JavaScript中创建对象的两种方法: ① 字面上: var myObj ...

  10. 一步步学习javascript基础篇(5):面向对象设计之对象继承(原型链继承)

    上一篇介绍了对象创建的几种基本方式,今天我们看分析下对象的继承. 一.原型链继承 1.通过设置prototype指向“父类”的实例来实现继承. function Obj1() { this.name1 ...

随机推荐

  1. Ng1从1.3开始的变更史

    从今有个ng1 spa项目,项目可能会有ng1的版本升级问题,特简要摘录从1.3的主要版本变更,所以内容来自migration guide. 1.3的主要变更: 1.controller不能再以全局简 ...

  2. 免费私有gitLab服务推荐

    阿里云code :https://code.aliyun.com/,可以免费开50个私有项目. 配套的持续交付:https://crp.aliyun.com

  3. Invoke-ASCmd 部署SSAS database

    Install-Module -Name SqlServer -RequiredVersion 21.0.17099 -AllowClobberInvoke-ASCmd -Server 10.162. ...

  4. 文本分类学习 (七)支持向量机SVM 的前奏 结构风险最小化和VC维度理论

    前言: 经历过文本的特征提取,使用LibSvm工具包进行了测试,Svm算法的效果还是很好的.于是开始逐一的去了解SVM的原理. SVM 是在建立在结构风险最小化和VC维理论的基础上.所以这篇只介绍关于 ...

  5. python 关于操作文件的相关模块(os,sys,shutil,subprocess,configparser)

    一:os模块 os模块提供了许多允许你程序与操作系统直接交互的功能 os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname&quo ...

  6. linux中普通的文件查看操作(cat、more、less、head、tail)

    cat:基本是最常用的查看文件内容的linux命令. more 也是用来查看一个文件的内容.当文件内容太多,一屏幕不能占下,而你用cat肯定是看不前面的内容的,那么使用more就可以解决这个问题了.当 ...

  7. sql语句查询执行顺序

    http://blog.csdn.net/bitcarmanlee/article/details/51004767

  8. Java并发包之同步队列SynchronousQueue理解

    1 简介 SynchronousQueue是这样一种阻塞队列,其中每个put必须等待一个take,反之亦然.同步队列没有任何内部容量,甚至连一个队列的容量都没有.不能在同步队列上进行peek,因为仅在 ...

  9. Python初级教程

    Python语言的特点 优点: - 简单 - 易学 - 免费,开源 - 高层语言 - 可移植性(可再多平台运行) - 解释性(不需要编译,可直接运行) - 面向对象 - 可扩展性(缺点:运行效率相对较 ...

  10. 虚拟机搭建CentOS主机win10通过xshell连接

    目标:主机是win10系统,虚拟机搭建CentOS,在主机上通过XShell连接操作. 第一步 主机上安装虚拟机 第二步 下载CentOS 下载地址http://101.110.118.69/isor ...