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_)

相关文章
相关标签/搜索