在 Javascript 中扩展原型 - 好方法?

Extending prototypes in Javascript - good way?

我想验证在扩展原型时我使用的方法是否正确 - 假设 "extend" 是正确的词。

这个主题得到了很多克隆。我仍在努力正确理解这个话题...

目的是: - 编写干净和良好的代码。 - 避免使用框架,如果可能的话 Javascript。 - 获得有关不扭曲 JS 以获得 class 启用行为的干净框架的建议。

这是我的沙箱的父级原型:

function Parent(){

}

Parent.prototype = {

    "init":function(){

        this.name = "anon";
    },

    "initWithParameters":function(parameters){

        this.name = parameters.name ? parameters.name : "anon";
    },

    "talk": function(){

        console.log('Parent is: ' + this.name);
    }
}

现在是 Child 原型 - 它添加了 "position" 属性 并重新定义了行为:

function Child(){

    Parent.call(this);
}


Child.prototype = new Parent;
Child.prototype.constructor = Child;

Child.prototype.init = function(){

    Parent.prototype.call(this);

    this.setPosition(0, 0);
}

Child.prototype.initWithParameters = function(parameters){

    Parent.prototype.initWithParameters.call(this, parameters);

    if(!this.position){

        this.position = {x:0, y:0};
    }

    this.setPosition(parameters.pos.x, parameters.pos.y);
}

Child.prototype.setPosition = function(x, y){

    this.position.x = x;
    this.position.y = y;
}

Child.prototype.talk = function(){

    console.log('Child is: ' + this.name + ' and location is: ' + this.position.x + ', ' + this.position.y);
}

这是一个好习惯吗?是否没有 shorthand 来避免在覆盖 属性 时写 "Child.prototype." (可能使用文字,就像写的 Parent 原型一样)。

我知道 J. Resig 的 Class/extend 方法。但我宁愿使用 Javascript 作为原型语言,而不是让它作为 "class-like behaving class-less OO language".

感谢您的帮助:-)

您的方法是一种很好的纯 JavaScript 方法。每次小费 "Child.prototype" 的唯一方法是将其放入参考变量中。

喜欢:

  var children = Child.prototype;
  children.init = function(){ /*/someoverridecode*/}

但你还在做这背后的Child.prototype。你也可以定义一个函数来为你做这件事,见下划线的绑定,也许它适合你的需要。

干杯

我可能会因为这个建议而大吃一惊,因为有几篇文章可以反对我的示例中的某些做法,但这对我有用并且适用于看起来干净的代码,保持一致,缩小得很好,在严格模式,兼容IE8

我也喜欢使用原型方法(而不是随处可见的所有 'extend' 或 'apply' 样式)。

我这样写我的类。是的,它看起来很像一种你不想要的 OOP 语言,但它仍然遵循原型模型,同时与其他熟悉的语言有相似之处,这使得项目更容易导航。

这是我喜欢的风格 :) 我并不是说它是最好的,但它很容易阅读。

(function(ns) {

  var Class = ns.ClassName = function() {

  };

  Class.prototype = new baseClass();
  Class.constructor = Class;
  var _public = Class.prototype;
  var _private = _public._ = {};

  Class.aClassProperty = "aValue";

  Class.aClassMethod = function(params) {

  }

  _public.aMethod = function(params) {
      _private.myMethod.call(this, "aParam");
      Class.aClassMethod("aParam");
  }

  _private.myMethod = function(params) {

  }

})({});

编辑:

我继续将您的示例转换为这种样式,只是为了向您展示它的外观:

var namespace = {};

(function(ns) {

    var Class = ns.Parent = function() {

    };

    var _public = Class.prototype;
    var _private = _public._ = {};

    _public.init = function() {
        this.name = "anon";
    }

    _public.initWithParameters = function(parameters) {
        this.name = parameters.name ? parameters.name : "anon";
    }

    _public.talk = function() {
        console.log('Parent is: ' + this.name);
    }

})(namespace);

(function(ns) {

    var Class = ns.Child = function() {
        this.position = {x:0, y:0};
    };

    Class.prototype = new ns.Parent();
    Class.constructor = Class;
    var _public = Class.prototype;
    var _private = _public._ = {};

    _public.init = function() {
        _public.init.call(this);
        this.setPosition(0, 0);
    }

    _public.initWithParameters = function(parameters) {
        _public.initWithParameters.call(this, parameters);
        this.setPosition(parameters.pos.x, parameters.pos.y);
    }

    _public.setPosition = function(x, y) {
        this.position.x = x;
        this.position.y = y;
    }

    _public.talk = function() {
        console.log('Child is: ' + this.name + ' and location is: ' + this.position.x + ', ' + this.position.y);
    }

})(namespace);

一般来说,您的方法会奏效,但更好的方法是替换:

Child.prototype = new Parent;

与:

Child.prototype = Object.create(Parent.prototype);

这样您就不需要调用 new Parent,这有点反模式。您也可以直接定义新属性,如下所示:

Child.prototype = Object.create(Parent.prototype, {
  setPosition: {
    value: function() {
      //... etc
    },
    writable: true,
    enumerable: true,
    configurable: true
  }
});

希望对您有所帮助。

Object.create() at MDN

这些是我常用的方法:

使用辅助函数:

/**
 * A clone of the Node.js util.inherits() function. This will require
 * browser support for the ES5 Object.create() method.
 *
 * @param {Function} ctor
 *   The child constructor.
 * @param {Function} superCtor
 *   The parent constructor.
 */

function inherits (ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false
    }
  });
};

那么你可以简单地做:

function ChildClass() {
    inherits(this, ParentClass);
    // If you want to call parent's constructor:
    this.super_.apply(this, arguments);
}

使用 Lodash 扩展原型

_.assign(ChildClass.prototype, {
  value: key
});

或者给ES6一个机会!

class ParentClass {
    constructor() {
        var date = new Date();
        var hours = date.getHours();
        var minutes = date.getMinutes();
        var seconds = date.getSeconds();
        this.initializeTime = hours + ':' + minutes + ':' + seconds;
    }
}

class ChildClass extends ParentsClass {
  constructor() {
      super();
      console.log(this.initializeTime);
  }
}

来自 2019 年 google。

latest MDN documentation开始,扩展原型的方法是:

function MyClass() {
  SuperClass.call(this);
}

// inherit one class
MyClass.prototype = Object.create(SuperClass.prototype);
// mixin another
Object.assign(MyClass.prototype, {
  //... you own prototype ...
});

// re-assign constructor
MyClass.prototype.constructor = MyClass;

我的示例展示了几件事:私有变量、parant 和 child 构造函数的相同解析参数、重写 .toString() 函数尝试:""+this。 :)

带文档的完整示例:

输出:

构造函数参数

MM = new Parent("val of arg1", "val of arg2");
Child1 = new childInterface("1", "2");
Child2 = new child2Interface("a", "b");

console.log(MM + "args:", MM.arg1, MM.arg2);
// Parentargs: val of arg1 val of arg2
console.log(Child1 + "args:", Child1.arg1, Child1.arg2);
// childInterfaceargs: 1 2
console.log(Child2 + "args:", Child2.arg1, Child2.arg2);
// child2Interfaceargs: a b

扩展 child 中的函数 class

MM.init();
// Parent: default ouput
Child1.init();
// childInterface: new output
Child2.init();
// child2Interface: default ouput

增量变量

MM.increment();
// Parent: increment 1
Child1.increment();
// childInterface: increment 1
Child2.increment();
// child2Interface: increment 1
Child2.increment();
// child2Interface: increment 2
MM.increment();
// Parent: increment 2
console.log("p", "c1", "c2");
// p c1 c2
console.log(MM.value, " " + Child1.value, " " + Child2.value);
// 2  1  2

私有变量

MM.getHidden();
// Parent: hidden (private var) is true
MM.setHidden(false);
// Parent: hidden (private var) set to false
Child2.getHidden();
// child2Interface: hidden (private var) is true
MM.setHidden(true);
// Parent: hidden (private var) set to true


Child2.setHidden(false);
// child2Interface: hidden (private var) set to false
MM.getHidden();
// Parent: hidden (private var) is true
Child1.getHidden();
// childInterface: hidden (private var) is true
Child2.getHidden();
// child2Interface: hidden (private var) is false 

保护变量

function Parent() {
  //...
  Object.defineProperty(this, "_id", { value: 312 });
};

console.log(MM._id); // 312
MM._id = "lol";
console.log(MM._id); // 312

/**
 * Class interface for Parent
 *
 * @class
 */
function Parent() {
  this.parseArguments(...arguments);

  /**
   * hidden variable
   *
   * @type    {Boolean}
   * @private
   */
  var hidden = true;


  /**
   * Get hidden
   */
  this.getHidden = () => {
    console.log(this + ": hidden (private var) is", hidden);
  }


  /**
   * Set hidden
   *
   * @param {Boolean} state New value of hidden
   */
  this.setHidden = (state) => {
    console.log(this + ": hidden (private var) set to", !!state);
    hidden = state;
  }

  Object.defineProperty(this, "_id", { value: "312" });
}

Object.defineProperty(Parent.prototype, "nameString", { value: "Parent" });


/**
 * Parse arguments
 */
Parent.prototype.parseArguments = function(arg1, arg2) {
  this.arg1 = arg1;
  this.arg2 = arg2;
};


/**
 * Get className with `class.toString()`
 */
Parent.prototype.toString = function() {
  return this.nameString;
};


/**
 * Initialize middleware
 */
Parent.prototype.init = function() {
  console.log(this + ": default ouput");
};


/**
 * Increment value
 */
Parent.prototype.increment = function() {
  this.value = (this.value) ? this.value + 1 : 1;
  console.log(this + ": increment", this.value);
};

/**
 * Class interface for Child
 *
 * @class
 */
function childInterface() {
  this.parseArguments(...arguments);
}


// extend
childInterface.prototype = new Parent();
Object.defineProperty(childInterface.prototype, "nameString", { value: "childInterface" });

/**
 * Initialize middleware (rewrite default)
 */
childInterface.prototype.init = function(chatClient) {
  console.log(this + ": new output");
};

/**
 * Class interface for Child2
 *
 * @class
 */
function child2Interface() {
  this.parseArguments(...arguments);
}


// extend
child2Interface.prototype = new Parent();
Object.defineProperty(child2Interface.prototype, "nameString", { value: "child2Interface" });

//---------------------------------------------------------
//---------------------------------------------------------

MM = new Parent("val of arg1", "val of arg2");
Child1 = new childInterface("1", "2");
Child2 = new child2Interface("a", "b");

console.log(MM + " args:", MM.arg1, MM.arg2);
console.log(Child1 + " args:", Child1.arg1, Child1.arg2);
console.log(Child2 + " args:", Child2.arg1, Child2.arg2);
console.log(" ");

MM.init();
Child1.init();
Child2.init();
console.log(" ");
MM.increment();
Child1.increment();
Child2.increment();
Child2.increment();
MM.increment();

console.log("p", "c1", "c2");
console.log(MM.value, " " + Child1.value, " " + Child2.value);
console.log(" ");

MM.getHidden();
MM.setHidden(false);
Child2.getHidden();
MM.setHidden(true);

console.log(" ");
Child2.setHidden(false);
MM.getHidden();
Child1.getHidden();
Child2.getHidden();

console.log(MM._id);
MM._id = "lol";
console.log(MM._id);

我一般都是这样做的。我现在使用 class 运算符,但在 ES3 中仍然有一个很好的方法。

使用 Node.js 实用程序

Node.js 包括一个 utility function 用于 这件事

const { inherits } = require('util');

function SuperClass () {
  this.fromSuperClass = 1;
}

function ExtendingClass () {
  this.fromExtendingClass = 1;
}

inherits(ExtendingClass, SuperClass);

const extending = new ExtendingClass();
this.fromSuperClass; // -> 1
this.fromExtendingClass; // -> 1

以上有it's fair share of problems。它没有建立原型链,因此它被认为 class 运算符在语义上不兼容

使用 Object.create API

否则,您可以使用Object.create

function Person () {
  this.person = true;
}

function CoolPerson () {
  Person.call(this);
  this.cool = true;
}

CoolPerson.prototype = Object.create(Person);
CoolPerson.prototype.constructor = CoolPerson;

使用 "helper function"

请注意,在上面的例子中使用了Object.create API,如果你不在[=20=中调用Person("super class") ](扩展 class),在实例化 Person.

时将不会应用实例属性(和可选的初始化)

如果你想要更多"elegant",你甚至可以为此创建一个辅助函数,这对你来说可能更容易。

function extend(BaseClassFactory, SuperClass) {
  const BaseClass = BaseClassFactory(SuperClass.prototype, SuperClass);
  BaseClass.prototype = Object.assign(BaseClass.prototype, Object.create(SuperClass));
  BaseClass.prototype.constructor = BaseClass;
  return BaseClass;
}

function SuperClass() {
  this.superClass = true;
}

SuperClass.prototype.method = function() {
  return 'one';
}

const ExtendingClass = extend((Super, SuperCtor) => {
  function ExtendingClass () {
    SuperCtor.call(this);
    this.extending = true;
  }
  
  // Example of calling a super method:
  ExtendingClass.prototype.method = function () {
    return Super.method.call(this) + ' two'; // one two
  }

  return ExtendingClass;
}, SuperClass);

const extending = new ExtendingClass();
extending.method(); // => one two

使用 ES6 的 class 运算符

JavaScript 中有一个新的 class 运算符已在 JavaScript 中发布,这可能会使整个体验更具表现力。

class SuperClass {
  constructor() {
    this.superClass = true;
  }

  method() {
    return 'one';
  }
}

class ExtendingClass extends SuperClass {
  constructor() {
    super();
    this.extending = true;
  }

  method() {
    // In this context, `super.method` refers to a bound version of `SuperClass.method`, which can be called like a normal method.
    return `${super.method()} two`;
  }
}

const extending = new ExtendingClass();
extending.method(); // => one two

希望这对您有所帮助。