1

本指南提供了开发JavaScriptSDK的简介。

描述SDK的最好的一句话是:“ SDK是弥合用户和(浏览器)计算机之间差距的连接。”

通过使用本指南,SDK将能够在浏览器,台式机,移动网络和各种其他能够运行JavaScript的平台上运行。

本文的目标受众暂时不包括非浏览器环境,例如硬件,嵌入式和Node.js。但是,将来会添加一些材料来覆盖这些区域。

什么是SDK

答案显而易见,但是这里还是要重申一下。

“ 软件开发工具包的简称,一种代码包,使开发人员能够为特定平台开发应用程序。SDK通常包括一个或多个API,编程工具和文档。”

设计哲学

根据SDK服务的目的和用途,常见的共享特征包括但不限于本地的,简短的,快速的,简洁的,可读的和可测试的。

广泛采用的良好做法是使用原生JavaScript编写SDK。不建议使用编译为JavaScript的语言,例如LiveScript,CoffeeScript,TypeScript等。

还建议不要在SDK开发中使用诸如jQuery之类的库。如果非要用于DOM操作,还有其他类似jQuery的库如zepto.js 等可供选择。

如果有HTTP ajax请求要求,则用原生方法如window.fetch。它更轻巧,并在不断增长的平台中得到支持。

向后兼容性至关重要。每个新的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缺乏错误处理,这意味着您无法在响应状态代码404、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_)


seasonley
607 声望693 粉丝

一切皆数据