原型与继承
- 原型对象
- 函数的原型
- 构造函数与原型对象
- 原型链
- 创建原型链
- 原型检测
原型对象
每个对象都有一个原型
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__直接指向Objectconsole.log(lisi.__proto__);//构造函数的prototype指向Objectconsole.log(User.prototype);console.log(lisi.__proto__ == User.prototype);//构造函数的__proto__指向其父亲 functionconsole.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 -> obj1Object.setPrototypeOf(obj3,obj2);Object.setPrototypeOf(obj2,obj1);console.log(obj3.prop1);//输出:obj1console.log(obj3.prop2);//输出:obj2console.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继承于alet 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);//trueconsole.log(c instanceof A);//trueconsole.log(b instanceof A);//true
使用
isPrototypeOf
可以检测一个对象是否在另一个对象的原型链中,接上面的代码:
console.log(a.isPrototypeOf(b));//tureconsole.log(a.isPrototypeOf(c));//tureconsole.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()); // 92let 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
来让需要继承的多个对象进行合并,以实现需要使用到多个类的方法的情况。