>  기사  >  웹 프론트엔드  >  JavaScript 상속에 대한 자세한 설명(4)_js 객체지향

JavaScript 상속에 대한 자세한 설명(4)_js 객체지향

WBOY
WBOY원래의
2016-05-16 18:50:00738검색
JavaScript의 고전적 상속.
Crockford는 JavaScript 개발 커뮤니티에서 가장 잘 알려진 권위자이며 JSON, JSLint, JSMinADSafe의 아버지는 "JavaScript: The Good Parts"의 저자입니다. 그는 현재 Yahoo의 수석 JavaScript 설계자이며 YUI의 디자인 및 개발에 참여하고 있습니다. 크록포드의 삶과 저작을 자세히 설명한
기사를 소개합니다. 물론 크록포드도 우리와 후배들이 존경하는 인물이다.
호출 방법

먼저 Crockford 스타일 상속을 사용한 호출 메서드를 살펴보겠습니다.

참고: 코드의 메서드, 상속, uber는 모두 사용자 정의 개체이므로 이후 코드 분석에서 자세히 설명하겠습니다.

    // 定义Person类
    function Person(name) {
      this.name = name;
    }
    // 定义Person的原型方法
    Person.method("getName", function() {
      return this.name;
    }); 
    
    // 定义Employee类
    function Employee(name, employeeID) {
      this.name = name;
      this.employeeID = employeeID;
    }
    // 指定Employee类从Person类继承
    Employee.inherits(Person);
    // 定义Employee的原型方法
    Employee.method("getEmployeeID", function() {
      return this.employeeID;
    });
    Employee.method("getName", function() {
      // 注意,可以在子类中调用父类的原型方法
      return "Employee name: " + this.uber("getName");
    });
    // 实例化子类
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    

여기서 언급해야 할 몇 가지 단점이 있습니다.

    하위 클래스가 상위 클래스로부터 상속받는 코드는 하위 클래스와 상위 클래스가 모두 정의된 후에 이루어져야 하며, 하위 클래스의 프로토타입 메서드가 정의되기 전에 이루어져야 합니다.
  • 하위 클래스의 메소드 본문은 상위 클래스의 메소드를 호출할 수 있지만 하위 클래스의 생성자는 상위 클래스의 생성자를 호출할 수 없습니다.
  • 프로토타입 메소드 정의, 상위 클래스 호출 메소드(직관적이지 않음) 등 코드 작성이 우아하지 않습니다.

물론 Crockford의 구현은 다음 예와 같이 하위 클래스의 메서드를 지원하여 매개 변수를 사용하여 상위 클래스 메서드를 호출할 수도 있습니다.

    function Person(name) {
      this.name = name;
    }
    Person.method("getName", function(prefix) {
      return prefix + this.name;
    });

    function Employee(name, employeeID) {
      this.name = name;
      this.employeeID = employeeID;
    }
    Employee.inherits(Person);
    Employee.method("getName", function() {
      // 注意,uber的第一个参数是要调用父类的函数名称,后面的参数都是此函数的参数
      // 个人觉得这样方式不如这样调用来的直观:this.uber("Employee name: ")
      return this.uber("getName", "Employee name: ");
    });
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    

코드 분석

우선 메소드 함수의 정의는 매우 간단합니다.

여기서 이 점에 특히 주의하세요. 이를 볼 때 현재 함수에만 집중할 것이 아니라 현재 함수가 어떻게 호출되는지 생각해야 합니다. 예를 들어, 이 예제에서는 new를 통해 메서드를 호출하지 않으므로 메서드의 this는 현재 함수를 가리킵니다.
    Function.prototype.method = function(name, func) {
      // this指向当前函数,也即是typeof(this) === "function"
      this.prototype[name] = func;
      return this;
    };
    

상속함수 정의가 좀 복잡합니다.

참고로 상속함수에는 작은 버그가 있습니다. 즉, 생성자의 포인터가 재정의되지 않아 아래와 같은 오류가 발생합니다. :
    Function.method('inherits', function (parent) {
      // 关键是这一段:this.prototype = new parent(),这里实现了原型的引用
      var d = {}, p = (this.prototype = new parent());
      
      // 只为子类的原型增加uber方法,这里的Closure是为了在调用uber函数时知道当前类的父类的原型(也即是变量 - v)
      this.method('uber', function uber(name) {
        // 这里考虑到如果name是存在于Object.prototype中的函数名的情况
        // 比如 "toString" in {} === true
        if (!(name in d)) {
          // 通过d[name]计数,不理解具体的含义
          d[name] = 0;
        }    
        var f, r, t = d[name], v = parent.prototype;
        if (t) {
          while (t) {
            v = v.constructor.prototype;
            t -= 1;
          }
          f = v[name];
        } else {
          // 个人觉得这段代码有点繁琐,既然uber的含义就是父类的函数,那么f直接指向v[name]就可以了
          f = p[name];
          if (f == this[name]) {
            f = v[name];
          }
        }
        d[name] += 1;
        // 执行父类中的函数name,但是函数中this指向当前对象
        // 同时注意使用Array.prototype.slice.apply的方式对arguments进行截断(因为arguments不是标准的数组,没有slice方法)
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d[name] -= 1;
        return r;
      });
      return this;
    });
    
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    console.log(zhang.constructor === Employee);  // false
    console.log(zhang.constructor === Person);   // true
    

개선을 위한 제안

이전 분석을 토대로 개인적으로 메서드 기능은 필요하지 않다고 느꼈지만 시선을 혼동하기 쉽습니다. Inheres 메소드는 약간의 축소 작업을 수행할 수 있으며(Crockford는 더 많은 상황을 고려할 수 있기 때문입니다. 원본 기사에서는 상속을 사용하는 여러 가지 방법을 소개하고 우리는 그 중 하나만 집중적으로 설명합니다) 생성자의 포인팅 오류를 수정합니다.

전화 방법:
    Function.prototype.inherits = function(parent) {
      this.prototype = new parent();
      this.prototype.constructor = this;
      this.prototype.uber = function(name) {
        f = parent.prototype[name];
        return f.apply(this, Array.prototype.slice.call(arguments, 1));
      };
    };
    
    function Person(name) {
      this.name = name;
    }
    Person.prototype.getName = function(prefix) {
      return prefix + this.name;
    };
    function Employee(name, employeeID) {
      this.name = name;
      this.employeeID = employeeID;
    }
    Employee.inherits(Person);
    Employee.prototype.getName = function() {
      return this.uber("getName", "Employee name: ");
    };
    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    console.log(zhang.constructor === Employee);  // true
    

흥미롭습니다

기사 마지막 부분에서 Crockford는 실제로 다음과 같이 말했습니다.

저는 8년 동안 JavaScript를 작성해 왔지만 우버 기능을 사용해야 할 필요성을 느낀 적이 없습니다. 슈퍼 아이디어는 클래식 패턴에서는 상당히 중요하지만 프로토타입 패턴과 기능적 패턴에서는 불필요해 보입니다. 이제 나는 JavaScript에서 클래식 모델을 지원하려는 나의 초기 시도를 실수로 봅니다.
Crockford는 JavaScript의 객체 지향 프로그래밍을 거부하고 JavaScript가 프로그래밍을 위해 프로토타입 및 기능적 패턴을 따라야 한다고 주장하는 것을 볼 수 있습니다.
하지만 개인적으로는 복잡한 시나리오에서 객체지향 메커니즘이 있다면 훨씬 더 편리할 것 같습니다.
그러나 누가 이를 보장할 수 있습니까? jQuery UI와 같은 프로젝트도 상속을 사용하지 않는 반면, Extjs 및 Qooxdoo와 같은 프로젝트는 객체 지향 JavaScript를 강력하게 옹호합니다. Cappuccino 프로젝트는 객체 지향 JavaScript를 구현하기 위해 Objective-J 언어도 발명했습니다.
성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.