原型

182 阅读6分钟

原型

  • 在 JavaScript 中,每个对象都有一个原型(prototype)对象,它是一个指向另一个对象的引用。当访问一个对象的属性时,如果该对象本身没有该属性,则 JavaScript 引擎会沿着该对象的原型链向上查找,直到找到该属性或者到达原型链的末尾
  • image.png
  • image.png

一、原型对象

  • image.png
  1. JavaScript 规定,每一个构造函数都有一个 prototype 属性(显示原型),指向另一个对象,所以我们也称为原型对象
  2. 原型对象可以挂载函数,对象实例化不会多次创建原型对象里面的函数,节约内存
  3. 实例对象和原型对象关系 (三角)
  • image.png
  1. 构造函数、原型对象- this指向
  • 构造函数原型对象中的this 都指向 实例化的对象(new出来的那个对象)。
        // 构造函数中的this指向 实例对象  new出来的那个对象
        function Person(name){
            this.name = name 
            // console.log(this)
        }
        Person.prototype.sayHi = function(){
            console.log(this)
        }
        const p = new Person('jl')
        const mby = new Person('mby')
        p.sayHi()  //this 指向 Person {name: 'jl'}
        mby.sayHi() //this 指向 Person {name: 'mby'}
  • 注意:
  • 箭头函数不能做构造函数,因为箭头函数里面没有 this
  • 原型对象里面的函数如果需要用到this,也不要用箭头函数
  1. 我们可以往这个原型上添加属性和方法
  • 所有通过构造函数创建的实例,都可以共享原型上的属性和方法
  • 公共的属性写在构造函数里面:
        function Star(name, age){
            // c
            this.name = name 
            this.age = age
        }
  • 公共的方法写到原型上:
        function Star(){}
        Star.prototype.eat = function(){
            console.log('冰淇淋')
        }
        let p = new Star()
        p.eat() // 冰淇淋
     
        P实例对象是可以访问到构造函数的显示原(Star.prototype
  • 但是,原型上也是可以添加属性的:
   function Star() {}
      Star.prototype.cheer = "Every Step Counts";
      let p = new Star()
      console.log(p.cheer) // Every Step Counts     
  1. 所有对象的隐式原型指向构造函数的显示原型
  • 对象的隐式原型 ==== 实例对象的 proto
  • 构造函数的显示原型 ==== 构造函数的 prototype
      function Star(){}
      const kk = new Star()
      console.log(kk.__proto__ === Star.prototype)  // true
     // kk.__proto__  就是 对象的隐式原型
     // Star.prototype 就是 构造函数的显示原型

找到原型对象示例:

        function Star(name){
            this.name = name 
        }
        const ldh = new Star('刘德华')
  1. 通过 ldh.__proto__ ==> 可以找到原型
  2. 通过 Star.prototype ==> 可以找到原型

直接给原型赋值对象 导致问题 示例

  • 当你直接给原型赋值一个对象时,可能会导致一些问题。让我们看一个示例来说明这个问题:
        function Star(name){
            this.name = name 
        }
        Star.prototype = {
            sing:function(){
                console.log('唱歌')
            },
            dance:function(){
                console.log('跳舞')
            },
            rap:function(){
                console.log('rap')
            }
            kk.sing()  // 唱歌
            kk.rap()   // 跳舞
            kk.dance() // rap
            console.dir(kk)  // 原型中的默认的constructor属性丢失了
        }

  • 如果我们直接给原型赋值一个对象,相当于整个替换了原型对象的所有内容
  • 那么原型中的默认的constructor属性就丢失了,我们就不知道这个原型是哪个构造函数的原型了

解决方法 :

  1. 我们可以手动利用constructor重新添加上去,指回去
          Star.prototype = {
            constructor:Star,
            sing:function(){
                console.log('唱歌')
            },
            dance:function(){
                console.log('跳舞')
            },
            rap:function(){
                console.log('rap')
            }
        }
  1. 使用原型继承的方式来避免这个问题
        Star.prototype.sing = function(){
            console.log('唱歌')
        }

        Star.prototype.dance = function(){
            console.log('跳舞')
        }

        Star.prototype.rap = function(){
            console.log('rap')
        }
  • 使用原型继承的方式添加方法不会丢失constructor属性

二、constructor属性

  • constructor 属性是一个用于标识对象的构造函数的特殊属性。它指向创建该对象的构造函数。

  • 在 JavaScript 中,当你创建一个对象实例时,该对象会自动包含一个名为 constructor 的属性,它引用了用于创建该对象的构造函数。通过这个属性,你可以确定对象的构造函数是哪个

示例:

  1. 通过constructor找到创建自己的构造函数
        function Person(name) {
          this.name = name;
        }

        var person = new Person("Alice");

        console.log(person.constructor); // 输出:[Function: Person]

  1. 实例对象.prototype(原型).constructor 和创建自己的构造函数相等
        function Person(){}
        console.log(Person.prototype.constructor === Person) //true

注:

  • 继承:当你通过原型链创建一个派生对象时,你可以使用 constructor 属性来指定该派生对象的构造函数
  • 需要注意的是,如果你修改了对象的原型链,可能会影响到 constructor 属性的正确性。在这种情况下,你可能需要手动重设 constructor 属性,以确保它指向正确的构造函数。例如:
        function Person(name) {
          this.name = name;
        }

        Person.prototype = {
          greet: function() {
            console.log("Hello, my name is " + this.name);
          }
        };

        var person = new Person("Alice");

        // 重设 constructor 属性
        Person.prototype.constructor = Person;

        console.log(person.constructor); // 输出:[Function: Person]

  • 在这个示例中,我们修改了 Person.prototype 的引用,需要手动重设 constructor 属性来确保它指向正确的构造函数。

三、隐式原型

1.__ proto __

  • 隐式原型(Implicit Prototype)是 JavaScript 对象中的一个内部属性,用于实现对象之间的原型继承。每个 JavaScript 对象都有一个隐式原型,它指向了创建该对象的构造函数的原型对象。
  • 所有的对象,都有__proto__(隐式原型),属性值是一个普通的对象
  • 所有对象的隐式原型(proto)都指向它的构造函数的显示原型(prototype)

注:

  • 隐式原型可以通过 __proto__ 属性来访问,但请注意,__proto__非标准的属性,在一些环境中可能不被支持。更推荐的方式是使用Object.getPrototypeOf(obj) 方法来获取对象的隐式原型。
  • 函数也是一个对象,所以函数也有__proto__属性,但是函数还有prototype属性

示例:

function Car(make, model) {
  this.make = make;
  this.model = model;
}

Car.prototype.start = function() {
  console.log(this.make + " " + this.model + " is starting.");
};

var car = new Car("Toyota", "Camry");

console.log(car.__proto__ === Car.prototype); // 输出:true

  • 使用 car.__proto__,我们可以访问到 car 的隐式原型,它指向了 Car.prototype。这是因为 car 是通过 Car 构造函数创建的实例,JavaScript 在创建实例时会自动将构造函数的 prototype 属性赋值给实例的 __proto__ 属性。
  • 通过对比 car.__proto__ === Car.prototype,我们可以确认 car 的隐式原型与 Car.prototype 是相等的

2.Object.getPrototypeOf(obj)

  • Object.getPrototypeOf(obj) 和 ## __ proto __ 作用是一样的 只不过 该属性是标准的

示例:

function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.name);
};

var person = new Person("Alice");

var prototype = Object.getPrototypeOf(person);

console.log(prototype); // 输出:Person.prototype
console.log(prototype === Person.prototype); // 输出:true

  • 使用 Object.getPrototypeOf(person),我们可以获取到 person 的隐式原型。在这个例子中,它返回的是 Person.prototype
  • 我们将获取到的隐式原型赋值给变量 prototype,然后通过对比 prototype === Person.prototype,我们可以确认 prototypePerson.prototype
  • 注: 使用 Object.getPrototypeOf(obj) 方法可以获取对象的隐式原型,这是一种推荐的方式,因为它是标准的方法,在不同的 JavaScript 环境中都能够正常工作,具有更好的兼容性和可移植性。

总结

构造函数-实例-原型之间的关系

  1. 构造函数.prototypr ===> 原型
  2. 原型.construcyor ===> 构造函数
  3. 实例.__ proto__ ===> 原型
  4. new 构造函数 ===> new出实例

image.png