昨天发现这篇自己初学半年时写的文章,当时被各种原型链的知识玩懵了,也算是写了这篇文章之后,才对原型链有了更深刻的理解和认识。搬运过来当做复习~
原型(Prototype)——构造器(Constructor)
当我们谈论原型,我们在谈论什么——对象。
过去,我们这样声明一个对象:1
2
3
4var a = {
a:1,
b:2
};
当对象是一个用户时:1
2
3
4
5var whh = {
name: '王花花',
age: 18,
gender: 'female'
};
当我们需要多一个用户时1
2
3
4
5var lsd = {
name: '李拴蛋',
age: 19,
gender: 'male'
};
两个对象的结构毫无区别。
那么问题来了,如果我们需要创建100个用户时,也要这么做吗?Of Course Not ✘
两个原因:
- 懒。
- 用户的信息是动态的,我们不能在代码中把它们写死。
工厂函数现身
所以伟大的先人就想到一种办法简化我们的工作:创建一个function专门为我们生成user1
2
3
4
5
6function user(name, age, gender) {
var person = {};
person.name = name;
person.age = age;
person.gender = gender;
}
测试代码:1
2
3
4
5
6console.log(user('王花花', 18, 'female'));
/*{
name: '王花花',
age: 18,
gender: 'female'
}*/
所以,这个名为user的函数的本质就是生成对象。这样的函数被称为工厂函数(Factory Function),专门生产对象。
更加简便的方式:1
2
3
4
5function user(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
this指代它即将生成的对象
工厂函数的使用方式:new
关键字。1
var whh = new user('王花花', 18, 'female');
通常我们称作使用new关键字叫的函数为构造器/构造函数(Constructor),构造函数的首字母一般大写。
原型————prototype和proto
生成一个对象的过程叫实例化,这在任何一种语言中都一样。1
var whh = new user('王花花', 18, 'female'); //实例化
接下来给这个构造器添加一些功能1
2
3
4
5
6
7
8
9
10function User(name, age) {
this.name = name;
this.age = age;
this.eat = function() {
console.log('mia mia mia ...')
}
this.greet = fuction() {
console.log(`Yo, 我叫${this.name}, 我今年${this.name}岁了.`);
}
}
测试代码:1
2
3
4var whh = new User('王花花', 19);
var lsd = new User('李拴蛋', 22);
console.log(whh.greet()); //Yo, 我叫王花花, 我今年19岁了.
console.log(lsd.greet()); //Yo, 我叫李拴蛋, 我今年22岁了.
可见两个实例化的user都实例了greet能力。也就是说,whh这个对象中有greet这个方法,lsd这个对象中也有同样的方法。这两个方法是完全独立的,可以通过如下方式验证:1
whh.greet === lsd.greet; //false
以上说明,每一次实例化都会创造一份与其它对象同样的方法。这里可以理解为拷贝代码,将构造器中的方法拷贝到实例化的对象中。
那么问题来了,真的有必要每一次实例化都要拷贝吗?
这时伟大的先人出现了,就引入了原型(prototype)概念。
在控制台中随意创建一个function a() {}
,然后输入a.prototype
,我们可以发现它是一个对象。
原生JS就有这样一个机制,在我们创建一个function时,它首先将prototype对象放在function下。
prototype用于存放给它即将生产的对象继承下去的属性。也就是我们一眼看不出来,但它实际拥有的能力。举个栗子:1
2
3
4function A() {};
A.prototype.name = 'lalala';
var a = new A();
console.log(a);
可以看到,它没有在自有的属性中显示name属性,而是在继承的属性中显示。但使用时与自有属性无区别,而且不管我们new了几个对象,它们中的proto都是一样的(其实是指向同一对象的)。
测试代码:1
a.__proto__ === b.__proto__; //true
知道这个原理,就可以优化上面的User构造器了。1
2
3
4
5
6
7function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.greet = function() {
console.log(`Yo, 我叫${this.name}, 我今年${this.age}岁了.}`);
}
测试代码:1
whh.__proto__ === lsd.__proto__; //true
也就是说,现在的function只会存在一个地方,它不再需要每次都拷到实例的对象中去。
当我们想知道一个对象来自于哪个工厂函数,可以直接对象.consturctor
。
那么就意味着,如果我们想复制一个对象的结构,可以new whh.constructor()
,也就相当于new User()
。
原生对象的原型
抛出一个问题,当我们这样创建一个对象的时候,它的原型是什么呢?它有没有父亲呢?1
var a = {};
我们打印这个对象后可以看到,它的constructor是一个叫Object的函数。也就是说,以下两种写法是等价的:1
2var a = {};
var b = new Object();
测试代码:1
a.constructor === b.constructor; //true
再次抛出问题:当我们想创建一个不继承任何东西的对象时该怎么办呢?1
var a = Object.create(null);
可以在参数中设置原型,如:1
2
3
4var a = Object.create({
a:1,
b:2
});
测试代码:1
var a = [];
控制台中可以看到a的constructor是一个叫做Array的function。
也就相当于:var a = new Array()
。两种写法等价。
继续,我们可以看到Array()也有继承的属性,它的constructor是Object()。
综上,如果Object()是爷爷,那么Array()就是爸爸,而我们声明的数组对象a就是儿子。
总结: 在JS中只要我们不明确的用Object.create()来创建对象,其余都是继承Object.prototype的。
如何实现多级继承琏
有这样一条继承链:动物-哺乳动物-人类-lsd
如何在代码中实现这样一条继承链?1
2
3
4function Animal() {}
function Mammal() {}
function Person() {}
var lsd = new Person();
从最顶层开始看,实例两个对象。1
2
3
4
5
6
7
8
9
10function Animal() {
this.eat = function() {
console.log('mia mia mia...');
}
this.sleep = function() {
console.log('zzz...');
}
}
var a = new Animal();
var b = new Animal();
测试代码:1
a.eat === b.eat; //false
用原型优化构造器:1
2
3
4
5
6
7
8
9function Animal() {}
Animal.prototype.eat = function() {
console.log('mia mia mia...');
};
Animal.prototype.sleep = function() {
console.log('zzz...');
};
var a = new Animal();
var b = new Animal();
接下来看Mammal构造器1
2
3
4
5function Mammal() {}
Mammal.prototype.suckle = function() {
console.log('mia!');
};
var m = new Mammal();
那么此时实例化对象m是否有eat和sleep这种能力呢?
答案是:暂时还没有。
因为此时三个构造器是独立的,我们还没有指定它们之间的继承关系。那么如何指定这条继承琏?
拿Mammal和Animal来说,Mammal不仅要继承Animal的prototype,而且还要有自己的东西。
我们可以用到前面提到的create方法:1
Mammal.prototype = Object.create(Animal.prototype);
测试代码:1
2m.eat(); //mia mia mia...
m.sleep(); //zzz...
这样我们就可以说它继承成功了。
不过现在还有一个问题,我们可以查看一下m.constructor
。
打印结果为Animal而非Mammal。而事实上Mammal才是m对象的constructor。
仔细查看对象m的打印结果,发现并没有constructor。
这是因为我们在前面把它的prototype重写了,覆盖掉了。Mammal.prototype = Object.create(Animal.prototype);
所以才会继承上一级的constructor。
解决方案:重新明确指定即可。Mammal.prototype.constructor = Mammal;
]
接下来看Person。1
2
3
4
5
6
7function Person() {}
Persion.prototype.lie = function() {
console.log('你不帅');
}
Persn.prototype = Object.create(Mammal.prototype);
Person.prototype.constructor = Person;
var lsd = new Person();
到这里就完成了原型的三级继承~~~
不过还有一个问题:不同的动物毛色可能不一样,体重也不一样,等等等等。也就是说,它们回宫有一些属性,不过值不一样。
测试代码:1
2
3
4
5function Animal(color, weight) {
this.color = color;
this.weight = weight;
}
var a = new Animal('black', 120);
此时实例化的对象中就有了我们用this指定的两个显性属性”color”和”weight”。
但是当我们实例一个Mammal的对象并传入参数:1
var m = new Mammal('black', 100);
我们可以发现,是一个空对象。
我们希望的是,只要是动物(不管是哺乳动物还是人),都存在毛色、体重等等属性。如何实现?1
2
3function Mammal(color, weight) {
Animal.call(this, color, weight);
}
Person中同理1
2
3function Person(color, weight) {
Mammal.call(this, color, weight);
}
测试代码:1
2
3
4var m = new Mammal('black', 100);
var lsd = new Person('yellow', 180);
var whh = new Person('brown', 80);
console.log(m, lsd, whh);
可见,显性属性是拷贝的。它们之间互不影响。这就是显性属性的继承方式。
Bye~👋