Learning JavaScript Design Patterns P.2 (ok)

https://addyosmani.com/resources/essentialjsdesignpatterns/book/#introduction

Mediator Pattern

A Simple Mediator

Mediator là một đối tượng điều phối các tương tác (logic và hành vi) giữa nhiều đối tượng. Nó đưa ra quyết định về thời điểm gọi đối tượng nào, dựa trên hành động (hoặc không hành động) của các đối tượng khác và đầu vào.

Bạn có thể viết người dàn xếp bằng một dòng mã:

var mediator = {};

Vâng, tất nhiên đây chỉ là một đối tượng theo nghĩa đen trong JavaScript. Một lần nữa, chúng ta đang nói về ngữ nghĩa ở đây. Mục đích của trình dàn xếp là kiểm soát quy trình làm việc giữa các đối tượng và chúng tôi thực sự không cần bất cứ thứ gì khác ngoài một đối tượng theo nghĩa đen để thực hiện điều này.

var orgChart = {
  addNewEmployee: function(){
    // getEmployeeDetail provides a view that users interact with
    var employeeDetail = this.getEmployeeDetail();
    // when the employee detail is complete, the mediator (the 'orgchart' object)
    // decides what should happen next
    employeeDetail.on("complete", function(employee){
      // set up additional objects that have additional events, which are used
      // by the mediator to do additional things
      var managerSelector = this.selectManager(employee);
      managerSelector.on("save", function(employee){
        employee.save();
      });
    });
  },
  // ...
}

Event Aggregator (Pub/Sub) And Mediator Together

var MenuItem = MyFrameworkView.extend({
  events: {
    "click .thatThing": "clickedIt"
  },
  clickedIt: function(e) {
    e.preventDefault();
    // assume this triggers "menu:click:foo"
    MyFramework.trigger("menu:click:" + this.model.get("name"));
  }
});
// ... somewhere else in the app
var MyWorkflow = function() {
  MyFramework.on("menu:click:foo", this.doStuff, this);
};
MyWorkflow.prototype.doStuff = function() {
  // instantiate multiple objects here.
  // set up event handlers for those objects.
  // coordinate all of the objects into a meaningful workflow.
};

Prototype Pattern

Đối với những người quan tâm, kế thừa nguyên mẫu thực sự, như được định nghĩa trong tiêu chuẩn ECMAScript 5, yêu cầu sử dụng Object.create (mà chúng ta đã xem xét trước đó trong phần này). Để nhắc nhở chúng ta, Object.create tạo một đối tượng có một nguyên mẫu được chỉ định và tùy chọn cũng chứa các thuộc tính được chỉ định (ví dụ: Object.create (nguyên mẫu, tùy chọnDescriptorObjects)).

var myCar = {
  name: "Ford Escort",
  drive: function() {
    console.log("Weeee. I'm driving!");
  },
  panic: function() {
    console.log("Wait. How do you stop this thing?");
  }
};
// Use Object.create to instantiate a new car
var yourCar = Object.create(myCar);
// Now we can see that one is a prototype of the other
console.log(yourCar.name);

Object.create cũng cho phép chúng ta dễ dàng triển khai các khái niệm nâng cao như kế thừa vi phân trong đó các đối tượng có thể kế thừa trực tiếp từ các đối tượng khác. Chúng ta đã thấy trước đó rằng Object.create cho phép chúng ta khởi tạo các thuộc tính đối tượng bằng cách sử dụng đối số được cung cấp thứ hai. Ví dụ:

C:\Users\Administrator\Desktop\gulp\es6.js

var vehicle = {
  getModel: function () {
    console.log( "The model of this vehicle is.." + this.model );
  }
};
var car = Object.create(vehicle, {
  "model": {
    value: "Ford",
    enumerable: true
  }
});
console.log(car);

Ở đây, các thuộc tính có thể được khởi tạo trên đối số thứ hai của Object.create bằng cách sử dụng một đối tượng theo nghĩa đen có cú pháp tương tự như cú pháp được sử dụng bởi các phương thức Object.defineProperties và Object.defineProperty mà chúng ta đã xem xét trước đó.

Cần lưu ý rằng các mối quan hệ nguyên mẫu có thể gây ra rắc rối khi liệt kê các thuộc tính của các đối tượng và (như Crockford khuyến nghị) gói nội dung của vòng lặp trong một kiểm tra hasOwnProperty ().

Nếu chúng ta muốn triển khai mẫu nguyên mẫu mà không trực tiếp sử dụng Object.create, chúng ta có thể mô phỏng mẫu theo ví dụ trên như sau:

var vehiclePrototype = {
  init: function(carModel) {
    this.model = carModel;
  },
  getModel: function() {
    console.log("The model of this vehicle is.." + this.model);
  }
};
function vehicle(model) {
  function F() {};
  F.prototype = vehiclePrototype;
  var f = new F();
  f.init(model);
  return f;
}
var car = vehicle("Ford Escort");
car.getModel();

Cách triển khai thay thế cuối cùng của mẫu Nguyên mẫu có thể như sau:

var beget = (function() {
  function F() {}
  return function(proto) {
    F.prototype = proto;
    return new F();
  };
})();

Command Pattern

Để minh họa Command pattern, chúng ta sẽ tạo một dịch vụ mua xe đơn giản.

(function() {
  var carManager = {
    // request information
    requestInfo: function(model, id) {
      return "The information for " + model + " with ID " + id + " is foobar";
    },
    // purchase the car
    buyVehicle: function(model, id) {
      return "You have successfully purchased Item " + id + ", a " + model;
    },
    // arrange a viewing
    arrangeViewing: function(model, id) {
      return "You have successfully booked a viewing of " + model + " ( " + id + " ) ";
    }
  };
  carManager.execute = function(name) {
    return carManager[name] && carManager[name].apply(carManager, [].slice.call(arguments, 1));
  };
  var pi = carManager.execute("buyVehicle", "Ford Escort", "453543");
  console.log(pi);
})();

Facade Pattern

var module = (function() {
  var _private = {
    i: 5,
    get: function() {
      console.log("current value:" + this.i);
    },
    set: function(val) {
      this.i = val;
    },
    run: function() {
      console.log("running");
    },
    jump: function() {
      console.log("jumping");
    }
  };
  return {
    facade: function(args) {
      _private.set(args.val);
      _private.get();
      if (args.run) {
        _private.run();
      }
    }
  };
}());
// Outputs: "current value: 10" and "running"
module.facade({ run: true, val: 10 });

Factory Pattern

// Types.js - Constructors used behind the scenes
// A constructor for defining new cars
function Car(options) {
  // some defaults
  this.doors = options.doors || 4;
  this.state = options.state || "brand new";
  this.color = options.color || "silver";
}
// A constructor for defining new trucks
function Truck(options) {
  this.state = options.state || "used";
  this.wheelSize = options.wheelSize || "large";
  this.color = options.color || "blue";
}
// FactoryExample.js
// Define a skeleton vehicle factory
function VehicleFactory() {}
// Define the prototypes and utilities for this factory
// Our default vehicleClass is Car
VehicleFactory.prototype.vehicleClass = Car;
// Our Factory method for creating new Vehicle instances
VehicleFactory.prototype.createVehicle = function(options) {
  switch (options.vehicleType) {
    case "car":
      this.vehicleClass = Car;
      break;
    case "truck":
      this.vehicleClass = Truck;
      break;
      //defaults to VehicleFactory.prototype.vehicleClass (Car)
  }
  return new this.vehicleClass(options);
};
// Create an instance of our factory that makes cars
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle({
  vehicleType: "car",
  color: "yellow",
  doors: 6
});
// Test to confirm our car was created using the vehicleClass/prototype Car
// Outputs: true
console.log(car instanceof Car);
// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console.log(car);

Approach #1: Modify a VehicleFactory instance to use the Truck class

var movingTruck = carFactory.createVehicle({
  vehicleType: "truck",
  state: "like new",
  color: "red",
  wheelSize: "small"
});
// Test to confirm our truck was created with the vehicleClass/prototype Truck
// Outputs: true
console.log(movingTruck instanceof Truck);
// Outputs: Truck object of color "red", a "like new" state
// and a "small" wheelSize
console.log(movingTruck);

Approach #2: Subclass VehicleFactory to create a factory class that builds Trucks

function TruckFactory() {}
TruckFactory.prototype = new VehicleFactory();
TruckFactory.prototype.vehicleClass = Truck;
var truckFactory = new TruckFactory();
var myBigTruck = truckFactory.createVehicle({
  state: "omg..so bad.",
  color: "pink",
  wheelSize: "so big"
});
// Confirms that myBigTruck was created with the prototype Truck
// Outputs: true
console.log(myBigTruck instanceof Truck);
// Outputs: Truck object with the color "pink", wheelSize "so big"
// and state "omg. so bad"
console.log(myBigTruck);

Abstract Factories

Cũng hữu ích khi nhận thức được mô hình Nhà máy trừu tượng, nhằm mục đích gói gọn một nhóm các nhà máy riêng lẻ với một mục tiêu chung. Nó tách các chi tiết của việc triển khai một tập hợp các đối tượng khỏi cách sử dụng chung của chúng.

Một nhà máy trừu tượng nên được sử dụng khi một hệ thống phải độc lập với cách tạo ra các đối tượng mà nó tạo ra hoặc nó cần phải làm việc với nhiều loại đối tượng.

Một ví dụ vừa đơn giản vừa dễ hiểu hơn là nhà máy sản xuất xe, nơi xác định các cách lấy hoặc đăng ký các loại xe. Nhà máy trừu tượng có thể được đặt tên là abstractVehicleFactory. Nhà máy Abstract sẽ cho phép định nghĩa các loại phương tiện như "ô tô" hoặc "xe tải" và các nhà máy bê tông sẽ chỉ triển khai các lớp đáp ứng hợp đồng xe (ví dụ: Vehicle.prototype.drive và Vehicle.prototype.breakDown).

// Types.js - Constructors used behind the scenes
// A constructor for defining new cars
function Car(options) {
  // some defaults
  this.doors = options.doors || 4;
  this.state = options.state || "brand new";
  this.color = options.color || "silver";
}
// A constructor for defining new trucks
function Truck(options) {
  this.state = options.state || "used";
  this.wheelSize = options.wheelSize || "large";
  this.color = options.color || "blue";
}
var abstractVehicleFactory = (function() {
  // Storage for our vehicle types
  var types = {};
  return {
    getVehicle: function(type, customizations) {
      var Vehicle = types[type];
      return (Vehicle ? new Vehicle(customizations) : null);
    },
    registerVehicle: function(type, Vehicle) {
      var proto = Vehicle.prototype;
      // only register classes that fulfill the vehicle contract
      if (proto.drive && proto.breakDown) {
        types[type] = Vehicle;
      }
      return abstractVehicleFactory;
    }
  };
})();
// Usage:
abstractVehicleFactory.registerVehicle("car", Car);
var car = abstractVehicleFactory.getVehicle("car", {
  color: "lime green",
  state: "like new"
});
console.log(car);

Mixin Pattern

Trong các ngôn ngữ lập trình truyền thống như C ++ và Lisp, Mixins là các lớp cung cấp chức năng có thể dễ dàng được kế thừa bởi một lớp con hoặc một nhóm lớp con nhằm mục đích tái sử dụng hàm.

Sub-classing

var Person = function(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.gender = "male";
};
// a new instance of Person can then easily be created as follows:
var clark = new Person("Clark", "Kent");
// Define a subclass constructor for for "Superhero":
var Superhero = function(firstName, lastName, powers) {
  // Invoke the superclass constructor on the new object
  // then use .call() to invoke the constructor as a method of
  // the object to be initialized.
  Person.call(this, firstName, lastName);
  // Finally, store their powers, a new array of traits not found in a normal "Person"
  this.powers = powers;
};
Superhero.prototype = Object.create(Person.prototype);
var superman = new Superhero("Clark", "Kent", ["flight", "heat-vision"]);
console.log(superman);
// Outputs Person attributes as well as powers

Mixins

var myMixins = {
  moveUp: function(){
    console.log( "move up" );
  },
  moveDown: function(){
    console.log( "move down" );
  },
  stop: function(){
    console.log( "stop! in the name of love!" );
  }
};

Sau đó, chúng ta có thể dễ dàng mở rộng nguyên mẫu của các hàm khởi tạo hiện có để bao gồm hành vi này bằng cách sử dụng một trình trợ giúp như phương thức Underscore.js _.extend ():

C:\Users\Administrator\Desktop\gulp\index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/underscore@1.12.0/underscore-min.js"></script>
  <script type="text/javascript" src="jquery.ba-tinypubsub.min.js"></script>
</head>
<body>
  <script src="es6.js"></script>
</body>
</html>

C:\Users\Administrator\Desktop\gulp\es6.js

var myMixins = {
  moveUp: function(){
    console.log( "move up" );
  },
  moveDown: function(){
    console.log( "move down" );
  },
  stop: function(){
    console.log( "stop! in the name of love!" );
  }
};
// A skeleton carAnimator constructor
function CarAnimator() {
  this.moveLeft = function() {
    console.log("move left");
  };
}
// A skeleton personAnimator constructor
function PersonAnimator() {
  this.moveRandomly = function() { /*..*/ };
}
// Extend both constructors with our Mixin
_.extend(CarAnimator.prototype, myMixins);
_.extend(PersonAnimator.prototype, myMixins);
// Create a new instance of carAnimator
var myAnimator = new CarAnimator();
myAnimator.moveLeft();
myAnimator.moveDown();
myAnimator.stop();
// Outputs:
// move left
// move down
// stop! in the name of love!

Thay vào đó, ví dụ này sẽ chứng minh cách tăng cường một hàm tạo để bao gồm chức năng mà không cần phải sao chép quy trình này cho mọi hàm tạo mà chúng ta có thể có.

// Define a simple Car constructor
var Car = function(settings) {
  this.model = settings.model || "no model provided";
  this.color = settings.color || "no colour provided";
};
// Mixin
var Mixin = function() {};
Mixin.prototype = {
  driveForward: function() {
    console.log("drive forward");
  },
  driveBackward: function() {
    console.log("drive backward");
  },
  driveSideways: function() {
    console.log("drive sideways");
  }
};
// Extend an existing object with a method from another
function augment(receivingClass, givingClass) {
  // only provide certain methods
  if (arguments[2]) {
    for (var i = 2, len = arguments.length; i < len; i++) {
      receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
    }
  }
  // provide all methods
  else {
    for (var methodName in givingClass.prototype) {
      // check to make sure the receiving class doesn't
      // have a method of the same name as the one currently
      // being processed
      if (!Object.hasOwnProperty.call(receivingClass.prototype, methodName)) {
        receivingClass.prototype[methodName] = givingClass.prototype[methodName];
      }
      // Alternatively (check prototype chain as well):
      // if ( !receivingClass.prototype[methodName] ) {
      // receivingClass.prototype[methodName] = givingClass.prototype[methodName];
      // }
    }
  }
}
// Augment the Car constructor to include "driveForward" and "driveBackward"
augment(Car, Mixin, "driveForward", "driveBackward");
// Create a new Car
var myCar = new Car({
  model: "Ford Escort",
  color: "blue"
});
// Test to make sure we now have access to the methods
myCar.driveForward();
myCar.driveBackward();
// Outputs:
// drive forward
// drive backward
// We can also augment Car to include all functions from our mixin
// by not explicitly listing a selection of them
augment(Car, Mixin);
var mySportsCar = new Car({
  model: "Porsche",
  color: "red"
});
mySportsCar.driveSideways();
// Outputs:
// drive sideways

Decorator Pattern

Example 1: Decorating Constructors With New Functionality

// A vehicle constructor
function Vehicle(vehicleType) {
  // some sane defaults
  this.vehicleType = vehicleType || "car";
  this.model = "default";
  this.license = "00000-000";
}
// Test instance for a basic vehicle
var testInstance = new Vehicle("car");
console.log(testInstance);
// Outputs:
// vehicle: car, model:default, license: 00000-000
// Lets create a new instance of vehicle, to be decorated
var truck = new Vehicle("truck");
// New functionality we're decorating vehicle with
truck.setModel = function(modelName) {
  this.model = modelName;
};
truck.setColor = function(color) {
  this.color = color;
};
// Test the value setters and value assignment works correctly
truck.setModel("CAT");
truck.setColor("blue");
console.log(truck);
// Outputs:
// vehicle:truck, model:CAT, color: blue
// Demonstrate "vehicle" is still unaltered
var secondInstance = new Vehicle("car");
console.log(secondInstance);
// Outputs:
// vehicle: car, model:default, license: 00000-000

Example 2: Decorating Objects With Multiple Decorators

// The constructor to decorate
function MacBook() {
  this.cost = function() {
    return 997;
  };
  this.screenSize = function() {
    return 11.6;
  };
}
// Decorator 1
function memory(macbook) {
  var v = macbook.cost();
  macbook.cost = function() {
    return v + 75;
  };
}
// Decorator 2
function engraving(macbook) {
  var v = macbook.cost();
  macbook.cost = function() {
    return v + 200;
  };
}
// Decorator 3
function insurance(macbook) {
  var v = macbook.cost();
  macbook.cost = function() {
    return v + 250;
  };
}
var mb = new MacBook();
memory(mb);
engraving(mb);
insurance(mb);
// Outputs: 1522
console.log(mb.cost());
// Outputs: 11.6
console.log(mb.screenSize());

Pseudo-classical Decorators

Interfaces

// Create interfaces using a pre-defined Interface
// constructor that accepts an interface name and
// skeleton methods to expose.
// In our reminder example summary() and placeOrder()
// represent functionality the interface should
// support
var reminder = new Interface("List", ["summary", "placeOrder"]);
var properties = {
  name: "Remember to buy the milk",
  date: "05/06/2016",
  actions: {
    summary: function() {
      return "Remember to buy the milk, we are almost out!";
    },
    placeOrder: function() {
      return "Ordering milk from your local grocery store";
    }
  }
};
// Now create a constructor implementing the above properties
// and methods
function Todo(config) {
  // State the methods we expect to be supported
  // as well as the Interface instance being checked
  // against
  Interface.ensureImplements(config.actions, reminder);
  this.name = config.name;
  this.methods = config.actions;
}
// Create a new instance of our Todo constructor
var todoItem = new Todo(properties);
// Finally test to make sure these function correctly
console.log(todoItem.methods.summary());
console.log(todoItem.methods.placeOrder());
// Outputs:
// Remember to buy the milk, we are almost out!
// Ordering milk from your local grocery store

Abstract Decorators

var Macbook = function() {
  //...
};
var MacbookWith4GBRam = function() {},
  MacbookWith8GBRam = function() {},
  MacbookWith4GBRamAndEngraving = function() {},
  MacbookWith8GBRamAndEngraving = function() {},
  MacbookWith8GBRamAndParallels = function() {},
  MacbookWith4GBRamAndParallels = function() {},
  MacbookWith8GBRamAndParallelsAndCase = function() {},
  MacbookWith4GBRamAndParallelsAndCase = function() {},
  MacbookWith8GBRamAndParallelsAndCaseAndInsurance = function() {},
  MacbookWith4GBRamAndParallelsAndCaseAndInsurance = function() {};

Đây là giao diện chúng tôi sẽ xác định cho Macbook:

var Macbook = new Interface("Macbook",
  ["addEngraving",
    "addParallels",
    "add4GBRam",
    "add8GBRam",
    "addCase"
  ]);
// A Macbook Pro might thus be represented as follows:
var MacbookPro = function() {
  // implements Macbook
};
MacbookPro.prototype = {
  addEngraving: function() {},
  addParallels: function() {},
  add4GBRam: function() {},
  add8GBRam: function() {},
  addCase: function() {},
  getPrice: function() {
    // Base price
    return 900.00;
  }
};

Để giúp chúng tôi dễ dàng thêm nhiều tùy chọn hơn nếu cần sau này, một lớp Abstract Decorator được định nghĩa với các phương thức mặc định được yêu cầu để triển khai giao diện Macbook, mà phần còn lại của các tùy chọn sẽ phân loại. Trình trang trí trừu tượng đảm bảo rằng chúng ta có thể trang trí một lớp cơ sở một cách độc lập với bao nhiêu trình trang trí cần thiết trong các kết hợp khác nhau (hãy nhớ ví dụ trước đó chứ?) Mà không cần dẫn xuất một lớp cho mọi kết hợp có thể có.

// Macbook decorator abstract decorator class
var MacbookDecorator = function(macbook) {
  Interface.ensureImplements(macbook, Macbook);
  this.macbook = macbook;
};
MacbookDecorator.prototype = {
  addEngraving: function() {
    return this.macbook.addEngraving();
  },
  addParallels: function() {
    return this.macbook.addParallels();
  },
  add4GBRam: function() {
    return this.macbook.add4GBRam();
  },
  add8GBRam: function() {
    return this.macbook.add8GBRam();
  },
  addCase: function() {
    return this.macbook.addCase();
  },
  getPrice: function() {
    return this.macbook.getPrice();
  }
};

Điều đang xảy ra trong ví dụ trên là Macbook Decorator chấp nhận một đối tượng (Macbook) để sử dụng làm thành phần cơ sở của chúng ta. Nó sử dụng giao diện Macbook mà chúng tôi đã xác định trước đó và đối với mỗi phương thức chỉ gọi cùng một phương thức trên thành phần. Bây giờ chúng ta có thể tạo các lớp tùy chọn của mình cho những gì có thể được thêm vào, chỉ bằng cách sử dụng Macbook Decorator.

// First, define a way to extend an object a
// with the properties in object b. We'll use
// this shortly!
function extend(a, b) {
  for (var key in b)
    if (b.hasOwnProperty(key))
      a[key] = b[key];
  return a;
}
var CaseDecorator = function(macbook) {
  this.macbook = macbook;
};
// Let's now extend (decorate) the CaseDecorator
// with a MacbookDecorator
extend(CaseDecorator, MacbookDecorator);
CaseDecorator.prototype.addCase = function() {
  return this.macbook.addCase() + "Adding case to macbook";
};
CaseDecorator.prototype.getPrice = function() {
  return this.macbook.getPrice() + 45.00;
};

Những gì chúng tôi đang làm ở đây là ghi đè các phương thức addCase () và getPrice () cần được trang trí và chúng tôi đạt được điều này bằng cách đầu tiên gọi các phương thức này trên macbook gốc và sau đó chỉ cần thêm một chuỗi hoặc giá trị số (ví dụ: 45,00) phù hợp với họ.

Vì đã có khá nhiều thông tin được trình bày trong phần này cho đến nay, chúng ta hãy cố gắng tập hợp tất cả lại trong một ví dụ duy nhất hy vọng sẽ làm nổi bật những gì chúng ta đã học được.

// Instantiation of the macbook
var myMacbookPro = new MacbookPro();
 
// Outputs: 900.00
console.log( myMacbookPro.getPrice() );
 
// Decorate the macbook
var decoratedMacbookPro = new CaseDecorator( myMacbookPro );
 
// This will return 945.00
console.log( decoratedMacbookPro.getPrice() );

Decorators With jQuery

var decoratorApp = decoratorApp || {};
// define the objects we're going to use
decoratorApp = {
  defaults: {
    validate: false,
    limit: 5,
    name: "foo",
    welcome: function() {
      console.log("welcome!");
    }
  },
  options: {
    validate: true,
    name: "bar",
    helloWorld: function() {
      console.log("hello world");
    }
  },
  settings: {},
  printObj: function(obj) {
    var arr = [],
      next;
    $.each(obj, function(key, val) {
      next = key + ": ";
      next += $.isPlainObject(val) ? printObj(val) : val;
      arr.push(next);
    });
    return "{ " + arr.join(", ") + " }";
  }
};
// merge defaults and options, without modifying defaults explicitly
decoratorApp.settings = $.extend({}, decoratorApp.defaults, decoratorApp.options);
// what we have done here is decorated defaults in a way that provides
// access to the properties and functionality it has to offer (as well as
// that of the decorator "options"). defaults itself is left unchanged
$("#log")
  .append(decoratorApp.printObj(decoratorApp.settings) +
  + decoratorApp.printObj(decoratorApp.options) +
  + decoratorApp.printObj(decoratorApp.defaults));
// settings -- { validate: true, limit: 5, name: bar, welcome: function (){ console.log( "welcome!" ); },
// helloWorld: function (){ console.log( "hello world" ); } }
// options -- { validate: true, name: bar, helloWorld: function (){ console.log( "hello world" ); } }
// defaults -- { validate: false, limit: 5, name: foo, welcome: function (){ console.log("welcome!"); } }

Last updated

Navigation

Lionel

@Copyright 2023