【JS学习笔记 08】 对象的原型与继承

原型对象

每个对象都有一个原型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__);

输出:
原型
关系图如下:
js原型

构造函数与原型对象

下面是使用构造函数创建对象的原型体现:

  • 构造函数拥有原型对象,利用prototype属性访问
  • 构造函数在创建对象时把原型赋予对象
  • 对象原型在配置后可以通过constructor(指向构造函数)访问构造函数
  • 实例对象可以直接通过__proto__访问对象原型
  • 可以通过实例对象的constructor访问构造函数,但是constructor本质上是对象原型的属性
    JS原型
    下面的示例展示利用对象实例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原型

原型链

在前面我们可以了解到,JS中大部分的数据类型其实都是对象类型的。如果仔细看上面代码中Array构造的原型其实就是Object类型的一个实例对象,可以使用__proto__属性访问到Object

执行console.log(Array.prototype);可以在控制台看到Array的原型对象中的__proto__指向Obeject的构造的原型对象
js原型
同时也就意味可以直接通过Array.prototype.__proto__访问Object的原型对象

有如下的关系图:
在这里插入图片描述
在上面的橙色标记的引用链中,通过引用类型的原型,继承另一个引用类型的属性与方法,这也是实现继承的步骤。

创建原型链

  • 使用Object.setPrototypeOf 可设置对象的原型。下面的例子将创建一条原型链,使obj3继承obj2,obj2继承obj1obj3将同时拥有三者的属性。
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关键字会对原型链上所有属性描述enumerableture的属性进行遍历(向上攀升),使用 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*/

借用原型

callapply可以改变函数体内的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来让需要继承的多个对象进行合并,以实现需要使用到多个类的方法的情况。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流水线程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值