顯示具有 javascript:understanding the weird parts 標籤的文章。 顯示所有文章
顯示具有 javascript:understanding the weird parts 標籤的文章。 顯示所有文章

2017年6月28日

[學習筆記目錄] JavaScript: Understanding the Weird Part(JavaScript 全攻略:克服JS 的奇怪部分)



這是我在Udemy上修的一門課,當初想增進自己JavaScript的能力,這門課在特價,同時老師的評價也相當的好,所以就想說學學看。這門課和其他一般線上免費的JavaScript教程很不一樣,不只是教程式怎麼寫,而是進一步說明背後的原理和邏輯,學到的很多之前不清楚的概念,特別是在JavaScript中關於繼承、原型還有建構式的地方之前看了很多教學都還是不太瞭解,但這堂課說明的超級清楚,同時又有搭配英文字幕,歡迎大家參考,也很推薦大家上這門課!

JavaScript基本觀念:認識不同的資料型別、運算子和重要概念


JavaScript物件建立:瞭解基本物件建立的方法


JavaScript函式:為什麼說函式也是物件的一種呢?


JavaScript函式進階:瞭解什麼是IIFEs、什麼是closures?


JavaScript原型、繼承和建構式:這是我上過最清楚的說明


親手打造屬於你的 Framework/Library


其他資源


2017年1月18日

[筆記] 親手打造屬於你自己的 JavaScript Framework/Library(下)




在上一篇 [筆記] 親手打造屬於你自己的 JavaScript Framework/Library(中),我們初步建立了好了自己的 framework ,並且可以成功運用它。最後,我們要來試著讓我們所做的 Framework   和 jQuery 做更多的結合,並且開始應用它。

我們的目標是希望能夠利用我們所做的這個 library 做一個簡單的登入呈現頁面,現在就讓我開始吧!

HTML部分


首先 HTML 的部分我們先寫一個很簡單的登入視窗,像是這樣:


<body>
 <div id="app">
  <div id="login">
   <select id='Lang'>
    <option value="en" selected>English</option>
    <option value="zh">中文</option>
   </select>
   <button>登入</button>
  </div>
  <h1 id="logMsg"></h1>
 </div>
 
    <script src="jquery-3.1.1.js"></script>
    <script src="greetr.js"></script>
    <script src="app.js"></script>
</body>


JS 部分(greetr.js)


接下來,我們要在之前做的 Library 中加入 jQuery 的方法,一樣放在 Greetr.prototype 裡面,我們加入一個方式叫做 greetHTML ,這個方法可以直接幫我們把 greet 輸出的文字,寫入 HTML 當中:


greetHTML: function(selector, formal) {
    if (!$) throw "jQuery not loaded";

    if (!selector) throw "no seletor";

    let msg = "";

    if (formal) {
        msg = this.formalGreeting();
    } else {
        msg = this.greeting();
    };

    $(selector).html(msg);

    return this;

}


於是我們就做好我們的 Library 了,順便把完整的 Library Code (greetr.js) 放一份在下面:
;
(function(global, $) {

    var Greetr = function(firstname, lastname, language) {
        return new Greetr.init(firstname, lastname, language);
    }

    let supportedLangs = ['en', 'zh'];

    let greetings = {
        en: 'Hello',
        zh: '你好'
    };

    let formalGreetings = {
        en: 'Greetings',
        zh: '您好'
    };

    let logMessages = {
        en: 'Logged',
        zh: '登入'
    };

    Greetr.prototype = {
        fullName: function() {
            return this.firstname + ' ' + this.lastname;
        },
        validate: function() {
            if (supportedLangs.indexOf(this.language) === -1) {
                throw 'Invalid Language';
            }
        },
        greeting: function() {
            return greetings[this.language] + ' ' + this.firstname;
        },
        formalGreeting: function() {
            return formalGreetings[this.language] + ', ' + this.fullName();
        },
        greet: function(formal) {

            let msg;

            // if undefined or null, it will coerced to 'false'
            if (formal) {
                msg = this.formalGreeting();
            } else {
                msg = this.greeting();
            }

            if (console) {
                console.log(msg);
            }

            // 'this' refers to the calling object at execution time
            // makes method chainable
            return this;

        },
        log: function() {
            if (console) {
                console.log(logMessages[this.language] + ' ' + this.fullName());
            }
            return this;
        },
        setLang: function(lang) {
            this.language = lang;
            this.validate();
            return this;
        },
        greetHTML: function(selector, formal) {
            if (!$) throw "jQuery not loaded";

            if (!selector) throw "no seletor";

            let msg = "";

            if (formal) {
                msg = this.formalGreeting();
            } else {
                msg = this.greeting();
            };

            $(selector).html(msg);

            return this;

        }
    };

    Greetr.init = function(firstname, lastname, language) {
        let self = this;
        self.firstname = firstname || '';
        self.lastname = lastname || '';
        self.language = language || 'zh';
        self.validate();
    }

    Greetr.init.prototype = Greetr.prototype;

    global.Greetr = global.G$ = Greetr;


}(window, jQuery))

接著,我們就可以實際應用我們的 Library 了。

Library 應用(app.js)


實際應用的時候,我們會寫在另外一支 app.js 中,現在我們希望當我按下登入之後,就會在網頁上出現打招呼,因此,我們可以在 app.js 中這樣寫:


$('#login').on('click', 'button', function() {
    let pj = G$("Po-Jung", "Chen");
    pj.setLang($("#Lang").val()).greetHTML('#logMsg', true);
})


透過這樣的方式,當我們按下按鈕之後,就會出現相對應的打招呼文字:


看起來是相當簡單的原理,但只要再加上一些 CSS 的修飾,就可以是一個相當出色的登入頁面,可以參考看看這個頁面





→回到此系列文章目錄



2016年7月22日

[筆記] 親手打造屬於你自己的 JavaScript Framework/Library(中)




在上一篇 [筆記] 親手打造屬於你自己的 JavaScript Framework/Library(上),我們開始建立了自己的 framework ,並且可以成功運用它來建立物件,但在這個物件裡面還沒有方法,因此在這篇筆記中,我們就要繼續往下做嘍。

在 framework 中建立變數


在開始建立方法(method)前,我想要先建立一些變數是我之後可以在方法中使用,但這些變數又不會和外層的 global environment 有所衝突,我們可以在哪裡建立這些變數呢?
我們可以直接將變數建立在這個 IIFEs 裡面就可以了,我們不用放在 prototype 或 function constructors 裡面,這樣會佔據額外的記憶體位置,但是,當我們需要使用時,透過 closure的概念 我們依然可以提取到這些變數。透過這種方法,我們可以讓使用 framework 的人沒有辦法去改這些變數的值,但是在使用 method 的時候,仍然可以提用到這些變數。


因此,就讓我們繼續寫下去啦。

於是我們的程式碼現在長這樣子,多了第8-23行,我分別建立了變數 supportedLangs, greeting, formalGreetings 和 logMessages 這四個變數。


  (function(global, $){
  
    var Greetr = function(firstname, lastname, language){
      return new Greetr.init(firstname, lastname, language);
    }
  
    var supportedLangs = ['en','zh-tw'];
  
    var greetings = {
      en = 'Hello',
      zh-tw = '你好'
    };
  
    var formalGreetings = {
      en = 'Greetings',
      zh-tw = '歡迎您'
    };
  
    var logMessages = {
      en = 'Logged in',
      zh-tw = '登入'
    };
  
    Greetr.prototype = {}
  
    Greetr.init = function(firstname, lastname, language){
  
      var self = this;
      self.firstname = firstname || '';
      self.lastname = lastname || '';
      self.language = language || 'zh-tw';
  
    }
  
    Greetr.init.prototype = Greetr.prototype;
  
    global.Greetr = global.G$ = Greetr;
  
  })(window, jQuery)



建立方法(method)


有兩個地方可以放置我們想要的方法,分別是如下圖的第 1 部分和第 2 部分。但是我們知道,如果放在這個 function constructor 中(第 2 部分)變成每一個所建立的物件都會直接帶有這個方法,如此會佔據相當多的記憶體空間;所以,比較好的方式是利用原型的概念,把根據這個 function constructor 所建立的物件,都可以使用到的方法,放到第 1 部分的 prototype 當中



接著,我們要來在 prototype 中開始建立一些 framework 裡面可以使用的方法。

我們先來看一下建立完方法後完整的程式碼長什麼樣子,這些程式碼都是放在Greetr.prototype{ } 內,接著我們再來分別一一解釋每個方法的意義:


  Greetr.prototype = {
    fullName: function(){
      return this.firstname + ' ' + this.lastname;
    },

    validate: function(){
      if(supportedLangs.indexOf(this.language) === -1 ){
        throw "Invalid language";
      }
    },

    greeting: function(){
      return greetings[this.language] + ' ' + this.firstname + '!';
    },

    formalGreeting: function(){
      return formalGreetings[this.language] + ' ' + this.fullName();
    },

    greet: function(formal){
      var msg;

      //  if undefined or null, it will be coerced to 'false'
      if(formal){
        msg = this.formalGreeting();
      }else{
        msg = this.greeting();
      };

      if(console){
        console.log(msg);
      };

      // 'this' refers to the calling object at execution time
      // makes the method chainable
      return this;
    },
    log: function(){
      if(console){
        console.log(logMessages[this.language] + ' ' + this.fullName());
      }

      return this;
    },

    setLang: function(lang){
      this.language = lang;

      this.validate();

      return this;
    }

  }


fullName(第3行 - 第5行):這個 method 很簡單,只是讓我們可以得到使用者的全名。

validate(第7行 - 第11行):這個 method 主要是用來檢測使用者所輸入的語言我們有沒有支援。記得我們剛剛在最上有建立了一個陣列是 supportedLangs ,裡面含有兩個元素,分別是 en 和 zh_tw,如果使用者在參數當中所輸入的語言並不屬於這兩種時,我們使用 indexOf 這個函式時,就無法在 supportedLangs 的陣列中找到,於是就會回傳 -1 的結果。此時,我們就要拋出錯誤訊息。

greeting(第13行 - 第15行)、formalGreeting(第17行 - 第19行):這兩個 method 都是讓我們跟使用者打招戶用的,其中函式裡面的 greetings 和 formalGreetings 其實是我們在前面所建立的物件,這裡我們透過括號 [ ] 的方式,來取得該物件的屬性值(因為 [ ] 內才可以放變數)。
另一個可以注意到的地方是,在 formalGreeting 這個函式的最後是使用 this.fullName( ),也就是直接呼叫並執行了前面第3行 - 第5行的這個 method。

greet(第21行 - 第38行):這個 method 稍微比較長一些。透過 greet 這個方法,我可以直接控制我要使用的是greeting 或 formalGreeting ,而不用打出這兩個方法。我們利用強制轉換的概念(coercion),如果我們給予的參數 formal 是 true 的話,那麼請幫我們使用 formalGreeting,否則使用 greeting。
為了避免有些IE版本不支援 console 這個物件,我們用 if(console) 這樣的方法,意思是如果有 console 這個物件的話,在幫我輸出。
再來很重要的一個是第37行的 return this ,記得我們前面提到的方法鍊(method chaining)嗎?透過方法鍊我們可以讓一個方法接著一個方法接下去使用。
log(第39行 - 第45行):透過這個 method 我們可以回傳登入的訊息,這同樣有使用到方法鍊的技巧。

setLang(第47行 - 第53行):透過這個 method,我們可以改變我們當初在參數中所建立的語言,同時為了驗證這個語言是不是我們所支援的,我們在這個 method 中加入了 this.validate( )來驗證,validate( )是我們在第7行 - 第11行所寫的 method 。這也同樣有使用到方法鍊的技巧。




寫到這裡,我們就差不多把我們 framework 當中要建立的方法給建立完畢啦,接著就讓我們來看看怎麼使用了!

測試所建立的 framework


接著在 app.js 中,我們可以試著來測試一下所建立的 framework 拉。

假設我輸入:


var g = G$('PJ', 'CHEN'); //  language的預設是zh-tw
g.greet().setLang('en').greet(true);


我就可以得到以下的結果。因為我們有使用了方法鍊的技巧,所以我可以一個接著一個方法的使用,當中我又用了 setLang 這個方法,把預設的語言改成英文:


接著,假設我輸入:

var g = G$('PJ', 'CHEN'); //  language的預設是zh-tw
g.log().setLang('zh_cn').greet(true);

首先,會回傳登入的訊息,接著因為我把語言設成 "zh_cn" 但實際上,我們並不支援這樣的語言,所以回拋出錯誤的訊息給我們。大家可以自己測試自己所寫的 framework 來測試玩玩看。


程式範例


(function(global, $){

  var Greetr = function(firstname, lastname, language){
    return new Greetr.init(firstname, lastname, language);
  }

  var supportedLangs = ['en','zh_tw'];

  var greetings = {
    en: 'Hello',
    zh_tw: '你好'
  };

  var formalGreetings = {
    en: 'Greetings',
    zh_tw: '歡迎您'
  };

  var logMessages = {
    en: 'Logged in',
    zh_tw: '登入'
  };

  Greetr.prototype = {
    fullName: function(){
      return this.firstname + ' ' + this.lastname;
    },

    validate: function(){
      if(supportedLangs.indexOf(this.language) === -1 ){
        throw "Invalid language";
      }
    },

    greeting: function(){
      return greetings[this.language] + ' ' + this.firstname + '!';
    },

    formalGreeting: function(){
      return formalGreetings[this.language] + ' ' + this.fullName();
    },

    greet: function(formal){
      var msg;

      //  if undefined or null, it will be coerced to 'false'
      if(formal){
        msg = this.formalGreeting();
      }else{
        msg = this.greeting();
      };

      if(console){
        console.log(msg);
      };

      // 'this' refers to the calling object at execution time
      // makes the method chainable
      return this;
    },
    log: function(){
      if(console){
        console.log(logMessages[this.language] + ' ' + this.fullName());
      }

      return this;
    },

    setLang: function(lang){
      this.language = lang;

      this.validate();

      return this;
    }

  }

  Greetr.init = function(firstname, lastname, language){

    var self = this;
    self.firstname = firstname || '';
    self.lastname = lastname || '';
    self.language = language || 'zh_tw';

  }

  Greetr.init.prototype = Greetr.prototype;

  global.Greetr = global.G$ = Greetr;

})(window, jQuery)




→回到此系列文章目錄



2016年7月19日

[筆記] 親手打造屬於你自己的 JavaScript Framework/Library(上)




在上一篇[筆記] 跟著JQuery原始碼一起學習程式設計中,我們從 jQuery 的原始碼中,學到了一些建立 framework 的概念和技巧,在這篇筆記中,我們就要來試著打造我們自己的 framework 啦。

Requirement


當我們在打造一個 framework 時,事先規劃好這個 framework 要具備哪些功能是很重要的,而不是一股腦的就打開程式編輯器開始寫,所以我們來規劃一些這個 framework 要具備哪些功能吧!

在這系列的課程,我們大部分都是用 greeting(打招呼) 來當作程式撰寫的例子,在這堂課中也不例外,我們要來打造一個和 greeting 有關的 framework ,我們把它稱做 "greetr"。

我們的需求是這樣的:

  1. 當我們告訴它我們的姓(lastname)名(firstname)還有選擇的語言(language)時,它可以用正式(formal)和非正式(informal)的方式和我們打招呼。
  2. 支援英文(English)和繁體中文(zh_tw)兩種語言。
  3. 是一個可重複使用的 library/framework,也就是說,每一個安裝此 framework 的人可以直接使用,不會和它原本程式碼有所衝突。
  4. 和 jQuery 只需要輸入 "$( )" 一樣,我們可以使用 "G$( )" 來建立物件。
  5. 支援 jQuery ,可以把 greetr 產生的訊息直接顯示於HTML中。

HTML部分


在HTML中,我們總共會匯入三支js檔,第一支是 jQuery,因為在我們的 framework 中會使用到一些 jQuery 的功能;第二支是 greetr.js 這支就是我們寫 framework 的地方;最後一支是 app.js ,這一支則是我們用來應用我們所寫的 framework 的地方。


  <!doctype html>
  <html>
  <head>
      <meta charset="UTF-8">
      <title>Untitled Document</title>
  </head>
  <body>
    <script src="jquery-3.0.0.js"></script>
    <script src="greetr.js"></script>
    <script src="app.js"></script>
  </body>
  </html>



讓程式碼位於安全的位置

現在,我們可以開始在 greetr.js 中開始編輯我們的 library 了。

首先,為了避免我們所寫的 framework 被外層的變數所影響,我們要善用 IIFEs ,也就是將我們所寫的 library 放到 IIFEs 裡面。
同時,因為我們要讓我們的 framework 能夠影響到 global 的內容,同時還要支援 jQuery ,因此在參數的地方,我們會帶入 global 和 $ 。



不需要使用 new 就可以建立物件


在上堂筆記中看過 jQuery 的架構後,我們希望可以將學到的東西應用到我們的 framework 中,也就是說,我們希望可以輸入 var g = G$(firstname, lastname, language) 如此就能建立出一個新的物件而不用用到 new 這個關鍵字,我們可以怎麼做呢?
從上一堂課的筆記中,我們知道我們可以利用 return 一個 function constructor 的方式來達到這樣的效果,如圖中第 1 部分所示,而圖中的第 2 部分,才是我們真正建立函式的地方。

第 1 部分之所以可以寫在第 2 部分前面,是因為第 1 部份要在我們執行 Greetr 時,才會真的加以執行


建立函式裡面的預設值


接著,我們要在函式裡面為 firstname, lastname和 language 來建立預設值,還記得我們可以怎麼做嗎?

首先,因為我們這是一個 function constructor ,為了避免 this 可能在後面使用時碰到一些問題,所以在第 1 部分的地方,我們用 var self = this 這樣的方法,來避免 this 在後續操作上可能會碰到的問題;在第 2 部分的地方,我們則是透過 JavaScript 中強制轉換(coercion)的特性,使用 OR operator  || 的方式來達到預設值的效果。


建立建構子的原型(prototype)


接著,我們要來建立這個函式建構子(function construct)的原型了,還記得原型(prototype)嗎?
為了程式容易閱讀,我想要將這個建構子的原型,用 Greetr.prototype 來表示,於是就如下圖第 1 部分所示。

可是,我們知道,我們現在函式建構子的名稱是 Greetr.init ,所以這個建構子的原型名稱其實是 Greetr.init.prototype 才對,因此,如同 jQuery 中所使用的方式,我們可以在第 2 部分的地方寫上 Greetr.inti.prototype = Greetr.prototype ,如此,我們就可以直接在 Greetr.prototype 中撰寫程式碼了。


使用 G$( ) 即可建立物件


就像在 jQuery 中,我們可以使用 jQuery( ) 或 $( ) 來建立物件,在這裡,我們希望我們可以使用 Greetr( ) 或 G$( ) 這兩種方式都可以建立物件,記得在 jQuery 中是怎麼做到的嗎?

我們只需要加上這一行,就可以達到這樣的效果了。


到目前為止...


到目前為止,我們的框架 greetr.js 長這樣子,而且它已經可以簡單使用了。


  (function(global, $){
  
    var Greetr = function(firstname, lastname, language){
      return new Greetr.init(firstname, lastname, language);
    }
  
    Greetr.prototype = {}
  
    Greetr.init = function(firstname, lastname, language){
  
      var self = this;
      self.firstname = firstname || '';
      self.lastname = lastname || '';
      self.language = language || 'zh_tw';
  
    }
  
    Greetr.init.prototype = Greetr.prototype;
  
    global.Greetr = global.G$ = Greetr;
  
  })(window, jQuery)


如果我們在 app.js 當中,輸入以下內容:


var g = G$('PJ', 'CHEN');
console.log(g);


已經可以成功獲得一個物件了!






→回到此系列文章目錄



2016年7月16日

[筆記] 跟著JQuery原始碼一起學習程式設計


在這篇筆記中,我們會要進入jQuery的原始碼中。

雖然這堂課老師一直希望我們可以自己多透過這些開源的程式碼來學習程式設計,但是說真的,直接進入原始碼的時候,還真的是看不太懂阿...,好險有老師一步一步點出重點來,就讓我們從中發掘一些可以學習或應用的部分,趕快看下去吧!

看一下JQuery這個物件


我們可以先輸入以下的HTML架構:

  <nav>
    <ul class = 'people'>
      <li><a href="#">Home</a></li>
      <li><a href="#">About</a></li>
      <li><a href="#">Products</a></li>
      <li><a href="#">Contact</a></li>
    </ul>
  </nav>


然後我們可以在js檔中,輸入以下jQuery語法:

  var q = $("ul.people li");
  console.log(q);


如此,我們就可以檢視看看jQuery物件的樣子。在console視窗中我們可以看到它是一個array-like的東西,名稱是 jQuery.fn.init ,先稍微對這個名稱有個印象,等等我們在去原始碼中看看這個東西:


另外,我們可以在 __proto__ 裡面,看到一大堆的jQuery方法:


接著,就讓我們正式進入jQuery原始碼啦!

說實話,要我自己看這個原始碼,還真的不知道要從何看起...,不過透過老師的引導,幾乎是把這堂課所學到的重要內容再次複習了一次。

IIFEs的使用


一打開jQuery原始碼,我們就會看到一個IIFEs,記得我們有提過在IIFEs中所建立的變數,都不會影響到Global Execution Context所建立的變數,也就是說,透過IIFEs,它避免了我們的變數間可能會互相干擾覆蓋的情況 ,而jQuery同樣是使用了IIFEs的方式來撰寫這個library。

IIFEs開頭
接著,我們可以往下看到IIFEs的結尾,其中有一串是 typeof window !== "undefiend" ? window : this ,這個意思是如果window的類型不是undefiend的話,則global object就是window,否則是this(根據當時的環境所決定)。

IIFEs結尾


為什麼在jQuery中不需要輸入 new 來建立物件


我們知道在jQuery中,我們只需要寫 $( )jQuery( ) 就可以建立jQuery的物件了,而不需要使用關鍵字 new ,為什麼可以這樣呢?

原因在於它用了 return new ... 這樣的用法,等於當我輸入jQuery時,它就會直接幫我帶入 new 這個關鍵字,讓我們不用每次用new這個關鍵字。

從這裡,我們也可以知道 jQuery.fn.init 是一個function constructor,我們應該可以在後面的地方找到它。


在jQuery中為什麼可以使用方法鍊(method chaining)?


在jQuery中,我們可以在一個方法之後直接接著另一個方法使用,像是這樣:

  var q = $("nav ul.people").addClass("newClass").removeClass("people");
  console.log(q);


我們可以直接針對ul這個元素,去添加class,同時直接在後面移除class,而不用重複打兩次。這種一個方法接著一個方法,而且每個方法都可以影響到父層物件的情形,我們就稱為方法鍊(method chaining)

這是一個很有趣的情形,因為我們知道第一個 addClass 因為接在 jQuery 物件之後,所以它會是 jQuery 物件裡面的一個方法,同時可以對 jQuery 物件產生影響;但是,對於 removeClass 這個方法來說,它是放在另一個方法後面,為什麼可以找到這個方法,而且仍可對我們的 jQuery 物件有作用呢?

為了要解答這樣的問題,我們要進入到 jQuery 原始碼中一探究竟。

搜尋 addClass 這個方法的地方,我們會發現到,這個方法的最後有一個關鍵,也就是它 return this ,而這裡的 this 指的也就是我們的 jQuery 物件,因此透過這個 return this,它可以先針對物件進行欲要進行的方法後,最後再次將它回傳成一個物件,於是,它就可以繼續在接著下一個方法,形成一個方法鍊的作法。

也就是說,addClass 這個方法執行完後,會回傳原本的 jQuery 物件,因此變得就像這樣子:

  var q = $("nav ul.people").removeClass("people")



在原始碼中,我們可以看到,在 removeClass 或其他許多的方法中,最後都會加上 return this 。


為什麼在jQuery中可以使用 $ 或者 jQuery 來建立物件?


接下來我們想要來看,為什麼在jQuery中,我可以透過 $( ) 或者 jQuery( )這兩種方式來建立物件都不會有問題呢?

我們在程式最下方的地方可以看到這個,而這也就說明了,為什麼我們可以使用 jQuery 或 $ 的方式來建立物件,因為他最後都是指稱到 IIFEs 裡面的這個 jQuery 函式。





→回到此系列文章目錄



2016年7月6日

[筆記] JavaScript 中利用 typeof 檢驗運算元所代表的型別


在這篇筆記中,我們會說明如何利用 typeof 這個內建的關鍵字來正確地檢驗運算元的型別。

讓我們來看一下 type of 的使用還有它回傳的結果:


  var a = 3;
  console.log(typeof a);  //  number
  
  var b = "Hello";
  console.log(typeof b);  //  string
  
  var c = {};
  console.log(typeof c);  //  object
  
  var d = [];
  console.log(typeof d);  //  object
  
  var e = false;
  console.log(typeof e);  //  boolean


這裡,我們可以看到大部分的型別都能正確回傳,除了陣列它一樣會回傳成Object。

為了避免這個問題,如果我們想要檢測該物件是否為一陣列的時候,我們可以用如下的語法:


  var d = [];
  console.log(typeof d);  //  object
  console.log(Object.prototype.toString.call(d)); //  [object Array]


如此,它將會回傳 [Object Array] ,讓我們知道它是個陣列。

讓我們再來看一下其他的資料型別:


  function Person(name){
    this.name = name;
  }

  console.log(typeof Person); //  function

  var f = new Person("Jane");
  console.log(typeof f);  //  object
  
  console.log(typeof undefined);  //  undefined
  console.log(typeof null); //  object


從這裡,我們可以看到function的話,能夠正確回傳function;而如果是用funtion當作建構子所建立出來的物件,則是物件沒錯;如果是undefined回傳一樣是屬於undefined型別;最後一個比較奇怪的是,如果是null,會傳成object,這個則是比較需要留意的地方。

在MDN的網站列了typeof的可能回傳值:

圖片來源:MDN - typeof

程式範例

var a = 3;
console.log(typeof a);  //  number

var b = "Hello";
console.log(typeof b);  //  string

var c = {};
console.log(typeof c);  //  object

var d = [];
console.log(typeof d);  //  object
console.log(Object.prototype.toString.call(d)); //  [object Array]

var e = false;
console.log(typeof e);  //  boolean

function Person(name){
  this.name = name;
}

console.log(typeof Person); //  function

var f = new Person("Jane");
console.log(typeof f);  //  object

console.log(typeof undefined);  //  undefined
console.log(typeof null); //  object


→回到此系列文章目錄



2016年7月4日

2016年6月29日

[筆記] 談談JavaScript中最單純的原型繼承(prototypal inheritance)─ Object.create

img
在先前的筆記中,我們談到可以使用 function constructor 來建立物件,同時我們也提到 function constructor 這種概念是用來模仿 Java 語言而產生的(參考:[筆記] 談談 JavaScrip t中的 function constructor 和關鍵字 new),在其它的程式語言中,會用 class 這個關鍵字來設定該物件要長什麼樣子,然後透過關鍵字 new 來建立物件。
然而,和其他程式語言不同的地方在於,JavaScript 實際上使用的是 prototypal inheritance 而不是 classic inheritance,所以為了讓 JavaScript 回歸單純的 prototypal inheritance,現在的瀏覽器大部分都支援Object.create() 這種單純的方式來建立物件。
讓我們透過下面的例子,更清楚 pure prototypal inheritance 和 Object.create() 的操作。
首先,我們先使用下面這段程式建立一個原型的物件:
var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}
注意一下 return 的地方,這裡我們用的是 this.firstName 而不是直接用 firstName。當我使用 this 這個關鍵字的時候,它會指稱到的是 Person 這個物件,但是如果沒有使用 this 的話,會變成在 getFullName 這個 execution context 中找 firstNamelastName 這兩個變數,如果找不到的話就會往最外層的全域環境(global environment)去找,最後如果還是找不到,就會出現 "Uncaught ReferenceError: firstName is not defined" 。

Object.create() 的使用

接下來我們說明如何使用 Object.create()
var john = Object.create(Person);
console.log(john);
輸出的結果如下,它是一個空物件,但是它繼承了 Person 這個物件當中的屬性和方法:
透過 Object.create() 可以建立一個空物件,同時可以將你帶入 Object.create() 的參數內容變成該物件的原型。
由於原型鍊(prototype chain),可以在這個物件當中建立相同屬性的內容,在執行的時候它會先找物件中最上層的屬性,於是就不會得到 default 的結果,像是這樣:
var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}

var john = Object.create(Person);

john.firstName = 'John';
john.lastName = 'Doe';
console.log(john.getFullName());
如此,對於 firstNamelastName 來說,在該物件就已經有這兩個屬性,因此它不會在往該物件的原型去尋找,而對 getFullName 來說,因為在 john 這個物件裡沒有這個方法,於是就會到 prototype 裡面去找,最後會回傳 "John Doe"。
透過 Object.create() 這種方法,是最單純使用 prototypal inheritance 的方式。如果你想要定一個物件的原型,就先建立一個 A 物件當做其他物件的基礎,然後再建立另一個空物件 B,指稱 A 物件當做它的原型,在透過為 B 物件賦予屬性或方法。
使用 Object.create() 的方式運用繼承和原型的概念能夠非常簡便的建立物件。

如果瀏覽器不支援 Object.create()

雖然大部分的新瀏覽器都支援這樣的做法,但如果某些瀏覽器的版本真的舊到無法使用 Object.create() 的話,可以怎麼辦呢?
這時候我們會寫一些程式來填補某些瀏覽器不支援的情況,我們把這些程式稱做 polyfill
如果我們不確定瀏覽器是不是有支援 Object.create() 的話,我們可以寫如下的 polyfill
// polyfill for Object.create()

if (!Object.create) {
  Object.create = function (o) {
    if (arguments.length > 1) {
      throw new Error('Object.create implementation only accepts the first parameter');
    }

    function F() {};
    F.prototype = o;
    return new F();
  };
}
讓我們來了解一下這段程式碼。
首先,if(!Object.create) 是用來判斷瀏覽器中是否有內建 Object.create() 的函式,如果沒有的話就會回傳 undefined ,在前面加一個 ! 這個邏輯運算子,則會把 undefined 轉換成布林值,所以這段程式碼轉成中文的話,意思就是「如果 Object.create 不存在的話,則執行... 」。
Object.create 不存在的時候,接著會建立 Object.create 這個 function。 if(arguments.length > 1) 是說明如果所代入的參數超過一個的話,會在 console.log 中回傳錯誤訊息。
最後會去執行 Object.create 這個函式原本會執行的內容,也就是先建立一個空的函式 F(){ },然後把原本基礎物件的內容放入 F.prototype 中,最後再用函式建構式(function constructors)的方式,回傳 new F( ) ,如此,就能夠達到 Object.create() 原本的效果。

程式範例

//  polyfill
if (!Object.create) {
  Object.create = function (o) {
    if (arguments.length > 1) {
      throw new Error('Object.create implementation only accepts the first parameter');
    }

    function F() {};
    F.prototype = o;
    return new F();
  };
}

var Person = {
  firstName: 'Default',
  lastName: 'Default',
  getFullName: function () {
    return this.firstName + " " + this.lastName;
  }
}

var john = Object.create(Person);

john.firstName = 'John';
john.lastName = 'Doe';
console.log(john.getFullName());

資料來源

[筆記] 談談 JavaScript 中 for ... in 這個 function

img
在 JavaScript 中有一個非常常用到的函式 for ... in 迴圈,for...in 的用法和 Array.prototype.forEach 很像,但它可以針對**物件(Object)陣列(Array)**來使用。

for in 針對物件的基本使用

讓我們先來看一下下面這個例子:
var john = {
  firstName: 'John',
  lastName: 'Doe'
};

for (var prop in john) {
  console.log(prop + ':' + john[prop]);
}
我們建立一個物件名稱為 john,而 prop 是自訂的變數,會把該物件的屬性存在這個變數中,接著讀取下一個屬性,重覆直到沒有屬性為止。透過 for...in,可以把該物件中的所有屬性名稱和屬性值都呼叫出來。
延伸閱讀:for...in @ MDN

可能問題一:包含繼承屬性的物件 - hasOwnProperty

然而,如果我們是透過函式建構式(function constructor)來建立物件時,這個物件可能會繼承該函式建構式的一些屬性或方法,這時候當我們直接使用 for...in 時,這些繼承而來的屬性和方法也會被一併輸出,如下:
//  function constructor
var Person = function (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

//  function constructor 的 prototype
Person.prototype.getFullName = function () {
  return this.firstName + ' ' + this.lastName
}

//  根據 function constructor 所建立的物件 Customer1
var Customer1 = new Person('John', 'Doe')

//  透過for...in輸出
for (var prop in Customer1) {
  console.log(prop + ': ' + Customer1[prop])
}
透過這種方法,我們會發現連同prototype中繼承的方法也被輸出了:
img
如果想要解決這個問題,我們會需要使用到 Object.prototype.hasOwnProperty 這個內建的函式,透過 obj.hasOwnProperty(prop),它會回傳 Boolean,以此區分這個屬性是直接的或是繼承而來的,也就是說,透過這個方法,它不會往該物件的原型鏈(prototype chain)去檢查。寫法如下:
for (var prop in Customer1) {
  // 不是繼承而來的屬性,才輸出...
  if (Customer1.hasOwnProperty(prop)) {
    console.log(prop + ': ' + Customer1[prop]);
  }
}
如此,我們輸出的結果就只會有 firstName 和 lastName 這兩個直接的屬性。

可能問題二:包含繼承屬性的陣列,儘可能不要使用 for...in

在 JavaScript 中,陣列(Array)也是一種物件,因此,我們也可以對陣列使用 for...in 的方法來輸出陣列的內容,如下:
var arr = ['John', 'Jane', 'Jim']
for (var prop in arr) {
  console.log(prop + ': ' + arr[prop])
}
如此,我們會得到如下的結果:
img
然而,當我們使用方括號 [ ] 來建立陣列的時候,其實就和使用 new Array ( ) 是一樣的意思。因此,如果原本的 Array.prototype 有被添加過一些屬性或方法時,使用 for...in 的結果一樣會把這些繼承的屬性和方法給輸出:
Array.prototype.website = 'pjchender'

var arr = ['John', 'Jane', 'Jim']

for (var prop in arr) {
  console.log(prop + ': ' + arr[prop])
}
輸出的結果會把website這個繼承而來的屬性給一併輸出:
img
為了避免這樣的問題,如果是針對陣列在處理的話,會建議可以使用一般的 for 迴圈來輸出陣列就可以了:
for (var i = 0; i < arr.length; i++) {
  console.log(i + ': ' + arr[i])
}
因此,當我們在處理陣列資料時,為了避免呼叫出不必要的屬性,應該儘可能不要使用 for...in 的用法來處理迴圈
var Person = function (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

Person.prototype.getFullName = function () {
  return this.firstName + ' ' + this.lastName
}

var Customer1 = new Person('John', 'Doe')

for (var prop in Customer1) {
  if (Customer1.hasOwnProperty(prop)) {
    console.log(prop + ': ' + Customer1[prop])
  }
}

/* ------------------------------------- */
Array.prototype.website = 'pjchender'

var arr = ['John', 'Jane', 'Jim']

for (var prop in arr) {
  console.log(prop + ': ' + arr[prop])
};

for (var i = 0; i < arr.length; i++) {
  console.log(i + ': ' + arr[i])
}

資料來源

2016年6月28日

[筆記] 談談 JavaScript 中內建的 function constructors 及應注意的地方

img
在這篇筆記中讓我們來看一些JavaScript中內建的 function constructors

new Number()

new Number();
首先,讓我們輸入:
var num = new Number(3);
console.log(num);
console.log(Number.prototype);
new 這種用法我們可以理解到 Number 是 JavaScript 中內建的一個 function constructor,也就是說它有內建的 prototype 在內,而且很重要的是,因為它是透過 function construct 加以建立的,所以它雖然看起來是個數值,但它實際上還是個物件
因此,輸出之後我們會看到以下結果:

new String()

new String();
類似的方式我們也可以看看用 String() 這個 function construct 所建立的物件:
var str = new String('網頁設計前端資源');
console.log(str);
console.log(String.prototype);
console.log(str.indexOf("設計"));
在這裡, str 一樣是透過 function constructor 所建立的物件,而不是真正的字串!同時 str 繼承String.prototype 裡面的內容,所以我們可以直接使用 str.indexOf 這樣的用法來查找字串,結果如下:

new Date( )

即使是 Date(),它輸出的結果雖然看起來很像是一個字串,但它實際上還是物件,我們可以這樣做:
var date = new Date("6/28/2016");
console.log(date);
console.log(typeof(date));
console.log(Date.prototype);
如此,我們會發現它依然是個物件,而這個物件繼承了來自 Date.prototype 的屬性和方法:
很重要的一點是:利用這些 function constructors 所建立的東西,看起來可能像數值、字串等等,但實際上,它們都還是物件
有些時候,JavaScript 會知道你想要針對原生值(primitive type)去做一些物件才能處理的事,因此它會幫你將所輸入的原生值進行物件般的處理。
例如,我們輸入:
console.log("PJCHENder".length);    //  9
這時候,一般的原生值是沒有屬性(property)和方法(method)的,我們之所以這樣輸入一樣可以得到 9 的結果,是因為 JavaScript 自動的幫我們把這個原生值進行了物件的處理

針對內建的 function constructor 去增添屬性和方法

在清楚瞭解了原型(prototype)、函式建構式(function constructor)和繼承(inheritance)的概念後,現在我們也可以針對內建的 function construct 去增添裡面的屬性和方法,像是這樣:
String.prototype.isLengthGreaterThan = function(limit){
  return this.length > limit;
}

console.log("PJCHENder".isLengthGreaterThan(4));    //  True
透過這樣的方法,我們幫原本內建的 String 這個 function constructor 新增了一個名為 isLengthGreaterThan 的方法,如此當我們輸入 "PJCHENder".isLengthGreaterThan(4) 就會回傳 true 的結果給我們。
在這裡,一樣可以注意到,原本我們輸入的 "PJCHENder" 是原生的字串值,但 JavaScript 知道我們希望對它進行和物件有關的方法,因此會自動用物件的方式將它做處理,也因此才會回傳 true 的結果。
❗️ 透過這種方式來增添屬性或方法時,要留意的是,不要不小心把原本內建的屬性或方法給無意間覆蓋掉了。
那麼,如果我們想要用同樣的方法,來針對 Number 這個內建函式來操作的話呢?
Number.prototype.isPositive = function(){
  return this > 0;
}

console.log(3.isPositive());
結果會出現錯誤:
之所以會有這個錯誤,主要是因為在上面 console.log(3.isPositive()); 的地方,在先前 JavaScript 雖然能夠幫我們順利將原生值的字串轉成物件來使用內部的方法,但直接使用數值的話會造成 JavaScript 引擎在解析時的錯誤。要解決這個問題只需把 3 先存成一個變數,或在用 () 包住 3 之後再執行就可以了:
Number.prototype.isPositive = function(){
  return this > 0;
}

var d = 3;
console.log(d.isPositive());    // true
console.log((3).isPositive());  // true
如此就能跑出預期的結果了!!
關於 prototype, function constructorinheritance 如果還有觀念不清楚的地方,建議先閱讀先前的文章,以幫助了解這篇的內容。

但最好不要使用內建的函式建構式來產生原生值

如果可以的話,最好不要使用和原生值有關的內建 function constructors。
在上半篇的筆記當中,我們雖然示範了如果針對和原生值有關的內建函式去增添其 prototype 的方法,但其實應該盡可能不要使用和原生值有關的這些內建函式建構式,為什麼呢?
讓我們看一下這段程式碼:
var numPrimitive = 5 ;
var numFromConstructor = new Number(5);
console.log(typeof numPrimitive + "; " + typeof numFromConstructor);    //    number; object
console.log(numPrimitive == numFromConstructor);    //  true
console.log(numPrimitive === numFromConstructor);   //  false
你會發現, numPrimitivenumFromConstructor 雖然看起來很像都是一樣的 5,但如同上面所提的,numPrimitive 是原生的數值,而 numFromConstructor 是物件,這也就是為什麼如果使用 numPrimitive == numFromConstructor 時會回傳 true ,但如果是使用 numPrimitive === numFromConstructor 時則會回傳 false
因為使用 == 時,它會把前後兩個東西強制轉換(coerce)成可比較的型式,但如果是 === 則不會強制轉換,而是會進一步比較兩個的類型(type),當兩者類型不同的時候,就會回傳 false
這也就是為什麼如果可以的話,建議不要使用和原生值有關的 function constructors,因為如果交替使用的話,可能會讓自己最後搞不清楚現在這個值到底是原生值,還是透過 function constructor 所建立出來的物件!
可以的話盡量不要使用內建的函式建構式來產生原生值,因為透過函式建構式產生出來的「原生值」其實是「物件」,這樣可能會導致後來型別判斷上的困難。

程式範例

/*--------------------*/

var a = new Number(3);
console.log(a);
console.log(Number.prototype);

/*--------------------*/

var b = new String("網頁設計前端資源");
console.log(b);
console.log(String.prototype);
console.log(b.indexOf("設計"));

/*--------------------*/

console.log("PJCHENder".length); //  9

/*--------------------*/

var c = new Date("6/28/2016");
console.log(c);
console.log(typeof c);
console.log(Date.prototype);

/*--------------------*/

String.prototype.isLengthGreaterThan = function (limit) {
  return this.length > limit;
}

console.log("PJCHENder".isLengthGreaterThan(4)); //  True

/*--------------------*/

Number.prototype.isPositive = function () {
  return this > 0;
}

var d = 3;
console.log(d.isPositive()); // true

/*--------------------*/

var e = 5;
var f = new Number(5);
console.log(typeof e + "; " + typeof f);
console.log(e == f); //  true
console.log(e === f); //  false

資料來源