继承
我们现在呢,有一个拖拽的功能需要实现:假设有一个div
,我们需要对它实现拖拽的功能,那么按照我们平常的方式,实现的方法如下:
// 面向过程
const mydiv = document.querySelector(".mydiv");
mydiv.onmousedown = e => {
let ev = e || window.event;
let x = ev.clientX - mydiv.offsetLeft;
let y = ev.clientY - mydiv.offsetTop;
mydiv.onmousemove = e => {
let ev = e || window.event;
let xx = ev.clientX;
let yy = ev.clientY;
mydiv.style.left = xx - x + "px";
mydiv.style.top = yy - y + "px";
}
mydiv.onmouseup = function() {
mydiv.onmousemove = "";
}
}
// 面向对象
function Drag(ele) {
this.ele = ele;
this.downFn();
}
Drag.prototype.downFn = function () {
this.ele.onmousedown = e => {
let ev = e || window.event;
let x = ev.clientX - this.ele.offsetLeft;
let y = ev.clientY - this.ele.offsetTop;
this.moveFn(x, y);
this.upFn();
}
}
Drag.prototype.moveFn = function (x, y) {
this.ele.onmousemove = e => {
let ev = e || window.event;
this.setStyle(ev.clientX - x, ev.clientY - y);
}
}
Drag.prototype.setStyle = function(leftNum,topNum){
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
Drag.prototype.upFn = function () {
this.ele.onmouseup = () => {
this.ele.onmousemove = "";
}
}
let mydiv = document.querySelector(".mydiv");
new Drag(mydiv);
此时,我们新增了一个需求,有两个div
,都需要实现拖拽功能,但是其中一个div
是需要限定范围的。那么我们可以通过传参的方式区分来实现这个功能,也可以通过继承的方式,继承的好处在于:不需要去更改之前的代码,只需要新增自己的功能或重写。
继承实现新的需求:
function Drag(ele) {
this.ele = ele;
this.downFn();
}
Drag.prototype.downFn = function () {
this.ele.onmousedown = e => {
let ev = e || window.event;
let x = ev.clientX - this.ele.offsetLeft;
let y = ev.clientY - this.ele.offsetTop;
this.moveFn(x, y);
this.upFn();
}
}
Drag.prototype.moveFn = function (x, y) {
this.ele.onmousemove = e => {
let ev = e || window.event;
this.setStyle(ev.clientX - x, ev.clientY - y);
}
}
Drag.prototype.setStyle = function(leftNum,topNum){
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
Drag.prototype.upFn = function () {
this.ele.onmouseup = () => {
this.ele.onmousemove = "";
}
}
let mydiv = document.querySelector(".mydiv");
new Drag(mydiv);
function LimitDrag(ele) {
Drag.call(this, ele);
}
// 组合继承
let Link = function () { };
Link.prototype = Drag.prototype;
LimitDrag.prototype = new Link();
LimitDrag.prototype.constructor = LimitDrag;
// 重写方法
LimitDrag.prototype.setStyle = function (leftNum, topNum) {
leftNum = leftNum < 0 ? 0 : leftNum;
topNum = topNum < 0 ? 0 : topNum;
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
let mydiv2 = document.querySelector(".mydiv2");
new LimitDrag(mydiv2);
class继承实现:
class Drag {
constructor(ele) {
this.ele = ele;
this.downFn();
}
downFn() {
this.ele.onmousedown = e => {
let ev = e || window.event;
let x = ev.clientX - this.ele.offsetLeft;
let y = ev.clientY - this.ele.offsetTop;
this.moveFn(x, y);
this.upFn();
}
}
moveFn(x, y) {
this.ele.onmousemove = e => {
let ev = e || window.event;
this.setStyle(ev.clientX - x, ev.clientY - y);
}
}
setStyle(leftNum, topNum) {
this.ele.style.left = leftNum + "px";
this.ele.style.top = topNum + "px";
}
upFn() {
document.onmouseup = () => {
this.ele.onmousemove = "";
}
}
}
let mydiv = document.querySelector(".mydiv");
new Drag(mydiv);
class LimitDarg extends Drag {
constructor(ele) {
// 继承必须要执行一下,一种语法形式
super(ele);
}
setStyle(leftNum, topNum) {
// 重写方法
leftNum = leftNum < 0 ? 0 : leftNum;
topNum = topNum < 0 ? 0 : topNum;
// 调用父类的被重写的方法
super.setStyle(leftNum, topNum);
}
}
let mydiv2 = document.querySelector(".mydiv2");
new LimitDrag(mydiv2);
那么,我们先来介绍一下继承的几种方式:
首先是比较基础的原型链继承,就是通过将父类的实例作为子类的原型来达到继承的目的。
- 原型链继承
function Dad() {
this.name = "唐三";
}
function Son() {}
// 子类原型等于父类实例
Son.prototype = new Dad();
let newSon = new Son();
console.log(newSon.name); // 唐三
原型链继承的缺点之一就是不能向父类构造函数传参,而借用构造函数继承是可以的,它是借用call
等方式调用父类构造函数来实现继承。
- 借用构造函数继承
function Dad(height) {
this.name = "唐三";
this.height = height;
}
function Son(height) {
// 调用父类构造函数来实现继承
Dad.call(this, height);
// 这两种方式也都可以实现
// Dad.apply(this,[height])
// Dad.bind(this)(height);
}
let newSon = new Son("178cm");
console.log(newSon.height); // 178cm
console.log(newSon.name); // 唐三
但是勒,在借用构造函数继承方式中,并不能把父类的原型继承过来,如下的代码就报错了。
function Dad(height) {
this.name = "唐三";
this.height = height;
}
Dad.prototype.hobby = function() {
console.log("高尔夫");
}
function Son(height) {
Dad.call(this, height);
}
let newSon = new Son("178cm");
newSon.hobby(); // 报错
那么我们可以试想,把父类的原型赋值给子类的原型是否可以继承父类的原型。以下结果表明Son.prototype = Dad.prototype
可以继承父类的原型,但同样存在问题,重写父类的方法会相互影响,会修改父类的方法。
function Dad(height) {
this.name = "唐三";
this.height = height;
}
Dad.prototype.hobby = function() {
console.log("高尔夫");
}
function Son(height) {
Dad.call(this, height);
}
Son.prototype = Dad.prototype;
Son.prototype.hobby = function() {
console.log("篮球");
}
let newSon = new Son("178cm");
newSon.hobby(); // 篮球
let newDad = new Dad("179cm");
newDad.hobby(); // 篮球
那么我们可以让子类原型等于父类实例,这样不仅继承了父类的原型,也会继承父类的属性。将原型链继承和借用构造函数继承结合起来,就是组合继承了。
- 组合继承
function Dad(height) {
this.name = "唐三";
this.height = height;
}
Dad.prototype.hobby = function() {
console.log("高尔夫");
}
function Son(height) {
Dad.call(this, height);
}
Son.prototype = new Dad()
// 但prototype里都有一个预定义属性constructor,所以我们还需要设置其指向自己的构造函数
Son.prototype.constructor = Son;
Son.prototype.hobby = function() {
console.log("篮球");
}
let newSon = new Son("178cm");
newSon.hobby(); // 篮球
let newDad = new Dad("179cm");
newDad.hobby(); // 高尔夫
那么我们创建一个新的构造函数作为媒介,切断子类原型和父类原型的关系。这样原型的方法就不会相互影响。
function Dad(height) {
this.name = "唐三";
this.height = height;
}
Dad.prototype.hobby = function() {
console.log("高尔夫");
}
function Son(height) {
Dad.call(this, height);
}
// 或采用es6新增Son.prototype = Object.create(Dad.prototype)依然可以达到同样的效果
let Link = function(){};
Link.prototype = Dad.prototype;
Son.prototype = new Link();
Son.prototype.constructor = Son;
Son.prototype.hobby = function() {
console.log("篮球");
}
let newSon = new Son("178cm");
newSon.hobby(); // 篮球
let newDad = new Dad("179cm");
newDad.hobby(); // 高尔夫
我们也可以使用深拷贝拷贝父类的原型来达到目的。
function deepCopy(obj) {
let newObj = Array.isArray(obj) ? [] : {};
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
if (typeof obj[i] === "object") {
if (obj[i] === null) {
newObj[i] = null;
} else {
newObj[i] = deepCopy(obj[i]);
}
} else {
newObj[i] = obj[i];
}
}
}
return newObj;
}
function Dad(height) {
this.name = "唐三";
this.height = height;
}
Dad.prototype.hobby = function() {
console.log("高尔夫");
}
function Son(height) {
Dad.call(this, height);
}
// 利用深拷贝继承父类的原型
Son.prototype = deepCopy(Dad.prototype)
Son.prototype.constructor = Son;
Son.prototype.hobby = function() {
console.log("篮球");
}
let newSon = new Son("178cm");
newSon.hobby(); // 篮球
let newDad = new Dad("179cm");
newDad.hobby(); // 高尔夫
- 类继承
class Dad {
constructor(height){
this.name = "唐三";
this.height = height;
}
hobby() {
console.log("高尔夫");
}
}
class Son extends Dad {
constructor(height){
super(height);
}
hobby() {
console.log("篮球");
}
}