面试题--原型和原型链你了解多少呢

196 阅读4分钟

一、原型

在上文说到:构造函数创建对象的时候,同一方法再创建一次,这很没必要。所以就用到了js中的原型(property)。

 function student(name, age) {
        this.name = name;
        this.age = age;
        this.sayHi = () => {
          console.log("i am " + this.name + ",i am " + this.age + " years old");
        };
      }
      const student1 = new student("小明", 8);
      const student2 = new student("小红", 8);
      console.log(student1.sayHi === student2.sayHi); // false

JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。(JavaScript 标准参考教程(alpha))

所以我们可以将公用属性和方法,定义在原型上面,那么每个实例被创建的时候不必要再创建公用的属性和方法,直接通过原型链就可以找到这个方法。

function student(name, age) {
    this.name = name;
    this.age = age;
}
const student1 = new student("小明", 8);
const student2 = new student("小红", 8);
student.prototype.sayHi = function(){
    console.log("i am " + this.name + ",i am " + this.age + " years old");
};
student1.sayHi()//i am 小明,i am 8 years old
student2.sayHi()//i am 小红,i am 8 years old

\

那么是以上代码的student1是怎么找到sayHi方法的?我们先看以下student1这个实例

这个实例里没有sayHi方法,但是有个_proto_ ,那这个_proto_是什么呢?我们打印出来可以看到,在proto_里面constructor是指构造这个实例的构造函数,sayHi就是我们定义在构造函数的原型对象上的公用方法了。

所以,_proto_它是实例的原型对象,我们刚刚说函数的原型对象是prototype。总结起来说就是

student1.__proto__ === student.prototype // true

实例的_proto__ 等于 构造函数的prototype。

二、原型链

     function student(name, age) {
        this.name = name;
        this.age = age;
      }
      const student1 = new student("小明", 8);
      const student2 = new student("小红", 8);
      student.prototype.sayHi = function(){
          console.log("i am " + this.name + ",i am " + this.age + " years old");
      };
      console.log(student1.toString()); // [object Object]

在上面的代码中我用了一个没有定义的toString方法,他是一个可以用的方法,但是我们却没有定义,这个方法得到的结果是// [object Object],当我们自己定义一个toString方法的话

  function student(name, age) {
        this.name = name;
        this.age = age;
      }
      const student1 = new student("小明", 8);
      const student2 = new student("小红", 8);
      student.prototype.sayHi = function() {
        console.log("i am " + this.name + ",i am " + this.age + " years old");
      };
      student.prototype.toString = function() {
        console.log("这是一个我定义的toString方法");
      };
      student1.toString() // 这是一个我定义的toString方法

结果我们发现toString方法是执行了我们定义的方法,然后我们再看以下student1这个实例

在student1里面出现了我们定义的sayHi方法和toString方法,那么我们没定义toString方法之前的toString方法是在那里的呢?继续点开_proto_

可以发现在student1.proto . _proto__里有一个toString方法,这个就是自带的公用方法,这个方法的结果就是// [object Object]

注:默认toString方法:这是一个将其他类型的数据转换成字符串的方法。

这就是原型链。

原型链的定义:JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……

果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。

那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null

读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。

注意,一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

举例来说,如果让构造函数的prototype属性指向一个数组,就意味着实例对象可以调用数组方法。( JavaScript 标准参考教程(alpha)

拿刚才的student.prototype 举例

      console.log(student.prototype);
输出内容:

我们得到了student构造函数的原型对象,那么可以通过_Proto去寻找toString方法,如果我们没有定义toString方法,它又会通过_proto. _protp__去寻找toString方法,直到原型链的尽头。