JavaScript原型浅析

210 阅读3分钟
  1. 何谓对象?

    对象是具有属性和方法的一个变量。

  2. 何谓原型?

    我们创建了一个函数: function A(){}, A本质上也是一个对象。 此时会隐含的创建一个对象b,A的prototype属性会指向对象b,b就称为function A的原型对象,对象b的constructor属性同时又指向对象A, 即A.prototype === b, b.constructor === A。

  3. 原型对象(prototype)存在的意义?

    prototype用于与其他对象共享属性。

    如何理解这句话?

    function A(){}
    A.prototype.print = function(name){
      console.log(name)
    }
    let a1 = new A();
    let a2 = new A();
    a1.print('wang'); // wang
    a2.print('li'); // li
    

    ​ 以上代码中,a1, a2均为A的实例对象,a1, a2共享在构造函数A的原型对象上定义的print方法。 那 a1 和 a2 是如何找到print方法呢? 由于我们并未在 a1 和 a2 对象上定义print方法,所以当在a1上调用print方法时,会隐性的去原型对象上去寻找print方法,即

    执行 a1.print 时,就是在执行 a1.proto.print,即

    console.log(a1.print === a1.__proto__.print)  // true
    // 我们还可以得出如下结论
    console.log(a1.__proto__.print === A.prototype.print) // true
    

    ​ 看到这里我们可以知道,A.prototype类似于Java中的父类,A.prototype.print类似于父类中定义的方法,可以用于不同对象间共享同样的方法和属性。

  4. 原型链又是什么鬼?

    上文我们知道,当一个对象访问一个方法或属性时,如果在当前对象无法找到,会自动通过 __proto__属性访问原型对象去寻找,即a1.print === a1.proto.print。假如我们现在要访问 a1的toString方法,我们看下会是什么结果

    console(a1.toString()) // [Object Object]
    

    由此可见,a1是可以访问toString方法。但是a1及 a1的原型上都没有定义toString方法,那这个方法是哪来的呢?先抛出一个结论,toString方法定义在Obhect.prototype上。

    // a1.__proto__ 指向的是A的原型对象,即A.protptype
    a1.toString === a1.__proto__.toString  // true
    // 由于a1.__proto__(即A.prototype)上也并未定义toString,故继续向上寻找
    // A.prototype本身也是一个对象,故其自身也会存在__proto__属性,通过__proto__找到对应的原型对象
    // A.prototype.__proto__ === Object.prototype,  Object.prototype上定义了toString方法,故找到了toString
    a1.__proto__.toString === a.__proto__.__proto__.toString // true  
    // 即
    a1.toString === a1.__proto__.toString === a.__proto__.__proto__.toString
    

    由此可见,原型链是一条可以向上级原型对象查找方法和属性的工具,通过__proto__的链式调用实现。

  5. 构造函数和原型对象的关系?

    以 function A(){} 为例,A 上有prototype属性,其指向A的原型;A的原型上有constructor属性,其指向A,即A.prototype.constructor === A

  6. 原型对象之于继承的作用?

    function Person(name){
      this.name = name;
      this.eat = function(){
        console.log(this.name, ' is eating');
      }
    }
    Person.prototype.say = function(){
      console.log(this.name, ' is saying');
    }
    
    function Student(name, sex){
      this.name = name;
      this.sex = sex;
      this.learning = function(){
        console.log(this.name, ' is learning');
      }
    }
    
    Student.prototype = new Person(); // Student的原型指向A的实例,即Student.prototype.__proto__ = A.prototype
    // Student.prototype本身是没有构造函数的,当我们调用Student.prototype.constructor时会去A实例的原型寻找。
    // 即Student.prototype.constructor === Student.prototype.__proto__.constructor;
    // 此时Student原型的构造函数指向已经发生了改变,指向了构造函数A,我们需要手动修改回来。(也可以说Student.prototype本身没有构造函数,我们需要手动添加构造函数)
    Student.prototype.constructor = Student;
    // 添加Student自己的方法
    Student.prototype.write = function(){
      console.log(this.name, ' is running')
    }
    

    由此可见,原型对象可以用于实现JavaScript的继承操作。