详解JavaScript中的对象、原型链、constructor、super以及this

 * 在MDN中关于继承与原型链的解释中,有这样一句话 : 对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个class的实现。(我觉得说的非常对,我就很难理解JavaScript中继承以及它为什么需要修改this的指向,因此我希望通过这样一篇文章来弄懂我的困惑。再次之外,能够给别人一些启发,我将十分高兴)

一.详解关于ES6中的对象以及对象的构造

  对象:JavaScript是基于对象的脚本语言,就是说JavaScript中也有对象的概念。在任何语言中对象的含义都是相同的:为一系列特征(名词 - 属性)和动作(动词 - 函数)的集合。这几乎没什么解释。但是JavaScript是解释型语言,他和Java这种面向对象的语言实现对象的方式是不同的。

  JavaScript中对象构造方法

  1.利用{}字面量创建对象 -只是对象,不能充当原型(下面解释)

const car = {
    color: 'red';
    run:function(){
        return '这车可以跑啊!';
    }
}

  2. 构造一个对象,使用new 关键字实例化一个对象

  使用new关键字构造一个对象,首先要使用function或者class关键字创造一个对象模板,这在JavaScript中被称作原型。举个例子,我要创建车对象,如果我只是随便造一个车的话,那么我可以是方法一创建对象,但如果我想要制造一堆车怎么办呢?我必须先画一个图纸或者先建立一个工厂。那么 function Car(){} 或者 class Car{}就是这样的过程。他们两的不同,我们在后面讨论。因为说道这里,我们必须要首先讨论原型链,见下图。

  

  从上图我们可以看出,当我们想要实例化一个对象时,我们必须首先构造一个原型或者也可以说原型对象的东西。为什么要这么做呢?理由很简单,当你需要批量创造具有相同属性只是值不同的对象时,这种方式是最经济以及最规范化的。如果你只是构造一个特殊的对象时,那么你当然可以采用方式1。

  那么在上述原型已经创造好的前提下,我们就要通过new关键字构造对象了。这里就要说道constructor的作用了。当我在使用new的时候,实际上是调用了constructor()这个函数来构造对象的,至于为什么非要调用constructor()来构造对象,这恐怕得去问JavaScript的创造者。但其实也不能理解,因为对象是一个概念性的东西,我们去构造一个概念性的东西时,当然还得调用具体的代码去完成其在计算机中的物理构造了,这个物理构造的过程就是借用了constructor这个函数了。

  

  总结:对象以及对象的构造

  1.使用字面量({})可以创造一个对象:一个不能成为原型,也不在原型链上的对象。

    * 如何使字面量创造的对象变成原型对象呢? ---> 当前对象 = Object.create(原型链上的上级原型对象);

  2.构造一个对象

    (1)需要通过function 或者 class,构造一个模板,也就是原型对象

    (2)使用new关键字构造一个基于原型链的对象,这个过程上实际上调用了 constructor()

二.详解 class 和 function 的区别

  我相信在你看完一之后,一定会有一个疑问,既然创建一个基于原型链的对象必然要调用constructor,那么在使用function的时候,似乎并没有调用constructor啊。因此我们就来看一下利用funciton创造对象是怎么样的一个过程。

  首先,使用function构造对象,叫做构造函数。对,和constructor同名。而实际上使用functio 构造对象就是使用constructor。你可以将 function Car(){} 理解为你调用了名字不是 constructor 而是 Car的构造函数。请你一定要记住 funciton Car(){} 是调用了一个名叫 Car(),等同于 constructor函数的构造函数。因为这在解释super和this的时候至关重要。

  其次,我们聊一下class。MDN上关于class是这样解释的,class是一块语法糖(指的是在计算机语言中添加的某种语法,这种语法对语言的编译结果和功能并没有实际影响, 但是却能更方便程序员使用该语言),并没有真正实现类似于Java的class那样的作用。这也是导致学习过Java很难理解JavaScript的class的一个重要诱因。我们可以这样理解class,使用class是我们在Javascript中隔离出了一个块作用域,当我们在其中定义constructor时,他就成为了可以构造对象的原型了。

  有时候我们使用class时,并没有定义constructor。好像也可以用啊。这是因为当我们没有定义constructor的时候,系统会自动通过原型链,调用他上一级原型中constructor函数,如果没有继承的话,调用的就是Object,prototype.constructor。我们可以可以理解为调用了一个“空”参构造函数(“空”指不包含当前原型对象的任何私有属性)。

  事实上我建议在class中,如果你的原型对象没有任何自己的私有属性,私有方法中也不包含任何需要传值的私有属性,那么我建议你不写constuctor。因为没有必要写。但是换一句话说,如果一个原型对象没有自己私有属性,那么其实没有必要建立一个原型对象,构造一个对象就可以了。至于为什么,可以看三。

  总结:各名词的含义,如下图:

三.详解super和this

  首先说this是什么。this是一个指针,他指向当前对象的堆空间,或者当前原型对象的prototype地址。super是什么,super()实际上是调用了当前原型的空参构造函数,这里的空是真的一个参数都没有。

  如果你写过react的代码,可能会对一下两对代码产生疑惑。

  我们暂时不对这个问题进行讨论。先看下面的代码。 

class Father{
      constructor(name,age){
         this.name = name;
         this.age = age;
      }
      say(){
        return '我是父亲';
      }
}
class Son extends Father{
     constructor(name){
       super(name);             // 等同于执行了 thi.name = name;
       console.log(name);       // 输出 erzi  ---->输出的是形参的值
       console.log(this.name);  // 输出 erzi-----> 不调用super(name),会报错
      }
      say(){
        console.log(name);      // 啥也不输出,因为找不到name这个属性,找不到自己的堆地址,也拿不到形参的值
        console.log(this.name); // 输出 erzi-----> 不调用super(name),会报错
      }
}
const son = new Son('erzi');
son.say();

  从上面代码中,我们可以看出继承过来的属性,必须要通过this这个指针才能访问到自己堆这个地址。this.name 和 name 完全是两个参数,就类似于实参和形参的区别。而 super(name) 的真实作用就是将形参的值赋给实参,相当于执行了 this.name = name ,但是JavaScript有一个反人类的操作,就是你执行 this.name = name 而不执行super(name)是会报错的,这可能也是一个bug。总是你必须在执行this 之前执行 super,否则就会报错。

  最后我们说下为什么在function中不需要使用this。因为我们在前文中说过使用function构造函数其实就是在调用constructor的过程,在这个块作用域内,我们可以自由的访问到形参的值,因此也不需要调用this去访问自己堆中的真实参数。这实际上也是react文档教程中,当需要用到state时,将函数组件转变为类组件的重要原因。

  总结:class中需要调用super的原因,是因为需要将形参赋给实参,以供constuctor 外部的函数可以访问到自己堆中的属性的值。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值