Home  >  Article  >  Web Front-end  >  A detailed explanation of JavaScript’s prototype chain and inheritance in simple terms

A detailed explanation of JavaScript’s prototype chain and inheritance in simple terms

黄舟
黄舟Original
2017-03-03 15:25:561113browse

The inheritance mechanism of Javascript language has no concepts of "subclass" and "parent class", nor the distinction between "class" (class) and "instance" (instance). It all relies on one A very peculiar "prototype chain" model to implement inheritance.

This part of knowledge is also one of the core focuses in JavaScript, and it is also a difficult point. I organized my study notes to make it easier for everyone to study and to deepen my own impression. There are many details in this part of the code and require repeated consideration. So let's get started.

Series Catalog

  • Introduction to JavaScript’s Closure in a Simple and Easy Way

  • In-depth introduction to JavaScript’s this

  • In-depth introduction to the prototype chain and inheritance of JavaScript

Quick try

Prototype chain example (the key points are written in the comments, you can copy the code to browse Tested in the device, the same below)

function foo(){}              //通过function foo(){}定义一个函数对象
foo.prototype.z = 3;          //函数默认带个prototype对象属性   (typeof foo.prototype;//"object")

var obj =new foo();           //我们通过new foo()构造器的方式构造了一个新的对象
obj.y = 2;                    //通过赋值添加两个属性给obj
obj.x = 1;                    //通过这种方式构造对象,对象的原型会指向构造函数的prototype属性,也就是foo.prototype

obj.x; // 1                 //当访问obj.x时,发现obj上有x属性,所以返回1
obj.y; // 2                 //当访问obj.y时,发现obj上有y属性,所以返回2
obj.z; // 3                 //当访问obj.z时,发现obj上没有z属性,那怎么办呢?它不会停止查找,它会查找它的原型,也就是foo.prototype,这时找到z了,所以返回3

//我们用字面量创建的对象或者函数的默认prototype对象,实际上它也是有原型的,它的原型指向Object.prototype,然后Object.prototype也是有原型的,它的原型指向null。
                                   //那这里的Object.prototype有什么作用呢?
typeof obj.toString; // ‘function'  

//我们发现typeof obj.toString是一个函数,但是不管在对象上还是对象的原型上都没有toString方法,因为在它原型链的末端null之前都有个Object.prototype方法,
//而toString正是Object.prototype上面的方法。这也解释了为什么JS基本上所有对象都有toString方法
'z' in obj; // true               //obj.z是从foo.prototype继承而来的,所以'z' in obj返回了true
obj.hasOwnProperty('z'); 
// false   
//但是obj.hasOwnProperty('z')返回了false,表示z不是obj直接对象上的,而是对象的原型链上面的属性。(hsaOwnProperty也是Object.prototype上的方法)

Just now we accessed x, y and z and searched through the prototype chain respectively. We can know: when we access a certain attribute of the object When, and there is no corresponding attribute on the object, it will search upward through the prototype chain. If it still finds null, it will return undefined.

Prototype-based inheritance

function Foo(){
   this.y = 2;     
}

Foo.prototype.x = 1;
var obj3 = new Foo();  
//①当使用new去调用的时候,函数会作为构造器去调用②this会指向一个对象(这里是obj3),而这个对象的原型会指向构造器的prototype属性(这里是Foo.prototype)
obj3.y; //2 
obj3.x; //1    //可以看到y是对象上的,x是原型链上的原型(也就是Foo.prototype上)

prototype attributes and prototype

Let’s take a look at what kind of structure Foo.prototype is. When we use function declaration to create an empty function, then this function has a prototype attribute, and it has two attributes by default, constructor and _ _proto__,

constructor attribute will point to itself Foo, __proto__ is exposed in chrome (not a standard attribute, just know it), then the prototype of Foo.prototype will point to Object.prototype. Therefore, some methods toString and valueOf on Object.prototype

will be used by every general object.

function Foo(){}
typeof Foo.prototype; // "object"
Foo.prototype.x = 1;
var obj3 = new Foo();

To summarize: We have a Foo function here. This function has a prototype object attribute. Its function is When using new Foo() to construct an instance, the prototype of this constructor Properties will be used as prototypes for these new objects.

So we need to understand that prototype and prototype are two different things. Prototype is the default attribute on the function object, and prototype is usually the prototype attribute on the constructor.

Implementing one class inheriting another class

function Person(name, age) {
   this.name = name;    
   //直接调用的话,this指向全局对象(this知识点整理)
   this.age = age;      
   //使用new调用Peoson的话,this会指向原型为Person.prototype的空对象,通过this.name给空对象赋值,最后this作为return值
}

Person.prototype.hi = function() {   
//通过Person.prototype.hi创建所有Person实例共享的方法,(可以参考上节的左图:对象的原型会指向构造器的prototype属性,所以想让obj1,obj2,obj3共享一些方法的话,只需在原型对象上一次性地添加属性和方法就可以了);
   console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now.')//这里的this是全局对象
};

Person.prototype.LEGS_NUM = 2;   //再设置一些对Person类的所有实例共享的数据
Person.prototype.ARMS_NUM = 2;
Person.prototype.walk = function() {
  console.log(this.name + ' is walking...');
};

function Student(name, age, className) {  //每个学生都属于人
  Person.call(this, name, age);  //在Student这个子类里面先调用一下父类
  this.className = className;
}

//下一步就是我们怎么去把Student的实例继承Person.prototype的一些方法

Student.prototype = Object.create(Person.prototype);    
//Object.create():创建一个空对象,并且这个对象的原型指向它的参数  
//这样子我们可以在访问Student.prototype的时候可以向上查找到Person.prototype,又可以在不影响Person的情况下,创建自己的方法
Student.prototype.constructor = Student;  
//保持一致性,不设置的话constructor会指向Person

Student.prototype.hi = function() {    
//通过Student.prototype.hi这样子的赋值可以覆盖我们基类Person.prototype.hi
  console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now, and from ' + this.className + '.');
}
Student.prototype.learn = function(subject) {    
//同时,我们又有自己的learn方法
  console.log(this.name + 'is learning ' + subject + ' at' + this.className + '.');
};

//test
var yun = new Student('Yunyun', 22, 'Class 3,Grade 2');
yun.hi(); //Hi,my name is Yunyun,I'm 22 years old now,and from Class 3, Grade 2.
console.log(yun.ARMS_NUM); // 2     
//我们本身对象是没有的,对象的原型也就是Student.prototype也没有,但是我们用了继承,继续向上查找,找到了Person.prototype.ARMS_NUM,所以返回2
yun.walk(); //Yunyun is walking...
yun.learn('math'); //Yunyun is learning math at Class 3,Grade 2.

Combined with the diagram, let’s analyze the above code in reverse: We first create a Student through new Student Instance yun, the prototype of yun points to the prototype attribute of the constructor (here is Student.prototype). There are hi method and learn method on Student.prototype. Student.prototype is constructed through Object.create (Person.prototype), so here Student.prototype is an empty object, and the prototype of this object points to Person.prototype. Then we also set the LEGS_NUM, ARMS_NUM attributes and hi, walk methods on Person.prototype. Then we directly defined a Person function. Person.prototype is a preset object. It will also have its prototype. Its prototype is Object.prototype. It is precisely because of this that any of our objects will have hasOwnProperty , valueOf, toString and other public functions, these functions are all derived from Object.prototype. In this way, Inheritance based on the prototype chain is implemented. So what happens when we call the hi, walk, and learn methods? For example, when we call the hi method, we first check whether there is a hi method on the object yun, but there is no hi method in this instance, so we search upwards and find the prototype of yun, which is the hi method on Student.protoype, so we finally call it. The one is Student.prototype.hi, and calling other methods is similar.

Change prototype

We know that the prototype prototype in JavaScript is not like the class in Java. Once the class in Java is written, it is difficult to change dynamically, but in JavaScript The prototype is actually an ordinary object, That means that during the running stage of the program, we can also dynamically add or delete some attributes to the prototype.

Based on the above code, we already have the instance of yun, let’s proceed with the experiment:

Student.prototype.x = 101;        //通过Student.prototype.x把yun的原型动态地添加一个属性x
yun.x;   //101                    //那我们发现所有的实例都会受到影响
//接着我们做个有趣的实验
Student.prototype = {y:2};        //我们直接修改构造器的prototype属性,把它赋值为一个新的对象
yun.y;  //undefined               
yun.x;  //101                     //所以我们得出:当我们修改Student.prototype值的时候,并不能修改已经实例化的对象
var Tom = new Student('Tom',3,'Class LOL KengB');  
Tom.x; //undefined                //但当我们创建一个新的实例时,这一次x就不见了,
Tom.y; //2                        //并且y是新的值

So when the prototype is dynamically modified At this time, it will affect all created or newly created instances, but if you modify the entire prototype and assign it to a new object, it will not affect the created instances, but it will affect subsequent instances.

实现继承的方式

实现继承有多种方式,下面我们还是以Person和Student来分析

function Person() {
}

function Student() {
}

Student.prototype = Person.prototype; // 我们可不可用这种方式呢?这种方法是错误的:因为子类Student有自己的一些方法
//,如果通过这样子赋值,改变Student的同时也改变了Person。

Student.prototype = new Person(); //这种方式是可以实现的,但是调用构造函数有时候也是有问题的,比如要传进Person一个name和age
//,这里的Student是个类,还没实例化,这时候有些奇怪了,传什么都不是。

Student.prototype = Object.create(Person.prototype); //相对来说这中方式是比较理想的,这里我们创建了一个空的对象
//,并且对象的原型指向Person.prototype,这样我们既保证了继承了Person.prototype上的方法,并且Student.prototype又有自己空的对象。
//但是Object.create是ES5以后才有的

以上就是深入浅出JavaScript之原型链和继承的详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn