<JavaScriptの>コンストラクタ、プロトタイプ、__ proto__プロトタイプチェーン、および

多くの読み取り、オンラインの関連記事では、それらの多くは、あなたが下手に書かれた前任者と言うが、それは読書で完全に一度か二度理解することは容易ではないことをするために、読み取りに無知ではない余儀なくされています。私はいくつかの記事を読んだ後、個人的には受け入れ、理解し、ここでそれを共有することが容易になるだろうと思いますが、自分自身の概要を整理し、いくつかの関連人物を描きます。したがって、すべての後、私は本当に誤解だろう彼らの未熟理論的基礎を心配理解し、間違った場所には、以下のハードプット前方誤り訂正と必ず先輩が私を許しても、としてください場合イラストは、個人的な理解のためのものです弟の残りの部分。

最初に、私たちはこの知識はどのような混乱を理解する理由についてお話しましょう

気持ちのための3つの理由があります。

  1. JSすなわち関数オブジェクト。
  2. 関数オブジェクトとObjectオブジェクトの両方の組み込みオブジェクトの特殊性。
  3. 添えものを説明するための図に多くのポイントが上に行く、[マニュアル犬の頭]を見て頭痛を持っています。

私はインターネット関連の記事の古い世代が尋することは困難である理由、としましょう

多くの先輩が、私は__proto__から関連する知識を説明するために起動したときに始めているが、私の意見では、__ proto__の原型と密接な関係がの面で白羽されていません(単独で理解することは困難であることを意味話す);およびプロトタイプおよびコンストラクタと密接に関連して、**非常に厄介な状況になった、我々は最初の__proto__話をしなければならない必然的にプロトタイプとコンストラクタプロパティを説明する必要があります**これは、これらの白の面で、なぜ私たちのためにあります概念を理解することはとても困難です。(参照のみのため、個人的な意見よりも)

そして、採用の理解の私の個人的な方法について話します

それは簡単に、よりやる気徹底的に理解できるようにするため、私は良い結果を持って、希望を理解する**道を「解体」ステップによって**コンストラクタのプロトタイプチェーンステップから__proto__するために使用しました。次のように記事を読み取ります。

  1. なぜ「オブジェクトである機能」を理解するために
  2. コンストラクタは非常に純粋です
  3. プロトタイプはの理由登場です
  4. 中コンストラクタ不動産の所有
  5. 自分のプロトタイプオブジェクトを見つけるために、インスタンスを聞かせて__proto__
  6. 正確には何プロトタイプチェーン
  7. 新しい継承へのプロトタイプチェーンリード
  8. シリーズを使用することを学ぶ|新しい手書き
  9. 概要

最後に、少しの知識を知って見下ろすする必要性について話:

①いずれかの関数はオブジェクトの一般的なクラスを作成するとき、それは、コンストラクタまたはコンストラクタと呼ばれています。

function Person() {}
var person1 = new Person()
var person2 = new Person()
复制代码

上記のコード人()コンストラクタPERSON1とPERSON2です。

②できる对象.constructorオブジェクトのインスタンスを取得するためのコンストラクタを作成します。

console.log(person1.constructor) // 结果输出: [Function: Person]
复制代码

人のコンストラクタ関数はPERSON1オブジェクトです。

③機能機能とオブジェクトJS機能がビルトインされているオブジェクト、またJSに独自のクラスをパッケージ化、内部クラスとして知られているので、あまりにも多く、実際に絡まることなく、奇妙な、予想外のセットの多くは、公式のアクション、妖精操作。

オブジェクトコンストラクタの独自のインスタンスで④すなわちプロトタイプオブジェクトのプロトタイプオブジェクト。

まず、なぜ「オブジェクトである機能」を理解することが第一

次のコードを見てください:

function Person() {...}
console.log(Person.constructor) // 输出结果:[Function: Function]
// 上面是普通函数声明方法,生成具名函数,在声明时就已经生成对象模型。
console.log(Function.constructor) // 输出结果:[Function: Function]
console.log(Object.constructor) // 输出结果:[Function: Function]
复制代码

上記のコードはPerson機能を構築し、我々はその情報を見ることができますか?

  1. けれども人は関数として宣言されているが、それはまたすることができPerson.constructor、出力内容を。説明機能出力機能は、[通常の関数宣言]人物コンストラクタ関数です。
  2. 機能はまた、コンストラクタの関数です。
  3. 関数オブジェクト機能も内蔵されているオブジェクトなどコンストラクタ。

実際には、三点以上のダウン要約である:でJS、関数は、目的関数の関数のインスタンスです。つまり、私たちは、そのオブジェクトの関数であると言います。以下のコードとほぼ同じ実際に関数宣言上記コード:

// 使用Function构造器创建Function对象
var Person = new Function('...')
// 几乎?因为这种方式生成的函数是匿名函数[anonymous],并且只在真正调用时才生成对象模型。复制代码

JSでは、機能とオブジェクト間の関係は、以下のものが含まれています。

IMG

概要:オブジェクトが関数によって作成され、関数はFunctionオブジェクトのインスタンスです。

二、実際には非常に純粋なコンストラクタ

__proto__無視し、プロトタイプ、直接理解コンストラクタ、コード例:

function Person() {}
var person1 = new Person()
var person2 = new Person()
复制代码

以下は、そのコンストラクタポイント(__proto__やプロトタイプを無視して)の絵を描きます。

IMG

図、青色の背景人がオブジェクトのインスタンスである、人、機能は、(オブジェクト)の関数です。

まず第一に、我々はすでに、すべてのオブジェクトができることを知っている对象.constructorオブジェクトのコンストラクタポイントを作成します。各オブジェクトにそのようなAのconstructorプロパティがあることを前提とし、次のように理解してみましょう:

注: constructorプロパティは、それ自体がオブジェクト属性にこの一般化して、破線のボックスの理解を容易にするために、第三のポイントは、細部に入る、常に属性オブジェクトそのものではありません。

  1. PERSON1とPERSON2人機能することを彼らのコンストラクタを作成するために、それらのコンストラクタ点Personオブジェクトのインスタンスです。
  2. 人は、そのコンストラクタポイントはそのコンストラクタ、すなわち関数functionを作成する機能に加えて、オブジェクトの関数インスタンスです。
  3. ファンクション機能として、それはJSが内蔵されているオブジェクト、最初のポイントは、我々はすでに自分自身の内側constructorプロパティポイント、それは独自のコンストラクタであることを知っています。

したがって、constructorプロパティは、実際に自分のコンストラクタの参照、他の特別な場所を保存するために使用されるプロパティです。

以下では、オブジェクトの機能機能意志オブジェクトの独自のインスタンスなどのオブジェクトのすべての例では、より良い概念を理解するその特異性を除去することです。

三、プロトタイプはの理由登場です

最後のステップでは、次のコードを記述し、非常に簡単に理解することがあり、その後、同じオブジェクトの2つのインスタンスを加えた方法の効果の人に行くように頼みます:

// 下面是给person1和person2实例添加了同一个效果的方法sayHello
person1.sayHello = function() {
    console.log('Hello!')
}
person2.sayHello = function() {
    console.log('Hello!')
}
console.log(person1.sayHello === person2.sayHello) // false,它们不是同一个方法,各自占有内存复制代码

次のように示します:

IMG

あなたは二つの方法を比較するために行くとき、あなたは彼らがちょうど同じ効果あることがわかります、同じ名前が、本質的に、それは、それぞれがメモリの一部を占める異なる方法を。問題のこの時間は、今回も同様の効果ようにインスタンス(文字通り)の数千人は、そのメモリは、我々が爆発しようとしているがあります。このとき、プロトタイプは、問題を解決するために登場しました。

プロセスは、同じ効果の多数のインスタンスを追加する必要がある場合、それらは共通の効果を共有するために、プロトタイプオブジェクトに格納され、これらの実施例のプロトタイプオブジェクトのコンストラクタ関数にすることができます。コードは以下の通りであります:

Person.prototype.sayHello = function() {
    console.log('Hello!')
}
console.log(person1.sayHello === person2.sayHello) // true,同一个方法复制代码

次のように示します:

IMG

このフォームは、同じクラスのインスタンスは、単に、関連するプロパティやメソッドのと同じ効果を作成すると、メモリの一部を過ごすために、不要になっによるメモリの無駄を減らすことができますが、プロトタイプオブジェクトの関数や呼び出しを見つけるために、構造体に直接行くことができるという理由。

要約:放電特性とメモリ実質的酒に、実施例の同じタイプを共有する方法でオブジェクトのプロトタイプ。

讲到这里,你需要知道的是,所有函数本身是Function函数的实例对象,所以Function函数中同样会有一个prototype对象放它自己实例对象的共享属性和方法。所以上面的图示是不完整的,应改成下图:

IMG

其实里面的sayHello也是个函数,也有自己的prototype,但不画出来了,免得头疼。

注意:接下来的用【原型对象】表示【创建自己的构造函数内部的prototype】!

四、真正的constructor属性藏在哪

看到上面,有些小伙伴就头疼了,你说的constructor属性为什么我就没在console出来的对象数据中看到呢?

思考个问题:new Person( )出来的千千万万个实例中如果都有constructor属性,并且都指向创建自己的构造函数,那岂不又出现了第三点的问题,它们都拥有一个效果相同但却都各自占用一部分内存的属性?

我相信你们懂我的意思了,constructor是完全可以被当成一个共享属性存放在原型对象中,作用也依然是指向自己的构造函数,而实际上也是这么处理的。对象的constructor属性就是被当做共享属性放在它们的原型对象中,即下图:

IMG

总结:默认constructor实际上是被当做共享属性放在它们的原型对象中。

这时候有人会拿个反例来问:如果是共享属性,那我将两个实例其中一个属性改了,为啥第二个实例没同步?如下面代码:

function Person() {}
var person1 = new Person()
var person2 = new Person()
console.log(person1.constructor) // [Function: Person]
console.log(person2.constructor) // [Function: Person]
person1.constructor = Function
console.log(person1.constructor) // [Function: Function]
console.log(person2.constructor) // [Function: Person] !不是同步为[Function: Function]复制代码

这个是因为person1.constructor = Function改的并不是原型对象上的共享属性constructor,而是给实例person1加了一个constructor属性。如下:

console.log(person1) // 结果:Function { constructor: [Function: Function] }复制代码

你可以看到person1实例中多了constructor属性。它原型对象上的constructor是没有改的。

嗯。嗯?嗯?!搞事?!! 这下共享属性能理解了,但上面的图解明显会造成很大的问题,我们根本不能通过一个对象.constructor找回创建自己的构造函数(之间没有箭头链接)!

好的,不急,第四点只是告诉你为什么constructor要待在创建自己的构造函数prototype上。接下来是该__proto__属性亮相了。

五、__proto__让实例能找到自己的原型对象

带着第四点的疑问,我们如果要去解决这个问题,我们自然会想到在对象内部创建一个属性直接指向自己的原型对象,那就可以找到共享属性constructor了,也就是下面的关系:

  1. 实例对象.__proto__ = 创建自己的构造函数内部的prototype(原型对象)
  2. 实例对象.__proto__.constructor = 创建自己的构造函数

也如下图所示:

上面说的proto属性实际上也的确是这样的设置的,对象的__proto__属性就是指向自己的原型对象。这里要注意,因为JS内所有函数都是Function函数的实例对象,所以Person函数也有个__proto__属性指向自己的原型对象,即Function函数的prototype。至于Function函数为何有个__proto__属性指向自己(蓝色箭头)也不用解释了吧,它拿自身作为自己的构造函数,反正就是个特例,不讲道理。

疑惑来了:实例对象.constructor 等于 实例对象.__proto__.constructor?

这个就是JS内部的操作了,当在一个实例对象上找不到某个属性时,JS就会去它的原型对象上找是否有相关的共享属性或方法,所以上面的例子中,person1对象内部虽然没有自己的constructor属性,但它的原型对象上有,所以能实现我们上面提到的效果。当然后面还涉及原型链,你只要知道上面一句话能暂时回答这个问题就好。

疑惑来了:prototype也是个对象吧,它肯定也有个__proto__吧?

的确,它也是个对象,也的确有个__proto__指向自己的原型对象。那我们尝试用代码找出它的构造函数,如下:

function Person() {}
console.log(Person.prototype.__proto__.constructor) // [Function: Object]复制代码

因为__proto__指向原型对象,原型对象中的constructor又指向构造函数,所以Person.prototype.__proto__.constructor指向的就是Person中prototype对象的构造函数,上面的输出结果说明了prototype的构造函数就是Object函数(对象)。

总结:这么说的话其实函数内的prototype也不过是个普通的对象,并且默认也都是Object对象的实例。

下面一张图就画出了文章例子中所有__proto__指向,我们试试从中找出它的猫腻。

IMG

猫腻一、所有函数的__proto__指向他们的原型对象,即Function函数的prototype对象

在第一点我们就讲了所有的函数都是Function函数的实例(包括Function自己),所以他们的__proto__自然也就都指向Function函数的prototype对象。

猫腻二、最后一个prototype对象是Object函数内的prototype对象。

Object函数作为JS的内置对象,也是充当了很重要的角色。Object函数是所有对象通过原型链追溯到最根的构造函数。换句话说,就是官方动作,不讲道理的神仙操作。

猫腻三、Object函数的prototype中的__proto__指向null。

这是由于Object函数的特殊性,有人会想,为什么Object函数不能像Function函数一样让__proto__属性指向自己的prototype?答案就是如果指向自己的prototype,那当找不到某一属性时沿着原型链寻找的时候就会进入死循环,所以必须指向null,这个null其实就是个跳出条件。

上面谈到原型链,有些小兄弟还不知道是什么东西,那接下来看看何为原型链,看懂了再回来重新理解一下猫腻三的解释。

六、究竟何为原型链

在让我告诉你何为原型链时,我先给你画出上面那个例子中所有的原型链,你看看能不能看出一些规律。上面的例子中一共有四条原型链,红色线连接起来的一串就是原型链

IMG

左边的图:原型链也就是将原型对象像羊肉串一样串起来成为一条链,好粗暴的解释,但的确很形象。

右边的图:之前说过Person函数(所有函数)其实是Function函数的实例,假设把它看成一个普通的实例对象,忽略它函数身份以及prototype对象,其实它和左边图中的person1没什么区别,只是它们的__proto__属性指向了各自的的原型对象。

IMG

左边的图:Function函数因为是个特殊的例子,它的构造函数就是自己,所以__proto__属性也指向自己的prototype对象;但它的特殊性并不影响它的prototype对象依然不出意外的是Object函数的实例

右边的图:这个理解起来就很难受,因为Object函数和别的函数一样也是Function函数的实例,所以它的__proto__属性毫无例外地是指向Function函数的prototype对象,但是问题是Function函数中的prototype本身又是Object函数的实例对象,所以Function函数中的prototype对象中的__proto__属性就指向Object函数的prototype对象,这就形成“我中有你,你中有我”的情况,也是造成难以理解的原因之一。

为了更好地理解原型链,我打算忽略掉那讨厌的特例,Function函数。

IMG

忽略掉Function函数后你会发现好清爽!相信大家也发现了,__proto__属性在其中起着关键作用,它将一个个实例和原型对象关联在一起,但由于所关联的原型对象也有可能是别人的实例对象,所以就形成了串连的形式,也就形成了我们所说的原型链。

七、原型链引出新的继承方式

个人认为原型链的出现只是一次巧合,不是特别刻意的存在。但是这种巧合确实有它自己的意义。还记得我之前说过的两点吗:

  1. prototype对象保存着构造函数给它的实例们调用的共享属性和方法。
  2. 实例对象当没有某一属性时,会通过__proto__属性去找到创建它们的构造函数的prototype对象,并在里面找有没有相关的共享属性或方法。

那这时就很有趣了。prototype对象本身也有一个__proto__属性指向它自己的原型对象,上面有着构造函数留下的共享属性和方法。那这么说的话,假如当在自己原型对象上找不到相关的共享属性或方法时,对于它现在所在的prototype对象而言,也是一次寻值失败的情况,那它自然也会去它自己的原型对象上找,世纪大片图示如下:

IMG

现在来想想,假如Object函数内的prototype对象中__proto__属性不指向空,而指向自己的prototype?那不完了咯,死循环。

可能这时有小兄弟会问,这不就是一个不断找值的过程吗,有什么意义?但是就因为这种巧合,让一些可爱的人想到了一种新的继承方式:原型链继承

请看下面代码:

function GrandFather() {
    this.name = 'GrandFather'
}
function Father() {
    this.age = 32
}
Father.prototype = new GrandFather() // Father函数改变自己的prototype指向
function Son() {}
Son.prototype = new Father() // Son函数改变自己的prototype指向

var son = new Son()
console.log(son.name) // 结果输出:GrandFather
console.log(son.age)  // 结果输出:32
console.log(Son.prototype.constructor) // 结果输出:[Function: GrandFather]复制代码

相关指向图如下:

IMG

两边的图都是忽略了Function函数的,同时将一些没有必要展示出来的属性给忽略了,如各大函数的__proto__属性。

左边的图:在没有改变各个函数的prototype的指向时,默认就是左边的图片所示。每个函数的prototype都是默认情况下将它们内部的__proto__指向Object函数的(黑色箭头)。

右边的图:Father函数和Son函数都丢弃了它们各自的prototype对象,指向一个新的对象。这形成了三个新的有趣现象:

  1. Father函数中的prototype指向了GrandFather的实例对象,这时候这个实例对象就成为了Father函数以后实例的原型对象,顺其自然GrandFather实例对象内的私有属性name就变成了Father函数以后实例的共享属性;
  2. 同样的,Son函数中的prototype指向了Father的实例对象,将Father的实例对象内的私有属性age就变成了Son函数以后实例的共享属性。
  3. 它们的__proto__属性将它们串了起来,形成一条新的原型链。

上面的操作我们能看到Son函数以后的实例都能通过原型链找到name和age属性,也就是实现了我们所说的继承,继承了父类的属性。不过相信眼尖的我们会发现这种继承方式问题很大:

  1. constructor的指向不可靠了,像Son实例对象.constructor最后得到的值是沿着原型链找到的GrandFather函数。可我们自己清楚Son实例对象就该是Son函数,但却不在我们的意料之中。
  2. 所有所谓继承下来的属性全都是共享属性,好致命的问题。

所以,Emmm,了解一下就好。

八、学了要用系列 | 手写一个new

new关键词的作用一句话来说就是创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。而我们要去手动实现new关键词,无非就是组织一场认亲活动,环节有两个:

  1. 让一个对象承认自己的构造函数(爹)就是该构造函数
  2. 让这个构造函数承认这个对象就是它自己的实例(子)

① 先造个Person构造函数(爹)做例子

function Person(identity){
    this.identity = identity || 'Person'
}复制代码

② 爹有了,得有个子吧,那就创建一个空对象

var obj = {}
复制代码

上面的语句为字面式创建对象,实则等同于下面一句

var obj = new Object()
复制代码

也即说明创建的空对象其实都是Object函数的实例,这么一看,完了吧,子不认爹。

还记得我们上面讲的吗,所谓的“空对象“内部并不是真正空空如也,它们内部都有一个__proto__属性指向自己的原型对象。而上面代码中的obj对象也是毫不例外有个__proto__属性指向Object对象中的prototype。

我们知道当创建某一构造函数的实例,创建出的实例应该将__proto__属性指向该构造函数内的prototype对象,那我们就走走形式,让它重新认爹。

**③ 手动将实例中的__proto__属性指向相应原型对象。**

obj.__proto__ = Person.prototype复制代码

图解如下:

IMG

你可以看到当指向变化后,Person函数中的prototype成为实例对象obj的原型对象,而自然而然我们拿到的obj.constructor就对应变成了Person函数。换句话说,obj已经承认Person函数是它自己的构造函数,也就说我们完成了认亲活动的第一环节。

那问题来了,Person函数承认这个实例(子)吗?

如果Person函数内部没有设置像:this.identity = identity || 'Person'这些语句(设置私有属性/方法),其实它也就承认了,因为成为它儿子不需要别的资格。但是不巧,Person函数确实有设置,而这些语句就像在说:

“你要成为我儿子就需要有这个资格:拥有我设置的私有属性。但我认了你后,你改不改那个属性、要不要那个属性,我就不管了。

所以现在得进入第二环节:

④ 在实例的执行环境内调用构造函数,添加构造函数设置的私有属性/方法。

Person.apply(obj, arguments) // arguments就是参数复制代码

我们先要知道构造函数为啥叫构造函数:

构造函数是一种特殊的方法,主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值。

看到关键作用了吗?“为对象成员变量赋初始值”。

再看回“老爹”,Person函数:

function Person(identity){
    this.identity = identity || 'Person' 
}
console.log(Person.identity) // 结果输出:undefined
// 注意不要拿name这个属性做例子,每个函数声明后都自带一个name属性用来保存函数名
复制代码

疑惑:这里的this不是指向构造函数自身的吗?为什么Person函数没有identity属性?

長い物語の感覚は、1に削減されます。ボディー関数宣言文の機能がすぐに実行されますが、真の呼び出した場合に実行されません。だから、これもポイントがなかった、または単にので、当然、それはすぐに自分がID属性を割り当て与えることはありません、財産、コードのちょうどスニペットとして扱われていない何も呼び出しはありません。実際にはそんなに、それはそうコンストラクタ本体ケースという、適用インスタンスのコンストラクタメソッドの呼び出しをリードして自分自身に本当のこの時点で、自分自身のために対応する初期プロパティ値を得ました。引数のように初期値のセットとして使用することができる方法のパラメータを調整するために、対応するパラメータです。

プロセスが完了した後、実施例は、以下に示すように、プロパティおよびメソッドは、コンストラクタ人内部要件を提供しています。

IMG

この時点で我々は、人のコンストラクタが正常に完了の第二部で独自のインスタンスであり、OBJ、このオブジェクトを認めるように完了しました。

次のように⑤プロセスコードがあります:

// 构造函数登场
function Person(identity){
    this.identity = identity || 'Person'
}
// 实例对象登场
var obj = {}
// 环节一:让obj承认自己的构造函数(爹)就是Person函数
obj.__proto__ = Person.prototype
// 环节二:obj调用Person,拥有Person给孩子们设置的属性/方法
// 让Person函数承认这个对象就是它自己的实例(子)
Person.apply(obj, ['son'])
// End 完成,验证
console.log(obj.constructor) // 输出结果:[Function: Person]
console.log(obj.identity) // 输出结果:son复制代码

上記次のように私たちも、それをカプセル化するために必要な新しい方法を実現するために、プロセスのうち、対象のちょうど新しいインスタンスであります:

新しいメソッドに⑥パッケージ

// 构造函数登场
function Person(identity){
  this.identity = identity || 'Person'
}
// 封装自己的new
function _new(Fuc) {
  return function() {
    var obj = {
      __proto__: Fuc.prototype
    }
    Fuc.apply(obj, arguments)
    return obj
  }
}
// 封装完成,测试如下
var obj = _new(Person)('son')
console.log(obj.constructor) // 输出结果:[Function: Person]
console.log(obj.identity) // 输出结果:son
复制代码

パーフェクト、幸せ!拍手!

ナイン、要約

どのようにマインドマッピングの最近の研究で、その直接のNa Siweiがまとめたマップを作ってみます。

IMG

IMG

IMG

この記事を書いた後、多くは、もちろん、私は内部のいくつかのポイントが正しいことを確認し、ポイントのほとんどは私の前任者組み合わせのポストであり、より多くのいくつかは正当化できるまとめた独自の考えを追加しないだということは明らかです引数。いずれかの重大なエラーがある場合は、ギャングの前任者をお読みいただき、ありがとうございました、そして必ず理解を作りました。

最後に、ブログの記事のおかげで、私はこれらの基本的な概念を知ってもらうよう:

あなたが徹底的にプロトタイプを知ってもらうJSのヘルプ、__ proto__およびコンストラクタ(グラフィック)

あなたが思うなら、私は何回かは、上記の記事を書い吸う行くことができます。

終わり

おすすめ

転載: www.cnblogs.com/isAndyWu/p/11915637.html