AI智能
改变未来

【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__直接指向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

    来让需要继承的多个对象进行合并,以实现需要使用到多个类的方法的情况。

    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » 【JS学习笔记 08】 对象的原型与继承