JavaScript高级 |彻底搞懂原型对象

本文已收录于专栏
⭐️ 《JavaScript》⭐️

对象的原型

JavaScript 当中每个对象都有一个特殊的内置属性[[prototype ]] ,这个特殊的对象可以指向另外一个对象。
那这个对象有什么用呢?

  • 当我们通过引用对象的属性Key 来获取一个value时,它会触发[[Get]]的操作;
  • 这个操作会首先检查该对象是否有对应的属性,如果有点话就使用它。
  • 如果对象中没有该属性,那么会访问对象的内置属性[[prototype]],在此属性指向的对象中查找是否有该属性。
  • 只要是对象都会都有这样的一个内置属性。

属性 [[Prototype]] 是内部的⽽且是隐藏的,所有我们可获取的方式有两种:

  • 方式一:通过对象的_proto_属性可以获取到(早期路由器自己添加的,存在一定的兼容性问题)
  • 方式二:通过Object.getPrototypeOf 方法可以获取到。

函数的原型

  1. 将函数看成是一个普通函数的对象时,它是具备[[Prototype]]或者说是_proto_(隐式原型)。
var obj = {
    
    }
function fun(){
    
    }

console.log(obj._proto_)
console.log(fun._proto_)
  1. 将函数看成是一个普通函数时,它是具备prototype属性的。(显式原型)
var obj = {
    
    }
function fun(){
    
    }

console.log(fun.prototype)
// console.log(obj.prototype) 对象是没有prototype属性的。

所以 只有函数才具有prototype属性。
注意这里的prototype和[[prototype]]是完全不同的两个概念。

new操作符

new Person(); 

new完 之后会发生什么?

var obj = {
    
    };
  • 创建空对象。
this = obj;
  • 将这个空对象赋值给this
obj._proto_ = Person.prototype
  • 将函数的显示原型赋值给创建的这个空对象的_proto_属性作为它的隐式原型。
  • 执行函数体中的代码。
  • 将这个对象默认返回。

再举一个例子来理解一下。

function foo(){
    
    

}
console.log(foo.prototype);
	// 先打印一下 foo 属性 
var fun = new foo();
  // 创建空的对象
  // 将foo的prototype原型(显示原型)赋值给空对象的_proto_(隐式原型)
console.log(fun._proto_);

console.log(foo.prototype === fun._proto_)// true

将方法放原型里

function Student(name,age,sno){
    
    
	this.name = name;
  this.age = age;
  this.sno = sno;

  this.running = function(){
    
    
    console.log(this.name +" running");
  }
  this.eating = function(){
    
    
    console.log(this.name +" eating");
  }
  this.studying = function(){
    
    
    console.log(this.name +" studying");
  }
}

var stu1 = new Student("why",18,111);
var stu2 = new Student("kobe",16,112);
var stu3 = new Student("jame",15,113);

之前我们编写多个函数方法的时候,会直接在对象中创建。
但我们发现在每调用一次函数时,都会同时创建多个相同的函数对象。
比如上面的例子中三个函数对象runningeatingstudying都分别创建了三次。

那有没有一种方法让我们每种对象只需要创建一次,然后共享这些属性呢?
答案就将函数方法放到显示原型里面!

function Student(name,age,sno){
    
    
	this.name = name;
  this.age = age;
  this.sno = sno;
}
Student.prototype.running = function(){
    
    
  console.log(this.name + "running")
	}
Student.prototype.eating = function(){
    
    
    console.log(this.name +" eating");
  }
Student.prototype.studying = function(){
    
    
    console.log(this.name +" studying");
  }
var stu1 = new Student("why",18,111);
var stu2 = new Student("kobe",16,112);
var stu3 = new Student("jame",15,113);
Student.prototype.running = function()
  • 由构造函数创建出来的所有对象,都会共享这些属性,而且每种只创建一次。

查找原理

  • 先在对象内部进行查找。
  • 如果没有找到,就去原型里查找。

那可不可以将其他属性也放到原型里面?
答案是否定的。
因为每个对象的属性值都是不一样的,而原型只有一个。
image.png

constructor

事实上原型对象上面是有一个属性的:constructor

  • 默认情况下原型上都会添加一个属性叫做 constructor,这个constructor指向当前的函数对象。
  • Person === Person.prototype.constructor
function Person{
    
    
  
}
console.log(Person)//[Function:Person]
console.log(Person.prototype.constructor); //[Function:Person]
console.log(p1.__proto__.constructor);//[Function:Person]
console.log(p1.__proto__.constructor);// Person

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

对应内存图如下:

  • 创建出来的对象中的prototype属性指向了显示原型对象的constructor

image.png

function Person(name,age){
    
    
	this.name = name;
  this.age = age;
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

console.log(p1.name);
console.log(p2.name);
  • new出来的两个新的空对象的prototype属性指向了显示原型对象的constructor

image.png

function Person(name,age){
    
    
	this.name = name;
  this.age = age;
}
//新增方法
Person.prototype.running = function(){
    
    
	console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

p1.running();
p2.running();
  • 在Person的原型上添加了running函数,于是新开辟的running这块内存也指向了Person显式原型对象。+

image.png

function Person(name,age){
    
    
	this.name = name;
  this.age = age;
}
Person.prototype.running = function(){
    
    
	console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

console.log(p1.name);
console.log(p2.name);

p1.running();
p2.running();

//新增属性
Person.protptype.address = "中国"
p1.__proto__.info = "中国很美丽!"

p1.height = 1.88

p2.isAdmin = true;
  • 在原型上添加新的属性并赋值,Person显式原型对象的内存中同样也声明了新增的这些属性。
  • p1对象中新增了height属性,在p2对象中新增了isAdmin属性。

image.png

function Person(name,age){
    
    
	this.name = name;
  this.age = age;
}
Person.prototype.running = function(){
    
    
	console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);

console.log(p1.name);
console.log(p2.name);

p1.running();
p2.running();

//新增属性
Person.protptype.address = "中国"
p1.__proto__.info = "中国很美丽!"

p1.height = 1.88

p2.isAdmin = true;

// 修改address
p1.address = "河北省"
console。log(p2.address)
  • Person显式原型对象中的 address属性并未被覆盖,而是被加到了p1对象里面。
  • p2.address打印的仍然是 Person显式原型对象中的 中国

image.png


	Person.prototype.message = "Hello Person";
  Person.prototype.info = {
    
    name:"沈七",age:30};

  Person.prototype.running = function(){
    
    };
  Person.prototype.eating = function(){
    
    };
      

我们也可以通过直接赋值一个新的原型对象来简写上面代码。

Person.prototype = {
    
    
  message:"Hello Person",
  info:{
    
    name:"沈七",age:30},
	running:function(){
    
    }
	eating:function(){
    
    }
	constructor:Person
}

总结梳理

隐式原型

  • JavaScript每一个对象都有[[prototype]]属性,它的属性值是对某个特定对象的引用。
  • 我们把这里的“某个特定对象”称为该实例对象的原型,也称之为隐式原型。
  • [[prototype]]属性是内部且是隐藏的,所以我们需要__proto__属性来操作[[prototype]]属性,因为__proto__属性存在于每一个对象当中且允许被访问。

显式原型

  • 对于每一个函数对象(非箭头函数)其都有prototype属性,被叫做显式原型。
  • prototype也会指向一个对象,这个对象的所有属性和方法都会被构造函数的实例所继承,这对象被成为原型对象。

两者之间的关系
每一个实例对象都通过__proto_指向它的构造函数的原型对象。

这是因为new操作符的底层实现决定的。

  • 当一个对象通过构造函数 new出实例后
  • 该对象prototype显式原型会通过__proto__赋值给[[prototype]]的隐式原型。
obj._proto_ = Person.prototype

注意[[prototype]]prototype是完全不同的两个属性.

原型对象

原型对象里面都有两个属性:__proto__constructor

  • constructor:用来记录实例对象是由哪个构造函数创建的,所以它默认指向创建它的构造函数。
  • __proto__:原型对象也是对象,所以它也有__proto__属性。

内存表现

function Person(){
    
    
  
}
var p1 = new Person();

image.png

  • Personprototype属性会指向它的显式原型,即Person函数的原型对象
  • Person函数的原型对象constructor属性会指向创建它的构造函数,即Person
  • new出来的实例对象p1的隐式原型__proto__会自动指向创建它的构造函数的显式原型,即Person函数的原型对象
console.log(p1._proto == Person.prototype)//true
  • 因为Person函数的原型对象本身也是一个对象,所以是由Object``new出来的。
  • 所以Person函数的原型对象的隐式原型__proto__属性指向Obejct的原型对象的显式原型。
console.log(Person.prototype.__proto__ == Obejct.prototype)//true
  • Object作为顶级父类,它的原型对象的隐式原型__proto__属性指向null
console.log(Object.prototype.__proto == null)// true

Person对象是由谁创建出来的呢?,它的隐式原型又指向谁?

我们来打印一下试试。

function Person(){
    
    

}
console.log(Person.__proto__)

image.png
我们发现Person.__proto__实际上是指向Function.prototety的。
也就是说:

  • Function是所有直接声明的函数的构造函数。
  • 所有直接声明的函数都是Function的一个实例对象。
  • 所有直接声明的函数的__proto__都指向同一个地方那就是Function.prototety.

我们可以进一步验证:

function Person(){
    
    

}
function foo(){
    
    

}
console.log(Person.__proto__==Function.prototype);// true
console.log(foo.__proto__==Function.prototype);   // true
console.log(foo.__proto__==Person.prototype);     // true

因为Function函数的原型对象本身也是一个对象,所以Function函数的原型对象的隐式原型__proto__也指向Obejct的显式原型.

console.log(Function.prototype.__proto__  == Object.prototype);// true

Obejct本身也是一个函数,所以它的隐式原型__proto__指向Function函数的原型对象的显式原型。

console.log(Object.__proto__ == Function.prototype);// true

Function的本身也是一个函数,所以它的隐式原型__proto__指向它自己的Function函数的原型对象的显式原型。

console.log(Function.__proto__  == Function.prototype);// true

于是就有了下面这张图。
image.png
小结:

  • p1Person的实例对象。
  • objObject的实例对象。
  • Function/Object/FOO都是Function的实例对象。
  • 原型对象默认创建时,隐式原型都是指向Object的显式原型的。(Object指向null)

又因为:
当A类的原型对象的隐式原型__proto__指向B类的显式对象时,我们称之为A类是继承于B类的。

if(A.prototype.__proto__ == B.prototype){
    
    
  A 继承于 B
}

推到出的结论:

  • ObjectPerson/Function的父类

FunctionObject的关系:

  • ObjectFunction 的父类。
  • FunctionObject的构造函数。

完结散花

ok以上就是对 JavaScript高级 |彻底搞懂原型对象 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。

参考文献

coderwhy老师JS高级视频教程

猜你喜欢

转载自blog.csdn.net/m0_66139206/article/details/128323241