本指南提供了开发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成功的关键。
范围
设计JavaScript SDK时需要考虑以下三个用例:
- 嵌入式控件 -嵌入到发布者网页上的小型交互式应用程序(Disqus,Google Maps,Facebook 窗体控件)
- 分析和指标 -用于收集有关访问者及其与发布者网站(GA,Flurry,Mixpanel)互动的数据
- 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
当使用subdomain
和path
时,使用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=value1
in domainhttp://github.com
, cookiesecond=value2
in domain pathhttp://github.com/path1
and cookiethird=value3
in 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
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的更多信息
- JSONP仅在GET HTTP请求中起作用。
- JSONP缺乏错误处理,这意味着您无法在响应状态代码404、500等中检测到案例。
- JSONP请求始终是异步的。
- 当心CSRF攻击。
- 跨域通信。脚本响应端(服务器端)不需要关心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
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.net
和http://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¤cy=USD&noscript=1" />
页面可见性API
有时,SDK希望检测用户是否关注特定页面。这些polyfills visibly.js和visibilityjs可以帮助实现这一点。
文档引荐来源
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/
有用的提示
- 选择元素
- 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.
像素比密度
为了在开发移动网络时更好地理解像素,比率,密度,尺寸等术语,以下链接可以提供更多见解:
获取样式值
获得内联样式的价值
<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请求头具有字段名称有趣的事实referer
不referrer
根据维基百科
它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的模板和样板。
书/推荐阅读
Third-Party JavaScript
JQuery Plugin
LightningJS
(灵感来自_http-api-design_)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。