JavaScript中的原型与原型链,蟒蛇缠绕般的查找过程

147 阅读4分钟

前言

JavaScript的学习需要慢慢沉淀,像过关斩将一般将一个个知识点掌握,在之前的学习中学习了作用域、预编译、闭包、对象与包装类,今天我们再继续攻克一个知识点:原型与原型链

原型

  1. 原型是函数自带的一个属性prototype,它定义了构造函数的实例对象的公共祖先 通过构造函数创建的对象,可以继承该函数原型的属性
  2. 实例对象隐式具有构造函数原型上的属性,且是只读属性

看看例子吧

//Car.prototype.speed = 260;
//Car.prototype.lang = 5000;
Car.prototype={
    speed:260,
    lang:5000
}
function Car(color,owner){
    this.color = color;//允许自定义
    this.owner = owner;
}

let car = new Car("red","张三");
let car2 = new Car("blue","李四");

console.log(car);
console.log(car2.lang);

通过构造函数Car创建了两个实例对象carcar2,于是这两个对象可以继承构造函数的属性,只不过是隐式继承,因此输出car时,只有自带的显式属性colorowner,但当要求输出car2.lang时会显示出隐式属性lang

原型属性分为显式和隐式

  • 实例具有的属性为显性

  • 隐式原型:对象上的__proto__属性,它等于构造函数的prototype属性。

Person.prototype.like = 'fuck';// 原型具有的属性为隐性
function Person(){
    this.speed = 260;  
    this.lang = 5000;
}
let p = new Person()

p.like = 'shit' // 实例具有的属性为显性
delete p.like  // 删除实例的属性,原型的属性不会被删除
console.log(p.like)

通过构造函数Person创建了实例对象p,其具有Person.prototype.like的属性fuck,现在为p增加了.like属性,输出的是先在实例属性找到的shit

接着再删除实例对象的.like属性,再输出会发现结果不为undefined,而输出的是原型属性fuck,于是可以得到结论:删除实例的属性,原型的属性不会被删除

原型的属性和方法

  • Object.prototype:所有对象的原型链顶端都是Object.prototype,它提供了一些通用的方法,如toString()hasOwnProperty()isPrototypeOf()等。
  • 构造函数的prototype属性:每个函数在创建时,都会自动创建一个prototype属性,这个属性是一个对象,包含了可以由该构造函数创建的所有实例共享的属性和方法。

原型的继承:JavaScript中的继承是通过原型链实现的。当一个对象作为另一个对象的原型时,子对象就可以访问父对象的属性和方法。

原型链

v8在查找对象的属性时,如果没有找到,就会顺着对象的隐式原型往上查找,还找不到, 再顺着隐式原型的隐式原型往上找,直到找到或者到达原型链的顶端,也就是null, 但凡这过程中有一个步骤能找到就会返回值。这个查找过程称为原型链。

Person.prototype.lastName = 'james';
    function Grand(){
        this.name = "tony";
    }
    Father.prototype = new Grand()
    function Father(){
        this.age = 18;
    }
    Son.prototype = new Father();
    function Son(){
        this.like = "goo";
    }
    let son = new Son();
    console.log(son.like);
    console.log(son.age);//18

这段代码定义了PersonGrandFatherSon四个构造函数,通过原型链的方式,设置了它们之间的继承关系。GrandFather的父类,FatherSon的父类。

在实例化Son对象时,会继承FatherGrand的属性和方法。最后通过创建Son的实例对象son,输出了sonlike属性和age属性,结果分别为goo18

__proto__是对象的一个隐式属性,它用于指向父级对象的原型(prototype)。

prototype是显式属性。它主要用于函数对象(即构造函数),用来定义该函数作为构造函数时,其实例将继承的属性和方法。

它的查找顺序便是

  • son 实例
  • __proto__: 指向 Son.prototype
  • Son.prototype.__proto__: 指向 Father.prototype
  • Father.prototype.__proto__: 指向 Grand.prototype
  • Grand.prototype.__proto__: 通常直接指向 Object.prototype
  • Object.prototype.__proto__: 指向 null,表示原型链的终点。
        __proto__: Son.prototype == new Father(){
            __proto__: Father.prototype == new Grand(){
                __proto__: Grand.prototype == new Person(){
                    __proto__: Person.prototype == new Object{
                        __proto__: Object.prototype {
                            __proto__: null
                    }
                }
            }
        }

每个构造函数的 .prototype 属性定义了由该构造函数创建的对象的原型,而这些原型对象通过 __proto__ 隐式链接到它们的父级原型,直至 Object.prototype,形成完整的原型链

注意事项

  • 原型链查找是单向的,从子对象到父对象,直到Object.prototype
  • 原型链的末端是null,当查找到达null时,表示链的结束。
  • 使用new关键字创建对象时,新对象的__proto__属性(在某些浏览器中是prototype)会指向构造函数的prototype属性。

如果感觉掌握了那就上大“boss”

试着将这一幅图的每一条步骤梳理清楚吧 5313e5521ab2ef44a9a790875cb7419.png

总结来说

显式原型prototype主要用于定义和管理构造函数的共享属性和方法,而隐式原型__proto__则负责在对象实例中建立到其构造函数原型的链接,实现继承和属性查找机制。两者共同构建了JavaScript强大的基于原型的继承系统。