js知识杂记

澳门新葡亰手机版 1

原文地址:

  1. 将整数每三位添加一个逗号

介绍

本指南为您介绍了在台式机和移动网络在不同的平台和浏览器( < 99.99
%我可能会跳过一些浏览器)开发的JavaScript SDK
,对于那些非浏览器开发的支持(硬件,嵌入式,节点/ IO JS
)被排除在本文档之外,在未来予以考虑。
因为我没有找到一个关于设计JavaScript
SDK的比较好的文档,所以我在这里收集并记下了我个人的经验。这份文档已经写了好几个月,有一点我们需要知道,JavaScript的SDK-设计不仅仅是设计SDK本身,这也是有关于开发者与设备浏览器中间的联系。我们写的越多,越会更多的思考我们真正关心的是不同平台和浏览器之间的性能和兼容问题。你可以根据情况自由的更改或者完全放弃我在文章里列出的建议。

什么是SDK

我知道它确实是很普通很常见。一般是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合。通常一个SDK包含一个或多个API,编程工具和档。

var num = 10000000;
var fun = (num) => {
    let rgx = /(d+)(d{3})/g,val = num.toString();
    if (val.length >= 5) { // 根据正则要求,将整数部分用逗号每三位分隔
        while (rgx.test(val)) { 
            val = val.replace(rgx, '$1' + ',' + '$2'); 
        } 
    }
    return val;
}
fun(num);

设计理念

这取决于你的SDK用来干什么的,但是它必须具备原生的,短,速度快,干净,可读可测试特性。用原生javascript写,不要用像Livescript,
Coffeescript,
Typescript和其它的编译语言。必须有更好的方法来编写自己的javascript原生代码比别人更快。请不要在你的SDK里用JQuery,除非它非常有必要。你可以使用其它的类似jQuery的库,譬如zetpo.js,用于DOM操作,如果你需要用到HTTP
Ajax请求,可以使用另外一种轻量库像window.fetch。

每一次的SDK版本发布,确保它不仅适用于旧版本而且适应于未来的新版本。所以,记得为你的SDK写文档,代码要写注释,同时做好单元测试和用户场景测试。

或者:

适应范围

基于《Third-Party
JavaScript》这本书。在何种情况下,你应该为你的应用设计一个JavaScript
SDK?

  • 嵌入式组件 – 嵌入在出发布者的网页中的交互式应用程序(Disqus, Google
    Maps, Facebook Widget)。
  • 分析与数据 – 搜集网站访问者以及其与网站互动的数据信息。(GA, Flurry,
    Mixpanel)
  • web服务API封装
    -对于发展与外部Web服务通信的客户端应用程序。(Facebook的图形API)

在什么情况下,我们应该在JavaScript环境中使用SDK呢?大家可以想想还有其它情没?

function commafy(num){
    return num && num
        .toString()
        .replace(/(d)(?=(d{3})+.)/g, function($1, $2){
            return $2 + ',';
        });
  }

引入SDK

建议你采用异步加载脚本的方式。我们要优化网站的用户体验,所以不希望我们的SDK库阻塞其它主要进程。
异步加载

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

在新的现代浏览器(chrome)你可以使用

<script asyncsrc="http://xxx.com/sdk.js"></script>

传统加载方法

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

对比:

下面是简单的图形显示异步加载和传统同步加载方式之间的区别

异步:

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

同步:

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

异步和延迟脚本执行解释

澳门新葡亰手机版 1

异步的问题

当你使用异步加载的时候,将会出现,页面中的函数无法正常调用SDK方法的情况。

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

结果会报undefined错误,因为SDKName()在脚本加载之前执行了。所以我们应该使用点技巧让脚本正确执行。把事件保存在SDKName.q数组里,SDK初始化的时候执行SDKName.q。

<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://xxx.com/sdk.js';
  var x =document.getElementsByTagName('script')[0];
  x.parentNode.insertBefore(s, x);
})();
  // execute your script immediately hereSDKName('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://xxx.com/sdk.js';
  var x =document.getElementsByTagName('script')[0];
  x.parentNode.insertBefore(s, x);
})();
// execute your script immediately hereSDKName.push(['some arguments']);
</script>

其他方式

还有其它不同方式加载脚本

Import in 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   informationfunctionmodule(name,imports,mod) {
// record module informationwindow.console.log('found module '+name);
modules[name] = {name:name, imports: imports, mod: mod};
// trigger loading of import dependenciesfor (var imp in imports) loadModule(imports[imp]);
// check whether this was the last module to be loaded// in a given dependency grouploadedModule(name);
}

// function loadModule// function   loadedModulewindow.module=module;
})();
  1. 通用的事件侦听器函数

SDK版本

避免使用自己的特例作为版本名称像

标识-v<时间戳>.js 标识-v<日期>.js 标识-v1-v2.js
它可能导致使用SDK的开发者很混乱不知道哪个是最新版本。
使用 Semantic
Versioning (语义化版本规范)去定义SDK的版本号以”大.小.补丁”形式。
版本以v1.0.0 v1.5.0 v2.0.0的形式,会让使用者搜索跟踪日志文件更容易。
通常情况下,我们会有不同的方式去声明SDK的版本,这取决于具体针对的业务和设计。

使用查询字符串路径

http://xxx.com/sdk.js?v=1.0.0

使用文件夹命名

http://xxx.com/v1.0.0/sdk.js

使用主机名或者子域名

http://v1.xxx.com/sdk.js

为了以后版本的升级迭代,建议用stable unstable alpha latest experimental
版本。

http://xxx.com/sdk-stable.js

http://xxx.com/sdk-unstable.js


http://xxx.com/sdk-alpha.js


http://xxx.com/sdk-latest.js


http://xxx.com/sdk-experimental.js

更新日志文件

你应该注意到如果你升级你的SDK却没通知用户,用户不会知道。记得写更新日志来记录无论是主要、次要甚至bug修复等修改。这将是一个好的开发经验,我们能快速的跟踪到SDK某个API的修改。所以保持更新日志
– Keep a Changelog, Github Repo

每个版本的日志应该有:

[新增] 新功能.
[更新] 修改现有的更能
[废弃] 在即将发布的版本中删除某个功能.
[删除] 在这个版本中删除弃用的功能.
[修正] bug修复
[安全] 邀请用户对安全进行升级

var event = (el,type,fn) => {
    if (el.addEventListener) {
        el.addEventListener(type,fn);
    }else{
        el.attachEvent(type,fn);
    }
}

命名空间

在你的SDK里只定义一个全局命名空间,并且不要用太过通用的名字,避免和其它类库名发生冲突。SDK的主体用(function
() { …
})()包裹。这种做法越来越普遍的应用于各种流行的javascript类库譬如jQuery,Node.js等等。这种创建私有的命名空间的技术很重要,有助于避免各种类库之间命名的冲突。

为了避免命名空间冲突

学习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的做法,支持通过给地址传递参数定义命名空间。

<script src="http://your_domain/sdk?namespace=yourcompany"></script>
  1. 下面这行代码的作用:

存储机制

cookie

使用cookie就会面临复杂的作用域范围问题,而且涉及到子域和路径问题。

比如在路径 path=/下, cookie first=value1 在域名 ,
另外一个 cookie second=value2 在域名 

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

有个 cookie first=value1 在 , cookie second=value2
在  另外一个 cookie third=value3
在 ,

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 可读写

检查第三方cookie仅仅通过客户端js是办不到的,需要服务器端配合。

写 读 删除 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

js写不了session,需要服务器端写。
一个页面的session会一直保存着只要浏览器是开着的即使页面重新加载。打开一个新页面会生成一个新的session。子窗口会和父窗口共享一个session。

[].forEach.call($$("*"),function(a){a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)})

LocalStorage

存储的数据没有时间限制。存储数据量大(至少5MB)并且信息不会传送到服务器。而且同一个域名从http和https访问localStorage是不共享的。你可以在你的网页上创建个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;
 }
};

这段代码只是首先获取了所有的页面元素,然后使用一个不同的颜色为它们添加了一个1ps的边框。
详细说明:从一行代码里面学点JavaScript

SessionStorage

针对一个 session 的数据存储(当用户关闭浏览器窗口后,数据会被删除).

检查 SessionStorage 可写

var checkCanSessionStorage = function() {
var mod = 'modernizr';
try {
sessionStorage.setItem(mod, mod);
sessionStorage.removeItem(mod);
return true;
} catch (e) {
return false;
}
}
  1. js延迟加载的方式

事件

在客户端浏览器有很多事件加载、卸载、绑定等会存在兼容问题。polyfills是个解决不同平台事件绑定的不错的解决方案。

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 – 所有DOM解析完会触发整个事件
不需要等到样式表、图片等加载完。

load 页面完整加载。

  • 使用script标签的defer和async属性。
  • 动态创建DOM方式
  • 按需异步载入js:通过ajax下载js脚本,动态添加script标签

Message Event

这里是实现iframe和父页面之间的数据通信, 这里有文档 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);

发送的数据是字符串, 对于使用更高级的json字符串. 不是所有的浏览器对支持
Structured Clone Algorithm on the parameter, (参数的结构化克隆)。

  1. Ajax 是什么? 如何创建一个Ajax?

Orientation Change 横屏事件

检测设备横屏

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;

ajax的全称:Asynchronous Javascript And XML。
异步传输+js+xml。
(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
(3)设置响应HTTP请求状态变化的函数
(4)发送HTTP请求
(5)获取异步调用返回的数据
(6)使用JavaScript和DOM实现局部刷新

Request

我们的SDK和服务器之间通信通过Ajax请求,因为我们知道我们可以使用jQuery的Ajax
方法。但是有更好的方案来实现它。

图片预加载

通过创建一个Image对象预加载一张图片。为了防止浏览器缓存记得加上时间戳。

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

要注意通过GET方式传输参数最大长度是2048个字节(取决于不同的浏览器和服务器)。这里要做一些处理如果超过长度。

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

你可能遇到问题在使用encodeURI 还是
encodeURIComponent的时候,最好理解它们的区别。 See below.

对于图像加载成功/错误回调

var img = new Image();
img.src = 'http://xxxxx.com/collect?id=1111';
img.onload = successCallback;
img.onerror = errorCallback;
  1. Ajax 解决浏览器缓存问题

单个 Post 请求

普通表单发送一个对应元素和值

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

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

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

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

form.submit();

1、在ajax发送请求前加上
anyAjaxObj.setRequestHeader(“If-Modified-Since”,”0″)。
2、在ajax发送请求前加上
anyAjaxObj.setRequestHeader(“Cache-Control”,”no-cache”)。
3、在URL后面加上一个随机数: “fresh=” + Math.random();。
4、在URL后面加上时间戳:”nowtime=” + new Date().getTime();。
5、如果是使用jQuery,直接这样就可以了
$.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。

多个 Post 请求

服务通常比较复杂,需要通过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'});
  1. 如何解决跨域

Iframe

当你在需要在页面中生成内容时候,你可以通过iframe嵌入。

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

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

body.appendChild(iframe);

清除iframe的边框,内部margin值。

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

iframe中插入html

<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>
  • jsonp
  • iframe

jsonp

这种情况下,你的服务器需要响应JavaScript
代码,并让浏览器执行它,仅仅通过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请求。
  • JSONP 缺少错误处理机制, 意味着你不能检测代码是否404还是500等状态。
  • JSONP 请求是异步的。
  • 当心 CSRF 攻击。
  • 跨域通信。脚本响应端(服务器端)不需要关心CORS。

关于jsonp:

XMLHttpRequest

自己写XMLHttpRequest不是个好主意,因为你要浪费很多时间去做IE或者其它浏览器的兼容。这里提供一些现成的解决方案供大家参考:

1 – window.fetch – A window.fetch JavaScript polyfill.
2 – got – Simplified HTTP/HTTPS requests
3 – microjs – list of ajax lib
4 – more

(1)首先是利用script标签的src属性来实现跨域;
(2)通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信;
(3)由于使用script标签的src属性,因此只支持get方法