Design Pattern Learning Note
Classical 在此並不是指傳統或者是經典,而是書中作者用來代表與 Class 相關的意思。而 Modern 則沒其他用意,就是指現在常用的意思。
Recommend Book: Learning Javascript Design Patterns
推薦書籍:Javascript 設計模式
The prototype pattern focuses on creating an object that can be used as a blueprint for other objects through prototypal inheritance. This pattern is inherently easy to work with in JavaScript because of the native support for prototypal inheritance in JS.
Prototype Pattern 旨在於利用原型繼承的概念將物件藍圖化並藉此創建新物件,而原生的 Javascript 便帶有原型繼承,因此在操作及說明上相對簡單許多。
預設用法
function inherit(Child, Parent) {
Child.prototype = new Parent();
}
function Parent(name) { this.name = name || 'Adam'; }
Parent.prototype.say = function() { return this.name; }
function Child(name) {}
inherit(Child, Parent);
var kid = new Child();
kid.say();
Child extends both this
and prototype props from parent.
子物件同時繼承了來自父物件的 this
以及 prototype 中的所有屬性及方法。
Disadvantage
this
function Child(name) { this.name = name; }
缺點
this
中許多不必要的屬性。借用建構式及原型鏈
function Parent(name) { this.name = name || 'Adam'; }
Parent.prototype.say = function() { return this.name; }
function Child(a, b, c, d) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();
var kid = new Child("James");
console.log(kid.name);
console.log(kid.say());
delete kid.name;
console.log(kid.say());
Child will get independent props from parent and also refer to parent prototype.
預設用法的加強版,子物件將會取得來自父物件的屬性且是獨立非參考,另外也會參考其父物件的原型。
Disadvantage
Parent.apply(this, arguments); <--
Child.prototype = new Parent(); <--
缺點
共享原型及代理建構式(聖杯模式)
var inherit = (function() {
var Proxy = function() {};
return function(Child, Parent) {
Proxy.prototype = Parent.prototype;
Child.prototype = new Proxy();
Child.uber = Parent.prototype;
Child.prototype.constructor = Child;
}
}());
function Parent() {}
function Child() {}
inherit(Child, Parent);
var kid = new Child();
kid.constructor.name
Creating a temporary constructor which prototype referred by child share prototype with parent.
建立一個暫時的代理建構式與父物件共享父原型,其代理原型則由子物件參考。
原型繼承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var parent = { name: "John" };
var child = object(parent);
console.log(child.name); // "John"
Just like holy grail pattern, creating an empty object and its prototype refer to parent, and return a new instance which created by this empty object.
如同聖杯模式,建立一個空物件其原型參考自父物件,並回傳一個以其為藍圖產生的實體。
ES5
var parent = { name: "John" };
var child = Object.create(parent, { age: { value: 2 } });
console.log(child.hasOwnProperty("age")); // true
ES6
class Parent {
constructor() {
this.name = "John";
}
}
class Child extends Parent {
constructor(age) {
super();
this.age = { value: age };
}
}
var child = new Child();
console.log(child.name, child.age("18")); // "John 18"
console.log(child.hasOwnProperty("name")); // true
console.log(child.hasOwnProperty("age")); // true
利用複製屬性實現繼承
Shallow Copy
Copying object or array in shallow copy will only make a reference to parent, so if you change object value of the child side, the other side will change at the same time.
淺複製
淺複製對於物件(包含陣列)的複製僅利用參考的方式,因此如果在 child 處變更物件的值,相對地 parent 處也會受到影響。
function extend(parent, child) {
var i;
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
return child;
}
var parent = { name: "John", job: ["Backend", "Frontend"] };
var child = extend(parent);
child.job.push("PO");
console.log(parent.job, child.job);
Deep Copy
Deep copy will make copy for every properties include object and array by recursion.
深複製
深複製對物件(包含陣列)則會使用遞迴的方式將其屬性一一完整複製。
function extendDeep(parent, child) {
var i,
toStr = Object.prototype.toString,
astr = "[object Array]";
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === "object") {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}
var parent = { name: "John", job: ["Backend", "Frontend"] };
var child = extendDeep(parent);
child.job.push("PO");
console.log(parent.job, child.job);
方法借用及綁定
// borrow method from array
// 從陣列借用 join 方法
function f() {
return [].join.call(arguments, "+");
// line above is an equivalent to return [].join.apply(arguments, ["+"]);
}
f(1,2,3,4,5);
// bind method to object
// 綁定方法至物件
function bind(object, method) {
return function () {
return method.apply(object, [].slice.call(arguments));
};
}
Borrowing Method
借用方法
// ES5
var parent = { name: "John", say: function (greet) { return greet + ", " + this.name; } };
var child = { name: "Jacob" };
var childSay = bind(child, parent.say);
console.log(childSay("Yo")); // "Yo, Jacob"
// ES6
class Parent {
constructor() {
this.name = "John";
}
say(greet) {
return greet + ", " + this.name;
}
}
class Child {
constructor(age) {
this.name = "Jacob";
}
}
let p = new Parent();
let c = new Child();
c.say = p.say.bind(c);
console.log(c.say("Yo"));
Binding Method
綁定方法
// ES5 Class
React.createClass({
onClick: function(event) {},
render: function() {
return <button onClick={this.onClick} />;
}
});
// ES6 Class
class Counter extends React.Component {
constructor() {
super();
this.tick = this.tick.bind(this);
}
tick() {}
}
// ES6 Arrow Function & ES7 Class Properties
class Counter extends React.Component {
tick = () => {}
}
// ES7 Bind Operator
class Counter extends React.Component {
onClick(event) {}
render() {
return <button onClick={::this.onClick} />;
}
}
// ES7 Decorator
class Counter extends React.Component {
@autobind
tick() {}
}
Ref:
Classes Are Not Hoisted
類別並不會提升
var p = new Parent();
class Parent {}
Ref: Classes - MDN
Lodash Related Functions