<JavaScript> constructor, prototype, __ proto__ prototype chain, and

In many read online related articles, many of them are ignorant forced to read, not to say that you predecessors poorly written, but that it is not easy to understand thoroughly once or twice in reading. After I read some articles, organize their own summary and draw some related figures, personally think that would be easier to accept and understand, share it here. Therefore all of the following understanding and illustrations are for personal understanding, if wrong place, please be sure seniors forgive me, and hard put forward error correction and below, I really worry about their immature theoretical foundation will mislead the rest of the little brother.

At first, let us talk about why this knowledge to understand how such a mess

There are three reasons for feeling:

  1. JS i.e. the function objects.
  2. Function Objects and Object object both the particularity of built-in objects.
  3. Many point to a diagram to explain the bells and whistles go on, have a headache looking at the [Manual dog's head].

Let me say, why the older generation of Internet-related articles are difficult to fathom

Many seniors are beginning when I start to explain the relevant knowledge from __proto__, but in my opinion, __ a close relationship with the proto__ prototype is not singled out in terms of (alone speak it means it is difficult to understand); and prototype and constructor and closely related, ** which resulted in a very awkward situation, we must first talk __proto__ will inevitably need to explain prototype and constructor property ** this is why for us in terms of these white the concept is so difficult to understand. (More than personal opinion, for reference only)

Then talk about my personal way of understanding adopted

To make it easier, more motivated to understand thoroughly, I used to __proto__ from ** constructor prototype chain step by step "dismantling" ** way to understanding, hope to have good results. The article reads as follows:

  1. To understand why "function that is the object"
  2. constructor is very pure
  3. prototype is why the emergence of
  4. Possession of real property in which the constructor
  5. __proto__ let instance to find their own prototype object
  6. What exactly prototype chain
  7. Prototype chain lead to new inheritance
  8. Learn to use the series | a new handwriting
  9. to sum up

Finally, talk about the need to look down to know the little knowledge:

① when any one function to create a general class of objects, it is called a constructor or a constructor.

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

The above Code Person () is the constructor person1 and person2.

② can 对象.constructorcreate a constructor to get the instance of an object.

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

Person constructor function is person1 object.

③ Function Function and Object JS functions are built-in objects, also known as inner classes, packaged JS own class, so a lot of strange, unexpected set without too much entangled in fact, official action, fairy operations.

④ i.e. prototype object prototype object in its own instance of an object constructor.

First, the first to understand why "function that is the object"

Look at the following code:

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

The above code constructs a Person function, we can see that information?

  1. Although Person is declared as a function, but it also can Person.constructoroutput content. Description Function output function is [Normal function declaration] the Person constructor function.
  2. Function is also a function of their constructor.
  3. Function Object function is also built-in objects such constructor.

In fact, the top three o'clock conclude that one: in the JS, the function is an instance of an object Function function. That is, we say that is a function of the object. The code above function declaration in fact almost identical to the code below:

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

In JS, the relationship between function and object contains the following:

img

Summary: The object is created by the function, the function is a Function object instance.

Two, constructor actually very pure

Ignore __proto__ and prototype, direct understanding constructor, code examples:

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

The following will draw a picture of their constructor points (ignoring __proto__ and prototype):

img

FIG, blue background Person is an instance of an object, and Person, Function is a function of (and object).

First of all, we already know that every object can 对象.constructorcreate the object's constructor points. Let's assume that there is such a constructor property on each object, and then understood as follows:

Note: constructor property is not always attribute object itself, only for ease of understanding this generalization it into the object itself attributes, dashed box, third points go into detail.

  1. person1 and person2 is an instance of the Person object, their constructor points to create their constructors that Person function;
  2. Person is a function, but also a Function instance of an object, its constructor points to create its constructor, ie Function function;
  3. As Function function, it is JS built-in objects, the first point we already know that it is its own constructor, the constructor property points inside themselves.

Therefore the constructor property is actually a property used to save their own constructor references, no other special places.

In the following are all examples of objects as Function Function will object's own instance of an object, by removing its specificity to better understand the concepts.

Three, prototype is why the emergence of

The last step is very easy to understand, and then ask you to go to the Person of the two instances of the same objects plus the effect of a method, you write the following code:

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

Illustrated as follows:

img

When you go to compare the two methods, you will find that they are just the same effect, the same name, in essence, it is each occupies a part of the memory of different methods. This time on the problem, if this time there are thousands of instances (literally) to the same effect such way, that memory are we going to explode. At this time, prototype appeared to solve the problem.

When the process is required to add a large number of instances of the same effect, they may be stored in the prototype object, and on the prototype object constructor function of these examples, to share a common effect. code show as below:

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

Illustrated as follows:

img

The reason that this form can reduce the waste of memory, due to no longer need to spend part of the memory as instances of the same class simply create the same effect of the relevant property or method, but you can go directly to the structure to find the prototype object functions and calls.

Summary: the prototype for an object in a discharge properties and methods of sharing the same type of example, to the memory substantially sake.

讲到这里,你需要知道的是,所有函数本身是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属性?

The feeling of a long story, is reduced to one: the function of the body function declaration statement is not executed immediately, but executed if true calling. So there's no call when this did not even point, or simply not be treated as property, just a snippet of code, so naturally it will not immediately give yourself assign a identity attribute. In fact so much, it is to lead the apply instance constructor method call, so that the constructor body case are real this point to themselves, and to give the corresponding initial property values ​​for themselves. As for the arguments is the corresponding parameters, how to adjust the parameters can be used as the initial value set.

After the process is complete, the Examples also have properties and methods provided constructor Person internal requirements, as shown below:

img

At this time we have completed so that the Person constructor admit this object obj is its own instance, which is the second part of the successful completion.

⑤ the process code is as follows:

// 构造函数登场
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复制代码

The above is just a new instance of the object out of the process, to realize the new method we also need to encapsulate it, as follows:

⑥ package into the new method

// 构造函数登场
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
复制代码

Perfect, happy! applaud!

Nine, summary

A recent study in how mind mapping, so try to make maps directly Na Siwei summarizes:

img

img

img

After writing this article, it is clear that a lot, of course, I am not sure some of the points inside is correct, most of the points are my predecessors combined post and add your own thoughts summed up some of the more can be justified statement. Thank you for reading Gangster predecessors, if there is any serious errors, and made sure understanding.

Finally, thanks to a blog post so I get to know these basic concepts:

JS help you get to know thoroughly the prototype, __ proto__ and constructor (graphic)

If you think I can go a few times suck written the above article.

End

Guess you like

Origin www.cnblogs.com/isAndyWu/p/11915637.html