顯示具有 JavaScript 標籤的文章。 顯示所有文章
顯示具有 JavaScript 標籤的文章。 顯示所有文章

星期日, 8月 06, 2017

[React] constructor bind this or Component event bind this?

最近在跟公司的團隊還有朋友再深入討論 React 的效能細節,有討論到 React 的 function bind this 要寫在constructor 還是使用 arrow function ?
後來實驗出來我個人推薦在 constructor bind ,並且使用 props 把所需參數傳入。
原因是因為如果是在 Component 的事件裡面 bind,或是使用 arrow function auto bind ,實際上在 React Render 裡面的 function 都是新產生的,而 constructor bind 則會同一個 function,當一個畫面 render 久了前者就有效能議題了。

星期四, 4月 13, 2017

[JavaScript] Strict Mode

[JavaScript] Strict Mode


ECMAScript 5的Strict Mode不是ECMAScript 的子集。
Strict Mode 執行上跟Non-Strict Mode下有明顯差異。
Strict Mode 跟Non-Strict Mode可並存。

Strict Mode 將JavaScript再執行一些陷阱語法直接丟明顯的錯誤。
Strict Mode 修正JavaScript Engine難以優化的錯誤。
Strict Mode 禁止了一些未來ECMAScript可能會使用的關鍵字。


開啟Strict Mode

"use strict";

  1. 放在語法前。
  2. 不要將”use strict”用在{}使用,他會失去效果。

{
"use strict";
}

  1. 可以在function scope開啟strict mode。
!function strict() {
  "use strict";
  function nested() {return "So am I!";}
  return "I am strict mode "+ nested();
}();
!function nonStrict() {return "I am non Strict";}();

Strict Mode執行上的不同

  1. 拼寫錯誤的變數(或沒有宣告就存取的變數)會拋出異常。
"use strict";
var nAme;
nEme = 17; // ReferenceError

  1. Silently Fail (程式碼執行不報錯也沒有效果的錯誤)會拋出異常。
"use strict";
var obj1 = {};
Object.defineProperty(obj1, "x", {value: 42, writeable: false}});
obj1.x = 9; //TypeError

var obj2 = {get x(){return 17;}};
obj2.x = 5; // TypeError

var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // TypeError

  1. 試圖刪除不可刪除之屬性會拋出異常
"use strict";
delete Object.prototype; // TypeError


  1. Gecko 34以前下方指令會丟出錯誤(ECMAScript6不存在)
"use strict";
var o = {p:1, p:2};

  1. 函數的參數要唯一
function sum(a, a, c) {
  "use strict";
  return a+b+c;
}

  1. 禁止八進位語法
var a = 0o10;
parseInt(a);

"use strict";
var sum = 015 + // SyntaxError
          197 +
          142;

  1. 禁止設置primitive值屬性
!function() {
"use strict";

false.true = "";              //TypeError
(14).sailing = "home";        //TypeError
"with".you = "far away";      //TypeError

}();


簡化變數(variable)的使用


  1. 禁止使用with
"use strict";
var x = 17;
with (obj) // SyntaxError
{
  x; // x mean obj.x or var x? it will make engine can not process optimization. 
}


  1. Strict Mode下的eval不在讓surrounding scope(包圍eval的scope),注入新變數。例如eval(‘var’)

function a () {
  eval('var x=10');
  console.log(x); //x will be create.
}


var x = 17;
var evalX = eval("'use strict'; var x = 42; x"); //this x !== var x=17
console.assert(x === 17);
console.assert(evalX === 42);

2.1 如果eval在Strict Mode下面執行,其代碼會被當作在Strict Mode下為執行。
function strict1(str) {
  'use strict';
  return eval(str); // str will be treated as strict mode code
}
function strict2(f, str) {
  'use strict';
  return f(str); // not eval(...): str is strict if and only
                 // if it invokes strict mode
}
function nonstrict(str) {
  return eval(str); // str is strict if and only 
                    // if it invokes strict mode
}

strict1("'Strict mode code!'");
strict1("'use strict'; 'Strict mode code!'");
strict2(eval, "'Non-strict code.'");
strict2(eval, "'use strict'; 'Strict mode code!'");
nonstrict("'Non-strict code.'");
nonstrict("'use strict'; 'Strict mode code!'");

  1. Strict Mode禁止刪除變數
'use strict';
var a={x:10};
delete a.x; // It can delete

var x;
delete x; // SyntaxError

eval('var y; delete y;'); // SyntaxError



簡化eval跟arguments

  1. 對eval跟arguments誤用的範例如下
"use strict";
eval = 17;
arguments++;
++eval;
var obj = { set p(arguments) { } };
var eval;
try { } catch (arguments) { }
function x(eval) { }
function arguments() { }
var y = function eval() { };
var f = new Function("arguments", "'use strict'; return 17;");

  1. Strict Mode 函數arguments的值,不會隨著參數變數被修改而跟著改變。
function f(a){
  "use strict";
  a = 42; //a 改變,但是函數的arguments仍是f(17);
  return [a, arguments[0]];
}
var pair = f(17);
console.assert(pair[0] === 42);
console.assert(pair[1] === 17);

  1. 不再支援arguments.callee (為了最佳化)
"use strict";
var f = function() { return arguments.callee; };
f(); // TypeError


安全的 JavaScript


  1. Strict Mode下回傳this將不再將整個this包裝(boxed)返回出去,而是undefined, 或是call, apply, bind的this。一般模式下會將this回傳出去,可能會包含許多額外資訊(ex window 物件),同時也有助效能提升。
  • 這也意味瀏覽器將不能透過Strict Mode下的function 回傳this來取得window物件捕捉額外資訊。
'use strict';
function fun() { return this; }
console.assert(fun() === undefined);
console.assert(fun.call(2) === 2);
console.assert(fun.apply(null) === null);
console.assert(fun.call(undefined) === undefined);
console.assert(fun.bind(true)() === true);

  1. Strict Mode下的arguments不再提供”walk” to JavaScript Stack的下面兩個方式 function.caller(誰呼叫我)跟function.arguments(呼叫我的函式的參數),因為這會導致不安全的存取。
function restricted()
{
  "use strict";
  restricted.caller;    // TypeError
  restricted.arguments; // TypeError
}
function privilegedInvoker()
{
  return restricted();
}
privilegedInvoker();

  1. Strict Mode下的arguments不再提供這個函數相關變數的存取方式,例如arguments.caller。這個在現在許多瀏覽器都已經不再存取的到。
 'use strict';
function fun(a, b) {
  'use strict';
  var v = 12;
  return arguments.caller; // throws a TypeError
}
fun(1, 2); // doesn't expose v (or a or b)

保留ECMAScript未來擴充的關鍵字

  1. 關鍵字範例如下
implements, interface, let, package, private, protected, public, static, yield

function package(protected){ // !!!
  "use strict";
  var implements; // !!!

  interface: // !!!
  while (true)
  {
    break interface; // !!!
  }

  function private() { } // !!!
}
function fun(static) { 'use strict'; } // !!!

  1. Strict Mode也禁止了Function Declaration 一些的宣告方式,這也是未來ECMAScript會制訂函式宣告式未來的一些規範。
 'use strict';
if (true)
  function f() { } // SyntaxError
  
for (var i = 0; i < 5; i++) 
  function f2() { } // SyntaxError


function baz() { // ok
  function eit() { } // ok
}


Reference: MDN Strict Mode 嚴格模式

星期四, 2月 09, 2017

Angular $rootScope and $scope Event System

Angular官方對的 $rootScope 的定義如下
Every application has a single root scope. All other scopes are descendant scopes of the root scope. Scopes provide separation between the model and the view, via a mechanism for watching the model for changes. They also provide event emission/broadcast and subscription facility.
 一個ng-app只會有一個 $rootScope,所有的 $scope 都是 $rootScope 的child scope。文件特別把angular的emission/broadcast機制放在定義,這也代表事件處理是 $rootScope 很重要的操作。

$scope.$emit up, $scope.$broadcast down


// firing an event upwards
$scope.$emit('myCustomEvent', 'Data to send');

// firing an event downwards
$scope.$broadcast('myCustomEvent', {
  someProp: 'Sending you an Object!' // send whatever you want
});

// listen for the event in the relevant $scope
$scope.$on('myCustomEvent', function (event, data) {
  console.log(data); // 'Data to send'
});


簡單說明這三個行為

  • $emit 往上(parent)觸發事件
  • $broadcast 往下(child)觸發事件
  • $on 當收到事件所執行的行為


但是$scope的互動並非都是parent-child關係,有可能是Sibling之間要觸發。這時候就會要先$emit parentScope在$broadcast,並不是很方便的寫法。


$scope.$parent.$broadcast('myevent', 'Some data');


$rootScope.($emit/$broadcast)


上面的這種sibling的事件觸發,則可以使用$rootScope代為處理,如下方案例。
簡單說明 $rootScope 的 $emit 跟 $broadcast 的差別。
  • $rootScope.$emit 因為$rootScope沒有父層,所以直接觸發 $rootScope 註冊的事件。
  • $rootScope.$broadcast 往下廣播事件。

<div ng-controller="ParentCtrl as parent" class="ng-scope">
  // ParentCtrl
  <div ng-controller="SiblingOneCtrl as sib1" class="ng-scope">
    // SiblingOneCtrl
  </div>
  <div ng-controller="SiblingTwoCtrl as sib2" class="ng-scope">
    // SiblingTwoCtrl
    <div ng-controller="ChildCtrl as child" class="ng-scope">
      // ChildCtrl
    </div>
  </div>
</div>



app.controller('SiblingOneCtrl',
  function SiblingOneCtrl ($rootScope) {

  $rootScope.$on('rootScope:emit', function (event, data) {
    console.log(data); // 'Emit!'
  });
  
  $scope.$on('rootScope:broadcast', function (event, data) {
    console.log(data); // 'Broadcast!'
  });
  
  $rootScope.$on('rootScope:broadcast', function (event, data) {
    console.log(data); // 'Broadcast!'
  });

});

app.controller('ChildCtrl',
  function ChildCtrl ($rootScope) {

  $rootScope.$emit('rootScope:emit', 'Emit!'); // $rootScope.$on
  $rootScope.$broadcast('rootScope:broadcast', 'Broadcast'); // $rootScope.$on && $scope.$on

});


Ref.1 AngularJS Offical Document: $rootScope
Ref.2 Understanding Angular’s $scope and $rootScope event system $emit, $broadcast and $on

星期五, 6月 07, 2013

[JavaScript] Prototype Chain

  這次延續上次的 prototype,稍微討論一下 prototype chain。這也是 JavaScript很特別的繼承方式下的方法。我參照 Professional JavaScript for Web Developers - Nicholas C. Zakas的 prototype chain的範例實作以及圖片說明如下。

function SuperType(){
  this.property = true;
}

SuperType.prototype.getSuperValue = function (){
  return this.property;
};

function SubType() {
  this.subproperty = false;
}

//inherit from SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function () {
  return this.subproperty;
};

var instance = new SubType();
window.console.log(instance.getSuperValue());Check out this Pen!


 
  我們有兩個類別 SuperType以及 SubType,都各自有其屬性跟方法。其差別在於 SubType有新增一個 SuperType的 instance並且指定為他的 prototype(Line 11)。這差異也造成三個地方要注意:

  1. 上圖有了  SubType Prototype 的 __proto__到 SuperType Prototype這個連結關係,而原本在 SuperType存在的屬性跟方法也同時存在 SubType.prototype。

  2.  getSuperValue()這方法仍然在 SuperType.prototype物件當中,那是因為我們宣告他是在 SuperType.prototype當中。而 property則會在 SubType.prototype這個物件當中,是因為他是 instance物件,隨著 new 的物件而存在,我們宣告了 SubType.prototype = new SuperType(),其 SuperType的  this就相當於 SubType.prototype (若要更加延伸探討原因可以參考 [JavaScript] Conditions of this object)。

  3. 同時也注意一點就是 instance.constructor指向 SuperType,原因是 SubType.prototype的 constructor屬性也被複寫過去而重新指向到SuperType了。

  Prototype Chaining 延展了 prototype搜尋方法跟屬性的機制,若在讀取一個 instance的屬性或方法的時候沒有被找到,會往他的 prototype繼續搜尋。在此案例當我們呼叫 instance.getSuperValue()時候會有三個搜尋的步驟: 1) instance ->2) SubType.prototype->3) SuperType.prototype。

  Default Prototypes

  在 JavaScript的 Reference Type都繼承自 Object,所以上述範例實際上會是下圖(參考Professional JavaScript for Web Developers Chapter.6 Object-Oriented Programming)。當我們呼叫 instance.toString(),實際上是呼叫 Object.prototype的 toString()方法。

  延伸閱讀: [JavaScript] Prototype Memo

  Prototype的觀念一直是 JavaScript蠻值得探討的一個話題,文章若有敘述不清楚地方歡迎大家找我討論,也歡迎大家指教補述。

星期二, 6月 04, 2013

[JavaScript] Prototype Memo

  大家好,這次來探討 JavaScript的 Prototype。老實說在很多地方都有討論這個功能,我自己本身也對這個 JavaScript使用的繼承方式想要探討一番,我用我的方式解釋一次,也希望大家不吝指教。

  首先  Prototype又稱為原型,使用方法我做了一個範例如下。左邊是沒有使用 prototype的方法,右方是使用 prototype進行的方法的差異。(感謝 Vexed的投影片指點。)可以發現使用 prototype所製作出來的 instance屬性跟方法會使用相同的記憶體區塊,這代表這當我們若改變 Person()的 prototype的內容以及方法,其 instance的值也會跟著變動。

  流程上,當上述右例的 Person()被建立的同時 (第一行 function Person (){}),他的 prototype屬性也被建立起來。其中 Prototype的 Constructor也被建立起來,並且指回 Person (如下圖),在這個例子裏面, Person.prototype.constructor 指向的是 Person。如下圖我參照 Professional JavaScript for Web Developers - Nicholas C. Zakas做了其 prototype的示意圖。


  由圖可知 Person的 prototype指向 Person.prototype,而 Person.prototype.constructor也指回 Person,形成一個LOOP。也知道被 new出來的 instance有__proto__屬性, 雖然 __proto__並不能夠直接被存取,我們可以透過 isPrototypeOf來證明上面的關係的正確與否。可以在 console試看看答案會如下。


  • Person.prototype.isPrototypeOf(p1); // true
  走過一次之後大概對 prototype有比較清楚的概念了,下次會介紹 prototype chain的概念。




星期一, 5月 27, 2013

[JavaScript] typeof memo

  可以使用 typeof來判斷變數宣告,以避免在某些情況下不確定此變數或 Member存在導致 JavaScript中斷。


  • if (window.android.changeId) {} 
    • 若window沒有 android或是 底下沒有changeId的方法時候,JavaScript會中斷執行
  • if (typeof window.android !== "undefined" &&typeof window.android.changeId !== "undefined") {}
    • 可正常判斷是否有此 Member或是變數。
  也可使用 typeof(window.android),作用一樣。

[Closure] JavaScript使用 Closure模擬出 Private Member

  JavaScript裡面其實並沒有 Private Member的概念,(至少在EMCAScript 4),那如果我們要實作Private Member要怎樣實作呢? 可透過 Closure來完成。

function person (){
  this.age = 10;
  this.name = "Kevin";
};
var p = new person();
console.log(p.age + ":" + p.name);

function person_closure (age, name) {
  this.getName = function () {return name; };
  this.setName = function (newName) {name = newName; };
  this.getAge  = function () {return age;};
  this.setAge  = function (newAge) {age = newAge;};
  
}
var p_c = new person_closure(10, "Kevin");
console.log(p_c.age + ":" + p_c.name);
console.log(p_c);
p_c = null;Check out this Pen!


  如上方例子第一個例子由於沒有使用 Closure來達成 Private,只要打開瀏覽器的 debugger都可以觀察的到裡面的欄位。而第二個 person_closure透過參數傳入 age以及 name也只能使用 getter以及 setter方式存取變數值,進而達到 private 變數。

  最後稍微提醒一下,Closure容易造成記憶體無法釋放的問題,在此例 age,name會一直存在記憶體。所以我最後設定 p_c = null讓瀏覽器回收記憶體。

  這是我參考MSDN JavaScript: 使用物件導向技術來建立進階 Web 應用程式所做的案例探討,也歡迎大家來信更正。

延伸閱讀: [JavaScript] Closure 概念


星期一, 5月 13, 2013

[JavaScript] Conditions of this object

  各位好, 又再一次介紹 this 物件. 這是重點放在 this 物件會出現的情況. 我列出我常使用的四種情況.

出現情況 this 說明 範例程式
 物件方法的呼叫  呼叫方法的物件
 (Caller)
 var obj = new Object();
 obj.id = "test";
 obj.findThis = function(){ alert(this.id)};
 obj.findThis(); // this is obj.
 Global Function 呼叫  window,
 strict mode is   undefined.
 Caller is window
 function test(){
 alert(this);
 }
 test(); 
 使用 new產生一個 instance  就是 這個物件本身  function test(){
  alert(this)
 }
 var o = new test(); //this is o
 使用 Apply, Call呼叫函數
 帶入第一個參數值改變
 Context.
 被改變的 Context值  function test(){
    alert(this.id);
 }
 var m = {id: "m"};
 var n = {id: "n"};
 var o = {id: "o"};
 o.fn = test;
 o.fn.apply(m); // this is m
 o.fn.call(n);    // this is n

  我們可以說, this 物件重點在於 caller. caller代表著 context, 在大多數情況下也代表著 this.如果有缺的部分或是我有敘述錯誤的地方歡迎來信補述!

  補充說明: [JavaScript][OO] this object memo

Reference

星期四, 4月 11, 2013

[JavaScript] Closure 概念

  最近跟 Vexed在討論 JavaScript的 Closure概念, 就參照了他的投影片加上自己參考 Nicholas C. Zakas的 Professional JavaScript for Web Developers以及 wiki當中的 Closure概念.自己試著講解了一次什麼是 JavaScript Closure.


  其實 Closure的定義就是一個方法能夠存取到非同一個scope的方法的變數. Closure在很多語言都有使用到, 而在 JavaScript當中是透過 Scope Chain去達成.

  下面這個例子很有趣, 很明顯的表達出 Scope Chain是 Lexical 分析時候決定, 而非 Execution time決定.
var arg = 1;   
 function funcTest() {   
      alert(arg);   
      var arg = 2;   
 }   

 funcTest(); 
 function funcTest2() {   
      alert(arg);   
      //var arg = 2;   
 }  
funcTest2();
Check out this Pen!



星期一, 3月 04, 2013

[JavaScript] ESC block href issue

  最近 Awoo遇到個很有趣的問題, 我們需要再按下 ESC鍵之後, 轉址到其他頁面 (使用 location.href 等方式轉址). 在 Chrom以及 FireFox底下都沒問題.但是在 IE就是怎麼按都不願意轉.

  後來找到原因是 ESC在 IE的預設行為是中斷連線, 所以切換網址等指令也因此失效.所以只要加上此 event的 preventDefault (不執行預設行為)即可.


YUI().use('event-key', function (Y) {
  Y.one("body").on("key", function(e){
    e.preventDefault();
    alert("esc press");
    location.href = "http://codepen.io";
  }, "esc");
});
Check out this Pen!



  超級小細節,不過也是F2E應該要知道的原理.


星期一, 2月 25, 2013

[JavaScript] Drag and Drop implementation - Drop

  上一篇我們介紹 Drag and Drop的 Drag, 現在我們來實作看看Drop.的困難點會在哪邊呢? 我們就來討論看看.

  Drag and Drop操作主要分做三部, mouseDown, mouseMove 以及 mouseUp. 而 Drag則複雜的在前面兩個 (Down 跟Move). 所以 Drop自然就在 mouseUp囉!

  我本來一度很天真的以為有辦法透過 onMouseUp(event) 的 event取得 drop區域的物件. 結果實作才知道, 這 event指的是我們操作的 draggable物件. 所以要取得 drop區域這邊其實是要經過計算的, 才能算出我們到底有沒有丟到到我們想要的區域.
  下面是範例, 請大家點擊 edit on 點開來看會比較完整.

Check out this Pen!

  我們在一開始宣告一個變數存放 dropArea, 而在onMouseUp這邊我們增加了了判斷. 判斷拖拉的物件是不是有落在掉落的此在範圍的 offsetWidth跟 offsetHeight裏面.

  這個 Drag and Drop的範例的重點我大致擷取如下.

  • 三個步驟
    • onMouseDown
    • onMouseMove
    • onMouseUp
  • onMouseDown: 取得滑鼠點擊物件的偏移量 ( mouseOffset 不然移動時候設定位置會錯位)
  • onMouseMove
    • 將拖拉物件 display設定為 absolute.
    • {x,y} = 滑鼠位置-滑鼠點擊物件偏移量
    • 滑鼠位置.x = event.clientX + document.body.scrollLeft - document.body.clientLeft
    • 滑鼠位置.y = event.clientY + document.body.scrollTop  - document.body.clientTop 
  • onMouseUp:
    • 利用物件的 offsetWidth跟 offsetHeight計算是否落在 drop區域.
  當然 Drag and Drop作法很多種. 只是趁這次探討這問題的同時提出一種做法而已.


星期五, 2月 22, 2013

[JavaScript] Drag and Drop implementation - Drag

  之前有人問過我, 如果沒有 jQuery或是 YUI的函式庫, 要你實作一個拖拉的效果 (Drag and Drop)的效果,你會怎麼做? 好的問題比好的答案更為難得, 雖然時間有點久了我還是找時間自己思考了一下我會怎樣做.

  先從下面三個行為來思考, 首先就是要算出滑鼠的位置.這邊真的就是一門數學大工程了.
Ref.1 瀏覽器的各位置寬度屬性
  
  雖然有點這範例圖示瀏覽器版本舊了, 不過很多概念都還是可以使用的.


  • onMouseDown: 將滑鼠點擊的物件取出.
  • onMouseMove
    • 設定滑鼠 style為 absolute.
    • 計算滑鼠位置: mousePosition - mouseOffset
      • x: (event.clientX + document.body.scrollLeft - document.body.clientLeft) - mouseOffset.x
      • y: event.clientY + document.body.scrollTop  - document.body.clientTop  - mouseOffset.y
      • mouseOffset: 設定物件為滑鼠的點擊後經過運算的位置 (請參考下方原始碼)
      • 因為沒人知道使用者會點在物件的哪個位置上, 所以這邊要稍微計算一下.(使用offsetLeft跟 offsetTop做運算.)
  • onMouseUp: 取消滑鼠物件,設定為NULL.
  這是 Drag的作法, 還有 Drop的做法待續.
Check out this Pen!


[Reference] 
[Ref.1] Browser client X scrollLeft client left
[Ref.2] Event对象的5种坐标


星期二, 2月 05, 2013

[JS] keyCode for KeyDown, KeyPress and KeyUp

  之前就一直蠻困惑這問題的, 這次索性做個例子解釋一下原因.首先還是從最基本原理先解釋一下. 為求看得清楚,我這邊就使用 KeyDown, KeyPress 跟KeyUp 大小寫區分.

  • KeyDown: 鍵盤按鍵按下去所觸發事件, 若一直按著, 就一直觸發. (合理! 因為一直按著字就一直出來.)
  • KeyPress: 鍵盤按鍵按下去所觸發事件, 順序是先 KeyDown 然後再觸發 KeyPress.
  • KeyUp: 放開鍵盤按鍵時候觸發事件.
  下面是我使用 YUI所建立的範例, 若執行可以很簡單看出來 KeyDown, KeyPress所回傳的 key code是不同的.
  而 KeyPress的 key code就是按鍵的 ASCII值. 而我們使用Shift去切換大小寫對KeyPress的key code有影響, 但是對 KeyDown沒影響 (以大寫的 key code為主).

  • KeyCode範例

Check out this Pen!


  附註一點我試做了若持續按著鍵盤觸發, 擷取 KeyDown跟 KeyPress先後順序的記錄下來看, 發現其先後順序並非交錯執行( KeyDown-> KeyPress-> KeyDown-> KeyPress), 這是要注意的地方.


星期五, 2月 01, 2013

[JavaScript][OO] this object memo

  最近在跟Josehpj (啊嗚)討論 JavaScript的 Object Oriented 時候討論到 this 物件, 啊嗚有提到一段話,我直接引用如下:
JavaScript 在語言本身、模擬物件導向的開發中,this 都是很清楚的代表一個 instance。

把 this 搞複雜的原因,其實是瀏覽器特有的 DOM API。
像是 DOM Event、或像是 window.XMLHttpRequest 這樣的全域方法。
this 在對應的處理函式中都不會是所預期 instance。
這也是為什麼在 Y.on 或 Y.io 都有提供設定 context 的參數。
或者得自己去使用 call 或 apply 取執行 Handler、讓 Handler 正確處理。

如果今天開發的是沒有瀏覽器的 JavaScript,例如 Node.js,
this 通常是不會造成太大的困擾的 :)

  再說程式之前我先名詞解釋一下: "context", 何謂 context, 中譯最接近的意思就是上下文, 前後文. 常常我們看到一個句子會不太懂他的由來的時候, 若知道他的上下文, 我們很快就可以知道他的涵義.

  這邏輯推到變數也是一樣, 看到一個變數我們可能不太知道意義的話, 若能知道他的上下文, 大概就比較知道他到底是誰使用他了, 而下面例子當中所舉的的 this, 就是上面說的 context(上下文). 以程式舉例, 我後面有標 (1) (2) (3) 註解:
 on: {
   viewload: function () { 
      api = this;
      api.get(node).
         one('form').on('submit', _handleSubmit, api); 
      // (1).api is the context
    }
 }
 _handleSubmit = function (event) {
   var _api = this; //(2) this is api from (1) context
   _api.get(node).all('input');

   _enableForm.call(_api, arg1, arg2); 
   //(3.1) this _api is context
   _enableForm.apply(_api, [arg1, arg2]); 
   //(3.2) this _api is context
 }
  上述的範例說當一個模組在頁面載入完成要執行的 function call (on viewload). 當按下 Submit 按鈕送出之後相對應的handler (_handleSubmit) 會處理相對應的動作.

submit是 event的名字,  _handleSubmit是 callback funciton, 而第三個參數就是context了.

  而 (3.1) , (3.2)則是 JavaScript常拿來執行函式的方式.

  下方例子則是特別情況, 假設我有個物件 _api有 confirm的方法( 類似於 JavaScript的 confirm), 傳入message, 以及 callback. 而此時callback有需要 _api當其 context.則此時可以使用YUI的 Y.bind.


_api.confirm(message, callback); 
//callback will need _api as context
_api.confirm(message, Y.bind(callback. _api)); 
//callback will need _api as context

這次的 JavaScript this之旅真是學到不少東西啊!


星期三, 1月 23, 2013

[CSS] Display Memo

  大家好! 太久沒上來寫東西了,大家應該都忘記這邊了吧. 沒辦法, 最近真的是太忙了, (我絕對不會承認我買了 XBOX, AppleTV等有的沒的東西就忘記這邊了).

   標題這邊是來筆記一下 CSS的 Display 這屬性, 為何會想要筆記這屬性呢? 原因是看了  Evenwu的給網頁設計師的建議. 這篇文章, 才發現這些問題如果當下問我, 我可能也都會回答不出來, 可是包括 CSS reset, float 的 clearfix, 我其實每天都在用.

   這時候我就來問我自己原因, 為什麼我每天再用的東西, 別人換個角度問就回答不出來呢? 答案很簡單, 就是我底子還不夠紮實.所以今天就從很簡單的 CSS Display自己筆記一下囉!


  • display: none 
    • 設定元素在網頁上呈現消失掉. 我們常常會拿他和 visibility比較. visibility是不顯示, 實際上還是佔有空間.而display: none則是不占空間.
  • display: inline
    • 寬度高度均不可設定, 文字圖片均不換行, 內容的文字或圖片寬度即是他的寬度.
    • 可以有 margin-left, margin-right, padding-left, padding-right.
    • 但margin-top, margin-bottom, padding-top, padding-bottom, width, height, background 為無效.[ref. 1]
    • 預設為inline的標籤有: span, a, input, img, em, i, b,iframe.
    • iframe 不是block 讓我訝異了一下!
  • display: block
    • 可設定寬度高度, 所以就會以區塊方式呈現. 至於呈現位置則相關於position以及float的相關設定.
    • 由於為區塊設定, margin, padding等範圍設定均可生效.
    • 預設為block的標籤有: div, p, h1, h2...
 
       Display還有很多可討論的, 不過我覺得這三個屬性最重要, 若之後有研究的更清楚的話,我再把一些研究整理出來.





參考資料

星期四, 11月 01, 2012

[jQuery] Deferred Object : Callbacks

  最近在玩 NodeJS, 然後由於 NodeJS 有child_process的操作, 不過卻都是 non-blocking的東西. 所以如果需要blocking的功能的話, 就會需要額外處理. 這時候就會想要試看看 Deferred 或是Promise 的概念, 這物件的概念代表的是當你的程序執行B是要等待A完成,再接著執行, 這聽起來Javascript 基本上都是用callback function去完成.但是callback function 太過繁瑣,而且完成的程式碼會一層包一層包一層包一層(如果有很多程序互相相關).

  就覺得應該要來看看啥是 Deferred 物件. 跑去看 Vexe'd 先生的 jQuery Deferred Object . 裏頭說道去看 jQuery 原始碼 (搜尋 Deferred: ), 發現一堆啥Callbacks("once memory").  才發現要從源頭理解 Deferred 物件運作原理,就得先從 jQuery.Callbacks下手.

  當然先從官方文件著手.大概涵義翻譯一下, 主要是提供一個callback list object, 在這個callback list object當中,我先介紹最基礎的四項操作.

  1. callbacks.add()       - 新增callback function 到 list 當中.
  2. callbacks.remove()  - 同理,就是從callback list 移除callback function.
  3. callbacks.fire()        -  觸發callback list當中的function.
  4. callbacks.disable()   -  取消掉任何之後對此callback list object 的事件觸發, fire()
  操作就是這四個動作, 然後再衍伸還有四個 Support Flags組合, 我下面使用官方案例配上 Fiddle來實作,應該會好理解很多. (按下F12開啟 debug console)


  1. 範例1 - 無任何Support Flags. 有啥東西在 callback list object 就fire()!
  2. 範例2 - once 確保這個callback list只會被fire()一次.如範例, 第一次fire()之後, 第二個fire並不會被觸發.
  3. 範例3 - memory  這個 Flags比較有趣一點, 如果新增了一個callback function之後,他會立刻觸發 fire() , 然後在觸發新的值.在範例當中看2nd add 以及 2nd  fire以及比對下方沒有設定Support Flags的執行情況,就可以很明顯知道差別.
    (在Deferred 原始碼看到很多once 跟memory的組合用法, 之後再深入理解Deferred物件後應該會知道這樣時做的原因吧.)
  4. 範例4 - unique 概念很簡單, 就是重複的callback function將只會執行一次!
  5. 範例5 - stopOnFalse 概念也不難,就是遇到 callback function回傳false時候,中斷其執行.
  說到這裡, 對於Callbacks的概念也有點清楚了, 接下來就是這些Flag交互使用, 以Deferred 物件,最常用的就是once 以及 memory. 直接翻 jQuery 的Deferred 物件.

 Deferred: function( func ) {
  var doneList = jQuery.Callbacks( "once memory" ),
      failList = jQuery.Callbacks( "once memory" ),
      progressList = jQuery.Callbacks( "memory" ),

  這邊有三個 list : doneList, failList 以及progressList. 其中doneList 跟failList 都是 once 以及memory. 稍微做了一下範例實驗, 確保doneList 跟failList 只會被若增加新的 callback function都會執行, 但是最多只能fire() 一次內容. 而 progressList 則是每次有新的callback function加入都會觸發之外, 還可以被fire() 多次.

  目前就先就 jQuery.Callbacks做個簡單介紹, 之後會進入jQuery.Deferred 物件以及Promise()方法, 這可是之後語言幾乎都會參考的一部分程式碼啊!



星期五, 7月 20, 2012

[Youtube][JavaScript] How to get Youtube Streaming URL, subtitle and resolution.

[ 2012/9/27更新 ]

  Youtube 在擷取影音串流多將原本的 sig參數改為 signature,但是在取得 Video Information的網址並沒有跟著修改,我參照了 youtube-dl的改法將 parse的規則改了一下,應該就過囉.只是不知道何時會再修改就是了.
記錄在下方這個commit.
https://github.com/kvzhuang/video-parser/commit/1d2c6cb7b5de852951d1eaf8153a6e0ee984409a


  最近因為一些需求在研究youtube的影音串流網址,其目的就是有一個 youtube影片的URL,最後得到此影片真實的影音串流.

  分做三個部分:取得youtube的原始影音串流,取得字幕檔,處理解析度.


影音串流:
  首先是先研究 Python的 youtube-dl ,目的是傳入一個 youtube的影片網址然後取得其真實的影音串流,思考方式是設置一個 API Server,當有使用者送出 Request後,透過 youtube-dl取得影片的資訊,可以選擇格式下載檔案( FLV,  MP4..包括 srt字幕檔案),或是取得影音串流 URL並且回傳給使用者,功能相當齊全.

  然而有一個問題就是跨國之間 youtube的影音串流網址不同,如果我們使用一個 API Server架在台灣,若其他國家來跟我們詢問,透過的是我們網段取得的影音串流網址,再轉給其他國家將會無法使用. 

  所以把目標轉到 JavaScript,這個網路上有很多人做,主要就是透過 Bookmark(或是Greasemonkey),在使用者端執行 Script Injection.做法有很多,如 Vexe'd有介紹用我的最愛下載 YouTube 影片.如果要更複雜一點的就如下方的 Script,可以把它貼到bookmark裡面,在看影片時候執行即可以下載影片.


javascript:(function() {
    function get_filetype(fmt_id) {
        switch(fmt_id) {
            case '5':
            case '6':
            case '34':
            case '35':
                return 'FLV';
            case '18':
            case '22':
            case '37':
            case '38':
            case '82':
            case '83':
            case '84':
            case '85':
                return 'MP4';
            case '43':
            case '44':
            case '45':
            case '46':
            case '100':
            case '101':
            case '102':
                return 'WEBM';
            default:
                return 'unknown';
        }
    }
    function get_video_url(fmt_stream_map, 
                           chosen_fmt_id, 
                           avail_fmt_ids) {
        fmt_stream_map=unescape(fmt_stream_map);
        if(avail_fmt_ids.indexOf(chosen_fmt_id)===0)
            return 


fmt_stream_map.substring(4,
                         fmt_stream_map.indexOf('&quality='));

        if(avail_fmt_ids.indexOf(chosen_fmt_id)===avail_fmt_ids.length-1)
            return 


fmt_stream_map.substring(fmt_stream_map.lastIndexOf('http'),
                         fmt_stream_map.lastIndexOf('&quality='));
        var pattern=new RegExp('&itag='+avail_fmt_ids[avail_fmt_ids.indexOf(chosen_fmt_id)-1]+',url=(.+?)&quality=');
        return fmt_stream_map.match(pattern)[1];
    }
    (function main() {
        var args=yt.playerConfig['args'];
        var fmt_quality_list='';
        var chosen_fmt_id=0;
        var fmt_info=args['fmt_list'].split(',');
        var fmt_resolution='';
        var avail_fmt_ids=new Array(fmt_info.length);
        for(var i in fmt_info) {
            avail_fmt_ids[i]=fmt_info[i].split('/')[0];
            fmt_resolution=fmt_info[i].split('/')[1];
            fmt_quality_list+=(avail_fmt_ids[i]+' = '+fmt_resolution+' ('+get_filetype(avail_fmt_ids[i])+')\n');
        }
        loop: while(true) {
            chosen_fmt_id=prompt('Please enter a format id.\n'+fmt_quality_list, avail_fmt_ids[0]);
            if(chosen_fmt_id===null) {
                return;
            }
            for(var i in avail_fmt_ids) {
                if(chosen_fmt_id===avail_fmt_ids[i]) {
                    break loop;
                }
            }
        }

        window.open(get_video_url(args['url_encoded_fmt_stream_map'], chosen_fmt_id, avail_fmt_ids)+'&title='+document.getElementsByName('title')[0].content);
    }());
}());



  主要原理使用 Script Injection的概念透過 youtube當中的 JavaScript object: yt ,來取得所有的資訊.列出幾個比較常用的關鍵字:url_encoded_fmt_stream_map(原始影音串流URL),fmt_list(影片支援解析度列表).

字幕檔Subtitle:

  Youtube把字幕檔放在他們家的 video.google.com,所以當有 Video ID跟確認有此語言的代碼,即可取得字幕檔.不過格式為 Google設計的XML檔案,要直接使用或許要做一些手腳.
範例連結:http://video.google.com/timedtext?lang=en&v=j3avqi1zObE
  以下擷取 youtube-dl的轉換 xml->srt的操作.
def _closed_captions_xml_to_srt(self, xml_string):
srt = ''
texts = re.findall(r'([^<]+)', xml_string, re.MULTILINE)
# TODO parse xml instead of regex
for n, (start, dur_tag, dur, caption) in enumerate(texts):
if not dur: dur = '4'
start = float(start)
end = start + float(dur)
start = "%02i:%02i:%02i,%03i" %(start/(60*60), start/60%60, start%60, start%1*1000)
end = "%02i:%02i:%02i,%03i" %(end/(60*60), end/60%60, end%60, end%1*1000)
caption = unescapeHTML(caption)
caption = unescapeHTML(caption) # double cycle, intentional
srt += str(n+1) + '\n'
srt += start + ' --> ' + end + '\n'
srt += caption + '\n\n'
return srt 



影片格式 以及解析度:

  Youtube也提供許多種影片格式,我們回到上面的 Script Injection Code可以知道,yt.playerConfig裏頭的  args['fmt_list']有此影片提供的格式.並且參照一次搞懂十種YouTube格式.整理出各fmt參數相對應的影片格式跟解析度.

  舉例來說,如果我要取得720P的MP4影音串流,我就要設定我的影音串流URL的ittag為22就會有不同的影音串流.

(PS.我試了很多次直接在 youtube url裏頭設定fmt=22, fmt=45似乎已經不能設定影音格式了?)