JavaScript笔记

95 阅读7分钟

Chome版本

  • 金丝雀
  • 开发版
  • 测试版
  • 正式版

Web Worker

Web Worker 是HTML5标准的一部分,这一规范定义了一套 API,它允许一段JavaScript程序运行在主线程之外的另外一个线程中。当然 Web Worker 提供的多线程编程能力并不像我们传统意义上的多线程编程,Worker 线程之间,不会共享任何作用域或资源,他们间唯一的通信方式就是一个基于事件监听机制的 message;

Web Worker 规范中定义了两类工作线程,分别是**专用线程Dedicated Worker共享线程 Shared Worker**

**专用线程Dedicated Worker**只能为一个页面所使用

**共享线程Shared Worker**则可以被多个页面所共享。

[浅谈Web Worker]  juejin.cn/post/684490… 

只需调用Worker() 构造函数并传入一个要在 worker 线程内运行的脚本的URI,即可创建一个新的worker。

var myWorker = new Worker("my_task.js");
​
// my_task.js中的代码 
var i = 0;
function timedCount(){
    i = i+1;
    postMessage(i);
    setTimeout(timedCount, 1000);
}
timedCount();

另外,通过URL.createObjectURL()创建URL对象,可以实现创建内嵌的worker

​
var myTask = `
    var i = 0;
    function timedCount(){
        i = i+1;
        postMessage(i);
        setTimeout(timedCount, 1000);
    }
    timedCount();
`;
​
var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));

注意:传入 Worker 构造函数的参数 URI 必须遵循同源策略。如果 此URL有一个无效的语句,或者违反同源策略,一个 SECURITY_ERR类型的DOMException被抛出。

Worker线程数据通讯方式

Worker 与其主页面之间的通信是通过 onmessage 事件和 postMessage() 方法实现的。

在主页面与 Worker 之间传递的数据是通过拷贝,而不是共享来完成的。传递给 Worker 的对象需要经过序列化,接下来在另一端还需要反序列化。页面与 Worker 不会共享同一个实例,最终的结果就是在每次通信结束时生成了数据的一个副本。

也就是说,Worker 与其主页面之间只能单纯的传递数据,不能传递复杂的引用类型:如通过构造函数创建的对象等。并且,传递的数据也是经过拷贝生成的一个副本,在一端对数据进行修改不会影响另一端。

var myTask = `
    onmessage = function (e) {
        var data = e.data;
        data.push('hello');
        console.log('worker:', data); // worker: [1, 2, 3, "hello"]
        postMessage(data);
    };
`;
​
var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));myWorker.onmessage = function (e) {
    var data = e.data;
    console.log('page:', data); // page: [1, 2, 3, "hello"]
    console.log('arr:', arr); // arr: [1, 2, 3]
};
​
var arr = [1,2,3];
myWorker.postMessage(arr);

应用场景

Web Worker 的实现为前端程序带来了后台计算的能力,可以实现主 UI 线程与复杂计运算线程的分离,从而极大减轻了因计算量大而造成 UI 阻塞而出现的界面渲染卡、掉帧的情况,并且更大程度地利用了终端硬件的性能;

同时把程序之间的任务更清晰、条理化;

其主要应用有几个场景:

  • 对于图像、视频、音频的解析处理;
  • canvas 中的图像计算处理;
  • 大量的 ajax 请求或者网络服务轮询;
  • 大量数据的计算处理(排序、检索、过滤、分析...);

Service Worker / PWA

借助Service Worker和cacheStorage缓存及离线开发

HTML5离线开发技术已经出现很多年了,但并不看好,用一句话解释就是“投入产出比有些低”。

对于web应用,掉线不能使用是理所当然的,绝不会有哪个开发人员会因为网页在没网的时候打不开被测试MM提bug,或者被用户投诉,所以,我们的web页面不支持离线完全不会有什么影响。但如果我们希望支持离线,那投入的精力和成本并不小,html5 manifest缓存技术还需要服务端配合,跨团队协作让工作成本就上去了。

Service Worker概念

平常浏览器窗口中跑的页面运行的是主JavaScript线程,DOM和window全局变量都是可以访问的。而Service Worker是走的另外的线程,可以理解为在浏览器背后默默运行的一个线程,脱离浏览器窗体。因此,window以及DOM都是不能访问的,此时我们可以使用self访问全局上下文。

Workers开辟的新线程是没有“窗体”这个概念的,都是在浏览器背后悄悄运行的线程,没有窗体的概念也就意味着没有window对象,因此Service Workers和Web Workers相关的脚本中是不能使用window这个对象的,在non-window上下文的环境中,我们可以使用self来表示全局作用域。注意,只能是self ,例如

self.addEventListener('install', function(event) {
 // ...
});

限制

  • 由于Service Worker设计为完全异步,同步API(如XHRlocalStorage)不能在Service Worker中使用。
  • Service Worker必须是https协议的

Service Worker的生命周期

Service Worker 生命周期如下:

Service Worker是有对应的事件名进行捕获的,为:

self.addEventListener('install', function(event) { /* 安装后... */ });
self.addEventListener('activate', function(event) { /* 激活后... */ });

最后,Service Worker还支持fetch事件,来响应和拦截各种请求。

self.addEventListener('fetch', function(event) { /* 请求后... */ });

Service Worker的兼容性

桌面端Chrome和Firefox可用,IE不可用。移动端Chrome可用,以后估计会快速支持。

借助Service Worker和cacheStorage离线开发的固定套路

  1. 页面上注册一个Service Worker,例如:

    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./sw-demo-cache.js');
    }
    
  2. sw-demo-cache.js
    

    这个JS中复制如下代码:

    var VERSION = 'v1';
    ​
    // 缓存
    self.addEventListener('install', function(event) {
      event.waitUntil(
        caches.open(VERSION).then(function(cache) {
          return cache.addAll([
            './start.html',
            './static/jquery.min.js',
            './static/mm1.jpg'
          ]);
        })
      );
    });
    ​
    // 缓存更新
    self.addEventListener('activate', function(event) {
      event.waitUntil(
        caches.keys().then(function(cacheNames) {
          return Promise.all(
            cacheNames.map(function(cacheName) {
              // 如果当前版本和缓存版本不一致
              if (cacheName !== VERSION) {
                return caches.delete(cacheName);
              }
            })
          );
        })
      );
    });
    ​
    // 捕获请求并返回缓存数据
    self.addEventListener('fetch', function(event) {
      event.respondWith(caches.match(event.request).catch(function() {
        return fetch(event.request);
      }).then(function(response) {
        caches.open(VERSION).then(function(cache) {
          cache.put(event.request, response);
        });
        return response.clone();
      }).catch(function() {
        return caches.match('./static/mm1.jpg');
      }));
    });
    
  3. cache.addAll()方法中缓存文件数组换成你希望缓存的文件数组。

预览:借助Service Worker和cacheStorage缓存及离线开发demo

install

调用了caches.open() 和我们想要的缓存名称, 之后调用 cache.addAll() 并传入文件数组。 这是一个promise 链( caches.open() 和 cache.addAll() )。 event.waitUntil() 方法接受一个承诺,并使用它来知道安装需要多长时间,以及它是否成功。

activate

在激活回调中发生的一个常见任务是缓存管理。你要在激活回调中这样做的原因是,如果你要在安装步骤中清除所有旧的缓存,任何保留所有当前页面的旧 Service Worker 将会突然停止服务来自该缓存的文件。

这里提供了一个如何从缓存中删除一些不在白名单中的文件的例子(在本例中,有VERSION=v1 实体):

fetch

event.respondWith() 中,我们传递了一个来自 caches.match()promise。 此方法查看请求,并查找来自 Service Worker 创建的任何缓存的任何缓存结果。

设计模式

抽象工厂模式

// 底层
function artFactory() {
  function getFunction(path, params) {
    console.log(path, params);
  }
  
  function postFunction(path, params) {
    console.log(path, params);
  }
  
  function putFunction(path, params) {
    console.log(path, params);
  }
  
  return function (type, path, params) {
    switch(type) {
      case 'get':
        this.getFunction(path, params)
        break;
      case 'post':
        this.postFunction(path, params)
        break;
      default:
        this.putFunction(path, params)
        break;
    }
  }
}
​
// 业务层
function user() {
  const USER_URL_MAP = {
    getUser: 'getUserUrl',
    updateUser: 'updateUserUrl',
  }
  
  return {
    getUser(params) {
      return artFactory()('get', USER_URL_MAP['getUser'], params)
    },
​
    postUpdate(params) {
      return artFactory()("post", USER_URL_MAP['updateUser'], params)
    }
  }
}
​
​
function sendUrl(businessType) {
  switch(businessType) {
    case 'user':
      return user;
​
    default: 
      console.log("无");
  }
}
​
const userAction = sendUrl('user')
userAction.getUser("usrname")

观察者模式 (响应式数据原理)

tree shaking

Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术

原理是通过ES6 module引入了静态分析,可以在编译的时候判断哪些模块代码是真正加载被加载,判断哪些是未使用的,未被引用的进而删除那些没有被使用到的模块。

common.js 和 es6 中模块引入的区别

CommonJS 是一种模块规范,最初被应用于 Nodejs,成为 Nodejs 的模块规范。运行在浏览器端的 JavaScript 由于也缺少类似的规范,在 ES6 出来之前,前端也实现了一套相同的模块规范 (例如: AMD),用来对前端模块进行管理。自 ES6 起,引入了一套新的 ES6 Module 规范,在语言标准的层面上实现了模块功能,而且实现得相当简单,有望成为浏览器和服务器通用的模块解决方案。但目前浏览器对 ES6 Module 兼容还不太好,我们平时在 Webpack 中使用的 export 和 import,会经过 Babel 转换为 CommonJS 规范。在使用上的差别主要有:

1、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

2、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口(静态编译)。

3、CommonJs 是单个值导出,ES6 Module 可以导出多个

4、CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层

5、CommonJs 的 this 是当前模块,ES6 Module 的 this 是 undefined