您现在的位置是:网站首页> HTML&JS

浏览器JS常用技术收集

  • HTML&JS
  • 2024-12-28
  • 269人已阅读
摘要

浏览器JS常用技术收集


一个PWA的例子包含PWA的所有功能

wasm相关开发与应用

Go开发wasm资料收集

JS获得buffer组件Uint8Array的例子


chrome的UDP权限

js复制文本到粘贴板方法

一般方法

复制文本到粘贴板的三种实现方法(chrome)


postMessage 用法(可以给iframe传值)跨域

URL参数加密详解

Js实现web应用浏览器通知

gps google 百度坐标相互转换

阻止 iPhone 视频自动全屏

JS之判断json对象中是否含有某个key值

video JS 循环播放

不同坐标系经纬度转换(天地图,高德,百度,腾讯)

《学习总结》天地图

基于Web Audio API实现音频可视化效果

使用import取代require

浏览器中使用import

JS异步变同步

WebSocket之JS发送二进制

浏览器串口编程

外部浏览器点击跳转微信




一个PWA的例子包含PWA的所有功能

这个示例是一个简单的天气应用,用户可以查询指定城市的天气情况。


1.文件结构:


weather-app/

  ├── index.html

  ├── manifest.json

  ├── service-worker.js

  ├── js/

  │   └── app.js

  ├── css/

  │   └── style.css

  └── images/

      ├── icon-128.png

      ├── icon-144.png

      ├── icon-152.png

      ├── icon-192.png

      ├── icon-256.png

      └── icon-512.png


2.index.html:


<!DOCTYPE html>

<html>

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Weather App</title>

  <link rel="manifest" href="manifest.json">

  <link rel="stylesheet" href="css/style.css">

</head>

<body>

  <h1>Weather App</h1>

  <input type="text" id="city" placeholder="Enter city name">

  <button id="search">Search</button>

  <div id="weather-info"></div>


  <script src="js/app.js"></script>

</body>

</html>


3.manifest.json:


{

  "name": "Weather App",

  "short_name": "Weather",

  "start_url": "/index.html",

  "display": "standalone",

  "background_color": "#ffffff",

  "theme_color": "#2196f3",

  "icons": [

    {

      "src": "images/icon-128.png",

      "sizes": "128x128",

      "type": "image/png"

    },

    {

      "src": "images/icon-144.png",

      "sizes": "144x144",

      "type": "image/png"

    },

    {

      "src": "images/icon-152.png",

      "sizes": "152x152",

      "type": "image/png"

    },

    {

      "src": "images/icon-192.png",

      "sizes": "192x192",

      "type": "image/png"

    },

    {

      "src": "images/icon-256.png",

      "sizes": "256x256",

      "type": "image/png"

    },

    {

      "src": "images/icon-512.png",

      "sizes": "512x512",

      "type": "image/png"

    }

  ]

}


4.service-worker.js:


const CACHE_NAME = 'weather-app-cache-v1';

const urlsToCache = [

  '/',

  '/index.html',

  '/css/style.css',

  '/js/app.js',

  '/manifest.json',

  '/images/icon-128.png',

  '/images/icon-144.png',

  '/images/icon-152.png',

  '/images/icon-192.png',

  '/images/icon-256.png',

  '/images/icon-512.png'

];


self.addEventListener('install', function(event) {

  event.waitUntil(

    caches.open(CACHE_NAME)

      .then(function(cache) {

        console.log('Opened cache');

        return cache.addAll(urlsToCache);

      })

  );

});



self.addEventListener('fetch', function(event) {

  event.respondWith(

    caches.match(event.request)

      .then(function(response) {

        // 如果缓存中存在匹配的响应,直接返回缓存的响应

        if (response) {

          return response;

        }


        // 如果缓存中没有匹配的响应,发起网络请求

        return fetch(event.request)

          .then(function(response) {

            // 克隆响应,因为响应是一个流,只能使用一次

            const responseToCache = response.clone();


            // 打开缓存

            caches.open(CACHE_NAME)

              .then(function(cache) {

                // 将请求的响应存入缓存

                cache.put(event.request, responseToCache);

              });


            // 返回原始的响应

            return response;

          });

      })

  );

});


5.js/app.js


if ('serviceWorker' in navigator) {

  window.addEventListener('load', function() {

    navigator.serviceWorker.register('/service-worker.js')

      .then(function(registration) {

        console.log('ServiceWorker registration successful with scope: ', registration.scope);

      }, function(err) {

        console.log('ServiceWorker registration failed: ', err);

      });

  });

}


const searchButton = document.getElementById('search');

const cityInput = document.getElementById('city');

const weatherInfo = document.getElementById('weather-info');


searchButton.addEventListener('click', function() {

  const city = cityInput.value;

  const apiKey = 'YOUR_API_KEY';

  const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}`;


  fetch(apiUrl)

    .then(function(response) {

      return response.json();

    })

    .then(function(data) {

      const temperature = Math.round(data.main.temp - 273.15);

      const description = data.weather[0].description;

      weatherInfo.innerHTML = `Temperature: ${temperature}°C<br>Description: ${description}`;

    })

    .catch(function(error) {

      console.log('Error fetching weather data:', error);

    });

});



6.css/style.css:


body {

  font-family: Arial, sans-serif;

  text-align: center;

}


h1 {

  color: #2196f3;

}


input, button {

  margin: 10px;

  padding: 5px;

}


这个PWA示例包含了以下功能:

Web App Manifest (manifest.json):

定义了应用的名称、图标和启动配置。

允许将应用添加到主屏幕,并以独立模式运行。

Service Worker (service-worker.js):

拦截网络请求并从缓存中返回响应,实现离线访问。

在安装阶段缓存了应用的关键资源。

响应式设计:

使用viewport元标签确保应用在不同设备上正常显示。

在线和离线功能:

当在线时,应用从OpenWeatherMap API获取天气数据。

当离线时,Service Worker从缓存中返回已缓存的资源,确保应用可以离线访问。

添加到主屏幕:

Web App Manifest中的配置允许用户将应用添加到设备的主屏幕。

推送通知(未在此示例中实现):

PWA还支持推送通知,可以向用户发送及时的更新和提醒。

这个示例展示了PWA的核心功能,包括Web App Manifest、Service Worker、离线访问和添加到主屏幕等。你可以在此基础上进一步扩展和优化应用,以提供更好的用户体验。




JS获得buffer组件Uint8Array的例子

function decodeImage() {

var oReq = new XMLHttpRequest();

oReq.open("GET", "demo_encode.png", true);

oReq.responseType = "arraybuffer";

oReq.onload = function (oEvent) {

var arrayBuffer = oReq.response; // 注意:不是oReq.responseText

if (arrayBuffer) {

var byteArray = new Uint8Array(arrayBuffer);

var decodeData = decode('KEY_HERE', byteArray);

document.getElementById("decodeJavaImage").src = URL.createObjectURL(

new Blob([decodeData], { type: 'image/png' })

);

}

};



chrome的UDP权限

chrome 应用中的 udp 需要什么权限?

我得到错误:

Unchecked runtime.lastError while running sockets.udp.bind: App does not have permission

但我找不到所需的许可。

我的 manifest.json 文件如下所示:

{

"name": "XX",

"description": "XX",

"version": "0.0.1",

"manifest_version": 2,

"app": {

    "background": {

        "scripts": ["main.js"]

    }

},

"icons": {

    "16": "icon-16.png",

    "128": "icon-128.png"

},

"sockets": {

    "udp": {

        "send": "*"

    }

},

"permissions": [

    "fullscreen", "alwaysOnTopWindows", "videoCapture", "storage", "browser"

]    


}

最佳答案

你需要请求绑定(bind)权限

"udp": {

      "send": ["*"],

      "bind": ["*"]

}



一般方法

let copyInput = document.createElement('input');//创建input元素

                document.body.appendChild(copyInput);//向页面底部追加输入框

                copyInput.setAttribute('value', '<%=share%>');//添加属性,将url赋值给input元素的value属性

                copyInput.select();//选择input元素

                document.execCommand("Copy");//执行复制命令

                copyInput.remove();//删除动态创建的节点

                layer.msg("分享链接已经复制!");//弹出提示信息,不同组件可能存在写法不同

            //复制之后再删除元素,否则无法成功赋值

                return;



复制文本到粘贴板的三种实现方法(chrome)

一.采用 textarea存放文本内容

// 将特定字符串拷贝到剪贴板

export function copyToBoard (text) {

    try {

        const input = document.createElement('textarea')

        input.value = text

        document.body.appendChild(input)

        input.focus()

        input.select()

        document.execCommand('copy')

        document.body.removeChild(input)

    } catch (err) {

    // ignore

    }

}


二.采用矩形框存放文本内容

export function copyBigDataToBoard (copyString) {

    try {

        // 移除矩形框

        window.getSelection().removeAllRanges()

        // 创建选中范围

        const range = document.createRange()

        // 创建div元素存放文本

        const divNode = document.createElement('div')

        divNode.innerHTML = copyString

        document.body.appendChild(divNode)

        // 选中div元素中的所有文本

        range.selectNode(divNode)

        window.getSelection().addRange(range)

        // 复制文本到粘贴板

        document.execCommand('copy')

        // 移除矩形框

        window.getSelection().removeAllRanges()

        document.body.removeChild(divNode)

    } catch (err) {

    // ignore

    }


}


三.采用navigator.clipboard.writeText( )复制文本内容

navigator.clipboard.writeText( 'hello clipboard')



postMessage 用法(可以给iframe传值)跨域

postMessage(data,origin)方法接受两个参数:

let iframeWindow = document.getElementById('myframe').contentWindow;

iframeWindow.postMessage('aaa', 'http://www.wrox.com');

1. data: 

- 要传输的数据,推荐使用字符串格式,其他格式的浏览器的兼容性不好

- 如果要传输结构化数据,可以通过JSON.stringify处理,接收时用JSON.parse转换回来

2. origin: 

- 指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写

- 如果不想限制接收目标源:可以传 '*'

- 如果目标与当前窗口同源:可以传 '/'

接收消息

window.addEventListener('message',(event)=>{

// 判断源路径是否来自预期发生者

if(event.origin.includes('http://www.wrox.com')){

// 获取传过来的数据

console.log(event.data)

// 再传回去一条消息

event.source.postMessage('已收到消息', 'p2p.wrox.com')

}

})

// event包含3个参数

- event.data: 接收到的数据

- event.origin: 发送消息的文档源

- event.source: 发生消息的文档中window对象的代理

点击发送消息



URL参数加密详解

一、URL参数加密JS

在前端,可以使用JavaScript对URL参数进行加密。首先,将需要加密的参数转成字符串,然后将字符串转成Unicode编码。可以使用encodeURIComponent()函数实现这一过程。


function encryptParam(param) {

  let str = JSON.stringify(param);

  let unicodeStr = encodeURIComponent(str);

  return unicodeStr;

}

这里使用JSON.stringify()来转换参数为字符串,再使用encodeURIComponent()对字符串进行编码。这样,参数就被成功加密了。


二、URL参数加密了怎么爬取

如果URL参数被加密了,那么在爬取的时候会带来一定的难度。如果使用普通的爬虫程序来爬取,可能无法直接获取加密的参数值,这时需要使用其他的技巧。


一种方法是使用浏览器开发者工具进行调试,在控制台中查看网络请求的请求参数。另外,也可以使用浏览器插件或脚本进行模拟,直接模拟网站的操作,并获取加密的参数值。


但值得注意的是,网站对于参数的加密可能是为了保护用户隐私或防止爬虫,爬虫应遵守网络爬虫的规范,不得恶意爬取。


三、URL参数加密算法

URL参数加密算法多种多样,下面简单介绍一些常见的加密算法。


1、Base64编码

Base64是一种将二进制数据转换成ASCII字符的编码方式。可以将任意二进制数据转化为纯文本,常用于邮件传输、表示图片、传输json数据等场景。


// Base64加密

function encryptParam(param) {

  let str = JSON.stringify(param);

  let Base64Str = window.btoa(str);

  return Base64Str;

}


// Base64解密

function decryptParam(Base64Str) {

  let str = window.atob(Base64Str);

  let param = JSON.parse(str);

  return param;

}

2、MD5加密

MD5(Message-Digest Algorithm 5)是一种消息摘要算法,可以将任意消息变成128位的哈希值,通常用于密码存储等场景。


// MD5加密

function encryptParam(param) {

  let str = JSON.stringify(param);

  let md5Str = CryptoJS.MD5(str).toString();

  return md5Str;

}

3、AES加密

AES(Advanced Encryption Standard)是一种对称加密算法,常用于数据传输、数据存储、数字签名等场景。


// AES加密

function encryptParam(param) {

  let str = JSON.stringify(param);

  let key = CryptoJS.enc.Utf8.parse('1234567812345678');

  let iv = CryptoJS.enc.Utf8.parse('1234567812345678');

  let encrypted = CryptoJS.AES.encrypt(str, key, {

    iv: iv,

    mode: CryptoJS.mode.CBC,

    padding: CryptoJS.pad.Pkcs7

  });

  return encrypted.toString();

}


// AES解密

function decryptParam(encryptedStr) {

  let key = CryptoJS.enc.Utf8.parse('1234567812345678');

  let iv = CryptoJS.enc.Utf8.parse('1234567812345678');

  let decrypted = CryptoJS.AES.decrypt(encryptedStr, key, {

    iv: iv,

    mode: CryptoJS.mode.CBC,

    padding: CryptoJS.pad.Pkcs7

  });

  let str = decrypted.toString(CryptoJS.enc.Utf8);

  let param = JSON.parse(str);

  return param;

}

四、URL参数加密解密

在前面的例子中,我们展示了URL参数的加密方法,也展示了Base64、MD5、AES三种常见的加密算法。如果加密了参数,必须要有对应的解密方法才能够使用。


// 加密参数

let param = {

  name: '张三',

  age: 20

};

let encryptedParam = encryptParam(param);


// 解密参数

let decryptedParam = decryptParam(encryptedParam);

console.log(decryptedParam); // { name: '张三', age: 20 }

五、URL参数加密的作用

URL参数的加密可以增加数据传输的安全性,防止敏感参数被窃取或篡改。比如,用户在提交表单数据时,可以将表单数据进行加密,再将加密后的数据作为参数传递给后端接口。这就可以防止黑客通过篡改URL参数来修改用户的数据。


六、URL参数加密解密传递

在前后端分离的架构中,由于前端和后端是分离的,因此需要对URL参数进行加密解密传递。


通常的做法是,在前端对参数进行加密,然后将加密后的参数作为请求参数传递给后端。后端接收到请求后,再对参数进行解密,以便进行后续的处理。


七、URL参数加密都是用的什么

在实际的开发中,URL参数的加密可以使用多种方式,比如上文介绍的Base64编码、MD5加密、AES加密等。


八、前端get请求URL参数加密算法

在前端get请求中,URL参数的加密算法可以选择Base64编码、MD5加密、AES加密等。


下面是一个使用Base64编码的例子:


let param = {

  name: '张三',

  age: 20

};

let encryptedParam = encryptParam(param);

let url = `https://api.example.com?param=${encryptedParam}`;


fetch(url)

  .then(response => response.json())

  .then(data => {

    console.log(data);

  })

  .catch(error => {

    console.error(error);

  });

九、URL地址加密

除了URL参数的加密,还可以对整个URL地址进行加密。对URL地址加密可以增加访问该链接的安全性。


这里以Base64编码为例,使用window.btoa()函数对URL地址进行加密:


let url = 'https://www.example.com?param=value';

let encryptedUrl = window.btoa(url);


window.location.href = encryptedUrl;

对加密后的URL进行解密:


let encryptedUrl = window.location.href;

let url = window.atob(encryptedUrl);


console.log(url); // https://www.example.com?param=value

十、URL在线加密

如果不想在代码中使用URL参数加密算法,可以使用在线工具进行加密。


一种常见的URL参数加密工具是URL Encoder/Decoder,使用该工具可以对URL进行编码和解码操作。



Js实现web应用浏览器通知

Js实现web应用浏览器通知,需要实现通知的可以参考以下代码,为避免犹豫长英文造成排版的混乱,需在代码前加多余的站位文字,以免此类问题的产生

Notification.requestPermission().then(function(result) {

    if (result === 'denied') {

        console.log('拒绝显示系统通知');

        return;

    }

    if (result === 'default') {

        console.log('默认');

        return;

    }

    console.log('允许显示系统通知')

}); 


当用户允许显示系统通知后,就可以发送通知了,这时可以使用 Notification() 构造函数创建一个新通知,这个方法可以传入两个参数,具体如下:

title(必传)定义一个通知的标题,当它被触发时,它将显示在通知窗口的顶部。

options(可选)options 对象包含应用于通知的任何自定义设置选项,常用的选项有:

dir : 文字的方向;它的值可以是 auto(自动), ltr(从左到右), or rtl(从右到左);

lang: 指定通知中所使用的语言。这个字符串必须在 BCP 47 language tag 文档中是有效的;

body: 通知中额外显示的字符串;

tag: 赋予通知一个 ID,以便在必要的时候对通知进行刷新、替换或移除;

icon: 一个图片的 URL,将被用于显示通知的图标;


let notification = new Notification('有一条新通知', {

    dir: 'ltr',

    lang: 'zh-CN',

    body: '通知的正文内容:你的请假流程已批准',

    icon: 'http://localhost/coder/favicon.ico'

});


Notification 通知有四种事件,可以通过监听通知事件来执行不同的操作,具体事件如下:

Notification.onclick:对 click 事件的处理,每当用户点击通知时被触发;

Notification.onshow:对 show 事件的处理,当通知显示的时候被触发;

Notification.onerror:对 error 事件的处理,每当通知遇到错误时被触发;

Notification.onclose:对 close 事件的处理,当用户关闭通知时被触发。


let notification = new Notification('有一条新通知', {

    dir: 'ltr',

    lang: 'zh-CN',

    body: '通知的正文内容:你的请假流程已批准',

    icon: 'http://localhost/coder/favicon.ico'

});


// 监听通知显示事件

notification.onshow = () => console.log('通知已显示');


// 监听通知点击事件

notification.onclick = () => console.log('通知被点击');


// 监听通知被关闭事件

notification.onclose = () => console.log('通知被关闭');


// 监听通知错误事件

notification.onerror = () => console.log('通知出现错误');


发送 Notification 通知代码


function sendNotification(title, body, icon, callback) {

    // 先检查浏览器是否支持

    if (!('Notification' in window)) {

        // IE浏览器不支持发送Notification通知!

        return;

    }

    

    if (Notification.permission === 'denied') {

        // 如果用户已拒绝显示通知

        return;

    }

    

    if (Notification.permission === 'granted') {

        //用户已授权,直接发送通知

        notify();

    } else {

        // 默认,先向用户询问是否允许显示通知

        Notification.requestPermission(function(permission) {

            // 如果用户同意,就可以直接发送通知

            if (permission === 'granted') {

                notify();

            }

        });

    }


    function notify() {

        let notification = new Notification(title, {

            icon: icon,

            body: body

        });

        notification.onclick = function() {

            callback && callback();

            console.log('单击通知框')

        }

        notification.onclose = function() {

            console.log('关闭通知框');

        };

    }

}

下面来测试一下:


sendNotification('下班通知', '今天周五,还有十分钟下班', 'http://localhost/coder/favicon.ico');



gps google 百度坐标相互转换

我之前写过两篇文章,一篇是《利用html5获取经纬度并且在百度地图中显示位置》,在那篇文章中我因为对百度地图坐标转换不熟悉,所以做出百度地图不准确这个结论。不过后来我发现这是因为百度地图坐标转换的问题,所以我又写了《关于百度地图API的地图坐标转换问题》,在文中我修复了关于坐标转换而出现偏差的bug,不过后来查看了下百度官网的一些代码,索性整理了下百度地图的坐标转换接口。

       其实这里面无非是两个函数而已,这里记录一下:

       google坐标转换百度坐标 

       BMap.Convertor.translate(ggPoint,2,translateCallback);     //GCJ-02坐标转成百度坐标


       GPS坐标转换百度坐标 

        BMap.Convertor.translate(gpsPoint,0,translateCallback);     //真实经纬度转成百度坐标


       百度坐标转换转换GPS坐标

        这是一个比较难的坐标转换,因为百度经过了加密,并没有公开转换的方法,这里提供一种替代的方法: 

       百度坐标和GPS坐标转换在很近的距离时偏差非常接近。

       假设你有百度坐标:x1=116.397428,y1=39.90923

       把这个坐标当成GPS坐标,通过接口获得他的百度坐标:x2=116.41004950566,y2=39.916979519873

       通过计算就可以得到GPS的坐标:

       x = 2x1-x2,y = 2y1-y2

      x=116.38480649434001

      y=39.901480480127


阻止 iPhone 视频自动全屏

最近一年都在做直播,遭video 全屏的问题困扰了很久。

下面将阻止 ios视频自动全屏的办法写出来。

添加 playsinline 和 webkit-playsinline="true";

例如:

<video id="video" playsinline  webkit-playsinline ></video>

备注:

  1、此方法仅仅针对 ios有效(safari、微信都有效)。

  2、playsinline属性针对ios 10以上系统有效。

  3、webkit-playsinline属性针对ios10以下系统有效。

  4、安卓目前没有办法(机型不一样自带浏览器也效果也不一样,有一部分浏览器是可以小窗播放的。)

--------------------

更新 2017-11-6

阻止安卓版微信 视频自动全屏的代码。

添加 x5-playsinline

例如:

<video id="video" x5-playsinline ></video>

备注:此方法仅仅针对安卓版微信有效。


JS之判断json对象中是否含有某个key值

var json = {"key1":"val1","key2":"val2","key3":"val3"};

if(json.hasOwnProperty("key1")){

console.log(json["key1]);

}


video JS 循环播放

<p style="text-align: center;">

    <strong><span style="font-size: 18px;">播放视频来自外站本站不存储视频资源</span></strong>

</p>

<video id="player" src="http://v.nrzj.vip/video.php?_t=0.1844860897601126" controls="controls" width="100%" height="400px" autoplay="autoplay"></video><script>var video = document.getElementById('player'); 

   video.addEventListener('ended', function(e) {

      console.log('视频播放完了')

     Play();

    });

video.addEventListener('error', function(e) {

      console.log('视频出错了')

      console.log(e);

       setTimeout(() =>{

        Play();

    }, 1000);

        

    })


  function Play()

  {

    

    console.log(video.src)     // http://127.0.0.1:8001/test.mp4   绝对地址,DOM 中是相对地址

    // video.src = 'test-2.mp4'   // 直接替换掉了原来的视频src

    setTimeout(() =>{

      video.src = 'http://v.nrzj.vip/video.php?_t='+Math.random();  // 播放到第 30s 的时候,自动切换视频

       video.play();

    }, 100);

    }</script>


不同坐标系经纬度转换(天地图,高德,百度,腾讯)

1.js转换代码

var x_pi = 3.14159265358979324 *3000.0 / 180.0;

var pi = 3.14159265358979324;

var a = 6378245.0;


var ee = 0.00669342162296594323;


  function transformLon(x, y) {


         var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));


          ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;


         ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;


        ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;


         return ret;


     };


     


    function transformLat(x, y) {


         var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));


         ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;


         ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;


         ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;


         return ret;


    }


    


    function outOfChina(lat, lon) {


         if (lon < 72.004 || lon > 137.8347)


             return true;


        if (lat < 0.8293 || lat > 55.8271)


            return true;


        return false;


     }


     


 /*    


   * WGS-84:是国际标准,GPS坐标(Google Earth使用、或者GPS模块、天地图)


  * GCJ-02:中国坐标偏移标准,Google Map、高德、腾讯使用


  * BD-09:百度坐标偏移标准,Baidu Map使用


 */


    


     /**


     * wgLat 纬度


      * wgLon 经度


     * WGS-84 到 GCJ-02 的转换(即 GPS 加偏)


      * */


     function wgs_gcj_encrypts(wgLat, wgLon) {


        var point={};


         if (outOfChina(wgLat, wgLon)) {


             point.lat=wgLat;


            point.lng=wgLon;


             return point;


         }


         var dLat = transformLat(wgLon - 105.0, wgLat - 35.0);


         var dLon = transformLon(wgLon - 105.0, wgLat - 35.0);


         var radLat = wgLat / 180.0 * pi;


         var magic = Math.sin(radLat);


         magic = 1 - ee * magic * magic;


         var sqrtMagic = Math.sqrt(magic);


         dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);


         dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);


         var lat = wgLat + dLat;


         var lon = wgLon + dLon;


        point.lat=lat;


         point.lon=lon;


         return point;


     };


    


     /**


      * wgLat 纬度


      * wgLon 经度


      * BD-09转换GCJ-02


      * 百度转google


      * */


     function bd_google_encrypt(bd_lat, bd_lon){


         var point={};


         var x = bd_lon - 0.0065;


         var y = bd_lat - 0.006;  


         var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);  


         var theta =Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);  


         var gg_lon = z * Math.cos(theta);  


         var gg_lat = z * Math.sin(theta);  


         point.lat=gg_lat;


         point.lon=gg_lon;


         return point;


     };


    


 


    /**


     * gg_lat 纬度


      * gg_lon 经度


      * GCJ-02转换BD-09


      * Google地图经纬度转百度地图经纬度


      * */


     function google_bd_encrypt(gg_lat, gg_lon){


         var point={};


         var x = gg_lon;


         var y = gg_lat;


        var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi);


         var theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi); 


         var bd_lon = z * Math.cos(theta) + 0.0065;


        var bd_lat = z * Math.sin(theta) + 0.006;


         point.lat=bd_lat;


         point.lon=bd_lon;


         return point;


     };


2.实例

复制代码

   //从服务器获取的(硬件/谷歌)的经纬度

        lngMy = Number(dataLocker.gps.lng);

        latMy = Number(dataLocker.gps.lat);

         

        //调用上面代码中的wgs_gcj_encrypts方法转成高德的经纬度

         var point = wgs_gcj_encrypts(latMy,lngMy);

         

       //刷新展示的手机端的高德地图上

        latMy = point.lat.toFixed(6);

         lngMy = point.lon.toFixed(6);

         reLocate();


《学习总结》天地图

国内比较常用的地图api开发平台大概就百度、高德、谷歌、天地图了


其中百度、高德、谷歌都要收费,天地图完全免费


天地图用的是CGCS-2000坐标系,与WGS84坐标系都是地心坐标系,相差不大。


高德采用的是GCJ-02坐标系,也就是再WGS84坐标系上进行了一次加密得到的坐标系


百度采用的是BD-09坐标系,是在GCJ-02上再进行了一次加密得到的


坐标系的转换方法如下:

 

const x_PI = 3.141592653589 * 3000.0 / 180.0

const PI = 3.141592653589793

const a = 6378245.0

const ee = 0.006693421622965943

 

 

/**

 * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换

 * 即 百度 转 谷歌、高德

 */

function bd09togcj02(bd_lon:number, bd_lat:number) {

    var x_pi = 3.141592653589793 * 3000.0 / 180.0;

    var x = bd_lon - 0.0065;

    var y = bd_lat - 0.006;

    var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);

    var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);

    var gg_lng = z * Math.cos(theta);

    var gg_lat = z * Math.sin(theta);

    return [gg_lng, gg_lat]

}

 

 

/**

 * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换

 * 即谷歌、高德 转 百度

 */

function gcj02tobd09(lng:number, lat:number) {

    var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);

    var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);

    var bd_lng = z * Math.cos(theta) + 0.0065;

    var bd_lat = z * Math.sin(theta) + 0.006;

    return [bd_lng, bd_lat]

}

 

/**

 * WGS84转GCj02

 * 即大地坐标系(天地图) 转 谷歌、高德

 */

function wgs84togcj02(lng:number, lat:number) {

    if (out_of_china(lng, lat)) {

        return [lng, lat]

    }

    else {

        var dlat = transformlat(lng - 105.0, lat - 35.0);

        var dlng = transformlng(lng - 105.0, lat - 35.0);

        var radlat = lat / 180.0 * PI;

        var magic = Math.sin(radlat);

        magic = 1 - ee * magic * magic;

        var sqrtmagic = Math.sqrt(magic);

        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);

        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);

        var mglat = lat + dlat;

        var mglng = lng + dlng;

        return [mglng, mglat]

    }

}

 

/**

 * GCJ02 转换为 WGS84

 * 即谷歌、高德 转 大地坐标系(天地图)

 */

function gcj02towgs84(lng:number, lat:number) {

    if (out_of_china(lng, lat)) {

        return [lng, lat]

    }

    else {

        var dlat = transformlat(lng - 105.0, lat - 35.0);

        var dlng = transformlng(lng - 105.0, lat - 35.0);

        var radlat = lat / 180.0 * PI;

        var magic = Math.sin(radlat);

        magic = 1 - ee * magic * magic;

        var sqrtmagic = Math.sqrt(magic);

        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);

        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);

        var mglat = lat + dlat;

        var mglng = lng + dlng;

        return [lng * 2 - mglng, lat * 2 - mglat]

    }

}

 

function transformlat(lng:number, lat:number) {

    var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));

    ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;

    ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;

    ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;

    return ret

}

 

function transformlng(lng:number, lat:number) {

    var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));

    ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;

    ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;

    ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;

    return ret

}

天地图的文档都比较老一些,而且使用人数变多,最近接口经常会超时,不过官方人员表示在筹备优化了。


代码范例都只有html的,所以用起来有些地方转换会花一些时间。

天地图的一些链接:

首页

代码范例

类的参考,能看参数之类的


延伸

JS原生方法有提供定位功能navigator.geolocation,这个方法获取到的坐标是基于WGS84坐标系的,所以用在高德、谷歌上需要转换一次,百度需要转换两次,用在天地图上就不需要转换。

但是在PC浏览器上很多都会失败,Chrome、火狐以及部分套壳浏览器接入的定位服务在国外,就没有成功过,IE和Edge可以获取到

在能够使用这个方法的情况下,获取定位又可以按设备有没有GPS可以分为两种情况:

1、设备有GPS,授权浏览器获取定位之后,调用这个方法就能获取到精确的GPS定位了

2、设备没有GPS,那么能获取到的只是设备连接的网络的位置,就不一定是当前的位置


再补充一下:安卓和ios原生方法获取到的也是WGS84坐标系的坐标


基于Web Audio API实现音频可视化效果

1.png

<!doctype html>

<html>

<head>

 <meta charset="UTF-8"/>

 <title>可视化音乐播放器</title>

</head>

<body>

<input type="file" name="" value="" id="musicFile">

<p id="tip"></p>

<canvas id="casvased" width="500" height="500"></canvas>

</body>

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

<script type="text/javascript">

//随机变颜色

function randomRgbColor() { //随机生成RGB颜色

 var r = Math.floor(Math.random() * 256); //随机生成256以内r值

 var g = Math.floor(Math.random() * 256); //随机生成256以内g值

 var b = Math.floor(Math.random() * 256); //随机生成256以内b值

 return `rgb(${r},${g},${b})`; //返回rgb(r,g,b)格式颜色

}

//随机数 0-255

function sum (m,n){

  var num = Math.floor(Math.random()*(m - n) + n);

  

}

console.log(sum(0,100));

console.log(sum(100,255));

//展示音频可视化

var canvas = document.getElementById("casvased");

var canvasCtx = canvas.getContext("2d");

//首先实例化AudioContext对象 很遗憾浏览器不兼容,只能用兼容性写法;audioContext用于音频处理的接口,并且工作原理是将AudioContext创建出来的各种节点(AudioNode)相互连接,音频数据流经这些节点并作出相应处理。

//总结就一句话 AudioContext 是音频对象,就像 new Date()是一个时间对象一样

var AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;

if (!AudioContext) {

 alert("您的浏览器不支持audio API,请更换浏览器(chrome、firefox)再尝试,另外本人强烈建议使用谷歌浏览器!")

}

var audioContext = new AudioContext();//实例化

// 总结一下接下来的步骤

// 1 先获取音频文件(目前只支持单个上传)

// 2 读取音频文件,读取后,获得二进制类型的音频文件

// 3 对读取后的二进制文件进行解码

$('#musicFile').change(function(){

 if (this.files.length == 0) return;

 var file = $('#musicFile')[0].files[0];//通过input上传的音频文件

 var fileReader = new FileReader();//使用FileReader异步读取文件

 fileReader.readAsArrayBuffer(file);//开始读取音频文件

 fileReader.onload = function(e) {//读取文件完成的回调

 //e.target.result 即为读取的音频文件(此文件为二进制文件)

 //下面开始解码操作 解码需要一定时间,这个时间应该让用户感知到

 var count = 0;

 $('#tip').text('开始解码')

 var timer = setInterval(function(){

  count++;

  $('#tip').text('解码中,已用时'+count+'秒')

 },1000)

 //开始解码,解码成功后执行回调函数

 audioContext.decodeAudioData(e.target.result, function(buffer) {

  clearInterval(timer)

  $('#tip').text('解码成功,用时共计:'+count+'秒')

  // 创建AudioBufferSourceNode 用于播放解码出来的buffer的节点

  var audioBufferSourceNode = audioContext.createBufferSource();

  // 创建AnalyserNode 用于分析音频频谱的节点

  var analyser = audioContext.createAnalyser();

  //fftSize (Fast Fourier Transform) 是快速傅里叶变换,一般情况下是固定值2048。具体作用是什么我也不太清除,但是经过研究,这个值可以决定音频频谱的密集程度。值大了,频谱就松散,值小就密集。

  analyser.fftSize = 256;

  // 连接节点,audioContext.destination是音频要最终输出的目标,

  // 我们可以把它理解为声卡。所以所有节点中的最后一个节点应该再

  // 连接到audioContext.destination才能听到声音。

  audioBufferSourceNode.connect(analyser);

  analyser.connect(audioContext.destination);

  console.log(audioContext.destination)

  // 播放音频

  audioBufferSourceNode.buffer = buffer; //回调函数传入的参数

  audioBufferSourceNode.start(); //部分浏览器是noteOn()函数,用法相同

  //可视化 创建数据

  // var dataArray = new Uint8Array(analyser.fftSize);

  // analyser.getByteFrequencyData(dataArray)//将数据放入数组,用来进行频谱的可视化绘制

  // console.log(analyser.getByteFrequencyData)

  var bufferLength = analyser.frequencyBinCount;

  console.log(bufferLength);

  var dataArray = new Uint8Array(bufferLength);

  console.log(dataArray)

  canvasCtx.clearRect(0, 0, 500, 500);

  function draw() {

  drawVisual = requestAnimationFrame(draw);

  analyser.getByteFrequencyData(dataArray);

  canvasCtx.fillStyle = 'rgb(0, 0, 0)';

        //canvasCtx.fillStyle = ;

  canvasCtx.fillRect(0, 0, 500, 500);

  var barWidth = (500 / bufferLength) * 2.5;

  var barHeight;

  var x = 0;

  for(var i = 0; i < bufferLength; i++) {

   barHeight = dataArray[i];

         //随机数0-255 Math.floor(Math.random()*255) 

         // 随机数 10*Math.random()

   canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ','+Math.floor(Math.random()*(20- 120) + 120)+','+Math.floor(Math.random()*(10 - 50) + 50)+')';

   canvasCtx.fillRect(x,500-barHeight/2,barWidth,barHeight/2);

   x += barWidth + 1;

  }

  };

  draw();

 });

 }

})

</script>

</html>


其他代码

<!DOCTYPE html>

<html>

<head>

    <meta charset="UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" href="style.css">

    <title>Document</title>

</head>

<body>

    <div id="container">

        <canvas id="canvas1"></canvas>

        <audio id="audio1" controls ></audio>

        <input type="file" id="fileupload" accept="audio/*">

    </div>

    <script src="script.js"></script>

</body>

</html>


script.js

let audio1 = new Audio();

const audioContext = new (window.AudioContext || window.webkitAudioContext)();

const file = document.getElementById('fileupload');


audio1.src = '8-Bit Sound Library/Mp3/Climb_Rope_Loop_00.mp3';

const container = document.getElementById('container');

const canvas = document.getElementById('canvas1')

canvas.width = window.innerWidth;

canvas.height = window.innerHeight;


const ctx = canvas.getContext('2d');


let audioSource;

let analyser;


file.addEventListener('change', function() {

    const files = this.files;

    const audio1 = document.getElementById('audio1');

    audio1.src = URL.createObjectURL(files[0]); 

    audio1.load();

    audio1.play();

    audioSource = audioContext.createMediaElementSource(audio1);

    analyser = audioContext.createAnalyser();

    audioSource.connect(analyser);

    analyser.connect(audioContext.destination);

    analyser.fftSize = 64;

    const bufferLength = analyser.frequencyBinCount;

    console.log(bufferLength);

    const dataArray = new Uint8Array(bufferLength);


    const barWidth = canvas.width / bufferLength;

    let barHeight;

    let x;


    function animate() {

        x = 0;

        ctx.clearRect(0, 0, canvas.width, canvas.height);

        analyser.getByteFrequencyData(dataArray);

        for (let i = 0; i < bufferLength; i++) {

            barHeight = dataArray[i]*3;

            ctx.fillStyle = 'white';

            ctx.fillRect(x, canvas.height-barHeight, barWidth, barHeight);

            x += barWidth;

        }

        requestAnimationFrame(animate);

    }

    animate();

})


style.css

* {

    margin: 0;

    padding: 0;

    box-sizing: border-box;

}

#container {

    position: absolute;

    top: 0;

    left: 0;

    background: black;

    width: 100%;

    height: 100%;

}

#canvas1 {

    position: absolute;

    top: 0;

    left: 0;

    width: 100%;

    height: 100%;

}

#audio1 {

    width: 50%;

    margin: 50px auto;

    display: block;

}

#fileupload {

    position: absolute;

    top: 150px;

    z-index: 100;

    color: white;

}


使用import取代require

首先,Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用import取代require。


// bad

const moduleA = require('moduleA');

const func1 = moduleA.func1;

const func2 = moduleA.func2;


// good

import { func1, func2 } from 'moduleA';

使用export取代module.exports。


// commonJS的写法

var React = require('react');


var Breadcrumbs = React.createClass({

  render() {

    return <nav />;

  }

});


module.exports = Breadcrumbs;


// ES6的写法

import React from 'react';


class Breadcrumbs extends React.Component {

  render() {

    return <nav />;

  }

};


export default Breadcrumbs;

如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,export default与普通的export不要同时使用。


不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。


// bad

import * as myObject from './importModule';


// good

import myObject from './importModule';

如果模块默认输出一个函数,函数名的首字母应该小写。


function makeStyleGuide() {

}


export default makeStyleGuide;

如果模块默认输出一个对象,对象名的首字母应该大写。


const StyleGuide = {

  es6: {

  }

};


export default StyleGuide;



浏览器中使用import

页面文件内容:

<!DOCTYPE html>

<head>

    <meta charset="utf-8" />

<meta name="viewport" content="width=device-width, minimum-scale=1.0">

    </head>

<body>

    <button onclick="hello1()">执行1</button> <button onclick="hello2()" style="margin-left:10px;">执行2</button>

 <script type="module">

    //function hello1()

     //{

    import { foo,name} from 'http://www.1xn1.com/JsonMode/代码工厂/View//amazeui/public/js/foo.js'; 

    import { foo2,nale,age} from 'http://www.1xn1.com/JsonMode/代码工厂/View//amazeui/public/js/foo2.js'; 

    window.foo=foo;

     window.fname=name;

     window.foo2=foo2;

     window.nale=nale;

     window.age=age;   

     alert(foo());

     alert(name);

    // }

</script>

    <script>

        function hello1()

        {

            alert(foo());

        }

        function hello2() {

            alert(foo2());

            alert(nale);

            alert(age);

        }

    </script>

</body>

</html>


foo.js文件内容:

export function foo() {

    return 'bar';

}

export var name='徐子童';


foo2.js文件内容:

function foo2() {

    return 'bar';

}

var name='橙汁';

var age = 28;

export{foo2,name as nale, age}



JS异步变同步

async getImageInfo({ imgSrc }) {

            return new Promise((resolve, errs) => {

                uni.getImageInfo({

                    src: imgSrc,

                    success: function(image) {

                        resolve(image);

                    },

                    fail(err) {

                        errs(err);

                    }

                });

            });

        }



// 调用方法

let _imgInfo = await _this.getImageInfo({ imgSrc: _this.HeadImg }); //头像图



const getData = (url, param) => {

  return new Promise((resolve, reject) => {

    wx.request({

      url: url,

      method: 'GET',

      data: param,

      success (res) {

        console.log(res)

        resolve(res.data)

      },

      fail (err) {

        console.log(err)

        reject(err)

      }

    })

  })

}


var myFirstPromise = new Promise(function(resolve, reject){

    //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)

    //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.

    setTimeout(function(){

        resolve("成功!"); //代码正常执行!

    }, 250);

});

 

myFirstPromise.then(function(successMessage){

    //successMessage的值是上面调用resolve(...)方法传入的值.

    //successMessage参数不一定非要是字符串类型,这里只是举个例子

    document.write("Yay! " + successMessage);

});



对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。


promise.then() 是 promise 最为常用的方法。


promise.then(onFulfilled, onRejected)

promise简化了对error的处理,上面的代码我们也可以这样写:


promise.then(onFulfilled).catch(onRejected);


实例

function ajax(URL) {

    return new Promise(function (resolve, reject) {

        var req = new XMLHttpRequest(); 

        req.open('GET', URL, true);

        req.onload = function () {

        if (req.status === 200) { 

                resolve(req.responseText);

            } else {

                reject(new Error(req.statusText));

            } 

        };

        req.onerror = function () {

            reject(new Error(req.statusText));

        };

        req.send(); 

    });

}

var URL = "/try/ajax/testpromise.php"; 

ajax(URL).then(function onFulfilled(value){

    document.write('内容是:' + value); 

}).catch(function onRejected(error){

    document.write('错误:' + error); 

});



WebSocket之JS发送二进制

JS如何存储和操作二进制

JavaScript中用ArrayBuffer来存储二进制数据。

JavaScript类型化数组将实现拆分为缓冲和视图两部分。一个缓冲(ArrayBuffer)描述的是内存中的一段二进制数据,缓冲没有格式可言,并且不提供机制访问其内容。为了访问在缓存对象中包含的内存,你需要使用视图。视图可以将二进制数据转换为实际有类型的数组。一个缓冲可以提供给多个视图进行读取,不同类型的视图读取的内存长度不同,读取出来的数据格式也不同。缓冲和视图的工作方式如下图所示:

1.png

ArrayBuffer是一个构造函数,可以分配一段可以存放数据的连续内存区域。


var buffer = new ArrayBuffer(8);

上面代码生成了一段8字节的内存区域,每个字节的值默认都是0。1 字节(Byte) = 8 比特(bit),1比特就是一个二进制位(0 或 1)。上面代码生成的8个字节的内存区域,一共有 8*8=64 比特,每一个二进制位都是0。


为了读写这个buffer,我们需要为它指定视图。视图有两种,一种是TypedArray视图,它一共包括9种类型,还有一种是DataView视图,它可以自定义复合类型。 基础用法如下:


var dataView = new DataView(buffer);

dataView.getUint8(0) // 0


var int32View = new Int32Array(buffer);

int32View[0] = 1 // 修改底层内存


var uint8View = new Uint8Array(buffer);

uint8View[0] // 1



数据类型与字节数

视图类型说明字节
Uint8Array8位无符号整数1字节
Int8Array8位有符号整数1字节
Uint8ClampedArray8位无符号整数(溢出处理不同)1字节
Uint16Array16位无符号整数2字节
Int16Array16位有符号整数2字节
Uint32Array32位无符号整数4字节
Int32Array32位有符号整数4字节
Float32Array32位IEEE浮点数4字节
Float64Array64位IEEE浮点数8字节

一个完整的例子:


// 创建一个16字节长度的缓冲

var buffer = new ArrayBuffer(16);

// 创建一个视图,此视图把缓冲内的数据格式化为一个32位(4字节)有符号整数数组

var int32View = new Int32Array(buffer);

// 我们可以像普通数组一样访问该数组中的元素

for (var i = 0; i < int32View.length; i++) {

  int32View[i] = i * 2;

}

// 运行完之后 int32View 为[0,2,4,6]

// 创建另一个视图,此视图把缓冲内的数据格式化为一个16位(2字节)有符号整数数组

var int16View = new Int16Array(buffer);


for (var i = 0; i < int16View.length; i++) {

  console.log(int16View[i]);

}

// 打印出来的结果依次是0,0,2,0,4,0,6,0

相信图片已经很直观的表达了这段代码的意思。这里应该有人会疑问,为什么2、4、6这三个数字会排在0的前面,这是因为x86的系统都是使用的小端字节序来存储数据的,小端字节序就是在内存中,数据的高位保存在内存的高地址中,数据的低位保存在内存的低地址中。就拿上面这段代码举例,上图中内存大小排列的顺序是从左向右依次变大,int32View[1]对应的4个字节,它填入的值是 10 (2的2进制表示),把0补齐的话就是 00000000 00000000 00000000 00000010(中间的分隔方便观看),计算机会倒过来填充,最终会成为 00000010 00000000 00000000 00000000。与小端字节序对应的就是大端字节序,它就是我们平时读数字的顺序


ArrayBuffer

用来表示通用的、固定长度的原始二进制数据缓冲区。

在MDN的文档中,我们能够看到ArrayBuffer的介绍。它是在JavaScript中用来进行二进制数据存储的一种数据对象。


下面我们通过一个示例来简单介绍下ArrayBuffer相关操作。


const buffer = new ArrayBuffer(8);  //8个字节


buffer.byteLength; // 结果为8

上面的示例通过创建一个长度为8Byte的二进制数据缓冲区。缓冲区只是一个数据存储的空间,如何对这个存储空间进行读取,完全取决于使用者。例如:8个字节可以当成是2个Int类型的数据,也可以是一个Long类型的数据,或者4个Short型的数据。


DataView

DataView 视图是一个可以从 ArrayBuffer 对象中读写多种数值类型的底层接口,在读写时不用考虑平台字节序问题。


在MDN中关于DataView的介绍。DataView提供了大量的API接口来进行数据的读和写操作。但是,首先我们得先看下说明中提到的字节序问题。


字节序

在现有的计算机体系中,有两种字节序:


大端字节序:高位在前,低位在后。符合人类阅读习惯。

小端字节序:低位在前,高位在后。符合计算机读取习惯。

上面所说的顺序均是针对多字节对象而言,如Int类型,Long类型。以Int类型数据0x1234为例,如果是大端字节序,那么数据从人类对数值的通常写法上来看就是0x1234;如果是小端字节序,那么从人类对数值的通常写法上来看,应该写成0x3412。


对于单字节对象如Byte类型数据而言,没有字节序一说。


在不同的平台中,可能使用不同的字节序,这就是所谓的字节序问题。DataView所谓的在读写时不需要考虑平台字节序问题是指:同时使用DataView进行写入和读取的数据保持一致。


JS数据转二进制

对ArrayBuffer和DataView有了一个大概的了解,下面让我们来看下它是如何进行二进制数据操作的。


let buffer = new ArrayBuffer(6); // 初始化6个Byte的二进制数据缓冲区

let dataView = new DataView(buffer);


dataView.setInt16(0, 3); // 从第0个Byte位置开始,放置一个数字为3的Short类型数据(占2 Byte)

dataView.setInt32(2, 15); // 从第2个Byte位置开始,放置一个数字为15的Short类型数据(占4 Byte)

通过上面的示例,我们一共初始化了6个Byte的存储空间,使用1个Short类型(占2 Byte)和一个Int类型(占4 Byte)的数据进行填充。


DataView还提供了许多的API接口来进行其他数据类型的处理,如无符号型,浮点数等。他们的使用方法和上面介绍的API相同,我们在这里就不一一进行介绍了,希望了解更多API接口的读者可以查看MDN文档。


JS中Long类型转换为二进制数据

通过DataView提供的API接口,我们知道了如何处理Short类型、Int类型、Float类型和Double类型。那么,如果是对于Long类型这种原生API中没有提供处理函数的数据类型,我们应该如何处理呢?


首先,我们需要理解Long数据类型的结构,它是由一个高位的4个Byte和低位的4个Byte组成的数据类型。因为Long类型表示的范围比Number类型大,所以我们在JavaScript中是使用了两个Number类型(即Int类型)的对象来表示Long类型数据,相关的具体细节可以见我之前的博客Long.js源码分析与学习。


理解了JavaScript中如何存储Long类型,我们就知道如果对其进行存储。


import Long from 'long';


let long = Long.fromString('123');

let buffer = new ArrayBuffer(8);

let dataView = new DataView(buffer);


dataView.setInt32(0, long.high); // 采用大端字节序放置

dataView.setInt32(4, long.low);

通过上面的示例,我们将一个Long类型的数据拆分成了两个Int类型的数据,按照大端字节序放入到了ArrayBuffer中。同理,如果是想按照小端字节序放置,只需要将数据进行部分处理后再放入即可,在此我就不过多介绍了。


如何将二进制数据转换为JS中的数据类型

当你知道了如何将数据转换为ArrayBuffer中存储的二进制数据后,就能够简单推测出如何进行反向操作——将数据从ArrayBuffer中读取出来,再转换成JavaScript中常用数据类型。


import Long from 'long';


let buffer = new ArrayBuffer(14); // 初始化14个Byte的二进制数据缓冲区

let dataView = new DataView(buffer);

let long = Long.fromString('123');


// 数据写入过程

dataView.setInt16(0, 3); // 从第0个Byte位置开始,放置一个数字为3的Short类型数据(占2 Byte)

dataView.setInt32(2, 15); // 从第2个Byte位置开始,放置一个数字为15的Short类型数据(占4 Byte)


dataView.setInt32(6, long.high); // 采用大端字节序放置

dataView.setInt32(10, long.low);


// 数据读取过程

let shortNumber = dataView.getInt16(0);

let intNumber = dataView.getInt32(2);


let longNumber = Long.fromBits(dataView.getInt32(10), dataView.getInt32(6)); // 根据大端字节序读取,该构造函数入参依次为:低16位,高16位

JS中WebSocket使用定制二进制通信

一般情况下,服务端都会定制一套自己的通信协议,如下每个字节定义


第1个字节必须以0xBF开头

第2个字节表示请求类型,如1-request 2-response 3-command

第3个字节开始,写入数据buffer的长度,共占4个字节

第5个字节开始,写入整个buffer

示例代码如下(以protobuf为例):


protobuf.load("proto/MessageDataProto.proto", function (err, root) {

            // Obtain a message type

            var RequestUser = root.lookupType("com.example.nettydemo.protobuf.RequestUser");

            // Exemplary payload

            var payload = data;

            // Verify the payload if necessary (i.e. when possibly incomplete or invalid)

            var errMsg = RequestUser.verify(payload);

            if (errMsg) {

                return;

            }

            // Create a new message

            var message = RequestUser.create(payload); // or use .fromObject if conversion is necessary


            // Encode a message to an Uint8Array (browser) or Buffer (node)

            var buffer = RequestUser.encode(message).finish();

            let num = 6; //定制协议前部固定长度

            let len = num+buffer.byteLength;//总字节长度

            let arrBuffer = new ArrayBuffer(len); // 初始化Byte的二进制数据缓冲区

            let dataView = new DataView(arrBuffer);


            //191===0xBF

            dataView.setInt8(0, 191); // 从第0个Byte位置开始,放置一个数字为3的Short类型数据(占1 Byte)

            dataView.setInt8(1, 1); //1-request 2-response 3-command

            //dataView.setInt32(2, 1001); // 从第2个Byte位置开始,放置一个数字为15的Short类型数据(占4 Byte)

            dataView.setInt32(2, buffer.byteLength); //占4个字节

            for (var i = 0; i < buffer.byteLength; i++) {

                dataView.setInt8(num+i, buffer[i]);

            }


            if (typeof success === "function") {

                success(dataView)

            }


            if (typeof complete === "function") {

                complete()

            }

        });

小结

通过使用ArrayBuffer和DataView,我们能够快速的将数字数据从二进制转换为JavaScript常用数据类型如Int、Short等;同时,我们也可以将这些数据类型转换为二进制数据或服务端定制的二进制协议。



浏览器串口编程

let port = null;

let reader = null;


// serialReceive();

if ("serial" in navigator) {

// The Web Serial API is supported.

console.log("The Web Serial API is supported");


} else {

console.log("The Web Serial API is not supported");


}


async function portSelect() {

port = await navigator.serial.requestPort();

console.log(JSON.stringify(port.getInfo()));

// let ele = document.createElement('option');

// ele.setAttribute("value",JSON.stringify(port.getInfo()));

// ele.setAttribute("selected","selected");

// ele.innerHTML=JSON.stringify(port.getInfo())

// $("#portName")[0].appendChild(ele);

if (port) {

$("#portName").val(JSON.stringify(port.getInfo()));

}

}



//打开串口

async function serialOpen() {

if (port == null) {

alert('打开串口出错');

return;

}

let opt = {

baudRate: 9600,

parity: 0,

dataBits: 8,

stopBits: 1

};

opt.baudRate = parseInt($('#baudRate option:selected').text());

opt.parity = $('#parity option:selected').text();

// switch (tmp) {

//  case "none":

//  opt.parityMode = 0;

//  break;

//  case "odd":

//  opt.parityMode = 1;

//  break;

//  case "even":

//  opt.parityMode = 2;

//  break;

// }

opt.dataBits = parseInt($('#dataBits option:selected').text());

opt.stopBits = parseInt($('#stopBits option:selected').text());


// Wait for the serial port to open.

await port.open(opt);



$("#btnOpen").attr('disabled', 'disabled');

// while (port.readable) {

reader = port.readable.getReader();



try {

while (true) {

const {

value,

done

} = await reader.read();

if (done) {

// Allow the serial port to be closed later.

reader.releaseLock();

break;

}

if (value) {

//value is a Uint8Array.

console.log(value);

const strvalue = new TextDecoder("utf-8").decode(value);

console.log(strvalue);

const temp = $("#receiverText").val();

$("#receiverText").val(temp + strvalue);

}

}

} catch (error) {

// TODO: Handle non-fatal read error.

}

// }

}


//关闭串口

async function serialClose() {

$('#btnOpen').removeAttr('disabled');

try {

await reader.cancel();

await port.close();

} catch (e) {

console.log(e);

//TODO handle the exception

}


}


//串口发送

async function serialSend() {


console.log(document.getElementById("sendText").value.length);

if (document.getElementById("sendText").value.length == 0) {

alert("发送内容不能为空");

return;

}


// console.log(document.getElementById("hexOption").checked);

// hexOption = document.getElementById("hexOption").checked;



const data = $("#sendText").val();


//是否选中16进制

// if (hexOption) {

//  const writer = port.writable.getWriter();


//  const data = new Uint8Array([104, 101, 108, 108, 111]); // hello

//  await writer.write(data);

//  // Allow the serial port to be closed later.

//  writer.releaseLock();

// } else {

const textEncoder = new TextEncoderStream();

const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);


const writer = textEncoder.writable.getWriter();


await writer.write(data);

// Allow the serial port to be closed later.

writer.releaseLock();

// }


}



//清空接收区

function receiverClear() {

console.log("clear");

document.getElementById("receiverText").value = "";

}


外部浏览器点击跳转微信

weixin://dl/scan 扫一扫
weixin://dl/feedback 反馈
weixin://dl/moments 朋友圈
weixin://dl/settings 设置
weixin://dl/notifications 消息通知设置
weixin://dl/chat 聊天设置
weixin://dl/general 通用设置
weixin://dl/officialaccounts 公众号
weixin://dl/games 游戏
weixin://dl/help 帮助
weixin://dl/feedback 反馈
weixin://dl/profile 个人信息
weixin://dl/features 功能插件
























































Top