菜鸟的逆袭


  • Home

  • Archives

排序算法js版本

Posted on 2018-03-22

排序算法是基础算法。虽然关键在于算法的思想而不是语言,但还是决定借助算法可视化工具结合自己常用的语言实现一遍。

目录

  • 冒泡排序

  • 选择排序

  • 插入排序

  • 合并排序

  • 快速排序

为了方便说明,默认按从小到大排序

冒泡排序

基本思路:

1.依次比较相邻的两个数,如果第一个比第二个小,不变。如果第一个比第二个大,调换顺序。一轮下来,最后一个是最大的数

2.对除了最后一个之外的数重复第一步,直到只剩一个数

图形展示:
avatar

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function bubbleSort(myArray){
var len = myArray.length;
var i;
var j;
var stop;

for (i = 0; i < len - 1; i++){
for (j = 0, stop = len - 1 - i; j < stop; j++){
if (myArray[j] > myArray[j + 1]){
swap(myArray, j, j + 1);
}
}
}
return myArray;
}

function swap(myArray, p1, p2){
var temp = myArray[p1];
myArray[p1] = myArray[p2];
myArray[p2] = temp;
}

选择排序

基本思路:

1.找出最小的数,和第一个交换位置

2.在剩下的数中,找出最二小的数,放在第二个

3.依次类推,排出顺序

图形展示:
avatar

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function selectionSort(myArray){

var len = myArray.length,
min;

for (i=0; i < len; i++){

// 将当前位置设为最小值
min = i;

// 检查数组其余部分是否更小
for (j=i+1; j < len; j++){
if (myArray[j] < myArray[min]){
min = j;
}
}

// 如果当前位置不是最小值,将其换为最小值
if (i != min){
swap(myArray, i, min);
}
}

return myArray;
}
function swap(myArray, p1, p2){
var temp = myArray[p1];
myArray[p1] = myArray[p2];
myArray[p2] = temp;
}

插入排序

基本思路:

1.把数组分为[已排序]和[未排序]两部分,第一个数为[已排序],其余为[未排序]

2.从[未排序]抽出第一个数,和[已排序]部分比较,插入到合适的位置

图形展示:
avatar

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function insertionSort(myArray) {

var len = myArray.length, // 数组的长度
value, // 当前比较的值
i, // 未排序部分的当前位置
j; // 已排序部分的当前位置

for (i=0; i < len; i++) {

// 储存当前位置的值
value = myArray[i];

/*
* 当已排序部分的当前元素大于value,
* 就将当前元素向后移一位,再将前一位与value比较
*/
for (j=i-1; j > -1 && myArray[j] > value; j--) {
myArray[j+1] = myArray[j];
}

myArray[j+1] = value;
}

return myArray;
}

合并排序(分而治之)

基本思路:

1.不断将数组对半分,直到每个数组只有一个

2.将分出来的部分重新合并

3.合并的时候按顺序排列

图形展示:
avatar

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 被拆分的数组重新合并
function merge(left, right) {
var result = [],
left_index = 0,
right_index = 0;

// 将两个数组合并
// 合并的时候按从小到大的顺序
while (left_index < left.length && right_index < right.length) {
if (left[left_index] < right[right_index]) {
result.push(left[left_index++]);
} else {
result.push(right[right_index++]);
}
}

// 和其他数组拼接
return result.concat(left.slice(left_index)).concat(right.slice(right_index));
}

function mergeSort(myArray) {
// 只有一个数的时候退出递归
if (myArray.length < 2) {
return myArray;
}

var middle = Math.floor(myArray.length / 2),
left = myArray.slice(0, middle),
right = myArray.slice(middle);

// 递归
// 不断拆分只到一个数组只有一个数
return merge(mergeSort(left), mergeSort(right));
}

快速排序

基本思路:

1.以一个数为基准(中间的数),比基准小的放到左边,比基准大的放到右边

2.再按此方法对这两部分数据分别进行快速排序(递归进行)

3.不能再分后退出递归,并重新将数组合并

图片展示:
avatar

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var quickSort = function(myArray) {  
// 当被分的数组只剩一个时,退出递归
if (myArray.length <= 1) {
return myArray;
}

// 中间基准值的index
var pivotIndex = Math.floor(myArray.length / 2);  
// 基准值
var pivot = myArray.splice(pivotIndex, 1)[0];  
var left = [];  
var right = [];  
// 小的放左边,大的放右边
for (var i = 0; i < myArray.length; i++) {    
if (myArray[i] < pivot) {  
left.push(myArray[i]); 
} else {
right.push(myArray[i]); 
}  
}  
// 递归
// 把数组合并在一起
return quickSort(left).concat([pivot], quickSort(right));
};

post提交方式

Posted on 2018-03-13

四种常见的 POST 提交数据方式

我们知道,HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范。规范把 HTTP 请求分为三个部分:状态行、请求头、消息主体。类似于下面这样:

1
2
3
4
5
6
7
8
9
10
<method> 
<request-url>
<version>
<headers>
<entity-body>
</entity-body>
</headers>
</version>
</request-url>
</method>

协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式。实际上,开发者完全可以自己决定消息主体的格式,只要最后发送的 HTTP 请求满足上面的格式就可以。

但是,数据发送出去,还要服务端解析成功才有意义。一般服务端语言如java、php、python 等,以及它们的 framework,都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分。下面就正式开始介绍它们。

application/x-www-form-urlencoded

这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了):

1
2
POST https://schacker.github.io/archives/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
首先,Content-Type 被指定为 application/x-www-form-urlencoded;其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。大部分服务端语言都对这种方式有很好的支持。例如 PHP 中,$_POST[‘title’] 可以获取到 title 的值,$_POST[‘sub’] 可以得到 sub 数组。

很多时候,我们用 Ajax 提交数据时,也是使用这种方式。例如 JQuery 的 Ajax,Content-Type 默认值都是「application/x-www-form-urlencoded;charset=utf-8」。

multipart/form-data

这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 form 的 enctyped 等于这个值。直接来看一个请求示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

这个例子稍微复杂点。首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 mutipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 –boundary 开始,紧接着内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 –boundary– 标示结束。关于 mutipart/form-data 的详细定义,请前往 rfc1867 查看。

这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。

上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段原生 form 表单也只支持这两种方式。但是随着越来越多的 Web 站点,尤其是 WebApp,全部使用 Ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,给开发带来更多便利。

application/json

application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。

JSON 格式支持比键值对复杂得多的结构化数据,这一点也很有用。

Google 的 AngularJS 中的 Ajax 功能,默认就是提交 JSON 字符串。例如下面这段代码:

1
2
3
4
var data = {'title':'test', 'sub' : [1,2,3]};
$http.post(url, data).success(function(result) {
...
});

最终发送的请求是:

1
2
3
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}

这种方案,可以方便的提交复杂的结构化数据,特别适合 RESTful 的接口。各大抓包工具如 Chrome 自带的开发者工具、charles、Fiddler,都会以树形结构展示 JSON 数据,非常友好。但也有些服务端语言还没有支持这种方式,例如 php 就无法通过 $_POST 对象从上面的请求中获得内容。这时候,需要自己动手处理下:在请求头中 Content-Type 为 application/json 时,从 php://input 里获得原始输入流,再 json_decode 成对象。一些 php 框架已经开始这么做了。

当然 AngularJS 也可以配置为使用 x-www-form-urlencoded 方式提交数据。

text/xml

它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范。典型的 XML-RPC 请求是这样的:

1
2
POST http://www.example.com HTTP/1.1
Content-Type: text/xml
1
2
3
4
5
6
7
8
9
<!--?xml version="1.0"?-->
<methodcall>
<methodname>examples.getStateName</methodname>
<params>
<param>
<value><i4>41</i4></value>

</params>
</methodcall>

XML-RPC 协议简单、功能够用,各种语言的实现都有。它的使用也很广泛,如 WordPress 的 XML-RPC Api,搜索引擎的 ping 服务等等。JavaScript 中,也有现成的库支持以这种方式进行数据交互,能很好的支持已有的 XML-RPC 服务。不过,我个人觉得 XML 结构还是过于臃肿,一般场景用 JSON 会更灵活方便。

很多人都会错的面试题

Posted on 2018-03-09

一道多数人都会错的前端面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Foo(){
this.b = 10
getName = function() {
console.log(1)
}
}
Foo.getName = function() {
console.log(2)
}
Foo.prototype.getName = function() {
console.log(3)
}
Foo.getName()
new Foo.getName()
Foo().b
Foo.b
Foo().getName()
new Foo().getName()

result

  • Foo.getName() //2
  • new Foo.getName() //2
  • new Foo.getName //2
  • Foo().b //Cannot read property ‘b’ of undefined
  • Foo.b //undefined
  • Foo().getName() //Cannot read property ‘getName’ of undefined
  • new Foo().getName() //3
Foo.getName()

这个很简单就是直接取Foo函数对象上的getName,自然是2

new Foo.getName()

这个其实是new (Foo.getName)(),首先new Foo.getName()执行过程:

  • 一个继承自 Foo.prototype 的新对象被创建。
  • 使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  • 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
new Foo.getName

同上

Foo().b

Foo()执行后没有任何返回结果

Foo.b

Foo.b上面是没有对应的b属性,上面函数中对应的this是指向window的


Foo().getName()

Foo()执行后没有任何返回结果

new Foo().getName()

new Foo()首先返回Foo类实例对象,然后查找当前对象上的getName函数,没有则上prototype查找

this的理解

Posted on 2018-03-08

this没有明确的定义,也就意味着this是在运行时确定的,不是在定义时确定的。

误解1:

指向函数自身,看上去很合理。
常用的递归,在函数内部调用自己活是一个在第一次被调用时会解除自己绑定的事件处理器。

1
2
3
4
5
6
7
8
9
10
11
function foo(num){
console.log(“foo:”+ num);
this.count++;
}
foo.count = 0;
for (var i =0;i < 10;i++) {
if (i > 5) {
foo(i);
}
}
console.log(foo.count); //0 这TM是怎么回事。。。?

— 解:当代码执行foo.count=0;它确实在函数对象foo中加入了一个count属性,但函数内部this.count,this指向的根本不是函数对象,而是window,那么这个是个什么鬼,那window上的count就是正确的执行次数?不,并不是,这个count是全局变量,而且值是NaN,因为刚刚开始this.count值为undefined,这样加上数字就是NaN。
我们可以创建一个对象来持有这个count值。但这是避免了this的问题,这是一个我们便于理解的词法作用域。
注意:在函数中有一个老大但现在被废弃了,而arguments.callee引用也指向当前正在执行的函数的内部。这通常是匿名函数在内部自己访问函数对象的唯一方法,但最佳的是完全避免匿名函数。
还有一种使用函数对象来执行调用。另一种是强制指向函数对象,call调用就好。

误解2:

this不知怎的指向了函数作用域。这种说法有错有对。
明确地说,this不会以任何方式指向函数的此法作用域。作用域好像是一个将所有可用标识符作为属性的对象,从内部看是对的。但JavaScript代码不能访问作用域对象,只是引擎内部实现。

1
2
3
4
5
6
7
8
9
function foo(){
var a = 2;
this.bar();
}

function() {
console.log(this.a);
}
foo();

— 执行结果无疑:undefined。
中间过程:这个代码不止一个错貌似还阔以执行。
首先,试图通过this.bar();来引用bar()函数。他几乎碰巧能够工作,调用bar最自然方式是避开this。代码想通过this来建立foo和bar之间的桥,使用bar可访问foo内部作用域的a。这样的桥是不可能的,使用this在词法作用域中查找东西,这是不可能的。
到底啥是this?
this不是编写时绑定,而是运行时绑定,依赖函数调用上下文。当函数被调用时,会建立一个活动记录,称为执行上下文环境。包含何处调用,参数等等,属性之一就是this。
this实际上是在函数调用时建立的绑定,它指向什么完全由函数被调用的调用点来决定。

js引擎执行过程

Posted on 2018-03-08

分析一段代码的创建执行过程

1
2
3
4
5
6
7
8
9
var y = 10;
function test(){
var y = 2;
return function(){
alert(y);
}
}
var fun = test();
fun();

对于上述代码的执行过程:
在执行全局代码前,创建全局上下文即:

1
2
3
4
5
globalEC = {
this: window,
vo: window,
sc: [winodw]
}

注:凡是创建上下文,总会有三个属性,this、vo、sc。
接下来进行变量实例化:在变量实例化时所有变量均为undefined,同时会创建test函数(testFun),由于在函数创建时确定函数的scope,
而函数的scope为函数创建时所在上下文的作用域链,testFun函数是在全局上下文中创建,所以testFun[[scope]]为globalEC.sc——[window]

1
2
3
4
5
vo:{
y: undefined,
fun: undefined,
test: testFun
}

全局代码执行:
首先 var y = 10; window的y属性由undefined变为10

1
2
3
4
5
vo:{
y: 10,
fun: undefined,
test: testFun
}

接下来到 var fun = test();
执行test函数时,创建test函数的函数上下文即:

1
2
3
4
5
testEC = {
this: window,
vo: testAO,
sc: [testAO, testFun.[[scope]]] = [testAO, window]
}

注:函数上下文的作用域链始终包含两个元素,当前激活对象和当前函数的[[scope]]。
激活对象就是testAO,而函数的[[scope]]则是当前函数创建时所在的上下文的作用域链,所以testEC的作用域链为[testAO,window]。
接下来变量实例化:

1
2
3
testAO: {
y: undefined
}

实例化后接着函数代码执行:
首先var y = 2; testAO.y值由undefined 变为 2

1
2
3
testAO: {
y: 2
}

接着执行:

1
2
3
return function(){        
alert(y);
}

创建匿名函数nFun:上面说到了创建函数的时候回确定函数的[[scope]],由于它是在test函数中创建的,所以nFun[[scope]] = testEC.sc = [testAO, window]

执行完fun = test()后,fun由undefined变为nFun
全局上下文中的变量对象

1
2
3
4
5
vo:{
y: 10,
fun: nFun,
test: testFun
}

接着执行fun();
前面说到了在函数执行之前,会创建函数上下文:它是在test函数中被创建的,所以nFun的作用域链为当前激活对象+test函数的上下文的作用域链。

1
2
3
4
5
nFunEC = {
this: window,
vo: nAO,
sc: [nAO, nFun[[scope]]] = [nAO, testAO, window]
}

最后执行alert(y)
从nFunEC.sc中查找y
在testAO中找到且值为2

databinding

Posted on 2018-03-08

很多的前端框架都支持数据双向绑定了,最近正好在看双向绑定的实现,就用Javascript写了几个简单的例子。

几个例子中尝试使用了下面的方式实现双向绑定:

  1. 发布/订阅模式
  2. 属性劫持
  3. 脏数据检测

发布/订阅模式

实现数据双向绑定最直接的方式就是使用PubSub模式:

  • 当model发生改变的时候,触发Model change事件,然后通过响应的事件处理函数更新界面
  • 当界面更新的时候,触发UI change事件, 然后通过相应的事件处理函数更新Model,以及绑定在Model上的其他界面控件

根据这个思路,可以定义’ui-binding-event’和’model-binding-event’两个事件,然后针对Model和UI分别进行这两个事件订阅和发布。

UI更新

对于所有支持双向绑定的页面控件,当控件的“值”发生改变的时候,就触发’ui-binding-event’,然后通过事件处理函数更新Model,以及绑定在Model上的其他界面控件

处理控件“值”的改变,发布“ui-binding-event”事件,(这里只处理包含“lj-binding”属性的控件):

// keyup和change事件处理函数
function pageElementEventHandler(e) {
    var target = e.target || e.srcElemnt;
    var fullPropName = target.getAttribute('lj-binding');

    if(fullPropName && fullPropName !== '') {
        Pubsub.publish('ui-binding-event', fullPropName, target.value);
    }

}

// 在页面上添加keyup和change的listener
if(document.addEventListener) {
    document.addEventListener('keyup', pageElementEventHandler, false);
    document.addEventListener('change', pageElementEventHandler, false);
} else {
    document.attachEvent('onkeyup', pageElementEventHandler);
    document.attachEvent('onchange', pageElementEventHandler);
} 

另外,对所有包含“lj-binding”属性的控件都订阅了“’model-binding-event”,也就是当Model变化的时候会收到相应的通知:

// 订阅model-binding-event事件, 根据Model对象的变化更新相关的UI
Pubsub.subscrib('model-binding-event', function(fullPropName, propValue) {   
    var elements = document.querySelectorAll('[lj-binding="' + fullPropName + '"]');

    for(var i = 0, len =elements.length; i < len; i++){
        var elementType = elements[i].tagName.toLowerCase();

        if(elementType === 'input' || elementType === 'textarea' || elementType === 'select') {
            elements[i].value = propValue;
        } else {
            elements[i].innerHTML = propValue;
        }

    }
});    

Model更新

对于Model这一层,当Model发生改变的时候,会发布“model-binding-event”:

// Model对象更新方法,更新对象的同时发布model-binding-event事件
'updateModelData': function(propName, propValue) {    
    eval(this.modelName)[propName] =propValue;   
    Pubsub.publish('model-binding-event', this.modelName + '.' + propName, propValue);
}

另外,Model订阅了“ui-binding-event”,相应的界面改动会更新Model

// 订阅ui-binding-event事件, 将UI的变化对应的更新Model对象
Pubsub.subscrib('ui-binding-event', function(fullPropName, propValue) {
    var propPathArr = fullPropName.split('.');
    self.updateModelData(propPathArr[1], propValue);
});

有了这些代码,一个简单的双向绑定例子就可以运行起来了:

  1. 初始状态
    1 1
  1. UI变化,Model会更新,绑定在Model上的其他控件也被更新
    1 2
  1. 通过”updateModelData”更新Model,绑定在Model上的控件被更新
    1 3

完整的代码请参考databinding:PubSub。

属性劫持

在“发布/订阅模式”实现双向绑定的例子中,为了保证Model的更新能够发布“model-binding-event”,对于Model对象的改变必须通过“updateModelData”方法。
也就是说,通过Javascript对象字面量直接更新对象就没有办法触发双向绑定。

Javascript中提供了“Object.defineProperty”方法,通过这个方法可以对对象的属性进行定制。

结合“Object.defineProperty”和“发布/订阅模式”,对Model属性的set方法进行重定义,将“model-binding-event”事件的发布直接放在Model属性的setter中:

'defineObjProp': function(obj, propName, propValue) {
    var self = this;

    var _value = propValue || '';

    try {
        Object.defineProperty(obj, propName, {
            get: function() {
                return _value; 
            },

            // 在对象属性的setter中添加model-binding-event事件发布动作
            set: function(newValue) {
                _value = newValue;
                Pubsub.publish('model-binding-event', self.modelName + '.' + propName, newValue);
            },
            enumerable: true,
            configurable: true
        });

        obj[propName] = _value;
    } catch (error) {
        alert("Browser must be IE8+ !");
    }
}

这样,就可以使用对象字面量的方式直接对Model对象进行修改:

2

但是,对于IE8及以下浏览器仍需要使用其它方法来做hack。

完整的代码请参考databinding:Hijacking。

脏数据检测

对于AngularJS,是通过脏数据检测来实现双向绑定的,下面就仿照脏数据检测来实现一个简单的双向绑定。

在这个例子中,作用域scope对象中会维护一个“watcher”数组,用来存放所以需要检测的表达式,以及对应的回调处理函数。

对于所有需要检测的对象、属性,scope通过“watch”方法添加到“watcher”数组中:

Scope.prototype.watch = function(watchExp, callback) {
    this.watchers.push({
        watchExp: watchExp,
        callback: callback || function() {}
    });

}

当Model对象发生变化的时候,调用“digest”方法进行脏检测,如果发现脏数据,就调用对应的回调函数进行界面的更新:

Scope.prototype.digest = function() {
    var dirty;

    do { 
        dirty = false;

        for(var i = 0; i < this.watchers.length; i++) {
            var newVal = this.watchers[i].watchExp(),
                oldVal = this.watchers[i].last;

            if(newVal !== oldVal) {
                this.watchers[i].callback(newVal, oldVal);
                dirty = true;
                this.watchers[i].last = newVal;
            }
        }
    } while(dirty);

}

完整的代码请参考databinding:Digest。

20180308_PWA

Posted on 2018-03-08

PWA初级

what is pwa ?

  • PWA,全称Progressive Web Apps是Google提出的用前沿的Web技术为网页提供APP般使用体验的一系列方案。

prepare

  • 首先准备好http-server、ngrok

  • 需要的基础html及css

  • manifest.json文件

manifest.json

  • 该文件主要是让PWA应用呗添加到主屏幕时,定义应用的名称,图标icon等信息

showtime😋

我们添加必要的html代码和引入css、manifest.json,然后加入index.js来注册serviceWorker。
https://www.w3.org/TR/service-workers-1/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (navigator.serviceWorker != null) {
navigator.serviceWorker.register('sw.js')
.then(function(registration) {
console.log('Registered events at scope: ', registration.scope);
});
}

在serviceWorker中会用到一些全局变量:
>self:表示Service Worker作用域,也是全局变量

>caches:表示缓存

>skipWaiting:表示强制当前处在waiting状态的脚本进入activate状态

>clients:表示Service Worker接管的页面

缓存

定义缓存

var cacheStorageKey = ‘minimal-pwa-1’ //cache-key
var cacheList = [ //cache-list
‘/‘,
“index.html”,
“index.css”,
“./image/clear.png”
]

借助Service Worker写缓存

1
2
3
4
5
6
7
8
9
self.addEventListener('install', function(e) {
e.waitUntil(
caches.open(cacheStorageKey).then(function(cache) {
return cache.addAll(cacheList)
}).then(function() {
return self.skipWaiting()
})
)
})

调用self.skipWaiting()可以在页面更新过程中,新的Service Worker脚本能立即激活和生效

动态缓存

网页抓取资源过程中,在Service Worker可以捕获fetch事件,可以自定义资源的响应请求

1
2
3
4
5
6
7
8
9
10
11

self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response != null) {
return response
}
return fetch(e.request.url)
})
)
})

例:我们可以通过fetch事件来处理获取资源异常的case,从而增强程序完整性和健壮性、可用性

更新静态资源

缓存的资源随着版本的更新会过期,所以会根据缓存的字符串名称清除旧缓存

1
2
3
4
5
6
7
8
9
10
11
12
self.addEventListener('activate', function(e) {
e.waitUntil(
caches.keys().then(function(keyList){
return Promise.all(keyList.map(function(key){
if (key !== cacheStorageKey) {
return caches.delete(key)
}
}))
})
)
return self.clients.claim()
})

在新安装的Service Worker中通过调用self.clients.claim()取得页面的控制权,保证使用最新缓存。旧sw脚本也会停止。
确保在每次修改了 service worker 后修改 cacheName,这能确保你永远能够从缓存中获得到最新版本的文件。过一段时间清理一下缓存删除掉没用的数据也是很重要的。

我就想看看它长什么样子?😏

1
2
http-server -c-1
ngrok http 8080

浏览器打开http://localhost:8080

页面更新

页面被缓存之后,需要适当处理缓存失败时页面失效的更新。被缓存的资源是无法发起请求来判断是否被更新了,只有sw.js会自动根据http缓存机制尝试去判断应用是否被更新。
所以页面发生修改时,要同步修改sw.js文件,保证缓存名称也修改了。

1
2
<h3>Revision 2</h3>
let cacheStorageKey = 'minimal-pwa-2'

然后重新打开页面,这个时候渲染的页面依然是旧的,不过从devtools里看到sw.js被安装和激活。之后再次打开可以网页版本为2

PWA进阶版本

何为进阶版本,那就是看起来比较牛逼的版本。

App Shell

从缓存中加载app shell

*Service workers 可以截获 Progressive Web App 发起的请求并从缓存中返回响应。这意味着我们能够 决定如何来处理这些请求,以及决定哪些网络响应能够成为我们的缓存。

*从内至外,caches.match() 从网络请求触发的 fetch 事件中得到请求内容,并判断请求的资源是 否存在于缓存中。然后以缓存中的内容作为响应,或者使用 fetch 函数来加载资源(如果缓存中没有该资源)。 response 最后通过 e.respondWith() 返回给 Web 页面。

Push

😋 Push API提供Web应用消息推送能力,需要获取用户权限。

第一种姿势-本地模拟

1
2
3
4
5
6
7
8
9
default: unknown
granted:已授权
let notification = new Notification({})
denied:未授权
Notification.permission === 'default' || Notification.permission !== 'denied'
Notification.requestPermission(permission => {
permission === 'granted' &&
(let notification = new Notification({}))
})

https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission

第二种姿势-firebase 😌鉴权登录有毒啊啊啊啊啊

https://console.firebase.google.com/project/schacker-pwa/notification
https://developers.google.com/web/fundamentals/engage-and-retain/push-notifications/subscribing-a-user

对于 Push Notification,一是因为 Push API 还处于 Editor Draft 的状态,二是目前浏览器与推送服务的互相支持都还不够成熟:Android 上的 Chrome(与其它基于 Blink 的浏览器)目前只支持基于 Google 私有的 GCM/FCM 的通知推送,只有 Firefox 已经实现了成在由 IETF 进行标准化的 Web 推送协议(Web Push Protocol)。

不过,如果你已经在使用 Google 的云服务(比如 Firebase),并且主要面向的是海外用户,那么在 Web 应用上支持基于 GCM/FCM 的推送通知并不是一件费力的事情。
对于
对于firebase推送这块,可以自己搭建服务器来调用Google SDK提供的API接口实现自定义推送,包括自己的推送后台

https://developers.google.com/web/fundamentals/engage-and-retain/push-notifications/sending-messages-with-web-push-libraries

有待挖掘

使用技术 ep:
Ajax、响应式设计、JavaScript 框架、ECMAScript Next、CSS Next、Houdini、Indexed DB、Device APIs、Web Bluetooth、Web Socket、Web Payment、孵化中的 Background Sync API、Streams、WebVR

12

schacker

17 posts
2 tags
© 2019 schacker
Powered by Hexo
|
Theme — NexT.Muse v5.1.4