Web前端的知识之旅哟——原型与原型链

原型

1.原型的定义:原型是function对象的一个属性,它定义了构造函数制造出来的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法原型也是对象


1. function Person() {} 
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们先定义一个构造函数,Person.prototype这个属性就是这个构造函数的原型,这个属性是天生就有的,并且这个属性的值也是一个对象。

我们可以在prototype上面添加属性和方法,每一个构造出来的对象都可以继承这些属性和方法。


1. Person.prototype.name = ‘scarlett’;
2. Person.prototype.age = 17;
3. var oPerson = new Person();
4. console.log(oPerson.name); // scarlett
5. console.log(oPerson.age); // 17
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

虽然每一个对象都是独立的,但是他们都有共同的祖先,当我们访问这个对象的属性的时候,如果它没有这个属性,就会向上找到它的原型,然后在原型上访问这个属性


1. Person () {
2.       this.money = 100;
3. }
4. Person.prototype = {
5.       money: 200;
6. }
7. var oPerson = new Person();
8. console.log(oPerson.money); // 100
9. delete oPerson.money;
10. console.log(oPerson.money); // 200
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里我们oPerson对象因为自身有一个money属性,所以就不会到原型上去寻找money属性,而是查询自身的money属性,因此打印的是100,但是当我们删除了自身的money属性之后,它就会到原型上去寻找money这个属性,因此就打印200。


1. oPerson.money = 1000;
2. console.log(oPerson.money); // 1000
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当我们再次给对象添加属性之后,打印money属性就是自身的属性。

2.利用原型特点和概念,可以提取公有属性

我们可以把每一个对象都有的公有属性不写在构造函数里面,而是提取到原型上,这样当我们用构造函数构造大量的对象的时候就不需要走多次构造函数里面的赋值语句了,而只需要走一遍,每一个对象调用属性的时候直接上原型上查找就可以了。

3.对象如何查看原型

前面我们提到了构造函数可以通过.prototype的方法来查看构造函数的原型,那么我们怎么查看对象的原型呢?

我们前面提到过用构造函数构造对象的时候,会隐式创建一个this对象,这个this对象里面有一个默认的属性叫做proto属性,这个属性的值就是指向的这个对象的原型。


1. var this = {
2.       // xxx
3.       __proto: Person.prototype; // 刚才Person构造函数的例子
4. }
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当查找的属性是自身没有的属性的时候,就会先查找proto这个属性,然后这个属性指向了原型,所以就到原型上面继续查找属性了。


1. Person.prototype = 100;
2. oPerson.proto.money // 100
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

注意:prototype是函数的属性,proto是对象的属性

4.对象如何查看构造自身的构造函数

在prototype里面,有一个隐式的属性叫做constructor,这个属性记录的就是对象的构造器,里面存的就是构造函数。

console.log(oPerson.constructor); // Person();

原型链

有了原型,原型还是一个对象,那么这个名为原型的对象自然还有自己的原型,这样的原型上还有原型的结构就构成了原型链。


1. Gra.prototype.firstName = ‘scarlett’;
2. function Gra () {
3.       this.name = ‘grandfather’;
4.       this.sex = ‘male’;
5. }
6. var grandfoo = new Gra();
7. grandfoo.word = ‘hello’
8. Foo.prototype = grandfoo;
9. function Foo () {
10.       this.age = 18;
11.       this.money = 100;
12. }
13. var father = new Foo();
14. function Son () {
15.       this.name = ‘son’;
16. }
17. Son.prototype = father;
18. var son = new Son();
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

Foo创造出来的每一个对象都继承自grandfoo这个对象,son的每一个对象都继承自father这个由Foo创造出来的对象,这样son就可以继承上面Foo和Gra的所有属性。

当我们查找son上的属性的时候,如果son自身有属性,那么就打印出来,如果没有,就向上查找原型father,如果father上面还有这个属性,那么继续向上查找grandfoo,如果有就输出,如果没有就返回undefined。


1. console.log(son.name) // son
2. son.money //100
3. son.age// 18
4. son.sex = ‘male’;
5. son.firstName // scarlett
6. son.word // hello;
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这种链式的查询结构就叫做原型链。

那么原型链有没有终点?我们写的Gra是不是这个原型链的终点?


1. console.log(Gra.prototype); // Object{‘name’: ‘scarlett’}
2. console.log(Gra.prototype.proto); //Object{}
3. console.log(Gra.prototype.proto.proto); // null
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

有测试可以看出,其实我们的Gra.prototype上面还有原型,这个原型是一个空对象,这个空对象上面就没有了。

其实,绝大部分的对象最终都会继承自Object.prototype这个对象


1. var obj = {};
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们没有规定原型的对象,它们的原型就是Object.prototype


1. var obj = new Object();
2. console.log(obj.proto); // Object{}
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

由此可见,原型链的终点一般是Object.prototype;

但是并不是所有的对象都有原型

我们上一篇提到过第三种构造对象的方法,使用Object.create方法。

Object.create()方法需要写一个参数,这个参数就是我们这个对象的原型。如果我们想要构造和var obj = {};一样的空对象,那么就需要写:


1. var obj = Object.create(Object.prototype);
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

当然,我们也可以写一个自定义的属性,让它成为原型。


1. var obj = Object.create({name: ‘scarett’);
2. console.log(obj.name); //scarlett
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

但是,当我们写参数为null的时候,我们就构造出来了一个没有原型的对象


1. var obj = Object.create(null);
2. console.log(obj.proto); // undefined
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

undefined null也都没有原型。它们之所以能打印出来,是因为不调用任何方法的,直接打印出来。

原型链上属性的增删改查

其实我们前面一直在使用着这些方法,这里说一下原型上的引用值。当我们通过一个对象改变了原型上的引用值类型的属性的话,那么所有对象的这个属性的值都会随之更改。


1. Person.prototype.arr = [1, 2, 3];
2. var person1 = new Person();
3. var person2 = new Person();
4. person1.arr.push(4);
5. console.log(person2.arr); // 1 2 3 4
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

删除


1. Person.ptototype.name = ‘father’;
2. Person () {
3.       this.name = ‘son’;
4. }
5. delete person.name;
6. person.name// father
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这个时候person对象上面没有了name属性,那么依据我们前面说的当自身没有这个属性的时候就会向原型查询这个属性的说法,我们再次删除这个属性是不是就可以删除原型上的属性了?


1. delete person.name;
2. person.name // father
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

然而事实并没有,由此可见,对象并不能删除原型上的属性


1. Person.prototype.name = ‘scarlett’;
2. Person.prototype.sayName = function () {
3.       console.log(this.name);
4. }
5. function Person() {
6.       this.name = ‘son’;
7. }
8. var oPerson = new Person();
9. oPerson.sayName();// son
JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这里提一下这个this的指向。谁调用这个方法,这个方法中的this就指向这个调用它的对象

所以这里的this指向的是oPerson这个对象,这个对象的name属性值是son。

那么今天的知识点就介绍到这里哟~