【4期】彻底搞懂JS原型继承之——__proto__、prototype、constructor

__proto__

__proto__属性指向原型对象,也可以理解为父类对象。

prototype

函数的原型对象,给其它对象提供共享属性,函数所独有的。所有对象,都可以作为另一个对象的 prototype。

原型链

当你在访问一个对象属性的时候,如果该对象内部不存在这个属性,那么就回去它的__proto__属性所指向的对象(父类对象)上查找,如果父类对象依旧不存在这个属性,那么就回去其父类的__proto__属性所指向的父类的父类上去查找。以此类推,知道找到 null。而这个查找的过程,也就构成了我们常说的原型链。

constructor

constructor属性是一个对象指向该对象的构造函数。对象所独有属性。

每一个对象都有其对应的构造函数,本身或者继承而来。

 


别放弃,马上柳暗花明了

__proto__与prototype

function Father(){ this.a =1; } // Father是函数对象

let child = new Father() // child是普通对象

  • Father._ proto_ === Function.prototype  // 函数对象的原型指向函数的原型对象
  • Function._ proto_ === Function.prototype // Function的原型指向函数原型对象
  • Object._ proto_ === Function.prototype //Object的原型指向函数原型对象
  • Function.prototype._ proto_ === Object.prototype //Function的原型对象的原型指向Object的原型对象
  • child._ proto_ === Father.prototype   //child 的原型指向构造函数Father的原型对象
  • Father.prototype._ proto_ === Object.prototype  //Father 的原型对象的原型指向Object的原型对象
  • Object.prototype._ proto_ === null   //Object的原型对象的原型指向null【原型链顶层】

prototype与constructor

1.函数.prototype.constructor===该函数本身

===》过函数创建的对象即使自己没有constructor属性,它也能通过__proto__找到对应的constructor,所以任何对象最终都可以找到其对应的构造函数。

2.特殊情况:

Function:它是它自己的构造函数。所以Function.prototype === Function.__proto。

 

__proto__、prototype与constructor关系

(注:虚线表示继承而来的 constructor 属性)

image.png


结合代码看几个例子

constructor并不表示(对象)被(它)构造。

var a = new Foo();

a.constructor === Foo为真意味着a有一个指向Foo的.constructor属性?

这是一个误解,实际上,.constructor引用是被委托给了Foo.prototype,而Foo.prototype.constructor默认指向Foo。

把.constructor 属性指向Foo看作是a对象由Foo“构造”非常容易理解,但这只是一种虚假的安全感。

a.constructor 只是通过默认的[[Prototype]]委托指向Foo,这和构造毫无关系。Foo.prototype的.constructor属性只是Foo函数在声明时候的默认属性。如果你创建一个新对象并替换函数默认的.prototype对象引用,那么新对象并不会自动获得.constructor属性。思考如下代码:

function Foo(){}
Foo.prototype = {} // 丢失了constructor属性
var a = new Foo();
a.constructor === Foo; //false
a.constructor === Object; //true
//a并没有constructor属性,所以它会委托[[Prototype]]上的Foo.prototype。但这个对象也没有constructor属性
//所以它会继续委托,这次会委托链顶端的Object.prototype。这个对象有constructor属性,指向内置Object函数


//如何修复丢失的constructor属性
//手动添加不可枚举的constructor属性
Object.defineProperty(Foo.prototype, "constructor",{
  enumerable: false,
  writable:true,
  configurable:true,
  value:Foo // 让constructor属性指向Foo
});

来看一段典型的“原型风格”:

function Foo(name) {
  this.name = name;
}
Foo.prototype.myName = function(){
  return this.name;
}
function Bar(name, label){
  Foo.call(this,name);
  this.label = label;
}
// 创建新的Bar.prototype对象并关联到Foo.prototype
Bar.prototype = Object.create(Foo.prototype);
//Notice!现在没有Foo.prototype.constructor了
Bar.prototype.myLabel = function(){
  return this.label;
}
var a = new Bar('a', 'obj a');
a.myName(); //'a'
a.myLabel(); //'obj a'

 注意:如下两种创建关联对象的方式是常见的错误做法

//1.并不会创建一个关联到Foo.prototype的新对象,而是直接引用Foo.prototype对象。
//因此你执行类似Bar.prototype.myLabel = ...的赋值语句时会直接修改Foo.prototype对象本身
Bar.prototype = Foo.prototype;

//2.的确会创建一个关联到Foo.prototype的新对象。
//但是使用了Foo构造函数,如果Foo有一些副作用(给this添加新数据属性等),就会影响到Bar()的“后代”
Bar.prototype = new Foo();

但是Object.create()也有缺陷:需要创建一个新对象且把旧对象抛弃掉,不能直接修改已有的默认对象。

是否有一个标准可靠的方法来修改对象的[[prototype]]关联?

ES6之前,能通过设置__proto__属性实现,但是这个方法并不标准且无法兼容所有浏览器。

ES6添加了辅助函数Object.setPrototypeOf(),标准可靠。

//es6之前需要抛弃掉默认的Bar.prototype
Bar.prototype=Object.create(Foo.prototype);

//es6可以直接修改现有的Bar.prototype
Object.setPrototypeOf(Bar.prototype, Foo.prototype);

猜你喜欢

转载自blog.csdn.net/m0_38073011/article/details/108500396