2015年12月25日 星期五

[JavaScript] JavaScript inheritance patterns

JavaScript 要怎麼做繼承,整理了一些寫法:
  • ES2015 (ES6)
  • Function Pattern
  • 不 return、使用 call (or apply) + new 的方法實作 (all in one)
  • Pseudo-Classical pattern 
環境:Chrome v47.0.2526.106

一些基礎知識 (須看懂這裡,下面才看的懂QQ)


__proto__:用來指向(pointing) prototype 的 property。

prototype:每個 object 都有 prototype,prototype 自己也是 object,Object 會繼承他們的 prototype 的屬性和方法。

Object.prototype是最上層的 prototype chain。

Object.create(prototype):建立一個 new object 並把其 __proto__ 設定成指定 object 的 properties。

Object.setPrototypeOf(obj, proto):設定 obj 的 prototype 為指定的 prototype,原型為 object.__proto__ = prototype。

prototype chain (__proto__ chain 或原型鏈):JavaScript 會先找 Instance 自己有沒有 property,沒有會經由 __proto__ 去找上一層的 prototype,沒有再找上一層,直到 Object.prototype。

下述範例 Car 的 prototype 就包含了 name、driver、getName()、run()。
var Car = function(name) {
    this.name = name || "car";
};
Car.prototype = {
    name    : "",
    driver  : "",
    getName : function() {
        return this.name;
    },
    run     : function () {
        console.log(this.name + " is running");
    },
};
把 Car 印出如下圖,他的上一層是 Function.prototyp,top 是 Object.prorotype


要呼叫 像 getName 需要使用 Car.prototype.getName() 才能呼叫,
直接 Car.getName() 會有錯誤 Uncaught TypeError: Car.getName is not a function。
不過要呼叫 Function.prorotype.call() 就可以直接使用 Car.call() 去呼叫 。
要呼叫 Object.prorotype.isPrototypeOf() 可以直接使用 Car.isPrototypeOf() 去呼叫 。
表示經由 __proto__往上尋找。

function: 把一個變數宣告為 function 時,如下
var Car = function(name) {
    this.name = name;
};
如圖,會繼承 Function.prototype,並且會把 Car 的 prototype.constructor 指向 Car 自己這個 function 。

prototype.constructor:指向 function 本身。本來以為跟 new constructor 會優先找這個並由這裡開始做 constructor 進入點,但實驗在 Chrome 時 new 不會看這個。

new constructor:
用實例來說明
var car = new Car(); 

console.log(car instanceof Car); //true


會先檢查 Car 是不是 function,需要是 function ,因為會拿來做 constructor。
  1. 建立 Object。
  2. 繼承 Car。
  3. 呼叫 Constructor (Car function)。
  4. 需要注意,如果 Constructor (Car function)有 return value 時 car 會被這個 return value 取代掉。
很像下列程式
var car = {};
car.__proto__ = Car.prototype; // 等同 Object.setPrototypeOf(car, Car.prototype);
Car.call(car, "CAR");

console.log(car instanceof Car); // true

或著

var car = Object.create(Car.prototype);
Car.call(car, "CAR");

console.log(car instanceof Car); // true
很接近不等於,差別是用 new 下圖是 Car,但用上列程式結果是 Object。
結果如下圖 (這裡 prototype.constructor 不見,是因為程式是直接設定 prototype 所以洗掉了)
用 new  constructor                          用 Object.create or __proto__ or Object.setPrototypeOf









ES 2015 (ES6)


ES2015 新增和支援 class、extends、constructor、super、set、get、static 等保留字,可以像其他語言寫出一個簡單的 class 並且使用 extend 來繼承母類別。最大的好處是類似 c++、java、php 等直覺寫法。不過目前尚未支援像是 public、protected、private 等 access modifiers,所以無法設定 private method。

如以下範例 (這裡是使用 Babel 來編輯)。
class Car {
    constructor(name){
        this.name = name || "car";
    }
    getName() {
        return this.name;
    }
    run () {
        console.log("car is running");
    }
    set driver(name) {
        this.driver = name;
    }
    get driver() {
        return this.driver;
    }
}
class Formula extends Car {
    constructor(name, color){
        super(name || "F1");
        this.color = color;
    }
    getColor() {
        return this.color;
    }
    run () {
        console.log("F1 is running");
    }
}
實驗結果,符合預期。
var car = new Car("i am a car");
console.log(car.getName());  // CAR
car.run();                   // CAR is running
car.driver = "toolman";
console.log(car.driver);     // toolman
console.log(car.getColor()); // Uncaught TypeError: car.getColor is not a function
console.log(car instanceof Car);     //true
console.log(car instanceof Formula); //false

var f1 = new Formula("i am a F1", "red");
console.log(f1.getName());   // F91
f1.run();                    // red F91 is running
console.log(f1.getColor());  // red
f1.driver = "prettygirl";
console.log(f1.driver);      // prettygirl
console.log(f1 instanceof Car);     //true
console.log(f1 instanceof Formula); //true

Function pattern(或稱 Factory constructor patter)


使用 Closure 的技巧直接在 function return 實做好的 object,子類別也是直接呼叫父類別 function 取得的 object 後,擴增完成,再 return。這個方法好處在於能實作出 private variable(name、color) 和 method (_run) 的效果。但缺點會失去繼承的結構,所以無法使用 instanceof 來判斷 instance 是屬於那個 class 產生的,或是那個 class 的子類別。(使用此 pattern 可以不用 new ,因為有沒有用結果都一樣)。
另外其實也可以自己連接 __proto__ 到父的 prototype 讓其有繼承關係。
var Car = function(setname) {
    var that = {};
    var name = setname || "car";
    that.getName = function() {
        return name;
    };
    var _run = function () {
        return name + " is running";
    };
    that.run = function () {
        console.log(_run());
    };
    that.driver = "";
    return that;
};

var Formula = function(name, color) {
    var that = Car(name);
    that.getColor = function() {
        return color;
    };
    that.run = function() {
        console.log(color + " " + that.getName() +" is running");
    };
    return that;
}
實驗結果 (其他結果跟 ES2015 一樣就不重複了,看 instanceof 的結果)
發現不屬於任何 class
var car = Car("i am a car");
console.log(car instanceof Car);     //false
console.log(car instanceof Formula); //false

var f1 = Formula("i am a F1", "red");
console.log(f1 instanceof Car);     //false
console.log(f1 instanceof Formula); //false
左邊是 car 的 instance                                       右邊是 f1 的 instance

可以由圖看出上一層直接是 Object 了,所以無法用 instanceof 判斷是不是從 Car or Formula 繼承的 instance。

不 return,使用 call (or apply) + new 的方法實作 (or all in one)


這個方法跟上述很接近,利用 call (or apply )+ new(需要用 new constructor 上述有寫 new 的行為) 所以直接使用 this 並且不 return 。也可以實作出有 private 效果的 method、variable,所以關鍵在子類別的 Car.call(this, arg1)把 this extend。優點在於能實作出 private 效果,有記住自己是由那個 class 所產生,無法判斷父類別是誰,所以像 design pattern 常用的 abscract class 就比較不適合。
一樣也可以自己連接 __proto__ 到父的 prototype 讓其有繼承關係。
var Car = function(setname) {
    var name = setname || "car";
    this.getName = function() {
        return name;
    };
    var _run = function () {
        return name + " is running";
    };
    this.run = function () {
        console.log(_run());
    };
    this.driver = "";
};

var Formula = function(name, color) {
    Car.call(this, name || "F1");
    this.getColor = function() {
        return color;
    };
    this.run = function() {
        console.log(color + " " + this.getName() +" is running");
    };
}
實驗結果,記得自己是由那個 class 衍生出來的,但無法判斷父類別是什麼。
var car = new Car("i am a car");
console.log(car instanceof Car);     //true
console.log(car instanceof Formula); //false

var f1 = new Formula("i am a F1", "red");
console.log(f1 instanceof Car);     //false
console.log(f1 instanceof Formula); //true
左邊是 car 的 instance                                                        右邊是 f1 的 instance

 

由圖可以看出
car -> Car -> Object
f1 -> Formula -> Object
的關係,符合 instanceof 測試結果。

Pseudo-Classical pattern


過去最標準的作法,使用 prototype 來設定 variable 和 method 並利用 Object.setPrototypeOf() 或 __proto__ 指向父類別來實現繼承。
使用 prototype 好處是在 new 時 prototyp 是參考型別( __proto__ 指向 prototype),不會重複建立 method 和 variable,較 function pattern 有效率和節省 memory。
這個方法優點是有完整的繼承關係,而且跟前述幾種方法 (除 ES2015,用 babel 模擬,非瀏覽器內建實作) 在 instance 結構上也是最省的。缺點要記得 extend 。

function extends(child, parent) {
    Object.setPrototypeOf(child.prototype, parent.prototype);
    // 等同 child.prototype.__proto__ = parent.prototype;
}

var Car = function(name) {
    this.name = name || "car";
};
Car.prototype = {
    constructor : Car,
    name    : "",
    driver  : "",
    getName : function() {
        return this.name;
    },
    run     : function () {
        console.log(this.name + " is running");
    },
};

var Formula = function(name, color) {
    this.name = name;
    this.color = color;
};
Formula.prototype = {
    constructor : Formula,
    getColor : function() {
        return this.color;
    },
    run      : function() {
        console.log(this.color + " " + this.name +" is running");
    }
};
extends(Formula, Car);
實驗結果
var car = new Car("i am a car");
console.log(car instanceof Car);     //true
console.log(car instanceof Formula); //false

var f1 = new Formula("i am a F1", "red");
console.log(f1 instanceof Car);     //true
console.log(f1 instanceof Formula); //true
左邊是 car 的 instance                                                        右邊是 f1 的 instance

由圖可以看出
car -> Car -> Object
f1 -> Formula -> Car -> Object
的關係,符合 instanceof 測試結果。


再來依據前述的一些觀念,可能會有人問為什麼不把 extend 放在子類別人?如下程式。
這樣好處是好讀比較會記得。但缺點是設定 __proto__是 cost 很大的動作(),放進去後,每次 new 都會重設一次,所以一般還是放在外面居多。不過這點我也是抱著懷疑,因為 javascript 在 set object 時,不是都是 call by sharing? reference to the object
function extend(child, parent) {
    Object.setPrototypeOf(child.__proto__, parent.prototype);
    // 等同 child.__proto__.__proto__ = parent.prototype;
}

var Car = function(name) {
    this.name = name || "car";
};
Car.prototype = {
    constructor : Car,
    name    : "",
    driver  : "",
    getName : function() {
        return this.name;
    },
    run     : function () {
        console.log(this.name + " is running");
    },
};

var Formula = function(name, color) {
    extend(this, Car);
    this.name = name;
    this.color = color;

};
Formula.prototype = {
    constructor : Formula,
    getColor : function() {
        return this.color;
    },
    run      : function() {
        console.log(this.color + " " + this.name +" is running");
    }
};

結論

方法 2,3 都可以用 pseudo classical 的 extend 類似的方法去完成繼承關係。這幾個差別就看實際上的要不要 private 應用、怎麼寫法自己喜歡、或是在不在意使不使用 prototype 的效能上的差異。好壞差異不大。未來當然都是使用 ES2015 最好的。


Reference:


http://www.w3schools.com/js/js_object_prototypes.asp
http://stackoverflow.com/questions/650764/how-does-proto-differ-from-constructor-prototype
http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
http://davidshariff.com/blog/javascript-inheritance-patterns/
https://medium.com/@PitaJ/javascript-inheritance-patterns-179d8f6c143c#.qbayiakzb
http://es6.ruanyifeng.com/#docs/class
http://www.codedata.com.tw/javascript/essential-javascript-14-constructor/
http://www.codedata.com.tw/javascript/essential-javascript-15-prototype/
http://www.codedata.com.tw/javascript/essential-javascript-16-introspection/
http://www.codedata.com.tw/javascript/essential-javascript-18-class-based-oo-simulation/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf

2015年12月23日 星期三

[HTML] HTML5 針對外部來源的 script 上的 async 和 defer

ps:async 和 defere 需要 script 有 src 時才會有效


1. 正常的 script 宣告如下

<script src="wait1.js"><script>
在以前習慣把 <script> 都放在 html 結構的最後面
主要原因是 browser 在 html parsing 碰到 script 時會暫停 html parsing ,會先下載 script 和執行 script 後才會繼續往下做 html parsing。

所以想看到一個網頁呈現必須等 script 處理完,才能看到網頁,而大部份實務上的應用, script 的執行幾乎都是在 HTML ready 後,才需要執行。

2. async 

<script src="wait1.js" async><script>
async 在碰到 script 時,會開始下載 script 但不會暫停 HTML parsing ,直到 script 下載完,才會暫停 HTML parsing,把 script 執行完,才會繼續做 HTML parsing。

但如果引用多個外部的 script 都是使用 async 時,async 並不保證順序性,誰先下載完,誰就會先行執行。

3. defer

defer 碰到 script 時不會暫停 html parsing 並同時下載 script 直到 HTML parsing 結束後才執行 script。defer 跟 async 不同的是把 script 執行 delay 到 HTML parsing 結束後才處理。

不過這個在 w3c 的文件中並沒有說明,當有多個 defer 的外部 script 時,他的執行順序會是如何?是否有保證為宣告順序?因為有些 javascript 是有前後相依性的。例如 bootstrap.js 就必須先執行 jquery.js 才能執行 boostratp.js (有檢查有沒有 jquery),

Ref:
http://www.w3.org/TR/html5/scripting-1.html#attr-script-defer
https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer
http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html (這篇是有說 defer 有保證順序性,但在測試時 chrome 有,但firefox 並非如此)






舉個實例

瀏覽器
Chrome 47.0.2526.106
Firefox 43.0.2
(由於跟瀏覽器版本支援度有關係,所以放上版號)
wait1.js
alert(1);
wait2.js
alert(2);
wait3.js
alert(3);

實驗 1
<script src="wait1.js" ></script>
hello world 
chrome 和 firefox 都是先 alert(1) 才會出現 hello world

實驗 2
<script src="wait1.js" async></script>
hello world
chrome 和 firefox 都是先出現 hello world 才 alert(1)

實驗 3
<script src="wait1.js" defer></script>
hello world
chrome 和 firefox 都是先出現 hello world 才 alert(1)

實驗 4
<script src="wait1.js" async></script>
<script src="wait2.js" async></script>
<script src="wait3.js" async></script>
hello world
chrom 和 firefox 會先出現 hello world 但是 alert 就會有 1>2>3、2>1>3、3>2>1 的情形

實驗 5
<script src="wait1.js" defer></script>
<script src="wait2.js" defer></script>
<script src="wait3.js" defer></script>
hello world
chrom 會先出現 hello world > alert(1)>alert(2)>alert(3) (有保證順序)
firefox 會先出現 hello world 但是 alert 就會有 1>2>3、2>1>3、3>2>1 的情形 (沒有保證順序)

結論

  • 不管有無使用 async 和 defer ,建議還是把 script 放在最後面宣告,並按相依性先後順序宣告,即使沒有支援 async 和 defer 或支援度較差的瀏覽器也能正確執行。
  • async 和 defer 都沒有順序上的保證 (雖然 chrome 有,不過 spec 上沒有),所以適合使用在 js 之間沒有前後相依性的情況。
  • async 特性比較適合使用在 Javascript 純粹增加 module、class、function 的,不關聯其他 javascript 或是 HTML 使用。
  • defer 實務上對使用體驗是最佳的,但主要也是在順序性上並沒有保證(w3c 建立的 spec),所以使用上也要考慮一下。

延伸


剛好 IThome 現代化網站技術分享上老木大師分享到
http://mei.homin.com.tw/Keynote_the_secrets_of_web_design_performance.html#!p=61
原理
https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts

可以自己動手做 async,但效果跟 async 差不多就是了,不過可以讓不支援 async 的瀏覽器使用
(ps. 這只是 prototype 實務上還要多實驗)

asyncjs.js
(function() {
    var loader = document.getElementsByTagName("script")[0];
    var scripts = loader.getAttribute("data-src");
    scripts = scripts.split("&");
    scripts.forEach(function(element, index, array){
        var script = document.createElement("script");
        script.src = element;
        document.head.appendChild(script);
    })
})();
index.html
<script src="asyncjs.js" data-src="wait1.js&wait2.js&wait3.js"></script>
hello world
實驗
<script src="asyncjs.js" data-src="wait1.js&wait2.js&wait3.js"></script>
hello world
結果是  hello world 先,alert 就會有 1>2>3、2>1>3、3>2>1 的情形
<script src="asyncjs.js" data-src="wait1.js&wait2.js&wait3.js"></script>
<script> alert("blocking") </script>
hello world
blocking、1、2、3 這幾個順序就不一定了,最後才出現 hello world


Defer ?
還沒想到怎麼實作



Reference:

http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/
http://www.w3.org/TR/html5/scripting-1.html#attr-script-defer
https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer
http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

2015年12月17日 星期四

[Node.js] Electron 發佈成可執行檔 APP

Electron v0.36.0
https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md
官網文件,看不懂 (未來應該會修正)

後來查了一下原來要裝 electron-packager
(Ref:https://www.youtube.com/watch?v=dz5SnmBzBXc (舊版 youtube 教學)

安裝 electron-packager

https://www.npmjs.com/package/electron-packager
# for use in npm scripts 
npm install electron-packager --save-dev
 
# for use from cli 
npm install electron-packager -g

修改 package.json

  "scripts": {
    "start": "electron main.js",
    "build_mac": "electron-packager . MyApp --platform=darwin --arch=x64 --version=0.36.0 --icon=myapp.icns --asar=true --ignore=node_modules/electron-* ",
    "build_win32": "electron-packager . MyApp --platform=win32 --arch=x64 --version=0.36.0 --icon=myapp.ico --asar=true --ignore=node_modules/electron-* ",
    "build_linux": "electron-packager . MyApp --platform=linux --arch=x64 --version=0.36.0 --icon=myapp.png --asar=true --ignore=node_modules/electron-* "
  },
electron-packager . MyApp 把目前目錄打包成 MyApp 這個名字的 app

--platform:平台
有那些 platform 可以看這個連結
https://github.com/atom/electron/releases

--arch:ia32, x64, all

--version:這裡指的是 electron 的版本
版本建議跟開發的 electron 的版本相同,此時我是使用 0.36.0

--icon:app 的 icon

--asar:true or false 把 source 封裝,比較不容易被看到原始碼

--ignore:忽略檔案,不要打包,建議把 electron 都忽略掉,因為打包後裡面就有了,可以減少檔案大小

要注意 node_modules 是要在 npm install 後的狀態下打包,不要清除

發佈

# Windows  x64
npm run build_win32

# MAC OS X 
npm run build_mac

# LINUX
npm run build_linux
執行完會在此目錄下產生一個新資料夾
像 win32 會出現 MarkApp-win32-x64 ,然後裡面有執行檔
另外此版本的 0.36.0 electron win32 還是怪怪的,開幾次就掛了,而且關不掉
建議先用 npm start 來執行





2015年12月16日 星期三

[Node.js] 初學 Node.js 注意這三點,會比較好上手

1. 同步和非同步執行順序抽像想像


node.js 是 single thread 所以不會有並行
所以 sync 部份跑完才會開始跑 async 或是在 even driven 才跑 async 有點像下圖



async 部份是看誰先排進 async query 內誰先優先執行
所以像
var async_function = function(val, callback){
    process.nextTick(function(){
        callback(val);
    });
};

async_function(1, function(val) {
    console.log(val);
    async_function(2, function(val) {
        console.log(val);
        async_function(3, function(val) {
            console.log(val);
        });
    });
});

console.log(100);
會列出
100
1
2
3
所以在同一個 async scope 會保證其順序性 1 > 2 > 3

再看一個範例,在 sync 等地方再加一個不同的 async scope
var async_function = function(val, callback){
    process.nextTick(function(){
        callback(val);
    });
};

async_function(1, function(val) {
    console.log(val);
    async_function(2, function(val) {
        console.log(val);
        async_function(3, function(val) {
            console.log(val);
        });
    });
});

console.log(100);

async_function(4, function(val) {
    console.log(val);
    async_function(5, function(val) {
        console.log(val);
        async_function(6, function(val) {
            console.log(val);
        });
    });
});
會列出
100
1
4
2
5
3
6
一樣在同一個 async scope 會保證其順序性
1 > 2 > 3
4 > 5 > 6
但不同 scope 會形成交叉,因為在 sync 的 scope 時 1>4 是這順序排進 queue
而印 1 後,2 被排進 queue 內,4 被執行
4印出後,5排進 queue 內,2被執行,以此類堆

推薦可以看下面這篇,會更清楚為什麼會這樣,雖然主要在講為什麼 async 會比較快,但圖片說明很棒,淺顯易懂
http://blogger.gtwang.org/2014/02/node-js-is-faster-than-java-for-concurrent-data-processing-operations.html

另外這篇在講 node.js 的 process queue
http://fred-zone.blogspot.tw/2012/03/nodejs.html


2. 區分是 sync 的 function 還是 async 的 function


這點 JSDC 2015 Jonathan 也有提到
http://www.jongleberry.com/async-control-flow/assets/player/KeynoteDHTMLPlayer.html#8

大部份只要有 callback 幾乎都是 async 的 function
除了 array iteration methods 操作,像 forEach、map...等
(Ref:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Iteration_methods)

另外很多 function 如果結尾有 sync 就是同步版的 function
像 fs 內就有,例如 fs.open 是 async 的版本 fs.openSync 就是 sync 的版本


3. Control Flow (俗稱 callback hell)


因為 callback 的方式導致閱讀程式變成很難
而且很多整理程式的方法,都不太適用,例如 design pattern

目前常聽到的解決是使用這三個方法來解:aync、promise、async/await

aync:
https://github.com/caolan/async
我個人目前都是用這個
比較常用的像有 waterfall

promise:
這裡指的是 ES6 的 promise
不過我也還沒研究:D
npm 有一些類似的 modules

async/await:
這裡指的是 ES7 的
npm 也有幾個,但都會降低效能
https://github.com/yortus/asyncawait#5-performance
像這個會降 20%左右

不過另一個經驗在大部份的 refactory 書(像易讀程式之美學)都有提到。
儘量把程式 function 化,讓 function 和 method 只做一件事,即使在 callback hell 中,也能讓程式變的比較好讀。




題外話
http://www.jongleberry.com/async-control-flow/assets/player/KeynoteDHTMLPlayer.html#12
有提到儘量使用 thrown new Error("boom") 取代 thrown "boom"
這樣才有 stack trace 能看

Reference:
http://blogger.gtwang.org/2014/02/node-js-is-faster-than-java-for-concurrent-data-processing-operations.html
http://fred-zone.blogspot.tw/2012/03/nodejs.html
http://www.jongleberry.com/async-control-flow/assets/player/KeynoteDHTMLPlayer.html#8
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Iteration_methods
https://github.com/caolan/async
https://github.com/yortus/asyncawait#5-performance

[docker] Docker 1.9 版防火牆問題

這是 docker 1.9 的 issue

docker 預設在啟動時會主動修改 iptable 的設定,讓 docker run -p (or --publish) 的 Port 都能直接穿過 host 的防火牆設定,跟外部連通。所以像 ubuntu ufw 的設定對 docker 就完全沒用。參考了一些方法讓 host 的 iptables 方法,都沒用
https://fralef.me/docker-and-iptables.html
https://svenv.nl/unixandlinux/dockerufw
http://docs.docker.com/engine/userguide/networking/default_network/container-communication/

上述文章,都說明原本可以修改
/etc/default/docker
DOCKER_OPTS 最後面加上 --iptables=false 在啟動時就不會自動修改 iptalbe 
DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 --iptables=false"

不過怎麼設定都沒有作用
看了一下,原來有開 issue
issue #9889 已經 closeed 等待下次更新吧
DOCKER_OPTS 目前會沒作用

待修好再來試了

目前不想對外連通的方法
就是拿掉 -p 
或是改成 -p 127.0.0.1:2222:22 讓 container 內的 22 port 只能對上本機 host (127.0.0.1) 2222 port 
這樣只有在 host 能 ssh 到 container
外部就不能透過 2222 port  連到 container 的 ssh






[JavaScript] Vue.js 初探一些要注意的地方


此文件有可能因為時間的演進而與現況不合, Vue.js 正在演進中
目前記錄自己在寫時,碰到一些要注意的地方


1. 不支援巢狀 (nested)
這裡指的巢狀是指 Vue instance 內再包含 Vue instance
雖然 Vue 不會報錯,但是行為會怪怪的。
比較好的做法是 children 是使用 Vue.componet 包起來,這點跟 React 比較接近。

2. methods 的 function name 和 data 的 data name 是共用的
例如下列情況,有可能 foo function 呼叫不到,被 foo data 覆蓋了
data: {
    foo:''
}, 
method:{
      foo:function(){}
}

3. 如果 data binding 的資料為 array 需要注意
(Ref:http://cn.vuejs.org/guide/list.html#问题)
  1. 直接用索引操作,像 vm.items[0] = {msg:"hellow"};
    Vue 有對 array 新增 $set() 的method 來操作
    例如:this.items.$set(0, {msg:"hello"});
  2. 直接對 array 長度修改 (0為清空)
    如 vm.items.leangth = 0; 會建議使用 vm.items = []; 方式操作
另外也有對 array 新增 $remove() 的 method
該方法行為如下

var index = this.items.indexOf(item)
if (index !== -1) {
    this.items.splice(index, 1)
}


4. v-html (等同 innerHTML) 預設不會過濾 xss
建議直接用 {{variable}} or v-text (等同 textContent)方式設置


5. Vue.extend() 
帶進的 options 大多都跟 Vue instance 相同 (平常使用的 Vue() )
除了 data 和 el 這兩個 options
例如以下例子,MyComponet1 和 MyComponet2 會共用 data,這應該不是我們平常想做的
var data = { a: 1 }
var MyComponent1 = Vue.extend({
    data: data
})
var MyComponent2 = Vue.extend({
    data: data
})
建議做法如下,這樣 data 內的值在每個 Componet 會獨立
var MyComponent = Vue.extend({
    data: function () {
        return { a: 1 }
    }
})


6. Vue.componet 的 prop
prop 概念基本上跟 React 差不多,在 Vue 預設是單向的
但可以用關鍵字
.sync 強迫雙向,這表示 children componet 如果修改該 prop 的值,parent 也會更新
.once 只綁定一次,之後不管改 parent 或是 children 都不會變化

另外目前還有一點問題,是如果 prop 是 array or object 會變成雙向的


7. 如果想要當成一個完整 Web Componet (Vue.componet),直接由 Parent 像一般 HTML 元件向 children 取值

可以使用 v-ref 來設定 componet 別名
<div id="parent">
  <user-profile v-ref:profile></user-profile>
</div>
在 parent 時,可以如下方式呼叫 children 的 method
this.$refs.profile.method()
實際上如果不用 v-ref,Vue 自動也會在 this.$children 內 Reference 他的子元件
但比較麻煩的是,children 是個 array 不容易直接判斷你要取某個 Componet (需要回圈)

在 children 也有 this.$parent 去呼叫 parent 的 method


8. vue-validator 還在 Alpha 所以會有一些問題
所以目前蠻多要自己實作的,沒有 angular 那麼多已完成,可以用的
我使用 2.0.0-alpha.6 基本功能上上 required valid 的行為都是正常的
不過 Vue.validator 這個因為有 bug 而無法使用
但換到最新版 2.0.0-alpha.8 基本功能上就 valid 就壞了,還沒深入了解
是我寫法錯誤還是 bug ,至少寫法沿用 alpha 6 是會有錯誤發生






2015年12月11日 星期五

[Growth Hack] Growth Hack 學習心得 & 筆記

這是 xdite 開的班 http://www.growthschool.com/courses/intro_to_growth_hack


基礎知識

AARRR Metrics
http://www.meetclub.tw/article/view/id/34939

MVP (Minimal Viable Product):最小可行性產品

https://twitter.com/ValaAfshar/status/628406092339519488

PMF (Product Market Fit):不會賠錢的狀能,建議創業選可以快速 PMF 的主題,由以下兩點來評估
  • 很熟該領域
  • 市場需求量大


Growth Hack

Growth Hack is a marketing technique developed by technology startupswhich use creativity, analytical thinking, and social metrics to sell products and gain exposure 

講師認為 Growth Hack 結論是做好產品
做好產品本身絕對是重要的基礎,但我個人更喜歡解釋為做好 UX,這樣在思考的視野會較為寬闊,而 Growth Hack 所講的內容也並非只有做好產品其他包含:廣告、行銷、客服...等

矽谷主要是在 PMF 使用的方法

重點環遶於三個點
  • 降低疑慮
  • 增強信心
  • Call to action


使用的方法
不管什麼方法,做好做對產品是必要的基礎,沒此一基礎任何方法都無效
  • Measuring
  • Landing Page
  • A/B Testing
  • Onboarding
  • Customer Support
  • Email List
  • Referral
  • Gaminification
  • Multiple call to action


Measuring:使用一些量測的工具,來數據分析和量化指標
其他工具可以參考:http://www.growthschool.com/posts/17-measurement-tools-funnel

Landing Page:簡單講就是要注重門面,使用者看到的第一個頁面,如果是活動就是活動頁,如果是網站就是首頁,任何使用者要進入你的服務的第一頁
一個好的結構

  • 一句話形容自己:這是做什麼的服務
  • 使用此服務的三大好處:為什麼要用?
  • 運作原理:如何操作
  • 使用者見證或媒體:越多大廠越好
  • Call to action:希望使用者做什麼,通常可能是註冊,或是付費買服服
  • 消除疑慮:免費註冊使用、退款保證、個資保護

A/B Testing:用來測試改善是不是真的有效,例如:optimizely

Onboarding:很重要,養客戶,包含了產品的 UI、行銷方式和客戶之間主動互動及回饋,不過行銷比較多
方法有
  • Remove Friction
    簡單的註冊(免費更好)和參與
    歡迎         
  • Deliver Immediate Value
    可以讓客戶立刻參與的體驗,例如,快速找到自己喜歡的歌
    跟客戶取得回饋,例如,使用上有碰到什麼問題?
    提供一些回饋,例如,填問卷送 xxx
  • Reward Desired behavios
    詢問是否幫忙推薦(要挑真的喜歡的人)
    資料分析
    Transition to a nurturing program:沒講到,幫助使用者更好上手

整體就是讓客戶感覺「就甘心」

Customer Support:客服,線上 or 電話和 FAQ,了解客戶問題在那,可以思考如何改善,另外也能消除疑慮

Email List:訂閱通知,不會寄 spam

Referaral
只有客戶真的喜歡,才問他要不要幫忙推薦
朋友推薦吸引力較大

Gaminification
像 Dropbox 增加空間的活動

Multiple Call to Action:連續技
像 Airbnb 會在 FB 邀請人可以省下多少 coco
像 Sublime text 免費 license 的活動,



個人的結論
做好做對的產品是必要的基礎
Growth hack 本身教的方法適合 PMF 之後
持續改善是不二法則
利用一些行銷手段來提升轉化率
使用一些量化工具,確定手段和改善是否有用處



Reference:
http://www.growthschool.com/guides/growth_hack





2015年12月9日 星期三

[JavaScript] HTML String 也能使用 CSS selector 取得資料


如果有一個 HTML 字串,或是來自其他網站的內容
在純前端 (Client) 的環境
不想使用正規表示法去取得某部份資料

也能利用 createHTMLDocument() 讓 HTML string 變成 DOMDocument 就能使用 querySelector or querySelectorAll 取得資料

利用 var doc = document.implementation.createHTMLDocument("Crawler");
會回傳一個 document object
再來利用 innerHTML 把 html string 插入
剩下就當成平常的 document 使用
程式碼如下





2015年12月1日 星期二

[git] ubuntu 使用 docker 安裝 Gitlab

如果沒有 docker 需安裝 docker
https://docs.docker.com/engine/installation/ubuntulinux/

下載 gitlab 的 Image 
裝 Community Edition
docker pull gitlab/gitlab-ce
如果想裝 Enterprise Edition 版,是以下
docker pull gitlab/gitlab-ee
啟動 gitlab
sudo docker run --detach \
    --hostname gitlab.example.com \
    --publish 8443:443 --publish 8080:80 --publish 2222:22 \
    --name gitlab \
    --restart always \
    --volume /srv/gitlab/config:/etc/gitlab \
    --volume /srv/gitlab/logs:/var/log/gitlab \
    --volume /srv/gitlab/data:/var/opt/gitlab \
    gitlab/gitlab-ce:latest
上述指令可以得知
本機 port 對映 container port
HTTPS:8443 對映 container 的 443
HTTP:8080 對映 container 的 80
SSH :2222 對映 container 的 80

Data 都會存在 ubuntu 的 (上述指令可以得知)
/srv/gilab/* 內

存取 Gitlab
直接進入 http://server-domain:8080

預設的帳密為
username:root 
password:5iveL!fe




關閉 Gitlab
sudo docker stop gitlab
重啟 Gitlab
sudo docker restart gitlab
如果要改 docker run 的設定值
需要先 remove gitlab container (檔案和資料會保留,存在 /srv/gilab/*,只是移除 container 而己)

sudo docker rm gitlab
再重新跑一次(下面參數請改成自己想要的,下述例子是改成直接對映 443、80、22 port)
sudo docker run --detach \
    --hostname gitlab.example.com \
    --publish 443:443 --publish 80:80 --publish 22:22 \
    --name gitlab \
    --restart always \
    --volume /srv/gitlab/config:/etc/gitlab \
    --volume /srv/gitlab/logs:/var/log/gitlab \
    --volume /srv/gitlab/data:/var/opt/gitlab \
    gitlab/gitlab-ce:latest
看 Log
sudo docker logs gitlab

如果要 follow 

sudo docker logs -f gitlab   
進入 bash
sudo docker exec -it gitlab /bin/bash
更新 Gitlab 到新版本
停止->移除->pull 到新版->run
sudo docker stop gitlab

sudo docker rm gitlab

sudo docker pull gitlaba/gitlab-ce:laster

sudo docker run --detach \
    --hostname gitlab.example.com \
    --publish 8443:443 --publish 8080:80 --publish 2222:22 \
    --name gitlab \
    --restart always \
    --volume /srv/gitlab/config:/etc/gitlab \
    --volume /srv/gitlab/logs:/var/log/gitlab \
    --volume /srv/gitlab/data:/var/opt/gitlab \
    gitlab/gitlab-ce:latest

Reference:
http://doc.gitlab.com/omnibus/docker/README.html

[Chrome Extension] 上架 Chrome Store

上架 Chrome Store
  1. Chrome extension 頁面點選封裝擴充功能 
  2. 在彈出的視窗點選擴充功能目錄 的瀏覽
  3. 再點選封裝擴充功能 
  4. 封裝後會產生*.crx (package) 和 *.pem (key)檔案,將產生 *.pem 改名成 key.pem 放入 extension folder 根目錄,並將整個目錄打包到 zip ,可以使用這個 zip 上架到 Chrome Store。第一次不需要選取秘密金鑰,*.pem 檔案請務必保留,未來如果要更新 extension 時都要使用相同的 key。*.crx 應該是用在 application 上,可以直接在 Chrome 安裝,但上架用不到。
  5. 到 https://chrome.google.com/webstore/developer/dashboard 上架,第一次要付 5美元(一次性)




2015年11月28日 星期六

[PHP] PHP Extension 寫 php class 類別

基礎請參考:
PHP 也有 Day #21 從 C 語言到 PHP Extension 錄影

實作一個在 PHP 是這樣的一個類別
Class MyClass {
    protected $name = "";
    public function __construct($name) {
        $this->name = $name;
    }
    public function getName() {
        return $this->name;
    }
    public function setName($name) {
        $this->name = $name;
    }
    public function helloWorld() {
        return "Hello World";
    }
}
在 extension.c 寫完會如下
#include "php_myclass.h"

#if COMPILE_DL_MYCLASS
    ZEND_GET_MODULE(myclass)
#endif
// 宣告一個 global zend_class_entry 變數
zend_class_entry *myclass_ce;

/*
    這裡由原本的 PHP_FE 改用 PHP_ME
    四個參數為
    className:class PHP 呼叫的名字
    methodName:method 呼叫的名字
    arg_info:先帶 NULL,可以設定帶進來的參數值,可參考 http://www.walu.cc/phpbook/preface.md
    flags:有以下參數 ZEND_ACC_PUBLIC, ZEND_ACC_PROTECTED, ZEND_ACC_PRIVATE, ZEND_ACC_STATIC, ZEND_ACC_FINAL, ZEND_ACC_ABSTRACT
           分別表示   public         , protected         , private         , static         , final         , abstract
    如果要寫 abstract method 是使用 PHP_ABSTRACT_ME,就可以不用實作內容

    下述就是定義 MyClass 這個類別
    有 php 的 construct public funciton __construct
    一般 method 有
    public funciton getName
    public funciton setName
    public funciton helloWord
 */
static const zend_function_entry myclass_functions[] = {
    PHP_ME(MyClass, __construct,        NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
    PHP_ME(MyClass, getName,            NULL, ZEND_ACC_PUBLIC)
    PHP_ME(MyClass, setName,            NULL, ZEND_ACC_PUBLIC)
    PHP_ME(MyClass, helloWorld,         NULL, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

/*
    原本這裡的第三個參數改成 NULL,它原本是帶上面這個 zend_function_entry 的變數
    第四個參數改成 PHP_MINIT(myclass)
    class 改成要在 module init 這個周期宣告 class
    Ref:http://www.walu.cc/phpbook/1.3.md
 */
zend_module_entry myclass_module_entry = {
    STANDARD_MODULE_HEADER,
    "myclass",                       // your extension name
    NULL,                            // where you define your functions
    PHP_MINIT(myclass),              // for module initialization
    NULL, // PHP_MSHUTDOWN(myclass), // for module shutdown process
    NULL, // PHP_RINIT(myclass)      // for request initialization
    NULL, // PHP_RSHUTDOWN(myclass)  // for reqeust shutdown process
    NULL, // PHP_MINFO(myclass),     // for providing module information
    "0.1",
    STANDARD_MODULE_PROPERTIES
};

/*
    PHP_MINIT(myclass) 實作內容
    註冊一個叫 MyClass 的類別
    myclass_functions 也在這裡被使用
    全域的 myclass_ce 內容在這裡實際取得
 */
PHP_MINIT_FUNCTION(myclass)
{
    zend_class_entry tmp_ce;
    INIT_CLASS_ENTRY(tmp_ce, "MyClass", myclass_functions);
    myclass_ce = zend_register_internal_class(&tmp_ce TSRMLS_CC);
    return SUCCESS;
}

/*
    實作 __construct
    大致跟之前寫 function 差不多

    不過這裡有用 zend_update_property* 的 function
    這一系列的都是用來設定 class property
    全域的 myclass_ce 也在這裡被使用到,寫進 myclass_ce 的意思

    這裡我用來設定
    Class MyClass {
        protected $name = "";
    }
    這個 name property

    getThis() 是取得這個 class handle

    Ref:http://www.walu.cc/phpbook/11.2.md
 */
PHP_METHOD(MyClass, __construct)
{
    char *name = NULL;
    int name_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|", &name, &name_len) == FAILURE) {
        RETURN_NULL();
    }
    zend_update_property_stringl(myclass_ce, getThis(), "name", sizeof("name") - 1, name, name_len TSRMLS_CC);
}

/*
    實作 getName
    zend_parse_parameters_none 判斷不能傳參數進來
    zend_read_property 取得 name 這個 property
    Z_STRVAL_P 從 zval 取得 string
    RETURN_STRING Return string
*/
PHP_METHOD(MyClass, getName)
{
    zval *name = NULL;
    if (zend_parse_parameters_none() == FAILURE) {
        RETURN_FALSE;
    }
    name = zend_read_property(myclass_ce, getThis(), "name", sizeof("name") - 1, 1 TSRMLS_CC);
    RETURN_STRING(Z_STRVAL_P(name), 1);
}

/*
    實作 setName
*/
PHP_METHOD(MyClass, setName)
{
    char *name = NULL;
    int name_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|", &name, &name_len) == FAILURE) {
        RETURN_NULL();
    }
    zend_update_property_stringl(myclass_ce, getThis(), "name", sizeof("name") - 1, name, name_len TSRMLS_CC);
}

/*
    實作 helloWorld
*/
PHP_METHOD(MyClass, helloWorld)
{
    if (zend_parse_parameters_none() == FAILURE) {
        RETURN_FALSE;
    }

    RETURN_STRING("Hello World", 1);
}




2015年11月20日 星期五

[PHP] PHP 也有 Day #21 - c9s 主講:從 C 語言到 PHP Extension 學習筆記

這是個很硬的題目:從 C 語言到 PHP Extension

萬事起頭難
我覺得因為有 c9s 整理重點、地雷和 skeleton 才會看起來比較簡單,但其實...這很不簡單!!
上次寫 c 大概在 7 年前了~
所以需要沉澱一下...
進入正題

一些觀念

關於變數 (http://php.net/manual/en/internals2.variables.intro.php)

"PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting."

所以有幾個重要的觀念要會
1. copy on write
2. reference counting
3. php 變數是沒有型態的,但實際在 c 語言中是使用 zval 這個 struct
typedef struct _zval_struct {
    zvalue_value value;        /* variable value */
    zend_uint refcount__gc;    /* reference counter */
    zend_uchar type;           /* value type */
    zend_uchar is_ref__gc;     /* reference flag */
} zval;
其中 zvalue_value 是下列 sturct
typedef union _zvalue_value {
    long lval;                 /* long value */
    double dval;               /* double value */
    struct {
        char *val;
        int len;               /* this will always be set for strings */
    } str;                     /* string (always has length) */
    HashTable *ht;             /* an array */
    zend_object_value obj;     /* stores an object store handle, and handlers */
} zvalue_value;

關於檔案結構 (http://php.net/manual/en/internals2.structure.files.php)

config.m4:configure 用的
// 這裡紅字部份就是在 ./configure --enable-foo 是相同的
PHP_ARG_ENABLE(foo, whether to enable foo extension support,
  [--enable-foo Enable foo extension support])

// php_foo.c 是實作的檔案
if test $PHP_FOO != "no"; then
    PHP_NEW_EXTENSION(foo, php_foo.c, $ext_shared)
fi

php_counter.h:自己的 extension header
php_counter.c:自己的 extension 主程式 (說明是寫 counter.c 改成跟 h 同名,檔案多時比較知道是什麼)
config.w32:windows 用的,先忽略

PHP  Life Cycle
https://fntlnz.github.io/php-extensions-development/#/3
http://www.phpxs.com/post/1039


第一個 Hello World

剛好最近在學習 Larvel 直接拿 homested 做環境 (什麼都不用安裝就能編譯)
(是 vagrant,可以參考 http://laravel.tw/docs/5.1/homestead 自行安裝)

1.  ssh 進 homested 後

2. 下載 c9s 整理好的 skeleton
git clone https://github.com/c9s/php-ext-skeleton
3. 改一下程式
vi php_foo.c
改這裡,改成
PHP_FUNCTION(foo_hello) {
    RETURN_STRING("Hello World", 1);
}
4. 編譯
phpize
./configure --enable-foo
make 
make install
編好的會在 php-ext-skeleton/modules 目錄下
install 完的 .so 檔案會在 php default extension 目錄下 (這環境在 /usr/lib/php5/20131226/)
加入 php.ini 加入 extension
例如
echo "extension=foo.so" >  /etc/php5/cli/conf.d/20-foo.ini 
(這裡只有 cli 才會 work,請依自已的環境調整)

5.執行看看
php -r "echo foo_hello();"
應該會顯示 Hello World


寫測試

(https://qa.php.net/write-test.php)
mkdir tests
vi tests/foo.phpt
寫入
--TEST--
Check foo_hello
--FILE--
<?php
echo foo_hello();
--EXPECT--
Hello World
--TEST--
測試名稱
--FILE--
寫 php code
--EXPECT--
比對 echo 的資料
make test
如果有錯誤,在 tests 目錄下會會產生該測試的結果檔 (log, diff, exp, out),可以由這些檔案去看原因

GDB debug
(https://github.com/rcousens/packer-php7-dev/blob/master/doc/02-debug-php-extension.md)

實戰寫一個自己的 extension

(http://php.net/manual/en/internals2.funcs.php)

實作一個 swap function
可以用 c9s 提的指令快速換內容
perl -i -pe 's/foo_hello/swap/g' config.m4 *.h *.c
perl -i -pe 's/foo/swap/g' config.m4 *.h *.c
perl -i -pe 's/Foo/Swap/g' config.m4 *.h *.c
perl -i -pe 's/FOO/SWAP/g' config.m4 *.h *.c
mv foo_hello.c swap.c
mv foo_hello.h swap.h
一般在 php 寫應該像這樣
function swap(&$a, &$b) {
    $tmp = $a;
    $a = $b;
    $b = $tmp;
}
C 應該是像這樣
void swap(int *a, int *b) {
    int tmp=*a;
    *a=*b;
    *b=tmp;
}
開始寫 swap.c 主要是改以下內容
PHP_FUNCTION(swap) {
    zval *za = NULL;
    zval *zb = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &za, &zb) == FAILURE) {
        RETURN_FALSE;
    }
    zval tmp = *za;
    *za = *zb;
    *zb = tmp;
    RETURN_TRUE;
}

1. zval 就是  php 的變數型態
因為要實作任何型態都能 swap 的 function 所以我使用 zval 宣告兩個指標變數 *za、*zb

2. PHP_FUNCTION(swap) 跟在程式開頭寫下面這段有關
static const zend_function_entry swap_functions[] = {
  PHP_FE(swap, NULL)
  PHP_FE(f1, NULL)
  PHP_FE(f2, NULL)
  PHP_FE_END
};
PHP_FE 是註冊在 php 時,call function name
PHP_FUNCTION(swap) 的 swap 就是指在實作 swap 這個 function
如果是實作 f1那就是 PHP_FUNCTION(f1)

3. zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &za, &zb)
這行就是取得 php call function 時的參數
這裡跟 scanf 很像就是用 za 和 zb 接下來
然後 "zz" 就是接幾個參數和該參數型態,這裡用 zval

4. 取得參數後就是 swap 的主邏輯了

完整 Source:https://github.com/ffbli666/php-extension-examples

延伸思考和學習(研究ing)

Reference: