学习指南:
- 对象的原型
- 函数的原型
- new操作符
- 将方法放原型里
- constructor
- 总结梳理
- 原型对象
- 内存表现
- 完结散花
- 参考文献
对象的原型
JavaScript 当中每个对象都有一个特殊的内置属性[[prototype ]] ,这个特殊的对象可以指向另外一个对象。
那这个对象有什么用呢?
- 当我们通过引用对象的属性
Key
来获取一个value时,它会触发[[Get]]的操作; - 这个操作会首先检查该对象是否有对应的属性,如果有点话就使用它。
- 如果对象中没有该属性,那么会访问对象的内置属性[[prototype]],在此属性指向的对象中查找是否有该属性。
- 只要是对象都会都有这样的一个内置属性。
属性 [[Prototype]] 是内部的⽽且是隐藏的,所有我们可获取的方式有两种:
- 方式一:通过对象的_proto_属性可以获取到(早期路由器自己添加的,存在一定的兼容性问题)
- 方式二:通过Object.getPrototypeOf 方法可以获取到。
函数的原型
- 将函数看成是一个普通函数的对象时,它是具备
[[Prototype]]
或者说是_proto_
(隐式原型)。
var obj = {}
function fun(){}
console.log(obj._proto_)
console.log(fun._proto_)
- 将函数看成是一个普通函数时,它是具备
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);
之前我们编写多个函数方法的时候,会直接在对象中创建。
但我们发现在每调用一次函数时,都会同时创建多个相同的函数对象。
比如上面的例子中三个函数对象running
、eating
、studying
都分别创建了三次。
那有没有一种方法让我们每种对象只需要创建一次,然后共享这些属性呢?
答案就将函数方法放到显示原型里面!
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()
- 由构造函数创建出来的所有对象,都会共享这些属性,而且每种只创建一次。
查找原理
- 先在对象内部进行查找。
- 如果没有找到,就去原型里查找。
那可不可以将其他属性也放到原型里面?
答案是否定的。
因为每个对象的属性值都是不一样的,而原型只有一个。
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
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
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显式原型对象
。+
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
属性。
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显式原型对象
中的中国
。
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();
Person
的prototype
属性会指向它的显式原型,即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__)
我们发现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
于是就有了下面这张图。
小结:
p1
是Person
的实例对象。obj
是Object
的实例对象。Function
/Object
/FOO
都是Function
的实例对象。- 原型对象默认创建时,隐式原型都是指向
Object
的显式原型的。(Object
指向null)
又因为:
当A类的原型对象的隐式原型__proto__
指向B类的显式对象时,我们称之为A类是继承于B类的。
if(A.prototype.__proto__ == B.prototype){
A 继承于 B
}
推到出的结论:
Object
是Person
/Function
的父类
Function
与Object
的关系:
Object
是Function
的父类。Function
是Object
的构造函数。
完结散花
ok以上就是对 JavaScript高级 |彻底搞懂原型对象 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。
参考文献
coderwhy老师JS高级视频教程