什么是原型:
我们可以将原型理解为Java或C#中父类的概念,但并不完全等同。
例如:
let arr = new Array();
我们创建了一个数组arr,arr是通过new 关键字实例化的Array对象,我们称Array为arr的原形。我们打印一下arr的结构,我们发现arr的Prototype就是指向Array
展开arr的原形Array我们可以发现
Array的原形为Object类型,而Object之上就没有原型了所以Object的原型为null,也就是Js原生的原型的最高对象为Object。
原型的好处:
使用原型的好处就是我们在处理数据的时候可以使用原型链上的方法和参数。
例如:
Array原型上拥有pop和push这两个方法,可以用于压入和弹出数据。
但是我们的arr本身并没这两个方法。
但并不会影响我们使用原型链上的pop,push方法
let arr = new Array();
arr.push('111');
console.dir(arr);
console.log(arr);
arr.pop('111');
console.dir(arr)
console.log(arr)
这是因为,在使用方法或参数时会现在本地寻找,若找不到,则会去原型上查找,一直查找到*原型链还是找不到则会返回方法未定义错误。我们把这个过程称为原型链攀升。
什么类型拥有原型:
通过arr的原型实例我们可以知道,JS中的一些原生类型皆为对象(Object),包括函数方法。只要是对象就会有原型。
对象和函数在原型的拥有上有何不同:
在对象中有且只拥有一个对象,那就是下图中的Prototype,老版本中浏览器会将该属性显示为__proto__,是浏览器为了获取到对象的原型而定义的一个属性。
注:虽然现在改为了Prototype但仍可以用过__proto__来获取到对象的原型.
而函数的对象,如下图所示,处理拥有Prototype属性还拥有一个不可枚举的prototype对象。
二者的区别在于Prototype是函数对象本身的原型,也就是f()函数原型,而prototype是函数所实例化的对象继承的原型。prototype里面包含了原型和constructor构造函数。
我们可以将其实例化:
function Fun() {
}
console.dir(Fun)
let fun = new Fun();
console.dir(fun);
可以看到实例化出来的对象结构与Fun函数的结构一样。我们可以判断Fun.prototype和fun.__proto__是否相等:
console.log(Fun.prototype == fun.__proto__)
结果很明显,两者相等。
这里可能会有一个疑惑,在fun当中Prototype的属性值为Object而Fun函数则不是,其实不然,我们将属性折叠:
我们可以看到prototype用{}进行包裹,说明这是一个对象类型,分别包含两个属性:实例化后的对象原型Prototype和实例化对象的指向的构造函数constructor。
如何继承原型:
在JS中存在多种继承原型的方法:
- 通过修改__proto__进行继承:
我们先创建一个函数和一个对象
function Animation() {}
let a = {};
此时两个对象的结构为:
Aniamtion:
a:
我们可以看到此时的a的原型是指向Object的,若进行修改可以使用:
a.__proto__ = Animation;
再次查看a的原型指向:
但这种方法只适用于对象,在函数中因为有构造函数constructor的存在,使用 这种方式会将constructor覆盖。可以使用方法2.
若存在两个函数A和B,想实现B对A的原型继承,因为我们知道,函数存在两个原型,一个自身的原型Prototype,和实例化对象的原型prototype,因为我们函数一般都是想要B函数实例化后的对象能够使用A函数的方法,所以我们要是使用:
B.prototype.__proto__ = A.prototype;
结果如下:
我们测试在A函数原型中添加show方法,输出‘这是A函数的show方法’:
A.prototype.show = function() {
console.log('这是A函数的show方法');
}
通过B函数的实例化对象调用:
let b = new B();
b.show();
结果:
此时我们可以理解为B实例化对象继承了A方法。
那我们要实现B函数本身对A函数的继承呢?
可以使用:
B.__proto__ = A.prototype;
此时我们再次查看B的结构:
此时B函数的实例化对象b就无法使用show方法了,因为b实例化指向的是B函数的实例化原型,而我们只是在B函数本身的原型上继承了A函数。此时用B调用show方法是可行的。
使用场景:我们在办公后台管理系统中,有一个角色基函数Role:
function Role() {}
Role.prototype.show = function() {
console.log(this.type);
}
我们抽象出type这个类型,因为此时我们Role基函数并没有type变量,所以这是一个抽象结构。
我们此时又创建了Admin,GUser两个函数,一个代表的是超级管理员一个代表的是普通用户。此时我们这两个函数中都要实现输出自己类别的功能,若每个函数都去写show方法,整个程序代码冗余度会很高,所以我们可以继承Role这个基类来实现对自身类型输出的功能。函数定义如下:
function Admin() {
this.type = '超级管理员';
}
function GUser() {
this.type = '普通用户';
}
实现原型继承:
Admin.prototype = Role.prototype;
GUser.prototype = Role.prototype;
let admin = new Admin();
let guser = new GUser();
输出show方法得到的结果:
为指向对象初始化其值,并未实现方法的继承。
2.通过Object.create方法
用法:
function A() {}
let a = {};
a.__proto__ = Object.create(A.prototype);
结果:
或者简写:
let a = Object.create(A.prototype);
Object.create的原理:
会创建一个空对象指向A.prototype,然后将创建的对象赋值给a
Object.create可能带来的问题:
如果函数A和B,B继承A:
function A() {}
function B() {}
B.prototype.time = function() {}
console.dir(B);
B实例原型:
此时B函数有一个time方法
若此时我们在进行原型链继承:
B.prototype = Object.create(A.prototype);
如果改变B的prototype,则B原有的time函数会被覆盖掉,并使得函数原型的构造函数丢失,所以我们要这样写:
function A() {}
function B() {}
console.dir(B);
B.prototype.__proto__ = Object.create(A.prototype);
B.prototype.time = function() {}
输出结果:
2.Object.setPrototypeOf
使用create方法为函数设置原型时,需要指定prototype中的__proto__
但是在setPrototypeOf中则不需要使用了,改写方法:
Object.setPrototypeOf(B, A.prototype);
可以将上述Create实现的代码进行简化。
将参数1的原型设置为参数2
同时获取对象原型为:
Object.getPrototypeOf(B)
这里获取的B函数本身的原型而非实例化对象原型:
bject.getPrototypeOf(B) == B.__proto__
返回结果为ture