原型对象
每个对象都有一个原型prototype
对象,通过函数创建的对象也将拥有这个原型对象。原型是一个指向对象的指针。
- 原型类似其他面向对象语言中的父类(基类)
- 所有对象的原型默认是
Object
的实例 - 原型包含
constructor
属性,指向构造函数,所以可以通过一个实例的对象找到其构造函数创建另一同类型对象 - 对象包含
__proto__
指向他的原型对象,__proto__
不是对象属性,理解为prototype
的 getter/setter 实现,他是一个非标准定义 - 多个对象共同继承一个原型可以共享原型中的成员,实现代码复用,可以解决构建函数创建对象时复制多个函数造成的内存占用问题
- 继承是为了复用代码,继承的本质是将原型指向到另一个对象
默认情况下创建的对象都有原型,下面的代码展示数组对象的层次,其中包含了变量arr
的原型Array
的成员:
let arr = ["a"];
//dir()可以像文件目录一样打印对象
console.dir(arr);
输出:
以下x、y的原型都为元对象Object
let a = {};
let b = {};
console.log(a,b);
输出:
函数的原型
函数比较特殊,prototype
用于实例对象时使用,__proto__
用于构造函数时使用。因为函数比较特殊,既可以被当做构造函数产生一个对象。也可以本身是function
创建的一个实例对象。
function User(){};
let lisi = new User();
//通过User构造函数实例的lisi实例对象的__proto__直接指向Object
console.log(lisi.__proto__);
//构造函数的prototype指向Object
console.log(User.prototype);
console.log(lisi.__proto__ == User.prototype);
//构造函数的__proto__指向其父亲 function
console.log(User.__proto__);
输出:
关系图如下:
构造函数与原型对象
下面是使用构造函数创建对象的原型体现:
- 构造函数拥有原型对象,利用
prototype
属性访问 - 构造函数在创建对象时把原型赋予对象
- 对象原型在配置后可以通过
constructor
(指向构造函数)访问构造函数 - 实例对象可以直接通过
__proto__
访问对象原型 - 可以通过实例对象的
constructor
访问构造函数,但是constructor
本质上是对象原型的属性
下面的示例展示利用对象实例arr找到其原型对象再利用其构造函数创建一个新的对象
let arr = new Array([1,2,3,4,5]);
//利用对象实例arr找到其原型对象再利用其构造函数创建一个新的对象
let newArr = new arr.__proto__.constructor([6,7,8,9,10]);//new可以省略
console.table(newArr);
输出:
原型链
在前面我们可以了解到,JS中大部分的数据类型其实都是对象类型的。如果仔细看上面代码中Array
构造的原型其实就是Object
类型的一个实例对象,可以使用__proto__
属性访问到Object
。
执行console.log(Array.prototype);
可以在控制台看到Array
的原型对象中的__proto__
指向Obeject
的构造的原型对象
同时也就意味可以直接通过Array.prototype.__proto__
访问Object
的原型对象
有如下的关系图:
在上面的橙色标记的引用链中,通过引用类型的原型,继承另一个引用类型的属性与方法,这也是实现继承的步骤。
创建原型链
- 使用
Object.setPrototypeOf
可设置对象的原型。下面的例子将创建一条原型链,使obj3
继承obj2
,obj2
继承obj1
。obj3
将同时拥有三者的属性。
let obj1 = {
prop1 : "obj1"
}
let obj2 = {
prop2 : "obj2"
}
let obj3 = {
prop3 : "obj3"
}
//继承关系如下 obj3 -> obj 2 -> obj1
Object.setPrototypeOf(obj3,obj2);
Object.setPrototypeOf(obj2,obj1);
console.log(obj3.prop1);//输出:obj1
console.log(obj3.prop2);//输出:obj2
console.log(obj3.prop3);//输出:obj3
- 采用构造函数直接赋值的方式也可以创建原型链:使
C的原型
继承B的原型
,B的原型
继承A的原型
function A(){};
function B(){};
function C(){};
let a = new A();
B.prototype = a;
let b = new B();
C.prototype = b;
let c = new C();
- 使用
Object.create
创建一个新对象时使用现有对象做为新对象的原型对象
let a ={};
//使b继承于a
let b = Object.create(a);
原型检测
instanceof
关键字可以用来检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上,即会向上对比是否为该类型。
function A(){};
function B(){};
function C(){};
let a = new A();
B.prototype = a;
let b = new B();
C.prototype = b;
let c = new C();
console.log(c instanceof B);//true
console.log(c instanceof A);//true
console.log(b instanceof A);//true
使用isPrototypeOf
可以检测一个对象是否在另一个对象的原型链中,接上面的代码:
console.log(a.isPrototypeOf(b));//ture
console.log(a.isPrototypeOf(c));//ture
console.log(b.isPrototypeOf(c));//ture
属性遍历
in
关键字会对原型链上所有属性描述enumerable
为ture
的属性进行遍历(向上攀升),使用 hasOwnProperty
可以判断对象中属性是否为自有属性,即非继承来的属性。
let a ={
name1:"a"
}
let b = {
name2:"b"
}
let c = {
name3:"c"
}
//继承关系 c -> b -> a (->:继承)
Object.setPrototypeOf(c,b);
Object.setPrototypeOf(b,a);
for (const key in c) {
console.log(key);
}
/*输出:name3 name2 name 1 */
for (const key in c) {
if (c.hasOwnProperty(key)) {
console.log(key)
}
}/*输出: name 3*/
借用原型
call
和apply
可以改变函数体内的this
指针,从而可以借用其他对象的方法来完成功能。根据传入的对象不同,两者都可以改变函数体中this
的值,两者的差别在于call
传入零散的参数,而apply
传入一个参数数组。
let exam = {
score: new Map([["C/C++",90],["Java",87],["Js",99]]),
average : function(){
let s = Array.from(this.score.values());
let sum = s.reduce((total,value) => total+=value);
return sum/s.length;
}
}
console.log(exam.average()); // 92
let game = {
score: [100,99,200,123,213]
}
//game对象没有average方法,但是可以借用exam的完成平均数的计算
console.log(exam.average.call(game)); // 147
原型工厂
原型工厂是将继承的过程封装,使用继承业务简单化
//使sub构造继承sup构造
function extend(sub,sup){
//继承原型
sub.prototype = Object.create(sup);
//定义构造函数,防止构造函数地址丢失
sub.prototype.constructor = sub;
}
对象工厂
在原型对象的基础上可以拓展到对象工厂,即子类的构造函数的创建。
function A(name,age){
this.name = name;
this.age = age;
}
A.prototype.show = function(){
console.log(this.name + " " + this.age);
}
function B(name,age){
let instance = Object.create(A.prototype);
//复用A的构造函数,执行B的实例化
A.call(instance,name,age);
//额外添加属性
instance.newProp = " B ";
return instance;
}
let b = new B("lisi",20);
b.show();//lisi 20
混合模式
JS
不能实现多继承,如果要使用多个原型的方法时可以使用mixin混合模式来完成。在JS中,使用Object.assign
来让需要继承的多个对象进行合并,以实现需要使用到多个类的方法的情况。