FE.ES-JavaScript SDK設計指南

本指南提供了開發JavaScriptSDK的簡介。javascript

描述SDK的最好的一句話是:「 SDK是彌合用戶和(瀏覽器)計算機之間差距的鏈接。」

經過使用本指南,SDK將可以在瀏覽器,臺式機,移動網絡和各類其餘可以運行JavaScript的平臺上運行。html

本文的目標受衆暫時不包括非瀏覽器環境,例如硬件,嵌入式和Node.js。可是,未來會添加一些材料來覆蓋這些區域。html5

什麼是SDK

答案顯而易見,可是這裏仍是要重申一下。java

「 軟件開發工具包的簡稱,一種代碼包,使開發人員可以爲特定平臺開發應用程序。SDK一般包括一個或多個API,編程工具和文檔。」node

設計哲學

根據SDK服務的目的和用途,常見的共享特徵包括但不限於本地的,簡短的,快速的,簡潔的,可讀的和可測試的。jquery

普遍採用的良好作法是使用原生JavaScript編寫SDK。不建議使用編譯爲JavaScript的語言,例如LiveScript,CoffeeScript,TypeScript等。git

還建議不要在SDK開發中使用諸如jQuery之類的庫。若是非要用於DOM操做,還有其餘相似jQuery的庫如zepto.js 等可供選擇。github

若是有HTTP ajax請求要求,則用原生方法如window.fetch。它更輕巧,並在不斷增加的平臺中獲得支持。ajax

向後兼容性相當重要。每一個新的SDK版本都應向後兼容。一樣,當前版本應設計爲支持未來的SDK版本。這稱爲漸進加強。算法

此外,良好的文檔,良好的註釋代碼,良好的單元測試覆蓋範圍以及端到端(用戶)方案是SDK成功的關鍵。

範圍

基於Third-Party JavaScript一書

設計JavaScript SDK時須要考慮如下三個用例:

  1. 嵌入式控件 -嵌入到發佈者網頁上的小型交互式應用程序(Disqus,Google Maps,Facebook 窗體控件)
  2. 分析和指標 -用於收集有關訪問者及其與發佈者網站(GA,Flurry,Mixpanel)互動的數據
  3. Web服務API包裝器 -用於開發與外部Web服務進行通訊的客戶端應用程序。(Facebook Graph API)

編寫一個JavaScript環境中使用SDK的示例是有必要的。

加載SDK

爲了將SDK包含在面向用戶的環境中,使用異步語法加載腳本是一個好習慣。

這有助於優化使用SDK的網站上的用戶體驗。這種方法減小了SDK庫干擾主要內容加載的機會。

異步語法

<script>
  (function () {
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.src = 'http://<DOMAIN>.com/sdk.js';
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
  })();
</script>

針對現代瀏覽器時使用async語法。

<script async src="http://<DOMAIN>.com/sdk.js"></script>

傳統語法

<script type="text/javascript" src="http://<DOMAIN>.com/sdk.js"></script>

比較
這是顯示異步和傳統語法之間區別的簡單圖形。

異步:

|----A-----|
    |-----B-----------|
        |-------C------|

同步:

|----A-----||-----B-----------||-------C------|

異步和延遲的JavaScript執行說明

https://developers.google.com...
避免或使用壓縮過的阻塞JavaScript(尤爲是在執行前必須先獲取的外部腳本)是一種很好的作法。能夠內聯呈現頁面內容所需的腳本,以免額外的網絡請求,可是內聯的內容必須很小,而且必須快速執行(非阻塞方式)以提供良好的性能。對於初始渲染不重要的腳本,應使其異步或推遲到第一次渲染以後進行。

異步的問題
使用異步方法時,建議在首屏中加載,解析和執行全部庫以前執行SDK初始化功能。

考慮如下代碼段做爲上一個語句的直觀示例:

<script>
  (function () {
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.src = 'http://<DOMAIN>.com/sdk.js';
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
  })();

  // execute your script immediately here
  SDKName('some arguments');
</script>

這種初始化終將致使錯誤。此時SDKName()未定義的函數在環境的全局變量中可用以前執行。該腳本還沒有加載。

爲了定期運行,須要一些技巧來確保腳本成功執行。該事件將(須要)存儲在SDKName.q隊列數組中。SDK應該可以處理和執行SDKName.q事件並初始化SDKName命名空間。

如下代碼段描述了上一段中的聲明。

<script>
  (function () {
    // add a queue event here
    SDKName = SDKName || function () {
      (SDKName.q = SDKName.q || []).push(arguments);
    };
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.src = 'http://<DOMAIN>.com/sdk.js';
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
  })();

  // execute your script immediately here
  SDKName('some arguments');
</script>

或使用[].push

<script>
  (function () {
    // add a queue event here
    SDKName = window.SDKName || (window.SDKName = []);
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.src = 'http://<DOMAIN>.com/sdk.js';
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
  })();

  // execute your script immediately here
  SDKName.push(['some arguments']);
</script>

其餘
還有其餘導入腳本的方法

在ES2015中導入

import "your-sdk";

模塊化導入腳本
這裏有完整的源代碼,而這本很棒的教程 "Loading JavaScript Modules" 可能有助於深刻理解上面討論的概念。

module('sdk.js',['sdk-track.js', 'sdk-beacon.js'],function(track, beacon) {
  // sdk definitions, split into local and global/exported definitions
  // local definitions
  // exports
});

// you should contain this "module" method
(function () {

  var modules = {}; // private record of module data

  // modules are functions with additional information
  function module(name,imports,mod) {

    // record module information
    window.console.log('found module '+name);
    modules[name] = {name:name, imports: imports, mod: mod};

    // trigger loading of import dependencies
    for (var imp in imports) loadModule(imports[imp]);

    // check whether this was the last module to be loaded
    // in a given dependency group
    loadedModule(name);
  }

  // function loadModule
  // function loadedModule

  window.module = module;
})();

SDK版本控制

使用如下版本控制樣式之一不是一個好習慣:

  • brand-v<timestamp>.js
  • brand-v<datetime>.js
  • brand-v1-v2.js

緣由是跟蹤最新版本變得混亂。所以,之前的樣式不能幫助使用SDK的開發人員。

可是,在對SDK進行版本控制時,最好使用Semantic Versioning(也稱爲SemVer)。它具備三個主要部分,每一個部分與發行版的重要性相對應:「 MAJOR.MINOR.PATCH」。例如,版本v1.0.0 v1.5.0 v2.0.0易於在changelog文檔中進行跟蹤。

根據服務設計,能夠按版本發佈(或跟蹤)SDK的一些方法以下:

  • 使用查詢字符串路徑— http://<DOMAIN>.com/sdk.js?v=1.0.0
  • 使用文件夾命名- http://<DOMAIN>.com/v1.0.0/sdk.js
  • 使用主機名(子域)— http://v1.<DOMAIN>.com/sdk.js

根據用例,一般建議使用其餘依賴於環境的表單:

stable版本中http://<DOMAIN>.com/sdk-stable.js
unstable版本中http://<DOMAIN>.com/sdk-unstable.js
alpha版本中http://<DOMAIN>.com/sdk-alpha.js
latest版本中http://<DOMAIN>.com/sdk-latest.js
experimental版本中http://<DOMAIN>.com/sdk-experimental.js
建議閱讀:Why use SemVer?npm博客上。

變動日誌文件

當沒有發佈公告時,很難注意到SDK是否已更新(或升級)。編寫變動日誌以記錄主要,次要甚至錯誤修復的更改是一個好習慣。跟蹤SDK API中的更改可提供良好的開發人員體驗。- Keep a Changelog(Github Repo)

每一個版本應具備:

[Added] for new features.
[Changed] for changes in existing functionality.
[Deprecated] for once-stable features removed in upcoming releases.
[Removed] for deprecated features removed in this release.
[Fixed] for any bug fixes.
[Security] to invite users to upgrade in case of vulnerabilities.

另外,commit-message-emoji使用表情符號來解釋提交自己的更改。

命名空間

爲避免與其餘庫衝突,最好定義一個以上的全局SDK命名空間。命名也應避免將經常使用的單詞和流行語用做命名空間。

舉個簡單的例子,SDK Playground能夠很好地使用(function () { ... })()或ES6塊{ ... }包裝全部源。

這是許多流行的JavaScript庫(如jQuery,Node.js等)中愈來愈常見的一種作法。此方法會在文件的整個內容周圍建立一個閉包,這多是最重要的是,建立一個私有命名空間,從而有助於避免不一樣JavaScript模塊和庫之間可能發生的名稱衝突。#

爲了不命名衝突

在Google Analytics中,經過更改值來定義名稱空間ga

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

根據OpenX experience的經驗,支持一個參數來請求名稱空間。

<script src="http://your_domain/sdk?namespace=yourcompany"></script>

儲存機制

Cookie
當使用subdomainpath時,使用cookie的域範圍很是複雜。

對於path=/,在http://github.com中有一個cookiefirst=value1,而在http://sub.github.com中有另外一個cookiesecond=value2

http://github.com http://sub.github.com
first=value1
second=value2

There is a cookiefirst=value1in domainhttp://github.com, cookiesecond=value2in domain pathhttp://github.com/path1and cookiethird=value3in domainhttp://sub.github.com,

http://github.com http://github.com/path1 http://sub.github.com
first=value1
second=value2
third=value3

檢查Cookie是否可寫
給定一個域(默認爲當前主機名),檢查cookie是否可寫。

var checkCookieWritable = function(domain) {
    try {
        // Create cookie
        document.cookie = 'cookietest=1' + (domain ? '; domain=' + domain : '');
        var ret = document.cookie.indexOf('cookietest=') != -1;
        // Delete cookie
        document.cookie = 'cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT' + (domain ? '; domain=' + domain : '');
        return ret;
    } catch (e) {
        return false;
    }
};

檢查第三方Cookie是否可寫
僅使用客戶端JavaScript進行檢查是不可能的,可是服務器能夠幫助實現這一點。

寫入/讀取/刪除Cookie代碼
寫入/讀取/刪除Cookie腳本的代碼段。

var cookie = {
    write: function(name, value, days, domain, path) {
        var date = new Date();
        days = days || 730; // two years
        path = path || '/';
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        var expires = '; expires=' + date.toGMTString();
        var cookieValue = name + '=' + value + expires + '; path=' + path;
        if (domain) {
            cookieValue += '; domain=' + domain;
        }
        document.cookie = cookieValue;
    },
    read: function(name) {
        var allCookie = '' + document.cookie;
        var index = allCookie.indexOf(name);
        if (name === undefined || name === '' || index === -1) return '';
        var ind1 = allCookie.indexOf(';', index);
        if (ind1 == -1) ind1 = allCookie.length;
        return unescape(allCookie.substring(index + name.length + 1, ind1));
    },
    remove: function(name) {
        if (this.read(name)) {
            this.write(name, '', -1, '/');
        }
    }
};

Session
重要的是要知道在JavaScript中不可能讀寫Session。那是服務器的責任。服務器端團隊應實施與session管理相關的用例。

頁面session的持續時間只要瀏覽器處於打開狀態,而且在頁面從新加載和還原後仍然存在。在新標籤或窗口中打開頁面將致使啓動新session。

LocalStorage
存儲沒有到期日期的數據,存儲限制更大(至少5MB),而且信息永遠不會傳輸到服務器。

從每一個localStorage的http和https在同一個域中不共享。在網站內部建立iframe並將postMessage其傳遞給他人。

HOW TO?
檢查LocalStorage可寫
並不是全部瀏覽器都支持window.localStorage,所以SDK在使用前應檢查其是否可用。

var testCanLocalStorage = function() {
   var mod = 'modernizr';
   try {
       localStorage.setItem(mod, mod);
       localStorage.removeItem(mod);
       return true;
   } catch (e) {
       return false;
   }
};

Session Storage
存儲一個會話的數據(關閉選項卡時數據丟失)。

檢查SessionStorage可寫

var checkCanSessionStorage = function() {
  var mod = 'modernizr';
  try {
    sessionStorage.setItem(mod, mod);
    sessionStorage.removeItem(mod);
    return true;
  } catch (e) {
    return false;
  }
}

Event

在客戶端瀏覽器中,有一些事件load unload on off bind....如下是一些polyfill供您處理全部不一樣的平臺。

Document Ready
在開始執行SDK函數以前,請確保整個頁面已完成加載(就緒)。

// handle IE8+
function ready (fn) {
    if (document.readyState != 'loading') {
        fn();
    } else if (window.addEventListener) {
        // window.addEventListener('load', fn);
        window.addEventListener('DOMContentLoaded', fn);
    } else {
        window.attachEvent('onreadystatechange', function() {
            if (document.readyState != 'loading')
                fn();
            });
    }
}
DOMContentLoaded-在文檔徹底加載和解析時觸發,而無需等待樣式表,圖像和subframes完成加載

load事件可用於檢測頁面已fully-loaded

Information from JS Tip -https://github.com/loverajoel/jstips/blob/master/_posts/en/javascript/2016-02-15-detect-document-ready-in-pure-js.md

element-readyfromsindresorhus

Message事件
關於iframe和window之間的跨域通訊,請閱讀API documentation

// in the iframe
parent.postMessage("Hello"); // string

// ==========================================

// in the iframe's parent
// Create IE + others compatible event handler
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";

// Listen to message from child window
eventer(messageEvent,function(e) {
  // e.origin , check the message origin
  console.log('parent received message!:  ',e.data);
},false);

Post message數據應爲String,要在JSON中進行更高級的使用,請使用JSON String。儘管現代的瀏覽器確實在參數上支持結構化克隆算法,但並不是全部瀏覽器都支持。

方向改變
檢測設備方向變化

window.addEventListener('orientationchange', fn);

獲取方向旋轉度

window.orientation; // => 90, -90, 0

Screen portrait-primary, portrait-secondary, landscape-primary, landscape-secondary (Experimental)

// https://developer.mozilla.org/en-US/docs/Web/API/Screen/orientation
var orientation = screen.orientation || screen.mozOrientation || screen.msOrientation;

禁止滾動

在網頁中,使用CSS樣式overflow: hidden,在某些移動網絡中,此CSS無效,請使用JavaScript事件。

document.addEventListener('touchstart', function(e){ e.preventDefault(); }); // prevent scroll
// or
document.body.addEventListener('touchstart', function(e){ e.preventDefault(); }); // prevent scroll
// use move if you need some touch event
document.addEventListener('touchmove', function(e){ e.preventDefault(); }); // prevent scroll
Request

請求

咱們的SDK與服務器之間的通訊使用Ajax請求。最多見的用例是利用jQuery的ajax http請求與服務器進行通訊。好消息是,有一個更好的解決方案來實現這一目標。

Image Beacon

使用Image Beacon要求瀏覽器執行GET方法request以獲取圖像。

你們應該永遠記得添加時間戳(Cache Buster),以防止在瀏覽器中進行緩存。

(new Image()).src = 'http://<DOMAIN>.com/collect?id=1111';

關於GET Query String的一些注意事項,其長度限制爲2048(基本上取決於不一樣的瀏覽器和服務器)。如下技巧有助於處理超出長度限制的狀況。

if (length > 2048) {
    // do Multiple Post (form)
} else {
    // do Image Beacon
}

使用encodeURI或存在衆所周知的問題encodeURIComponent。可是,最好了解這兩種方法如何工做。在下面閱讀詳細信息。

對於圖像加載成功/錯誤回調

var img = new Image();
img.src = 'http://<DOMAIN>.com/collect?id=1111';
img.onload = successCallback;
img.onerror = errorCallback;

Single Post

可使用本機表單元素POST方法發送鍵值。

var form = document.createElement('form');
var input = document.createElement('input');

form.style.display = 'none';
form.setAttribute('method', 'POST');
form.setAttribute('action', 'http://<DOMAIN>.com/track');

input.name = 'username';
input.value = 'attacker';

form.appendChild(input);
document.getElementsByTagName('body')[0].appendChild(form);

form.submit();

Multiple Posts

該服務一般很複雜,尤爲是在須要經過POST方法發送更多數據時。

function requestWithoutAjax( url, params, method ){

    params = params || {};
    method = method || "post";

    // function to remove the iframe
    var removeIframe = function( iframe ){
        iframe.parentElement.removeChild(iframe);
    };

    // make a iframe...
    var iframe = document.createElement('iframe');
    iframe.style.display = 'none';

    iframe.onload = function(){
        var iframeDoc = this.contentWindow.document;

        // Make a invisible form
        var form = iframeDoc.createElement('form');
        form.method = method;
        form.action = url;
        iframeDoc.body.appendChild(form);

        // pass the parameters
        for( var name in params ){
            var input = iframeDoc.createElement('input');
            input.type = 'hidden';
            input.name = name;
            input.value = params[name];
            form.appendChild(input);
        }

        form.submit();
        // remove the iframe
        setTimeout( function(){
            removeIframe(iframe);
        }, 500);
    };

    document.body.appendChild(iframe);
}
requestWithoutAjax('url/to', { id: 2, price: 2.5, lastname: 'Gamez'});

iframe

嵌入在html中的iframe始終能夠用於覆蓋在頁面內生成內容的用例。

var iframe = document.createElement('iframe');
var body = document.getElementsByTagName('body')[0];

iframe.style.display = 'none';
iframe.src = 'http://<DOMAIN>.com/page';
iframe.onreadystatechange = function () {
    if (iframe.readyState !== 'complete') {
        return;
    }
};
iframe.onload = loadCallback;

body.appendChild(iframe);

從iframe內移除多餘的邊距

<iframe src="..."
 marginwidth="0"
 marginheight="0"
 hspace="0"
 vspace="0"
 frameborder="0"
 scrolling="no"></iframe>

將HTML內容放入iframe

<iframe id="iframe"></iframe>

<script>
  var html_string= "content <script>alert(location.href);</script>";
  document.getElementById('iframe').src = "data:text/html;charset=utf-8," + escape(html_string);
  // alert data:text/html;charset=utf-8.....
  // access cookie get ERROR

  var doc = document.getElementById('iframe').contentWindow.document;
  doc.open();
  doc.write('<body>Test<script>alert(location.href);</script></body>');
  doc.close();
  // alert "top window url"

  var iframe = document.createElement('iframe');
  iframe.src = 'javascript:;\'' + encodeURI('<html><body><script>alert(location.href);</body></html>') + '\'';
  // iframe.src = 'javascript:;"' + encodeURI((html_tag).replace(/\"/g, '\\\"')) + '"';
  document.body.appendChild(iframe);
  // alert "about:blank"
</script>

Script jsonp

在這種狀況下,您的服務器須要發送JavaScript response並讓客戶端瀏覽器執行它。僅包括JS腳本連接。

(function () {
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.src = '/yourscript?some=parameter&callback=jsonpCallback';
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
  })();

要了解有關jsonp的更多信息

  1. JSONP僅在GET HTTP請求中起做用。
  2. JSONP缺少錯誤處理,這意味着您沒法在響應狀態代碼40四、500等中檢測到案例。
  3. JSONP請求始終是異步的。
  4. 小心CSRF攻擊。
  5. 跨域通訊。腳本響應端(服務器端)不須要關心CORS。

Navigator.sendBeacon()

查看 documentation

此方法解決了分析和診斷代碼的需求,這些代碼一般在卸載文檔以前嘗試將數據發送到Web服務器。儘快發送數據可能會致使錯過收集數據的機會。可是,確保數據在文檔卸載期間已發送是開發人員傳統上難以作到的事情。

經過API發送POST beacon。這個很酷。

navigator.sendBeacon("/log", analyticsData);

XMLHttpRequest

編寫XMLHttpRequest不是一個好主意。我假設您不想浪費時間與IE或其餘瀏覽器做戰。如下是一些您能夠嘗試使用的polyfill或代碼:

<ol>
<li>window.fetch - A window.fetch JavaScript polyfill. (check also ky)</li>
<li>got - Simplified HTTP/HTTPS requests</li>
<li>microjs - list of ajax lib</li>
<li>more</li>
</ol>

Fragment Identifier

請記住,結尾帶有哈希標記的請求不會在http請求中傳遞。
例如,您在頁面中 http://github.com/awesome#hueitan

// Sending a request with a parameter url which contains current url
(new Image()).src = 'http://yourrequest.com?url=http://github.com/awesome#hueitan';

// actual request will be without #
(new Image()).src = 'http://yourrequest.com?url=http://github.com/awesome';

// Solution, encodeURIComponent(url):
(new Image()).src = 'http://yourrequest.com?url=' + encodeURIComponent('http://github.com/awesome#hueitan');

最大鏈接數

檢查瀏覽器請求鏈接的最大數量。browserscope
max number of connection

URI的組成部分

重要的是要知道SDK是否須要解析位置網址。

authority
                   __________|_________
                  /                    \
              userinfo                host                          resource
               __|___                ___|___                 __________|___________
              /      \              /       \               /                      \
         username  password     hostname    port     path & segment      query   fragment
           __|___   __|__    ______|______   |   __________|_________   ____|____   |
          /      \ /     \  /             \ / \ /                    \ /         \ / \
    foo://username:password@www.example.com:123/hello/world/there.html?name=ferret#foo
    \_/                     \ / \       \ /    \__________/ \     \__/
     |                       |   \       |           |       \      |
  scheme               subdomain  \     tld      directory    \   suffix
                                   \____/                      \___/
                                      |                          |
                                    domain                   filename

解析URI

這是使用本機URL()接口的簡單方法,但並不是全部瀏覽器都支持。它也不是一個標準。

var parser = new URL('http://github.com/hueitan');
parser.hostname; // => "github.com"

The DOM 'screateElement('a')can be used in browsers that don't have theURL()Interface yet.

var parser = document.createElement('a');
parser.href = "http://github.com/hueitan";
parser.hostname; // => "github.com"

調試

模擬多個域

要模擬多個域,無需註冊其餘域名。編輯操做系統的主機文件能夠解決這個問題。

$ sudo vim / etc / hosts

添加如下條目

# refer to localhost
127.0.0.1 publisher.net
127.0.0.1 sdk.net

每一個網站的網址均可以經過http://publisher.nethttp://sdk.net訪問

開發者工具

瀏覽器帶有針對每一個供應商的調試工具。顯然,這些工具可用於調試SDK JavaScript代碼- Chrome Developer Tools Safari Developer Tools Firebug。開發人員工具也簡稱爲DevTools。

DevTools爲Web開發人員提供了對瀏覽器及其Web應用程序內部的深刻訪問。使用DevTools能夠有效地跟蹤佈局問題,設置JavaScript斷點並得到有關代碼優化的看法。

控制檯日誌

爲了測試預期的輸出文本和其餘常規調試,Console Logs能夠經過瀏覽器API使用console.log()。格式化和輸出消息有多種類型。在此連接上討論了更多有關此內容:Console API

調試代理

調試代理使咱們能夠在開發中測試SDK。涉及的領域包括:

  • 調試流量
  • 修改Cookie
  • 檢查頭
  • 驗證緩存
  • 編輯http請求/響應
  • SSL代理
  • 調試Ajax等

這是您能夠嘗試的一些軟件

瀏覽器同步

經過同步文件更改和跨多個設備的交互,BrowserSync使調整和測試更快變得容易。它速度快,徹底免費。

跨多種設備測試SDK確實頗有幫助。徹底值得一試=)

調試Node.js應用

在Chrome開發者工具中調試SDK腳本。(須要Node.js v6.3.0 +)

$ node --inspect-brk [script.js]

技巧和竅門

Piggyback

在某些狀況下,有時不須要包括全部SDK源代碼。這是一個簡單的1x1像素請求的狀況-例如:當有人訪問「謝謝」(最後)頁面時發出請求。在這種狀況下,開發人員能夠包括具備(url)連接的圖像文件,如如下代碼段所述。

<img height="1" width="1" alt="" style="display:none" src="https://yourUrlLink.com/t?timestamp=1234567890&type=page1&currency=USD&noscript=1" />

頁面可見性API

有時,SDK但願檢測用戶是否關注特定頁面。這些polyfills visibly.jsvisibilityjs能夠幫助實現這一點。

文檔引薦來源

document.referrer可用於獲取當前或前一頁面的URL。可是,建議記住該引薦來源網址爲「瀏覽器引薦來源網址」,而不是「人類已知引薦來源網址」。用戶單擊瀏覽器後退按鈕(例如pageA-> pageB-> pageC->(後退按鈕)pageB)的狀況下,當前pageB的引薦來源網址是pageA,而不是pageC。

控制檯Polyfill

如下不是特殊的polyfill。它只是確保調用console.logAPI不會向客戶端拋出錯誤事件。

if (typeof console === "undefined") {
    var f = function() {};
    console = {
        log: f,
        debug: f,
        error: f,
        info: f
    };
}

EncodeURI或EncodeURIComponent

瞭解escape() encodeURI() encodeURIComponent() here

值得一提的是,使用encodeURI()encodeURIComponent()有11個字符的不一樣。這些字符是:#$&+,/:; =?@ more discussion

您可能不須要JQUERY

如標題所述,您可能不須要jquery。若是您正在尋找一些實用程序代碼-AJAX EFFECTS, ELEMENTS, EVENTS, UTILS,這真的頗有用

你不須要jQuery

經過擁抱和理解現代Web API並發現各類有向庫來幫助您填補空白,從jQuery的鏈中解放本身。

http://blog.garstasio.com/you-dont-need-jquery/
有用的提示

  1. 選擇元素
  2. DOM操做

使用回調加載腳本

這相似於帶有附加回調事件的異步腳本加載

function loadScript(url, callback) {
  var script = document.createElement('script');
  script.async = true;
  script.src = url;

  var entry = document.getElementsByTagName('script')[0];
  entry.parentNode.insertBefore(script, entry);

  script.onload = script.onreadystatechange = function () {
    var rdyState = script.readyState;

    if (!rdyState || /complete|loaded/.test(script.readyState)) {
      callback();

      // detach the event handler to avoid memory leaks in IE (http://mng.bz/W8fx)
      script.onload = null;
      script.onreadystatechange = null;
    }
  };
}

一次性函數

功能的實現 once

一般,有些功能只須要運行一次便可。這些功能一般以事件偵聽器的形式出現,可能難以管理。固然,若是它們易於管理,建議刪除監聽器。如下是使之成爲可能的JavaScript函數!
// Copy from DWB
// http://davidwalsh.name/javascript-once
function once(fn, context) {
    var result;

    return function() {
        if(fn) {
            result = fn.apply(context || this, arguments);
            fn = null;
        }

        return result;
    };
}

// Usage
var canOnlyFireOnce = once(function() {
    console.log('Fired!');
});

canOnlyFireOnce(); // "Fired!"
canOnlyFireOnce(); // nada. nothing.

像素比密度

爲了在開發移動網絡時更好地理解像素,比率,密度,尺寸等術語,如下連接能夠提供更多看法:

設備像素比率-移動Web開發
移動設備像素-移動Web開發

獲取樣式值

得到內聯樣式的價值

<span id="black" style="color: black"> This is black color span </span>
<script>
    document.getElementById('black').style.color; // => black
</script>

得到真實風格的價值

<style>
#black {
    color: red !important;
}
</style>

<span id="black" style="color: black"> This is black color span </span>

<script>
    document.getElementById('black').style.color; // => black

    // real
    var black = document.getElementById('black');
    window.getComputedStyle(black, null).getPropertyValue('color'); // => rgb(255, 0, 0)
</script>

參考:https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle

檢查視口中的元素

還有更多的在這裏

函數 isElementInViewport(el){

function isElementInViewport (el) {

    //special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }

    var rect = el.getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
        rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
    );
}

檢查元素是否可見

var isVisible = function(b) {
    var a = window.getComputedStyle(b);
    return 0 === a.getPropertyValue("opacity") || "none" === a.getPropertyValue("display") || "hidden" === a.getPropertyValue("visibility") || 0 === parseInt(b.style.opacity, 10) || "none" === b.style.display || "hidden" === b.style.visibility ? false : true;
}

var element = document.getElementById('box');
isVisible(element); // => false or true
Get Viewport Size

獲取視口大小

var getViewportSize = function() {
    try {
        var doc = top.document.documentElement
          , g = (e = top.document.body) && top.document.clientWidth && top.document.clientHeight;
    } catch (e) {
        var doc = document.documentElement
          , g = (e = document.body) && document.clientWidth && document.clientHeight;
    }
    var vp = [];
    doc && doc.clientWidth && doc.clientHeight && ("CSS1Compat" === document.compatMode || !g) ? vp = [doc.clientWidth, doc.clientHeight] : g && (vp = [doc.clientWidth, doc.clientHeight]);
    return vp;
}

// return as array [viewport_width, viewport_height]

用戶追蹤

假設Evil廣告公司想要跟蹤用戶,Evil能夠經過使用指紋很好地生成個性化的惟一hash。可是,Evil公司使用Cookie並提供Opt out解決方案。

Opt-out
DIGITAL ADVERTISING ALLIANCE, POWERED BY YOURADCHOICES提供支持的數字廣告聯盟提供了一種工具,能夠幫助任何人從全部參與公司opt-out。

WTF

Referrer拼寫錯誤

爲何HTTP請求頭具備字段名稱有趣的事實refererreferrer

根據維基百科

misspelling of referrer起源於計算機科學家Phillip Hallam-Baker提出的將該領域歸入HTTP規範的原始建議。在將拼寫錯誤歸入Request for Comments標準文件RFC 1945時已陷入僵局; 該文檔的合著者Roy Fielding指出,Unix spell checker這段時間的標準既不承認「Referer」,也不拼寫錯誤的「referer」 。此後,在討論HTTP引薦來源網址時,「Referer」已成爲業界普遍使用的拼寫形式;不過,拼寫錯誤的用法並不廣泛,由於在某些網絡規範(例如 Document Object Model)中使用了正確的拼寫「referrer」 。

模板

本指南提供了用於構建SDK的模板和樣板。

TEMPLATE.md

書/推薦閱讀

Third-Party JavaScript
JQuery Plugin
LightningJS
(靈感來自_http-api-design_)

相關文章
相關標籤/搜索