小白之原型和原型链 看不懂的我手摸手教你

881 阅读6分钟

一.基础

      1.构造函数

       (1)什么是构造函数?

       用new关键字调用的统称为构造函数

   function Person(){...}
   let person1 = new Person()      // Person就是person1的构造函数

       (2)为什么使用构造函数

   场景:
   var p1 = { name: 'zs', age: 6, gender: '男', hobby: 'basketball' };  // 录入单条信息
   如果有大量的这样的数据呢?不能这么单条单条的写把
   此时构造函数上场表演了:
   function Person(name, age,gender, hobby) {     this.name = name;
     this.age = age;
     this.gender = gender;
     this.hobby = hobby;
   }
   var p1 = new Person('xx',12,'男','xx')          // 通过构造函数创建对象   var p2 = new Person('xx',10,'女','yy')   

      (3)构造函数调用过程

              1.创建一个新的实例对象

              2.设置创建实例的原型,指向构造函数的prototype属性

              3.构造函数内部的this指向当前实例

              4.执行函数内的代码体

              5.返回这个创建对象(如果构造函数内有return语句,并且return后跟着一个对象,则                   返回return后指定的这个对象)

      2.基本概念

           (1)JavaScript中一切引用类型都是对象,对象就是属性的集合。

               Array类型、Function类型、Object类型、Date类型、RegExp类型等都是引用类                     型。

          (2)函数就是对象

        function Person(){..}
        console.log(Person().constructor)                 // [Function:Function]
        console.log(Function().constructor)               // [Function:Function]
        console.log(Object().constructor)                 // [Function:Function]

              由上面代码段得出:

              1.Function函数是普通声明函数的构造函数。

              2.Function函数是自己的构造函数

              3.Function函数也是Object()这类内置对象的构造函数

              函数和对象的包含关系如下:

              

             则得出:对象是由函数创建,函数是Function实例对象

二.分开了解constructor,prototype,proto

      1.constructor

           作用:保存自己构造函数的引用属性

      function Person(){..}
      var person1 = new Person()
      console.log(person1.constructor)              //ƒ Person(){}

      2.prototype

           问:什么样的场景会用到prototype属性?

     person1.sayHello = function(){..}
     person2.sayHello = function(){..}
     console.log(person1.sayHello === person2.sayHello)             // false
     // 说明这是两个不同的方法,各自占有内存

           思考:同一个方法却各自占有内存,那如果把这个方法提成公用的,各自调用,就只用            占一个内存,此时prototype上场表演了。

     Person.prototype.sayHello = function(){..}
     var person1 = new Person();
     var person2 = new Person();
     console.log(person1.sayHello === person2.sayHello)            // true同一个方法

         结论:如果需要为大量实例添加同一个方法时,可以把这个方法放在prototype对象中,           并将prototype对象放在他们共同的构造函数上,实现共享。

         这里需要注意一点:因为所以函数都是Function函数的实例对象,所以Function函数内           部也存在一个prototype来存放公共属性和方法。

    3.proto

        先记住这个关系:

        实例对象._proto = 创建自己构造函数内部的prototype(原型对象)

        实例对象.proto.consrtuctor = 创建自己构造函数

这个图不够明显的话,我们来拆解这个图:

Person()方法里有prototype对象和_proto_属性,prototype上放着constructor和共享属性和方法

Person()函数是Function()函数的实例对象

Function()函数,自己是自己的构造函数,所以Function.proto = 构造函数内部prototype

问:实例对象.constructor 等于 实例对象.proto.constructor?

答:当在实例对象上找不到某个属性,就会去它的原型链上找是否有相关的公用属性或方法

function Person() {}var person1 = new Person()var person2 = new Person() console.log(person1.constructor)    // [Function: Person]   此处实例本身没有constructor
console.log(person2.constructor)    // [Function: Person]   则指向原型的共享constructor
person1.constructor = Function
console.log(person1.constructor)    // [Function: Function] 
console.log(person2.constructor)    // [Function: Person]
因为person1.constructor = Function改的并不是原型对象上的共享属性constructor,而是给实例person1加了一个constructor属性,原型的并没有改变

重点来了:

问:prototype是一个对象,那么它是否有_proro_?

答:

function Person(){..}
console.log(Person.prototype._proto_.constructor)       // ƒ Object() {}
Person.prototype._proto_.constructor = Person中prototype对象的构造函数

结论:函数内的prototype是个普通的对象,并且默认是Object对象的实例。

由图可以得出的结论:

1.所以函数都是Function函数的实例对象,所以他们的_proto_指向Function函数的prototype

2.函数内的prototype是对象,并且是Object对象的实例。函数内prototype的也有自己的_proto_和constructor.

3.Object函数是所有对象通过原型链追溯到最根的构造函数。

4.每个函数内的prototype的_proto_指向Object对象的实例的prototype

5.Object函数的prototype中的__proto__指向null

问:为什么Object函数不能像Function函数一样让__proto__属性指向自己的prototype?

答:如果指向自己的prototype,那当找不到某一属性时沿着原型链寻找的时候就会进入死循环,所以必须指向null,这个null其实就是个跳出条件。

三.原型链的继承时的缺点

1.引用类型的原型属性会被所有实例共享。

   在通过原型链来实现继承时,引用类型的属性被会所有实例共享,一旦一个实例修改了引用     类型的值,会立刻反应到其他实例上。

2.创建子类型的实例时,不能向父类型的构造函数传递参数

   实际上,应该说是没有办法在不影响其他对象实例的情况下,给父类型的构造函数传递参         数,我们传递的参数会成为所有实例的属性。

   解决办法有以下几种方法:

    (1)借用构造函数

         用法:apply()或call()方法,在子类型构造函数的内部调用父类型的构造函数,使得子类           型拥有父类型的属性和方法。

     function Person(param){
       this.param = [].concat(param);
       this.colors = [‘red’,’green’];
     }
     function person1(param){
       Person.apply(this,param)
     }
     var instance1 = new  person1(['instance1']);
     instance1.colors.push('black');   // [‘red’,’green’,'black']
     console.log(instance1.param[0]);    // 'instance1'

        此方法的好处:解决上面提到的两个问题,实例间不会共享属性,也可以向父类型传递参          数。

        缺点:子类型无法继承父类型原型中的属性。我们只在子类型的构造函数中调用了父类型          的构造函数,子类型和父类型的原型也就没有任何联系

   (2)组合继承

       思路:使用原型链实现对原型属性和方法的继承,又通过借用构造函数来实现对实例属性                   的继承

      就是在上面的例子上加一步:person1.prototype = new Person();(继承父类的原型)

      缺点:无论什么情况下,组合继承都会调用两次父类型的构造函数:一次是在创建子类型          原型的时候,另一次是在子类型构造函数内部。

 (3)寄生组合式继承(是《JavaScript高级程序设计》第六章的精华所在)

     思路:通过借用构造函数来继承属性,通过借用临时构造函数来继承原型。不必为了指定         子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本已。

 // 用于继承的函数
    function inheritPrototype(child,parent) {
     // new这个函数时,将构造函数指向子类
      var F = function () {this.constructor = child}
      F.prototype = parent.prototype;   //F函数的原型对象是父亲的原型对象(父类的副本)
      child.prototype = new F();        //将子类的原型指向父类原型的一个副本
    }
// 父类型    function Person(name) {      this.name = name;      this.colors = ["red", "blue", "green"];   }
   Person.prototype.sayName = function () {       console.log(this.name);   };
// 子类型function person1(name, age) {  // 继承基本属性和方法  Person.call(this, name);     //借用构造函数  this.age = age;}
// 继承原型上的属性和方法inheritPrototype(person1, Person);

总结: