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

news2024/11/26 11:54:46

目录

  • 一、原型继承与属性拷贝
    • 1.1 功能说明
    • 1.2 功能测试
  • 二、多重继承
    • 2.1 功能实现
    • 2.2 功能测试
  • 三、寄生式继承
  • 四、构造器借用
    • 4.1 简单实现
    • 4.2 进化版
      • 4.2.1 功能实现
      • 4.2.2 案例测试
  • 五、借用构造器和原型复制
  • 六 综合案例
    • 6.1 需求说明
    • 6.2 代码实现

一、原型继承与属性拷贝

1.1 功能说明

对于继承应用来说,主要目标是将一些现有的功能归为己有。也就是说,我们在新建一个对象时,通常首先应该继承于现有对象,然后在为其添加额外的方法和属性。对此,我们可以通过一个函数调用来完成,并且在其中混合使用我们刚才所讨论的两种方式:

  • 使用原型继承的方式克隆现存对象
  • 其他对象使用属性拷贝的方式
function objectPlus(o, stuff){
    var n;
    var F = function(){};
    F.prototype = o;
    n = new F();
    n.uber = o;

    for(var i in stuff){
        n[i] = stuff[i];
    }
    return n;
}

这个函数用于接受两个参数,o 用于继承,而另一个对象stuff 则用于拷贝方法和属性.

1.2 功能测试

var shape = {
    name: 'shape',
    toString: function(){return this.name;}
}
var twoDee = objectPlus(shape,{
    name: '2D shape',
    toString: function(){return this.uber.toString() + ", " + this.name; }
})
var triangle = objectPlus(twoDee,{
    name: 'triangle',
    getArea: function(){return this.side * this.height / 2 ;},
    side: 0,
    height: 0
})

var my = objectPlus(triangle,{
    side: 4,
    height: 4
})

console.log(my.getArea());  //8
console.log(my.toString()); //shape, 2D shape, triangle, triangle。这里有两个triangle,是因为我们在具体实例化时是
                            //继承于triangle对象的,所以多了一层继承关系,我们也可以给实例一个新的名字。例如下面

var my = objectPlus(triangle,{
    side: 4,
    height: 4,
    name: 'My Ext'
})
console.log(my.toString()); //shape, 2D shape, triangle, My Ext, 这里就变成了新的名字了

二、多重继承

所谓多重继承,通常是指一个子类对象中不止一个父对象的继承模式。对于JavaScript 这样的动态语言来说,实现多重继承是很简单的,尽管语言本身没有提供特殊的语法单元多重继承实现是极其简单的,我们只需要延续属性拷贝的继承思路依次扩展对象,不对其所继承的对象数量参数进行输入限制即可实现。

2.1 功能实现

下面,我们创建一个multi() 函数,它可以接受任意数量的输入性对象。然后,我们在其中实现了一个双重循环,内层循环用于拷贝属性,而外层循环用于遍历函数参数中所传递进来的所有对象。

function multi(){	//这里是按照参数传递的顺序循环的,而且是浅拷贝
    var n = {}, stuff, j = 0, len = arguments.length;
    for(var j = 0; j < len; j++){
        stuff = arguments[j];
        for(var i in stuff){
            n[i] = stuff[i];
        }
    }
    return n;
}

2.2 功能测试

需要注意的是:multi() 函数中循环是按照对象输入的顺序来进行遍历的。如果两个对象拥有相同的属性,以最后一个对象为准。

var shape = {
    name: 'shape',
    toString: function(){return this.name;}
}

var twoDee = {
    name: '2D shape',
    dimensions: 2,
    numbers: [1,2,3]
}

var triangle = multi(shape, twoDee, {
    name: 'triangle',
    getArea: function(){return this.side * this.height / 2; },
    side: 5,
    height: 10
})

console.log(triangle.getArea());    //25
console.log(triangle.dimensions);   //2
console.log(triangle.toString());   //triangle

triangle.numbers.push(4);
console.log(triangle.numbers);     //[1,2,3,4]
console.log(twoDee.numbers);        //[1,2,3,4] 显然这个是浅拷贝的属性

三、寄生式继承

基本思想是:我们可以在创建对象的函数中,直接吸收其他对象的功能,然后对其进行扩展并返回。就好像所有的工作都是自己做的一样。

代码示例如下

function object(o){
    var n;
    var F = function(){};
    F.prototype = o;
    n = new F();
    n.uber = o;
    return n;
}
var twoD = {
    name: "2D shape",
    dimensions: 2
}

function triangle(s, h){
    var that = object(twoD);
    that.name = 'triangle';
    that.getArea = function () {
        return this.side * this.height / 2;
    }
    that.side = s;
    that.height = h;
    return that;
}
//由于这里triangle是一个普通函数,不属于构造函数,所以调用它是不需要要new操作符的,但是
//该函数返回的是要给对象,所以在我们错误的使用了new操作符,也会正常工作
var t = triangle(5,6);
console.log(t.dimensions);  //2
console.log(t.getArea());   //15

var t2 = new triangle(5,5); //照样正常工作
console.log(t2.getArea());  //12.5

四、构造器借用

子对象构造器可以通过call()apply()方法来调用父对象的构造器。因而,它通常被称为构造器盗用法,或者构造器借用法。

4.1 简单实现

在这里新的Triangle 对象继承了父对象的id 属性,但它并没有继承父对象原型中的其他任何东西。之所以对象中不包含Shape的原型属性,是因为我们从来没有调用new Shape()创建任何一个实例,

function Shape(id){
    this.id = id;
}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {return this.name;}

function Triangle(){
    Shape.apply(this,arguments);
}
Triangle.prototype.name = "triangle";
var t = new Triangle(101);
console.log(t.name);    //triangle
//在这里新的Triangle对象继承了父对象的id属性,但它并没有继承父对象原型中的其他任何东西
//之所以对象中不包含Shape的原型属性,是因为我们从来没有调用new Shape()创建任何一个实例,
//自然其原型也从来没有被用到。所以底下打印的是[object object]
console.log(t.toString());  //[object object],

4.2 进化版

那么如使其包含Shape中的原型呢?我们可以对Triangle构造器进行如下重定义:

4.2.1 功能实现

function Triangle(){
    Shape.apply(this,arguments);
}
Triangle.prototype = new Shape();
Triangle.prototype.constructor = Triangle;

在这种继承模式中,父对象的属性是以子对象自身的属性的身份来重建的(这与原型链模式中的子对象属性正好相反)。这也体现了构造器借用法的一大优势:当我们创建一个继承于数组或者其他对象类型的子对象时,将获得一个完完全全的新值(不是一个引用),对它做任何修改都不会影响父对象。
但这种模式也是有缺点的,因为这种情况下父对象的构造器往往会被调用两次:一次发生在通过apply方法继承其自身属性时,而另一次则发生在通过 new 操作符继承其原型时。这样一来,父对象的自身属性事实上被继承了两次。

4.2.2 案例测试

下面,我们来做一个简单的演示:

function Shape(id){
    this.id = id;
}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {return this.name;}

function Triangle(){
    Shape.apply(this,arguments);
}
Triangle.prototype = new Shape(101);    //为了演示加上了101,如果不加的话底下打印的就是undefined,虽然是,为什么是undefined,因为你没有传入参数,在执行Shape构造器的时候给id就是没有定义的值,所以this。id就赋值成了undefined,但是原型中还有有这个属性的,还是重复定义了两次
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = "triangle";

var t = new Triangle(202);
console.log(t.id);  //202
console.log(t.__proto__.id);    //101

delete t.id;    //删除之后
console.log(t.id);  //101

五、借用构造器和原型复制

对于这种由于构造器的双重调用而带来的重复执行问题,实际上是很容易修正的。我们可以在父类构造器上调用apply()方法,以获得其全部的自身属性,然后在用一个简单的迭代器对其原型属性执行逐项拷贝。

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属性
}

function Shape(id){
    this.id = id;
}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {return this.name;}

function Triangle(){
    Shape.apply(this,arguments);
}
extend2(Triangle,Shape);
Triangle.prototype.name = "triangle";

var t = new Triangle(202);
console.log(t.toString());  //triangle
console.log(t.id);  //202
console.log(t.__proto__.id);    //undefined

六 综合案例

6.1 需求说明

我们利用所学的知识来写一个综合案例,结合canvas 绘制如下图形:

在这里插入图片描述

6.2 代码实现

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>canvas+prototype</title>
</head>
<body>
    <canvas id="canvas" height="600" width="800"></canvas>
</body>
</html>

	//定义点类
function Point(x, y) {
    this.x = x;
    this.y = y;
}

//计算两点之间的直线距离
function Line(p1, p2){
    this.p1 = p1;
    this.p2 = p2;
    this.length = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}

//定义父类形状
function Shape(){
    this.points = [];
    this.lines = [];
    this.init();
}
Shape.prototype = {
    constructor: Shape,
    init: function(){
        if(typeof this.context === 'undefined'){
            var canvas = document.getElementById('canvas');
            Shape.prototype.context = canvas.getContext("2d");
        }
    },
    draw: function(){
        var ctx = this.context;
        ctx.strokeStyle = this.getColor();
        ctx.beginPath();
        ctx.moveTo(this.points[0].x, this.points[0].y);
        for(var i = 1; i<this.points.length; i++){
            ctx.lineTo(this.points[i].x, this.points[i].y);
        }
        ctx.closePath();
        ctx.stroke();
    },
    getColor: function(){
        var rgb = [];
        for(var i = 0; i < 3; i++){
            rgb[i] = Math.round(255 * Math.random());
        }
        return 'rgb(' + rgb.join(',') + ')';
    },
    getLines: function(){
        if(this.lines.length > 0){
            return this.lines;
        }
        var lines = [];
        for(var i = 0; i < this.points.length; i++){
            lines[i] = new Line(this.points[i],(this.points[i+1]) ? this.points[i+1] : this.points[0]);
        }
        this.lines = lines;
        return lines;
    },
    getArea: function () {      //面积

    },
    getPerimeter: function(){   //z周长
        var lines = this.getLines();
        var perim = 0;
        for(var i = 0; i < lines.length; i++){
            perim += lines[i].length;
        }
        return perim;
    }

}

//定义三角形
function Triangle(a, b, c){
    this.points = [a, b, c];
    this.getArea = function () {
        var p = this.getPerimeter();
        var s = p / 2;
        return Math.sqrt(
            s * (s - this.lines[0].length)
              * (s - this.lines[1].length)
              * (s - this.lines[2].length)
        );
    }
}

//定义矩形
function Rectangle(p, side_a, side_b){
    this.points = [
        p,
        new Point(p.x + side_a, p.y),
        new Point(p.x + side_a, p.y + side_b),
        new Point(p.x, p.y + side_b)
    ]
    this.getArea = function () {
        return side_a * side_b;
    }
}

//定义正方形
function Square(p, side){
    Rectangle.call(this, p, side, side);
}

//修改原型链
var s = new Shape();
Triangle.prototype = s;
Rectangle.prototype = s;
Square.prototype = s;

//测试
var p1 = new Point(100, 100);
var p2 = new Point(300, 100);
var p3 = new Point(200, 0);

//三角形
var t = new Triangle(p1, p2, p3);
t.draw();
console.log(t.getPerimeter());
console.log(t.getArea());

//矩形
var r = new Rectangle(new Point(200, 200), 50, 100);
r.draw();
console.log(r.getPerimeter());
console.log(r.getArea());

//正方形
var s = new Square(new Point(130, 130), 50);
s.draw();
console.log(s.getPerimeter());
console.log(s.getArea());

new Square(p1, 200).draw();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/740277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

免费的音频转文字软件这么多,音频转文字软件推荐有哪些?

在一次音频转文字工作者的聚会上&#xff0c;艾米和迈克正在探讨如何提高他们的工作效率。他们都深知手动转录音频文件的困难和耗时&#xff0c;因此开始讨论是否有一种音频转文字软件可以推荐。 迈克&#xff1a;嘿&#xff0c;艾米&#xff01;我们都知道&#xff0c;音频转…

Scratch 星际飞船

Scratch 星际飞船 本程序主要增加了背景切换功能&#xff0c;飞船跟随鼠标移动&#xff0c;接触到右边的方块时切换到下一张背景&#xff0c;切换后飞船移动到左边&#xff0c;左边的椭圆则相反。随机生成另外两种飞船角色为背景&#xff0c;接触到边缘后移除。 图形化程序如下…

【算法与数据结构】150、LeetCode逆波兰表达式求值

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;我们常看见的表达式是中缀表达式&#xff08;关于中缀表达式的定义可以参考前缀、中缀、后缀表达式&am…

0经验也能轻松设计商标logo的方法

如今&#xff0c;越来越多的人开始创业&#xff0c;而拥有一个好看的商标就显得尤为重要。但是&#xff0c;很多人并不具备设计技能&#xff0c;对于商标设计也是一头雾水。不过&#xff0c;现在有了一键logo设计应用&#xff0c;即使零经验的人也能轻松设计出自己满意的商标。…

IoT开发者为王,涂鸦智能硬核“靠边站”

文 | 智能相对论 作者 | 沈浪 6月底&#xff0c;全球化IoT开发平台服务商涂鸦智能开了个TUYA开发者大会&#xff0c;面向行业传达了两个关键的信息点&#xff1a; 1. 当前IoT领域的行业竞争不再局限于技术、渠道的单一纬度&#xff0c;开始演化为整体的生态之争。 2. 紧随行…

STM32 Proteus仿真双机串口通讯同步电子时钟系统数码管显示 -0060

STM32 Proteus仿真双机串口通讯同步电子时钟系统数码管显示 -0060 Proteus仿真小实验&#xff1a; STM32 Proteus仿真双机串口通讯同步电子时钟系统数码管显示 -0060 功能&#xff1a; 硬件组成&#xff1a;本系统由2个设备组成 设备1&#xff1a;STM32F103R6单片机 8位数…

GaussDB OLTP云数据库配套工具DRS

目录 一、前言 二、DRS定义与使用场景 1、DRS定义 2、DRS场景示意图 三、DRS核心功能 1、实时迁移管理 2、实时同步管理 3、备份迁移管理 4、数据订阅管理 5、实时灾备管理 四、小结 一、前言 华为GaussDB云数据库提供了配套的生态工具数据复制服务DRS。 DRS围绕云…

IO流学习09(Java)

解压缩流/压缩流&#xff1a; 解压缩流&#xff1a; 解压本质&#xff1a;把每一个ZipEntry按照层级拷贝到本地另一个文件夹中 注&#xff1a;Java只认zip格式 在正式写之前&#xff0c;先准备好压缩文件和解压完需要存放文件的文件夹 下面直接给出案例&#xff1a; pack…

步进电机的MATLAB仿真程序分享(采用了卡尔曼滤波,对定子电流进行估计,并估算出转子的位置和速度)

两相步进电机的连续时间延长卡尔曼滤波器仿真&#xff0c; 根据定子电流的噪声测量&#xff0c;估计定子电流以及转子位置和速度。 主程序&#xff1a; function MotorKalman % 两相步进电机的连续时间延长卡尔曼滤波器仿真 % 根据定子电流的噪声测量&#xff0c;估计定子电…

为什么InnoDB存储引擎选择使用B+tree索引结构?

&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&…

CS231W assignment3 RNN

对作业进行一些形象的解释 首先是def rnn_step_forward&#xff1a; 这里的t时刻其实就是一个句子里面的单词数&#xff0c;为了方便会统一到一个最长长度&#xff0c;对于比这个长度短的部分用null进行填充&#xff0c;并且在方法内部会让Null不进行传播和梯度计算。 我们最…

Simpleitk简单应用-python版本

安装: pip install simpletik读取 目前主流的医疗图像格式是nifti格式&#xff0c;相比于dicom格式更加简单和更加容易读取和操作。后缀名为nii或者nii.gz&#xff08;nii为原格式&#xff0c;gz结尾的是经过压缩格式&#xff0c;因为医疗图像的重复像素特别多&#xff0c;通…

【Hello mysql】 mysql的基本查询(二)

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;介绍mysql的基本查询 mysql的基本查询&#xff08;二&#xff09; 将筛选出来的数据插入到数据库中&#xff08;insertselect&#xff09;聚合函数统计班级共有多少同学统计班级手机的qq号有多少统计本次考试去重的数学成绩…

Xcode doesn’t support iPhone’s iOS 15.7.3 (19H307).

Xcode真机调试时&#xff0c;出现了Xcode doesn’t support iPhone’s iOS 15.7.3 (19H307).&#xff0c;Xcode不支持iPhone的版本。升级Xcode太耗时&#xff0c;用了找支持SDK文件方式解决了此问题。下图是本地的SDK支持文件。 从GitHub下载了15.7的设备支持文件&#xff0c…

N-123基于springboot房屋租赁管理系统

开发工具&#xff1a;IDEA&#xff0c;jdk1.8 服务器&#xff1a;tomcat9.0 数据库&#xff1a;mysql5.7 前端&#xff1a;jsp、bootstrap 技术&#xff1a; springbootmybatis-plus 系统主要分前台和后台&#xff0c;分租客、房东、管理员三个角色 系统功能介绍说明&am…

19 动态库和静态库

文章目录 静态库动态库dll模块入口函数 静态库 选择桌面向导里面的静态库 设置为发布版,设置重新生成 创建文件夹放入库和头文件 将库放入工程目录下面 在属性中选择C/C附加包含目录&#xff0c;选择include 属性中链接器选择附加目录将lib选中 #include <stdio.h> #i…

八数码问题-c语言

八数码问题 每个局面是三行三列的数字方阵&#xff0c;每个位置为0-8的一个数码且互不相同&#xff0c;求从初始局面&#xff08;自己设定&#xff09;如何“最快”移动到终止局面&#xff08;自己设定&#xff09;。 移动规则&#xff1a;每次只能0与其上下左右四个方向相邻…

91. 解码方法

91. 解码方法 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 91. 解码方法 https://leetcode.cn/problems/decode-ways/ 完成情况&#xff1a; 解题思路&#xff1a; 参考代码&#xff1a; package 西湖算法题解…

LLM 与架构新纪元:适应代码生成模式,突破软件开发瓶颈

TL;DR 版本&#xff1a; 今年 2 月&#xff0c;我们在 QCon 上分享了《组织级架构治理的正确落地方式》&#xff0c;其背后的一个核心思想是&#xff1a;架构即代码。围绕这个核心思想&#xff0c;我们构建了 ArchGuard 的治理功能&#xff0c;即架构规范转换为代码。 今年 5 月…