>웹 프론트엔드 >JS 튜토리얼 >자바스크립트에서 상속을 구현하는 방법

자바스크립트에서 상속을 구현하는 방법

青灯夜游
青灯夜游원래의
2021-06-30 18:00:503507검색

JavaScript에서 상속을 구현하는 방법: 1. 프로토타입을 구성하고 프로토타입 프로토타입을 직접 사용하여 클래스 상속을 설계합니다. 2. 동적 프로토타입을 사용하여 상속을 구현합니다. 3. 팩토리 패턴을 사용하여 상속을 구현합니다. 클래스 상속을 사용하여 하위 클래스 생성자에서 상위를 호출하여 상속을 구현합니다.

자바스크립트에서 상속을 구현하는 방법

이 튜토리얼의 운영 환경: Windows 7 시스템, JavaScript 버전 1.8.5, Dell G3 컴퓨터.

JS에서 상속을 구현하는 여러 가지 방법

프로토타입 구성

프로토타입 프로토타입을 직접 사용하여 클래스 상속을 설계하는 데에는 두 가지 문제가 있습니다.

미리 생성자를 선언하고 클래스 구조를 선언한 후에 프로토타입 속성을 정의하기 때문에 생성자를 통해 프로토타입에 매개변수를 동적으로 전달할 수 없습니다. 이런 방식으로 인스턴스화된 개체는 모두 동일하게 보이고 개성이 없습니다. 프로토타입 속성 값을 변경하면 모든 인스턴스가 영향을 받습니다.

프로토타입 속성의 값이 참조형 데이터인 경우, 하나의 객체 인스턴스에서 속성 값이 수정되면 모든 인스턴스에 영향을 미칩니다.

예제 1

Book 유형을 정의한 다음 인스턴스화하기만 하면 됩니다.

function Book () {};  //声明构造函数
Book.prototype.o = {x : 1, y : 2};  //构造函数的原型属性o是一个对象
var book1 = new Book ();  //实例化对象book1
var book2 = new Book ();  //实例化对象book2
console.log(book1.o.x);  //返回1
console.log(book2.o.x);  //返回1
book2.o.x = 3;  //修改实例化对象book2中的属性x的值
console.log(book1.o.x);  //返回3
console.log(book2.o.x);  //返回3

프로토타입 속성 o는 참조 값이므로 모든 인스턴스의 o 속성 값은 동일한 객체에 대한 참조입니다. o 값이 변경되면 모든 인스턴스에 영향을 미칩니다.

Construction 프로토타입은 프로토타입 패턴을 해결하기 위해 탄생한 하이브리드 디자인 패턴으로, 위의 문제를 피하기 위해 생성자 패턴과 프로토타입 패턴을 혼합합니다.

구현 방법: 서로 영향을 미칠 수 있는 프로토타입 속성과 동적으로 매개변수를 전달하려는 속성의 경우 생성자 패턴을 사용하여 독립적으로 설계할 수 있습니다. 개별 디자인이 필요하지 않고 공통된 특성을 갖는 메소드나 속성의 경우 프로토타입 패턴을 사용하여 디자인할 수 있습니다.

예제 2

위의 디자인 원칙에 따라 두 가지 속성을 생성자 패턴으로 디자인하고, 디자인 방법은 프로토타입 패턴이다.

function Book (title, pages) {  //构造函数模式设计
    this.title = title;
    this.pages = pages;
}
Book.prototype.what = function () {  //原型模式设计
    console.log(this.title + this.pages);
};
var book1 = new Book("JavaScript 程序设计", 160);
var book2 = new Book("C语言程序设计", 240);
console.log(book1.title);
console.log(book2.title);

Constructed Prototype 패턴은 ECMAScript에서 클래스를 정의하는 데 권장되는 표준입니다. 일반적으로 모든 속성을 정의하려면 생성자 패턴을 사용하고, 모든 메서드를 정의하려면 프로토타입 패턴을 사용하는 것이 좋습니다. 이렇게 하면 모든 메서드가 한 번만 생성되고 각 인스턴스는 필요에 따라 속성 값을 설정할 수 있습니다. 이는 가장 널리 사용되는 디자인 패턴이기도 합니다.

동적 프로토타입

객체 지향 설계 원칙에 따르면 유형의 모든 멤버는 클래스 구조에 캡슐화되어야 합니다. 예:

function Book (title, pages) {  //构造函数模式设计
    this.title = title;
    this.pages = pages;
    Book.prototype.what = function () {  //原型模式设计,位于类的内部
        console.log(this.title + this.pages);
    };
}

그러나 인스턴스화될 때마다 Book 클래스에 포함된 프로토타입 메서드가 반복적으로 생성되어 수많은 프로토타입 메서드가 생성되고 시스템 리소스가 낭비됩니다. 프로토타입 메소드가 존재하는지 여부를 확인하기 위해 if를 사용할 수 있습니다. 존재하지 않으면 메소드가 생성되지 않습니다.

function Book (title, pages) {
    this.title = title;
    this.pages = pages;
    if (typeof Book.isLock == "undefined") {  //创建原型方法的锁,如果不存在则创建
        Book.prototype.what = function () {
            console.log(this.title + this.pages);
        };
        Book.isLock = true;  //创建原型方法后,把锁锁上,避免重复创建
    }
}
var book1 = new Book("JavaScript 程序设计", 160);
var book2 = new Book("C语言程序设计", 240);
console.log(book1.title);
console.log(book2.title);

typeof Book.isLock 표현식은 정의되지 않은 문자열을 반환하는 경우 속성 값이 존재하지 않음을 나타내며 프로토타입 메서드 생성을 허용합니다. 이 속성의 값을 true 로 설정하므로 프로토타입 메소드를 반복적으로 생성할 필요가 없습니다. 여기서는 프로토타입이 객체 인스턴스가 아닌 클래스 자체에 속하기 때문에 여기서는 Book이라는 클래스 이름을 사용했습니다.

동적 프로토타입 모드와 구축된 프로토타입 모드는 성능이 동일하며 사용자가 자유롭게 선택할 수 있지만 구축된 프로토타입 모드가 더 널리 사용됩니다.

팩토리 패턴

팩토리 패턴은 유형을 정의하는 가장 기본적인 방법이자 JavaScript에서 가장 일반적으로 사용되는 개발 패턴입니다. 이는 단순히 객체 인스턴스화를 함수로 캡슐화한 다음 함수를 호출하여 인스턴스 객체를 신속하게 일괄 생성합니다.

예제 1

다음 예에서는 Car 유형을 설계합니다. 여기에는 자동차 색상, 구동 바퀴 수, 100km당 연료 소비량이라는 세 가지 속성이 포함되어 있으며 자동차 색상을 표시하는 방법도 정의합니다.

function Car (color, drive, oil) {  //汽车类
    var _car =  new Object();  //临时对象
        _car.color = color;  //初始化颜色
        _car.drive = drive;  //初始化驱动轮数
        _car.oil = oil;  //初始化百公里油耗
        _car.showColor = function () {  //方法,提示汽车颜色
            console.log(this.color);
        };
        return _car;  //返回实例
}
var car1 = Car("red", 4, 8);
var car2 = Car("blue", 2, 6);
car1.showColor();  //输出“red”
car2.showColor();  //输出“blue”

위의 코드는 Car 클래스를 사용하여 여러 개의 자동차 인스턴스를 빠르게 생성할 수 있는 코드입니다. 구조는 동일하지만 속성이 다릅니다. 100km당 연료 소비량.

예제 2

타입에서 메소드는 초기화 매개변수를 기반으로 특정 작업을 완료할 수 있는 동작 또는 작업이며 공통된 특성을 가지고 있습니다. 따라서 메서드가 인스턴스화될 때마다 함수가 생성되는 것을 방지하고 각 인스턴스가 동일한 함수를 공유하도록 Car() 함수 외부에 메서드를 배치하는 것을 고려할 수 있습니다.

function showColor () {  //公共方法,提示汽车颜色
    console.log(this.color);
};
function Car (color, drive, oil) {  //汽车类
    var _car = new Object();  //临时对象
        _car.color = color;  //初始化颜色
        _car.drive = drive;  //初始化驱动轮数
        _car.oil = oil;  //初始化百公里油耗
        _car.showColor = showColor;  //引用外部函数
    return _car;  //返回实例
}

위에서 다시 작성한 코드에서는 showColor() 함수가 Car() 함수 앞에 정의되어 있습니다. Car() 내부에서는 외부 showColor() 함수를 참조하여 인스턴스화될 때마다 새 함수를 생성할 필요가 없습니다. 기능적으로는 함수를 반복적으로 생성하는 문제를 해결하지만 의미적으로는 함수가 객체 메서드와 덜 비슷합니다.

클래스 상속

클래스 상속의 설계 방법: 하위 클래스에서 상위 클래스 생성자를 호출합니다.

JavaScript에서 클래스 상속을 구현할 때 다음 3가지 기술적 문제에 주의해야 합니다.

在子类中,使用 apply 调用父类,把子类构造函数的参数传递给父类父类构造函数。让子类继承父类的私有属性,即 Parent.apply(this, arguments); 代码行。

在父类和子类之间建立原型链,即 Sub.prototype = new Parent(); 代码行。通过这种方式保证父类和子类是原型链上的上下级关系,即子类的 prototype 指向父类的一个实例。

恢复子类的原型对象的构造函数,即 Sub.prototype.constructor=Sub;语句行。当改动 prototype 原型时,就会破坏原来的 constructor 指针,所以必须重置 constructor。

示例1

下面示例演示了一个三重继承的案例,包括基类、父类和子类,它们逐级继承。

//基类Base
function Base (x) {  //构造函数Base
    this.get = function () {  //私有方法,获取参数值
        return x;
    }
}
Base.prototype.has = function () {  //原型方法,判断get()方法返回值是否为0
    return ! (this.get() == 0);
}
//父类Parent
function Parent () {  //构造函数Parent
    var a = [];  //私有数组a
    a = Array.apply(a, arguments);  //把参数转换为数组
    Base.call(this, a.length);  //调用Base类,并把参数数组长度传递给它
    this.add = function () {  //私有方法,把参数数组补加到数组a中并返回
        return a.push.apply(a, arguments)
    }
    this.geta = function () {  //私有方法,返回数组a
        return a;
    }
}
Parent.prototype = new Base();  //设置Parent原型为Base的实例,建立原型链
Parent.prototype.constructor = Parent;  //恢复Parent类原型对象的构造器
Parent.prototype.str = function (){  //原型方法,把数组转换为字符串并返回
    return this.geta().toString();
}
//子类Sub
function Sub () {  //构造函数
    Parent.apply(this, arguments);  //调用Parent类,并把参数数组长度传递给它
    this.sort = function () {  //私有方法,以字符顺序对数组进行排序
        var a = this.geta();  //获取数组的值
        a.sort.apply(a, arguments);  //调用数组排序方法 sort()对数组进行排序
    }
}
Sub.prototype = new Parent();  //设置Sub原型为Parent实例,建立原型链
Sub.prototype.constructor = Sub;  //恢复Sub类原型对象的构造器
//父类Parent的实例继承类Base的成员
var parent = new Parent (1, 2, 3, 4);  //实例化Parent类
console.log(parent.get());  //返回4,调用Base类的方法get()
console.log(parent.has());  //返回true,调用Base类的方法has()
//子类Sub的实例继承类Parent和类Base的成员
var sub = new Sub (30, 10, 20, 40);  //实例化Sub类
sub.add(6, 5);  //调用Parent类方法add(),补加数组
console.log(sub.geta());  //返回数组30,10,20,40,6,5
sub.sort();  //排序数组
console.log(sub.geta());  //返回数组10,20,30,40,5,6
console.log(sub.get());  //返回4,调用Base类的方法get()
console.log(sub.has());  //返回true,调用Base类的方法has()
console.log(sub.str());  //返回10,20,30,40,5,6

【设计思路】

设计子类 Sub 继承父类 Parent,而父类 Parent 又继承基类 Base。Base、Parent、Sub 三个类之间的继承关系是通过在子类中调用的构造函数来维护的。

例如,在 Sub 类中,Parent.apply(this, arguments); 能够在子类中调用父类,并把子类的参数传递给父类,从而使子类拥有父类的所有属性。

同理,在父类中,Base.call(this, a.length); 把父类的参数长度作为值传递给基类,并进行调用,从而实现父类拥有基类的所有成员。

从继承关系上看,父类继承了基类的私有方法 get(),为了确保能够继承基类的原型方法,还需要为它们建立原型链,从而实现原型对象的继承关系,方法是添加语句行 Parent.prototype=new Base();。

同理,在子类中添加语句 Sub.prototype=new Parent();,这样通过原型链就可以把基类、父类和子类串连在一起,从而实现子类能够继承父类属性,还可以继承基类的属性。

示例2

下面尝试把类继承模式封装起来,以便规范代码应用。

function extend (Sub, Sup) {  //类继承封装函数
    var F = function () {};  //定义一个空函数
    F.prototype = Sup.prototype;  //设置空函数的原型为父类的原型
    Sub.prototype = new F ();  //实例化空函数,并把父类原型引用传给给子类
    Sub.prototype.constructor = Sub;  //恢复子类原型的构造器为子类自身
    Sub.sup = Sup.prototype;  //在子类定义一个私有属性存储父类原型
    //检测父类原型构造器是否为自身
    if (Sup.prototype.constructor == Object.prototype.constructor) {
        Sup.prototype.constructor = Sup;  //类继承封装函数
    }
}

【操作步骤】

1) 定义一个封装函数。设计入口为子类和父类对象,函数功能是子类能够继承父类的所有原型成员,不涉及出口。

function extend (Sub, Sup) {  //类继承封装函数
    //其中参数Sub表示子类,Sup表示父类
}

2) 在函数体内,首先定义一个空函数 F,用来实现功能中转。设计它的原型为父类的原型,然后把空函数的实例传递给子类的原型,这样就避免了直接实例化父类可能带来的系统负荷。因为在实际开发中,父类的规模可能会很大,如果实例化,会占用大量内存。

3) 恢复子类原型的构造器为子类自己。同时,检测父类原型构造器是否与 Object 的原型构造器发生耦合。如果是,则恢复它的构造器为父类自身。

下面定义两个类,尝试把它们绑定为继承关系。

function A (x) {  //构造函数A
    this.x = x;  //私有属性x
    this.get = function () {  //私有方法get()
        return this.x;
    }
}
A.prototype.add = function () {  //原型方法add()
    return this.x + this.x;
}
A.prototype.mul = function () {  //原型方法mul()
    return this.x * this.x;
}
function B (x) {  //构造函数B
    A.call (this.x);  //在函数体内调用构造函数A,实现内部数据绑定
}
extend (B, A);  //调用封装函数,把A和B的原型捆绑在一起
var f = new B (5);  //实例化类B
console.log(f.get());  //继承类A的方法get(),返回5
console.log(f.add());  //继承类A的方法add(),返回10
console.log(f.mul());  //继承类A的方法mul(),返回25

在函数类封装函数中,有这么一句 Sub.sup=Sup.prototype;,在上面代码中没有被利用,那么它有什么作用呢?为了解答这个问题,先看下面的代码。

extend (B, A);
B.prototype.add = function () {  //为B类定义一个原型方法
    return this.x + "" + this.x;
}

上面的代码是在调用封装函数之后,再为 B 类定义了一个原型方法,该方法名与基类中原型方法 add() 同名,但是功能不同。如果此时测试程序,会发现子类 B 定义的原型方法 add() 将会覆盖父类 A 的原型方法 add()。

console.log(f.add());  //返回字符串55,而不是数值10

如果在 B 类的原型方法 add() 中调用父类的原型方法 add(),避免代码耦合现象发生。

B.prototype.add = function () {  //定义子类B的原型方法add()
    return B.sup.add.call(this);  //在函数内部调用父类方法add()
}

【相关推荐:javascript学习教程

위 내용은 자바스크립트에서 상속을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.