第4集丨JavaScript 使用原型(prototype)实现继承——最佳实战2

news/2025/1/16 0:43:14/

目录

  • 一、临时构造器方式
    • 1.1 代码实现
    • 1.2 代码分析
  • 二. 增加uber属性,用于子对象访问父对象
    • 2.1 实现分析
    • 2.2 代码实现
  • 三. 将继承封装成extend()函数
    • 3.1 代码实现
      • 3.1.1 临时构造器实现extend()
      • 3.1.2 原型复制实现extend2()
    • 3.2 代码测试
      • 3.2.1 测试extend()函数
      • 3.2.1 测试extend2() 函数
  • 四、实例对象原型继承:以实例对象为原型创建新对象
    • 4.1 功能分析
    • 4.2 功能测试

书接上集,在上集中我们给出了一个需求说明,要求利用现学的知识实现原型(prototype)继承,并且我们给出了三种实现方式。但是这三种方式各自有优缺点,都不能很好的满足要求,那是否还有其他更好的实现方式呢?

在看本文之前,诸位可以自己思考下?带着问题去学习总是能够学到更多内容。在以后的岁月中,会找一些有意义的句子,与诸君共勉。

在时间的稿纸上,每个人都在写着自己的历史。当你抓着今天时,你就会前进一步,当你丢弃今天时,你就会停滞不动。

一、临时构造器方式

上集中,我们利用prototype方式实现继承,会带来一个问题:所有的属性都指向了一个相同的对象,父对象就会受到子对象属性的影响。

如何解决这个问题?

这时就必须利用某种中介来打破这种连锁关系。我们可以用一个临时的构造器函数来充当中介。即:我们创建一个空函数f(),并将其原型设置为父级构造器,然后我们既可以用new F()来创建一些不包含父对象属性的对象,同时又可以从父对象prototype中继承一切了。

1.1 代码实现

function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {return this.name;
}
function TwoDShape(){}
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = "2D Shape";
function Triangle(side, height){this.side = side;this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = "Triangle";
Triangle.prototype.getArea = function () {return this.side * this.height / 2;
}
var my = new Triangle(5, 10);
console.log(my.getArea());  //25
console.log(my.name);   //Triangle
console.log(my.toString()); //Triangle
var supers = new Shape();
console.log(supers.name);   //Shape 注意

1.2 代码分析

从下图中我们可以看出my 对象的结构

在这里插入图片描述
那么这个时候toString() 方法查找分几步呢?

  • 在本类中查找
  • my对象的my.[[Prototype]] 中找,此时该[[Prototype]] 对象为TwoDShape的实例,里面只有一个name = "Triangele"属性。
  • my对象的my.[[Prototype]].[[Prototype]] 中找,为Shape的实例,同样里面也只有一个name="2D Shape" 属性
  • my.[[Prototype]].[[Prototype]].[[Prototype]] 中找,为Object实例,此时,就已经找到了 toString() 方法。

二. 增加uber属性,用于子对象访问父对象

2.1 实现分析

uber 属性的名字原本应该是“superclass”,但是这样一来显得JavaScript中有了类的概念(在ES6中引入了class),或许该叫super,但是superJavaScript的保留字,因为改成uber

  • 我们给构造函数增加一个uber属性,使其值为父类的原型Triangle.uber = TwoDShape.prototype;
  • 如下图所示,此时构造函数Triangle() 中,包含了uber属性
    在这里插入图片描述

2.2 代码实现

function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {var result = [];if(this.constructor.uber){result[result.length] = this.constructor.uber.toString();}result[result.length] = this.name;return result.join(', ');
}
function TwoDShape(){}
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.uber = Shape.prototype;   //增加uber属性,指向父类的原型
TwoDShape.prototype.name = "2D Shape";
function Triangle(side, height){this.side = side;this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.uber = TwoDShape.prototype;
Triangle.prototype.name = "Triangle";
Triangle.prototype.getArea = function () {return this.side * this.height / 2;
}
var my = new Triangle(5, 10);
console.log(my.getArea());  //25
console.log(my.name);   //Triangle
console.log(my.toString()); //Shape, 2D Shape, Triangle
var supers = new Shape();
console.log(supers.name);   //Shape

三. 将继承封装成extend()函数

  • 为了代码的封装和重用,我们可以将实现继承的部分抽取出来,定义一个extend()函数。

3.1 代码实现

extend()相比,extend2() 显得略逊一筹。因为这里执行的是子对象原型的逐一拷贝,而非简单的原型链查询。所以我们必须要记住,这种方式仅适用于只包含基本数据类型的对象,所有的对象类型(包括函数和数组)都是不可复制的,因为他们只支持引用传递。

这里toString()方法实际上是同一个函数对象。这里只是一个函数引用,函数本身并没有再次被创建。

优缺点

  • extend2()方法的效率要低于extend()方法,主要是前者对部分原型属性进行了重建。
  • 当然了,这对于只包含基本数据类型的对象来说,是有好处的。因为这样做能使属性查找操作更多的停留在对象本身,从而减少了原型链上的查找。

3.1.1 临时构造器实现extend()

//定义extend函数
function extend(Child, Parent){var F = function () {};F.prototype = Parent.prototype;Child.prototype = new F();Child.prototype.constructor = Child;Child.uber = Parent.prototype;}

3.1.2 原型复制实现extend2()

function extend2(Child, Parent){var p = Parent.prototype;var c = Child.prototype;for (var i in p){c[i] = p[i];}c.uber = p; //扩展子类原型,增加一个uber属性
}

3.2 代码测试

3.2.1 测试extend()函数

  • 利用上述extend() 函数实现继承。

//定义Shape类
function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {return this.name;
}//定义TwoDShape类
function TwoDShape(){}
extend(TwoDShape, Shape);
TwoDShape.prototype.name = "2D Shape";//定义Triangle类
function Triangle(side, height){this.side = side;this.height = height;
}
extend(Triangle, TwoDShape);
Triangle.prototype.name = "Triangle";
Triangle.prototype.getArea = function () {return this.side * this.height / 2;
}//测试
var my = new Triangle(5, 10);
console.log(my.getArea());  //25
console.log(my.name);   //Triangle
console.log(my.toString()); // Triangle
var supers = new Shape();
console.log(supers.name);   //Shape

3.2.1 测试extend2() 函数

//定义Shape类
function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {return this.name;
}//定义TwoDShape类
function TwoDShape(){}
extend(TwoDShape, Shape);//extend测试结果
var td = new TwoDShape();console.log(td.name);   //Shapeconsole.log(TwoDShape.prototype.name); //Shapeconsole.log(td.__proto__.name); //Shapeconsole.log(td.hasOwnProperty("name")); //falseconsole.log(td.__proto__.hasOwnProperty("name"));   //false//extend2测试结果
var td = new TwoDShape();
console.log(td.__proto__.hasOwnProperty("name"));   //true
console.log(td.__proto__.hasOwnProperty("toString"));   //true
console.log(td.__proto__.toString == Shape.prototype.toString); //true

四、实例对象原型继承:以实例对象为原型创建新对象

4.1 功能分析

基于这种在对象之间直接构建继承关系的理念,我们可以创建一个object()函数,接受父对象,并返回一个以该对象为原型的新对象。这和Object.create() 类似。如下代码所示:

function object(o){var F = function(){};F.prototype = o;return new F();
}

如果需要访问父类,则可加添加uber属性。

function object(o){var n;var F = function(){};F.prototype = o;n = new F();n.uber = o;return n;
}

4.2 功能测试

//定义Shape类function Shape(){}Shape.prototype.name = "Shape";Shape.prototype.toString2 = function () {return this.name;}var shapeObj = new Shape();var twoDShape = object(shapeObj);twoDShape.name = "HHH"console.log(twoDShape.toString2()); //HHH
  • twoDShape 对象的结构如下所示
    在这里插入图片描述

http://www.ppmy.cn/news/774541.html

相关文章

进销存系统的一些事儿

1)List与Set的区别: a) List有序【有索引】、Set无序【没有索引、TreeSet底层使用二叉树结构实现,所以有序】 b) List允许数据重复、Set不允许重复 c) 实际开发中可以使用Set集合来去除重复数据 2)列举你熟悉的几种数据结构&#…

进销存软件的功能主要有哪些

进销存软件的功能主要有哪些呢?1.客户管理:公共客户池、客户分配、客户管理、客户三证上传、客户卡片、交往记录管理、客户漏斗图、超期客户预警、客户关怀、客户生日、客户转移、客户统计 2.销售机会管理:项目机会管理、机会漏斗图、销售报价单、解决方案 3.产品管理:产品信…

php超市进销存单位换算,进销存软件如何设置商品单位换算比率小数显示位数?...

进销存软件中支持多计量单位,可以添加商品辅助单位,即同一个商品存在多个不同的计量单位,这几个单位之间存在一定的换算关系,开单时不管输入哪一个单位,软件都可以根据换算比例来自动进行换算转换。辅助单位信息内显示…

毕设经典-超市进销存管理系统(SSM)-页面之间传参

前言 其实这是前端的知识,当我们从数据库里获得了参数信息,查看详情的时候,需要跳转到另一个页面,或者弹出另一个页面,这个时候,不需要再从数据库里查询一次参数,只需要把已经查询到的参数传给…

EasyBPM进销存之物料管理

本文是EasyBPM平台实现进销存系列中的一篇,主要讲述物料的相关的管理。 在ERP系统中,“物料”一词有着广泛的含义,它是所有产品、半成品、在制品、原材料、配套件、协作件、易耗品等与生产有关的物料的统称。物料是ERP的基础信息之一&#x…

MySQL数据库的进销存

一、建表 1、用户 DROP TABLE IF EXISTS tb_user; CREATE TABLE tb_user (user_id int(20) NOT NULL auto_increment,user_name varchar(50) NOT NULL,user_login_name varchar(50) NOT NULL,user_login_password varchar(50) NOT NULL,user_tel varchar(50) NOT NULL,PRIMAR…

进销存软件单机两台或多台电脑能用吗

进销存软件单机版故名思义是单台电脑上使用的意思,那么多台电脑是否能够使用进销存软件单机版的?针对这个问题,也许我们首先想到的就是共享,因为平时工作的时候我们经常会把文件或文件夹共享到网上邻居,同事在别的电脑…

进销存管理系统——商品管理

《进销存管理系统——商品管理》项目研发阶段性总结 作者:朱海丁 本次任务完成时间:2021年6月30日~2021年7月7日 开发工具与关键技术:VS和C# 完成模块功能:商品管理 一、功能实现 商品管理主要包括商品类别、商品…