if(window.Worker){ // 使用Worker }
var worker = new Worker("./worker.js");
生成了“两个对象”(你可能会问:为什么是两个不是一个呢?请往下看)
“第一个”对象是我们在当前脚本中通过构造函数显式创建出来的worker对象,它拥有一套API:postMessage和onmessage,通过postMessage方法可以向worker脚本(上文worker.js)发送数据, 通过onmessage方法可以从worker脚本接收数据 “第二个”对象是在Web Worker脚本(上文的worker.js)中隐式创建出来的全局变量对象,它叫DedicatedWorkerGlobalScope(这个时候在work.js全局变量对象是它而不是Window!!),而它也拥有一套API:postMessage和onmessage,通过postMessage方法可以向当前执行任务的脚本发送数据, 通过onmessage方法可以从当前执行任务的脚本接收数据 【注意】关于DedicatedWorkerGlobalScope 1. 它是在Web Worker脚本中生成的特殊的全局变量对象,也就是在全局执行环境中使用this指向的不是Window而是它 2. 它不能像Windows那样通过变量名直接访问,但在Web Worker脚本中你能通过this取到它 所以现在数据传递方向有两条: 1. 调用当前脚本中worker对象的postMessage方法, 然后在Web Worker脚本(上文的worker.js)中通过onmessage这个回调方法接收数据 2. 调用Web Worker脚本中的this.postMessage方法(this指向DedicatedWorkerGlobalScope),然后在当前脚本中worker对象的onmessage回调方法接收数据 看到这里可能有点懵,来让我们通过一个例子看看1中的数据传递: 先看示例吧,这是我们的目录结构├─worker.js
├─main.js
└─index.html
index.html:
<html> <head> <meta charset="utf-8" /> <button id="work-button">传递数据</button> </head> <body> <script type="text/javascript" src="./main.js"></script> </body> </html>
main.js:
var button = document.querySelector("#work-button"); if(window.Worker){ var worker = new Worker("./worker.js"); button.onclick = function () { worker.postMessage("你好,我是当前脚本"); } }
worker.js:
this.onmessage = function (e) { console.log('work接收到的数据为:', e.data); }
点击按钮后,在main.js中调用worker对象的postMessage方法, 这个数据就被发给了work.js中的全局变量对象DedicatedWorkerGlobalScope, 所以我们在work,js中通过this.onmessage接收数据并输出
├─worker.js
├─main.js
└─index.html
index.html:
同上
main.js:
var button = document.querySelector("#work-button"); if(window.Worker){ var worker = new Worker("./worker.js"); button.onclick = function () { worker.postMessage("你好,我是当前脚本"); worker.onmessage = function (e) { console.log('当前脚本接收到的数据:',e.data) } } }
worker.js:
this.onmessage = function (e) { console.log('work接收到的数据为:', e.data); this.postMessage("你好,我是worker发来的数据") }
demo如下 点击传递数据输出:
<canvas id="canvas"></canvas>
这样取得上下文对象:
let canvas = document.getElementById("canvas"); // 首先取得canvas元素对象 let ctx = canvas.getContext("2d"); //通过getContext()取得关键的上下文对象,2d表示画布是“平面”的
fillRect(x, y, width, height) // 绘制一个填充的矩形 strokeRect(x, y, width, height) // 绘制一个矩形的边框
上面的x,y代表相对于canvas画布左上角的横纵坐标:
例子: html部分:
<canvas id="canvas" width="200px" height="100px"> 你的浏览器不支持canvas </canvas>
JS部分:
let canvas = document.getElementById("canvas"); if(canvas.getContext){ let ctx = canvas.getContext("2d"); ctx.fillRect(20,20,40,40); // 绘制矩形 }
【注意】. canvas标签内的内容(例如上面的文本)是否呈现取决于浏览器是否支持canvas,如果支持,则不出现,如果不支持,则会呈现出来 demo:
let ctx = canvas.getContext("2d"); ctx.fillStyle = "#0081F0"; // 给上下文对象这支画笔添加填充颜色 ctx.fillRect(20,20,40,40);
demo:
let canvas = document.getElementById("canvas"); if(canvas.getContext){ let ctx = canvas.getContext("2d"); ctx.font = "26px serif"; // 设置文字大小和样式 ctx.fillText("外婆的",20,20); // “实心”的文本 ctx.strokeText("彭湖湾",20,60); // “空心”的文本 }
demo:
这里要稍微提一下, 也许上面的那些绘制图形,绘制文本的操作对你来说都没有触动,因为它们离我们的直接需求似乎还有一定的距离,但我想接下来的这几个上下文API你或许有些兴趣。 例如我们可能有一个需求是载入已有的图片,对它截图(裁剪)后保存为一张新的图片,这个时候我们就可以使用到canvas的绘制图片,裁剪图片,保存图片的API了
drawImage(image, x, y) // 其中 image 是 image 或者 canvas 对象
我们可以通过下面的一段代码动态获取img元素对象
let img = new Image(); img.onload = function () { // 运行这个函数的时候可以确保img已经被加载好了 }; img.src = "./beach.jpg" // 指定src后图片开始加载
废话不多说,直接上demo! 在相同目录下有这么一张图片
JS代码:
let canvas = document.getElementById("canvas"); let img = new Image(); img.onload = function () { if(canvas.getContext){ let ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0) } }; img.src = "./beach.jpg"
demo: 我们发现, 图片加载完成后被写入了画布当中!
let canvas = document.getElementById("canvas"); let img = new Image(); img.onload = function () { if(canvas.getContext){ let ctx = canvas.getContext("2d"); ctx.beginPath(); // 开始绘制路径 ctx.arc(100,100,100,0,Math.PI*2,true); // 绘制一个起点(100,100),半径为100的圆 ctx.clip(); // 裁剪 ctx.drawImage(img, 0, 0); // 画图 } }; img.src = "./beach.jpg"
【注意】clip方法的调用要在drawImage方法之前,否则不能成功! 也就是说要“先裁剪,再画图”
canvas.toDataURL() // 默认返回的是png图片 canvas.toDataURL('image/jpeg') // 返回jpeg图片 canvas.toDataURL('image/jpeg', quality) // 创建一个JPG图片。你可以有选择地提供从0到1的品质量,1表示最好品质
看下面的例子
let canvas = document.getElementById("canvas"); let img = new Image(); img.onload = function () { if(canvas.getContext){ let ctx = canvas.getContext("2d"); ctx.beginPath(); // 开始绘制路径 ctx.arc(100,100,100,0,Math.PI*2,true); // 绘制一个起点为(100,100),半径为100的圆 ctx.clip(); // 裁剪 ctx.drawImage(img, 0, 0); // 画图 let src = canvas.toDataURL('image/png') console.log(src); } }; img.src = "./beach.jpg"
控制台输出了base64格式的数据: DBA05AD6DB.png" alt="" width="441" height="286" />
我们通过网上的还原软件看看会把这个base64数据还原成什么图片:
正是我们想要的图片
indexedDB.open([ 数据库名称 ], [数据库版本])
调用open方法时候,如果对应名称的数据库不存在,则创建一个新的数据库,如果已存在,则打开已存在的那个数据库 需要说明的是, indexedDB里面绝大多数操作都是异步的, 上述的indexedDB.open并不会立即创建一个数据库, 你需要在异步的回调里面判断数据库是否创建成功,并对可能出现的错误做判断和处理 只有在onsuccess回调中,你才能通过request.result取得创建成功的数据库
var request = indexedDB.open("XXX", 1); request.onsuccess = function () { // request.result === 你通过open创建的数据库 }
通过open返回的request对象有三个回调: onsuccess 每次创建/打开数据库时候都会调用 onerror 创建/打开数据库发生错误的时候调用 onupgradeneeded 数据库版本变化的时候调用 (onupgradeneeded 是我们唯一可以修改数据库结构的地方) open一个indexedDB数据库后,一般在onupgradeneeded回调中初始化(或修改)数据库结构(划重点!!) 这包括两个方面的操作: 1. 通过db.createObjectStore创建对象存储空间,并取得ObjectStore对象(类似于SQL数据库中的建表操作) 2. 通过调用ObjectStore.createIndex创建该存储空间内的索引( 以便于提高查询时候的速度) 具体的可看下面的例子:
<script type="text/javascript"> if(!window.indexedDB) { alert("你的浏览器还不能支持indexedDB哦!") } var request = indexedDB.open("phwDataBase", 1); var db request.onsuccess = function () { // 将成功创建的数据库对象赋给db db = request.result; } request.onerror = function () { var errorDescribe = request.errorCode; // 打印错误 console.log(errorDescribe); } request.onupgradeneeded = function (e){ // 取得更新后的数据库对象, 并赋给db db = request.result; // 创建名为people数据存储空间, 第二个参数里的keyPath相当于“主键” var objectStore = db.createObjectStore("people", { keyPath: "id" }); // 创建索引, 加快查询速度 objectStore.createIndex("name", "name", {unique: false}); } </script>
运行一下, 然后让我们看看效果:
打开chrome的Application面板,点击左栏的Storage下的indexedDB使其展开 就可以看到我们新创建的phwDataBase数据库, 以及它内部的people数据存储空间了 (右边展示的是people数据存储空间的具体内容,因为现在什么数据都还没添加,所以key和value两列下是没有内容的) 看了上面的代码你可能会有些疑惑 onupgradeneeded 和onsuccess回调的关系是怎样的? 为什么我们必须在.onupgradeneeded中初始化数据库的结构,而不是在onsuccess中? 这主要是由两个回调调用的时机决定的: 1.对 onsuccess回调,在每次数据库创建/打开的时候都会调用(不仅是第一次创建的时候会调用,每次打开的时候也都会调用) 2. 对onupgradeneeded回调,在open提供第二个版本参数的前提下: 2.1 第一次调用open方法创建一个新的数据库的时候,onupgradeneeded一定会被调用 2.2 第二次或以后open该数据库,只在版本参数改变的时候, onupgradeneeded才会被调用 【注意】在缺少第二个版本参数的情况下,onupgradeneeded永远不会被调用!! 所以说: 1.open数据库的时候,最(yi)好(ding)要带上第二个参数(版本参数) 2. 修改数据库结构(例如创建和删除对象存储空间以及构建和删除索引。)要在onupgradeneeded回调中运行 (很显然每次打开都会被调用的onsuccess并不适合用于初始化数据库结构)
<button id="add-button">增加数据</button> <button id="delete-button">删除数据</button> <button id="get-button">获取数据</button> <button id="show-all-button">遍历全部数据</button> <button id="index-button">通过索引获取数据</button>
demo:
这里要说明一下的是,indexedDB的操作是以事务为基础的。 所以,对存储空间(objectStore)的操作都要基于事务来进行。 具体点说,就是需要先通过db.transaction()方法取得transaction对象,然后再通过 transaction.objectStore()方法取得目标objectStore,再然后才能调用objectStore的API进行“写改删查” 打个比方, 如果说我们存储的数据是粮食的话, objectStore就是一个个并排的粮仓,你可以往里面运粮食,也可以把粮食运出去, 但你对“粮食”做任何行为前, 都要和粮仓门前的守卫—— transaction(事务)“打声招呼”,得到准许才能进入粮仓 有两个方法要说一下 1. transaction方法 transaction 方法 一般接受两个参数,并返回一个事务对象。 1.1第一个参数是一个数组, 一个我们希望事务能够操作的objectStore所组成的数组,如果你希望这个事务能够操作所有的objectStore,那么传入空数组[]即可 1.2 第二个参数是一个字符串, 默认是“onlyread”, 如果我们有需要对数据进行写操作的需求的话可传入“readwrite” 例如我们下面的一行代码:
var transaction = db.transaction(["people"],"readwrite");
2. transaction.objectStore方法 这个方法接受一个参数: 指定的objectStore的名称, 方法返回的是获取到的objectStore 例如我们下面的一行代码:
var objectStore = transaction.objectStore("people");
function addData () { // 确保这个时候异步的open方法已经完成,并取得数据库对象 if(!db){ return; } // 我们要写入的数据 var data = [ {id: '1',name:'a', age: 10}, {id: '2',name:'b', age: 20}, {id: '3',name:'c', age: 30} ]; // 创建事务,并使其可读可写 var transaction = db.transaction(["people"],"readwrite"); transaction.oncomplete = function () { alert("添加事务已经完成") } transaction.onerror = function () { alert("出现错误") } // 通过事务对象取得people存储空间 var objectStore = transaction.objectStore("people"); for(let d of data) { // 调用add方法添加数据 objectStore.add(d); } } var addButton = document.getElementById("add-button"); addButton.onclick = addData
demo: 点击“增加数据”后弹出
再看看application面板下的indexedDB:
我们已经成功添加了三条数据进去了
function deleteData () { if(!db){ return; } var transaction = db.transaction(["people"],"readwrite"); var objectStore = transaction.objectStore("people"); objectStore.delete("1"); transaction.oncomplete = function () { alert("删除事务已经完成") } } var deleteButton = document.getElementById("delete-button"); deleteButton.onclick = deleteData;
点击上面的“删除数据”按钮(删除id = 1的数据)
再来看看, id为1的那一行已经被删除了
function getData () { if(!db){ return; } var transaction = db.transaction(["people"], "readwrite"); var objectStore = transaction.objectStore("people"); var request = objectStore.get("2"); request.onsuccess = function () { alert(JSON.stringify(request.result)); } } var getButton = document.getElementById("get-button"); getButton.onclick = getData;
demo: 点击“获取数据”按钮,弹出
(这里固定查找id为2的数据)
function showAllData () { if(!db){ return; } var transaction = db.transaction(["people"], "readwrite"); var objectStore = transaction.objectStore("people"); console.log("遍历开始") objectStore.openCursor().onsuccess = function (event) { var cursor = event.target.result; if(cursor) { console.log(cursor.key, cursor.value); cursor.continue(); } } } var showAllButton = document.getElementById("show-all-button"); showAllButton.onclick = showAllData;
点击“遍历全部数据”按钮,看看控制台
function getByIndex () { if(!db){ return; } var transaction = db.transaction(["people"], "readwrite"); var objectStore = transaction.objectStore("people"); var index = objectStore.index("name"); var request = index.get("c"); request.onsuccess = function (event) { alert(JSON.stringify(request.result)); } } var indexButton = document.getElementById("index-button"); indexButton.onclick = getByIndex;
点击“通过索引获取数据”按钮:
好! 现在让我们对indexedDB做一个小小的总结: 1. indexedDB是面向对象的, 与传统的以二维表为基础的数据库不同 2. IndexedDB是一个事务型数据库系统 3. indexedDB大多数API都是异步的,这意味着调用一个方法你不能马上得到关键的那个对象,而在对应的success回调中才能取得
<div> <img id = "myImg" src="./clock.jpg" draggable="true" ondragstart="dragstart(event)" /> </div> <div id="targetDiv" ondragover="dragover(event)" ondrop="drop(event)"> </div> <script type="text/javascript"> function dragstart (event) { event.dataTransfer.setData("text/plain", event.target.id) } function drop (event) { // 阻止默认行为——禁止在浏览器中打开新的链接 event.preventDefault(); var imgId = event.dataTransfer.getData("text/plain"); var targetDiv = document.getElementById("targetDiv"); targetDiv.appendChild(document.getElementById(imgId)); } function dragover (event) { // 组织默认行为——禁止放置 event.preventDefault(); } </script>
拖拽前
拖拽后
参考资料: HTML5-MDN https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/HTML5 【完】