Giter Site home page Giter Site logo

blog's Introduction

Description

🏡小码农的自留地

喜欢开坑(新文),不时完善(旧文),欢迎收藏(star)

Some Articles ⬇︎

JS

HTML && CSS

数据结构与算法

leetcode

other

Library

HTTP

开发小记

设计模式

翻译

其他

点击加入博客聊天群

博客封面

blog's People

Contributors

aermin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

js事件详解

javaScript 与 HTML 之间的交互是通过事件实现的。

事件流

概念:事件流描述的是从页面中接收事件的顺序。

事件冒泡

概念:即事件开始时由最具体的元素(文档中嵌套层次最深 的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

image

理论及运用

  • 任何可以冒泡的事件都不仅仅可以在事件目标上进行处理,目标的任何祖先节点上也能处理。

运用事件委托 更详细的介绍看这篇文章

事件委托原理:以将事件处理程序附加到更高层的地方负责多个目标的事件处理。

大多数 Web 应用在用户交互上大量用到事件处理程序。页面上的事件处理程序的数量和页面响应 用户交互的速度之间有个负相关。为了减轻这种惩罚,最好使用事件代理。

事件委托实例

<ul id="list">  
        <li>我是孩子1</li>  
        <li>我是孩子2</li>  
        <li>我是孩子3</li>  
        <li>我是孩子4</li>  
        <li>我是孩子5</li>  
</ul> 
<div id = "btn">添加一个新孩子</div>

场景1:需要在点击列表项的时候响应一个事件。如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

场景2:动态添加新的Li元素。在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;

代码: 代码运行

//事件委托
var ul = document.getElementById('list'); 
ul.addEventListener('click', function (event) {
  // 兼容性处理
  var event = event||window.event;
  var target = event.target || event.srcElement;//target表示在事件冒泡中触发事件的源元素,在IE中是srcElement  
  // 判断是否匹配目标元素
  if (target.nodeName.toLowerCase() == 'li') {
       alert(target.innerHTML);
  }
});
//添加li元素
document.getElementById('btn').addEventListener('click', function (event) {
        var li = document.createElement('li');  
        li.innerHTML="我是新孩子";  
        ul.appendChild(li);  
})
  • 不是所有的事件都能冒泡。以下事件不冒泡:blur、focus、load、unload。

  • 有些业务场景需要阻止冒泡事件的冒泡,比如在一个HTML结构中,父级包含着子级,当事件在子级发生时(click,mouseenter,mouseleave等),由于事件冒泡就会触发父级的同名事件。示例:

<ul id="parent">
    <li id="child">son</li>
</ul>
   child.addEventListener("click",function(event){
        alert(1);
        // event stopPropagation(); //标准浏览器
        //或者是
        //event cancelBubble = true; //IE
    })
    parent.addEventListener("click",function(event){
        alert(1);
    })

当点击li时,会弹出两个1,可以通过阻止冒泡防止这样的行为,点击li就弹出一个1。
event.stopPropagation()或event.cancelBubble = true 可以阻止事件冒泡。

  • 阻止冒泡并不能阻止对象默认行为。比如submit按钮被点击后会提交表单数据,这种行为无须我们写程序定制。

应用

  • 事件代理,事件冒泡允许多个操作被集中处理(把事件处理器添加到一个父级元素上,避免把事件处理器添加到多个子级元素上),它还可以让你在对象层的不同级别捕获事件

  • 让不同的对象同时捕获同一事件,并调用自己的专属处理程序做自己的事情,就像老板一下命令,各自员工做自己岗位上的工作去了。

事件捕获

概念:事件捕获的** 是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在 事件到达预定目标之前捕获它。

image

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段::事件捕获阶段、处于目标阶段和事件冒泡阶段。

首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶 段,可以在这个阶段对事件做出响应。

image

在 DOM 事件流中,实际的目标(

元素)在捕获阶段不会接收到事件。这意味着在捕获阶段, 事件从 document 到再到后就停止了。下一个阶段是“处于目标”阶段,于是事件在
上发生,并在事件处理(后面将会讨论这个概念)中被看成冒泡阶段的一部分。然后,冒泡阶段发生, 事件又传播回文档。

事件处理程序

概念:事件就是用户或浏览器自身执行的某种动作。诸如 click、load 和 mouseover,都是事件的名字。 而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以"on"开头,onclick等。

HTML事件处理程序

概念:某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的 HTML 特性来指定。

例如,要在按钮被单击时执行一些 JavaScript,可以像下面 这样编写代码:

<input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)" />

缺点

首先,存在一个时差问题。因为用户可能会在 HTML 元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。

另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。

最后一个缺点是 HTML 与 JavaScript 代码紧密耦合。如果要更换事 件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码。

DOM0 级事件处理程序

简单来说就是取得一 个要操作的对象的引用,然后为它指定了某事件处理程序(如onclick)。使用 DOM0 级方法指定的事件处理程序被认为是元素的方法,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
    alert(this.id); //"myBtn" 
};

btn.onclick = null;//删除事件处理程序

将事件处理程序设置为 null 之后,再单击按钮将不会有任何动作发生。

DOM2 级事件处理程序

“DOM2 级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener() 和 removeEventListener()。

onclick 事件会被覆盖,addevenlistener 事件先后执行。

所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获 阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。没有指定的话,默认为 false。

target.addEventListener(type, listener, useCapture);
var btn = document.getElementById("myBtn"); 
btn.addEventListener("click", function(){ 
    alert(this.id); 
}, false);

上面的代码为一个按钮添加了 onclick 事件处理程序,而且该事件会在冒泡阶段被触发(因为最后一个参数是 false,此时和DOM0级的onclick事件差不多)。

使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id); 
}, false); 
btn.addEventListener("click", function(){
    alert("Hello world!"); 
}, false);

这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的 ID,其次会显示"Hello world!"消息。

MDN 对第三个参数的解释是:Boolean,是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件目标,接收到该事件。

沿着DOM树向上冒泡的事件不会触发被指定为为true的listener(因为此时为true是比冒泡更早的事件捕获阶段)。

例子: (引自csdn的一篇文章

20170502210420519

<style>
.parent
{
    position: relative;
    background-color: coral;
    border: 1px solid;
    padding: 50px;
}
.child {
    position: relative;
    background-color: pink;
    width: 100px;
    height: 100px;
}
</style>
<body>
<p>该实例演示了在添加事件监听时冒泡与捕获阶段的不同。</p>
<div id="parent" class="parent">
    <div id="child" class="child">点击该方块, 我是冒泡</div>
</div><br>
<div id="parent2" class="parent">
    <div id="child2" class="child">点击该方块, 我是捕获</div>
</div>
<script>
//事件冒泡    child的DOM事件先发生(粉),parent的DOM事件再发生(橙)。
document.getElementById("child").addEventListener("click", function(){
    alert("你点击了 粉色!");
}, false);
document.getElementById("parent").addEventListener("click", function(){
    alert("你点击了 橙色!");
}, false);

//事件捕获  parent的DOM事件先发生(橙),parent的DOM事件再发生(粉)。
document.getElementById("child2").addEventListener("click", function(){
    alert("你点击了 粉色!");
}, true);
document.getElementById("parent2").addEventListener("click", function(){
    alert("你点击了 橙色!");
}, true);
</script>
</body>

点击查看实例效果

大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需 要,我们不建议在事件捕获阶段注册事件处理程序。

事件对象

兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什 么方法(DOM0 级或 DOM2 级),都会传入 event 对象。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(event){
    alert(event.type); //"click" 
}; 
btn.addEventListener("click", function(event){
alert(event.type); //"click" 
}, false);

触发的事件类型不一样,可用的属性和方法也不一样。常见的有event.type,event.target 等

事件类型

Web 浏览器中可能发生的事件有很多类型。如前所述,不同的事件类型具有不同的信息,而“DOM3 级事件”规定了以下几类事件。

  • UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发; 如resize,scroll
  • 焦点事件,当元素获得或失去焦点时触发; 如blur (在元素失去焦点时触发,这个事件不会冒泡)
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发;如click
  • 滚轮事件,当使用鼠标滚轮(或类似设备)时触发;
  • 文本事件,当在文档中输入文本时触发;
  • 键盘事件,当用户通过键盘在页面上执行操作时触发;
  • 合成事件,当为 IME(Input Method Editor,输入法编辑器)输入字符时触发;
  • 变动(mutation)事件,当底层 DOM 结构发生变化时触发。

内存和性能

事件委托

这个前面讲到了 ,这边不重复。

移除事件处理程序

每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就 会建立一个连接。。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立 的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那 些过时不用的“空事件处理程序”(dangling event handler),也是造成 Web 应用程序内存与性能问题的 主要原因。

在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。 这可能是通过纯粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法。但更多地是发 生在使用 innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被 innerHTML 删除 了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。

<div id="myDiv"> 
    <input type="button" value="Click Me" id="myBtn"> 
</div>
<script type="text/javascript">
    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
    //先执行某些操作
   btn.onclick = null; //移除事件处理程序
    document.getElementById("myDiv").innerHTML = "处理中..."; //把按钮替换成文字
}; 
</script>

这里,有一个按钮被包含在

元素中。为避免双击,单击这个按钮时就将按钮移除并替换成一 条消息;这是网站设计中非常流行的一种做法。但问题在于,当按钮被从页面中移除时,它还带着一个 事件处理程序呢。在
元素上设置 innerHTML 可以把按钮移走,但事件处理程序仍然与按钮保持 着引用关系。有的浏览器(尤其是 IE)在这种情况下不会作出恰当地处理,它们很有可能会将对元素和 对事件处理程序的引用都保存在内存中。如果你知道某个元素即将被移除,那么最好手工移除事件处理 程序

第二种情况,就是卸载页面的时候。如果在页面被卸载之前没 有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页 面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序 占用的内存并没有被释放。一般来说,最好的做法是在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序。 在此,事件委托技术再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。对这 种类似撤销的操作,我们可以把它想象成:只要是通过 onload 事件处理程序添加的东西,最后都要通 过 onunload 事件处理程序将它们移除。

模拟事件

概念:使用 JavaScript 在任意时刻来触发特定的事件,此时的事件就如同浏览器创建的事件一样。

模拟鼠标事件

思路: 1.创建事件对象 2.分发事件

          //创建事件对象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //分发事件 
          document.getElementById("myBtn").dispatchEvent(event);

ps:红包书介绍的创建事件对象的方法有点过时了,现被MDN不建议使用

    var event = document.createEvent("MouseEvents");  //创建事件对象 
    event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  //初始化事件对象 

image

实例运用 代码运行

这边有两个按钮,按钮1绑定一个鼠标点击事件1,alert出一句话;在按钮2绑定一个鼠标点击事件2,鼠标点击事件2将模拟用户去点击按钮1。所以点按钮2,触发一个模拟鼠标事件去点击按钮1。

<body>
    <button id="btn1">我是按钮1</button>
    <button id="btn2">我是按钮2,点我一下 按钮1也会被点击哦</button>
    <script type="text/javascript">
        var btn=document.getElementById("#btn1");
        // 按钮1的点击事件
        btn.addEventListener('click',function(e){
           console.log("我是按钮1,我被点击啦") // 点击按钮2,按钮1也会被点击(模拟事件);只点击按钮1(正常事件)
        })
        // 按钮2的点击事件
        document.querySelector("#btn2").addEventListener('click',function(e){
           //模拟鼠标事件步奏1  创建事件对象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //模拟鼠标事件步奏2  触发事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

模拟键盘事件

这边举一反三,不做解释。

模拟其他事件

自定义 DOM 事件

实例运用 代码运行

<body>
    <button id="btn1">我是按钮1,我没鼠标事件,点我不会输出</button>
    <button id="btn2">我是按钮2,点我一下会执行绑定在按钮1的自定义事件,会有输出</button>
    <script type="text/javascript">
       var btn= document.querySelector("#btn1");
        // 按钮1的点击事件
        btn.addEventListener('test',function(e){
           console.log("我是按钮1,我被点击啦") // 点击按钮2,有输出(自定义事件);只点击按钮1,没输出(自定义事件,非模拟鼠标事件)
        })
       // 按钮2的点击事件
        document.querySelector("#btn2").addEventListener('click',function(e){
          //自定义事件步奏1  创建事件对象
           var event = new Event('test', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //自定义鼠标事件步奏2  触发事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

红宝书的方法有点过时,新实现方法可看这篇文章

正则总结

正则表达式

使用下面类似 Perl 的语法,就可以创建一个正 则表达式。

var expression = / pattern / flags ;

其中的模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、 向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。 正则表达式的匹配模式支持下列 3 个标志。

  • g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即 停止;
  • i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模 式匹配的项。
var text = "aaa "; 
var pattern1 = /\s$/; //匹配字符串末尾的空格 
pattern1.exec(text);

正则定义了很多特殊意义的字符,有名词,量词,谓词等

简单字符

没有特殊意义的字符都是简单字符,简单字符就代表自身,绝大部分字符都是简单字符,举个例子

/abc/ // 匹配 abc
/123/ // 匹配 123
/-_-/ // 匹配 -_-

转义字符

\是转义字符,其后面的字符会代表不同的意思,转义字符主要有三个作用:

第一种,是为了匹配不方便显示的特殊字符,比如换行,tab符号等

第二种,正则中预先定义了一些代表特殊意义的字符,比如\w等

第三种,在正则中某些字符有特殊含义(比如下面说到的),转义字符可以让其显示自身的含义

下面是常用转义字符列表:

常用转义字符 意义
\n 匹配换行符 (newline)
\r 匹配回车符 (return)
\t 匹配制表符,也就是tab键
\v 匹配垂直制表符
\x20 20是2位16进制数字,代表对应的字符
\u002B 002B是4位16进制数字,代表对应的字符
\u002B 002B是4位16进制数字,代表对应的字符
\w 匹配任何一个字母或者数字或者下划线 单子字符 (word)
\W 匹配任何一个字母或者数字或者下划线以外的字符 非单子字符
\s 匹配空白字符,如空格,tab等 (space)
\S 匹配非空白字符
\d 匹配数字字符,0~9 (digit)
\D 匹配非数字字符
\b 匹配单词的边界 (boundary)
\B 匹配非单词边界
\ 匹配\本身

字符集合

有时我们需要匹配一类字符,字符集可以实现这个功能,字符集的语法用[]分隔,下面的代码能够匹配a或b或c

[abc]

如果要表示字符很多,可以使用-表示一个范围内的字符,下面两个功能相同

[0123456789]

[0-9]

在[]内的开头添加^,可表示非的意思,下面的代码能够匹配abc之外的任意字符

[^abc]

其实正则还内置了一些字符集,在上面的转义字符有提到,下面给出内置字符集对应的自定义字符集

匹配除了换行符(\n)以外的任意一个字符 = [^\n]
\w = [0-9a-zA-Z_]
\W = [^0-9a-zA-Z_]
\s = [ \t\n\v]
\S = [^ \t\n\v]
\d = [0-9]
\D = [^0-9]

量词

如果我们有三个苹果,我们可以说自己有个3个苹果,也可以说有一个苹果,一个苹果,一个苹果,每种语言都有量词的概念

如果需要匹配多次某个字符,正则也提供了量词的功能,正则中的量词有多个,如?、+、*、{n}、{m,n}、{m,}

{n}匹配n次,比如a{2},匹配aa

{m, n}匹配m-n次,优先匹配n次,比如a{1,3},可以匹配aaa、aa、a

{m,}匹配m-∞次,优先匹配∞次,比如a{1,},可以匹配aaaa...

?匹配0次或1次,优先匹配1次,相当于{0,1}

+匹配1-n次,优先匹配n次,相当于{1,}

*匹配0-n次,优先匹配n次,相当于{0,}

正则默认和人心一样是贪婪的,也就是常说的贪婪模式,凡是表示范围的量词,都优先匹配上限而不是下限

a{1, 3} // 匹配字符串'aaa'的话,会匹配aaa而不是a

var text = "aaa"; 
var pattern1 = /a{1,3}/;
pattern1.exec(text); //["aaa", index: 0, input: "aaa"]

有时候这不是我们想要的结果,可以在量词后面加上?,就可以开启非贪婪模式

var text = "aaa"; 
var pattern1 = /a{1,3}?/;
pattern1.exec(text);//["a", index: 0, input: "aaa"]

字符边界

有时我们会有边界的匹配要求,比如以xxx开头,以xxx结尾

^在[]外表示匹配开头的意思
^abc // 可以匹配abc,但是不能匹配aabc

$表示匹配结尾的意思
abc$ // 可以匹配abc,但是不能匹配abcc
上面提到的\b表示单词的边界

\b表示匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者没有其他“字”字符在其前面的位置。
abc\b // 可以匹配 abc ,但是不能匹配 abcc;
/\bm/匹配“moon”中得‘m’;
/oo\b/并不匹配"moon"中得'oo',因为'oo'被一个“字”字符'n'紧跟着。
/oon\b/匹配"moon"中得'oon',因为'oon'是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。
/\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。

选择表达式

有时我们想匹配x或者y,如果x和y是单个字符,可以使用字符集,[abc]可以匹配a或b或c,如果x和y是多个字符,字符集就无能为力了,此时就要用到分组

正则中用|来表示分组,a|b表示匹配a或者b的意思

123|456|789 // 匹配 123 或 456 或 789

分组与引用

分组是正则中非常强大的一个功能,可以让上面提到的量词作用于一组字符,而非单个字符,分组的语法是圆括号包裹(xxx)

(abc){2} // 匹配abcabc

分组不能放在[]中,分组中还可以使用选择表达式

(123|456){2} // 匹配 123123、456456、123456、456123

和分组相关的概念还有一个捕获分组和非捕获分组,分组默认都是捕获的,在分组的(后面添加?:可以让分组变为非捕获分组,非捕获分组可以提高性能和简化逻辑

'123'.match(/(?:123)/) // 返回 ['123']
'123'.match(/(123)/)  // 返回 ['123', '123']
和分组相关的另一个概念是引用,比如在匹配html标签时,通常希望<xxx></xxx>后面的xxx能够和前面保持一致

引用的语法是\数字,数字代表引用前面第几个捕获分组,注意非捕获分组不能被引用

<([a-z]+)><\/\1> // 可以匹配 `<span></span>` 或 `<div></div>`等

预搜索

如果你想匹配xxx前不能是yyy,或者xxx后不能是yyy,那就要用到预搜索

js只支持正向预搜索,也就是xxx后面必须是yyy,或者xxx后面不能是yyy

1(?=2) // 可以匹配12,不能匹配22
1(?!2) // 可有匹配22,不能匹配12

修饰符

默认正则是区分大小写,这可能并不是我们想要的,正则提供了修饰符的功能,修复的语法如下

/xxx/gi // 最后面的g和i就是两个修饰符

g正则遇到第一个匹配的字符就会结束,加上全局修复符,可以让其匹配到结束

i正则默认是区分大小写的,i可以忽略大小写

m正则默认情况下,^和$只能匹配字符串的开始和结尾,m修饰符可以让^和$匹配行首和行尾,不理解就看例子

/jing$/ // 能够匹配 'yanhaijing,不能匹配 'yanhaijing\n'
/jing$/m // 能够匹配 'yanhaijing, 能够匹配 'yanhaijing\n'

/^jing/ // 能够匹配 'jing',不能匹配 '\njing'
/^jing/m // 能够匹配 'jing',能够匹配 '\njing'

js使用正则表达式的方法

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法, 以及 String 的 match、replace、search 和 split 方法

方法 描述
exec 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回null)。
test 一个在字符串中测试是否匹配的RegExp方法,它返回true或false。
match 一个在字符串中执行查找匹配的String方法,它返回一个数组或者在未匹配到时返回null。
search 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。
split 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。

当你想要知道在一个字符串中的一个匹配是否被找到,你可以使用test或search方法;想得到更多的信息(但是比较慢)则可以使用exec或match方法。如果你使用exec或match方法并且匹配成功了,那么这些方法将返回一个数组并且更新相关的正则表达式对象的属性和预定义的正则表达式对象(详见下)。如果匹配失败,那么exec方法返回null(也就是false)。

var text = "cat, bat, sat, fat"; var pattern1 = /.at/;

var matches = pattern1.exec(text);
console.log(matches) //["cat", index: 0, input: "cat, bat, sat, fat"]

正则里面带变量

可用ES6模板语法,注意\要多写一次,不然会被new RegExp 过滤掉

const regexp = new RegExp(`@${this._userInfo.name}\\s\\S*|@${this._userInfo.name}$`);

参考鸣谢:
正则表达式教程——语法篇和mdn的正则表达式

正则匹配

web移动端开发总结1--适配篇(2017.10.10)

在公司主要写web移动端的项目,一开始较大的感触就是适配很麻烦,分ios和安卓,安卓生态又混乱得很,所以适配要做好了,不然这个设备好好的,有些设备却页面错乱。

在网上找了很多方案,踩了不少坑。

方案一:

(function (doc, win) {
          console.log("dpr:"+win.devicePixelRatio); 
          var docEle = doc.documentElement,
              isIos = navigator.userAgent.match(/iphone|ipod|ipad/gi),
              dpr=Math.min(win.devicePixelRatio, 3);
              scale = 1 / dpr,

              resizeEvent = 'orientationchange' in window ? 'orientationchange' : 'resize';

          docEle.dataset.dpr = dpr;

          var metaEle = doc.createElement('meta');
          metaEle.name = 'viewport';
          metaEle.content = 'initial-scale=' + scale + ',maximum-scale=' + scale;
          docEle.firstElementChild.appendChild(metaEle);
          

          var recalCulate = function () {
                  var width = docEle.clientWidth;
                  if (width / dpr > 640) {
                      width = 640 * dpr;
                   }
                docEle.style.fontSize = 20 * (width / 750) + 'px';
            };

          recalCulate()

          if (!doc.addEventListener) return;
          win.addEventListener(resizeEvent, recalCulate, false);
        })(document, window);

获取设备dpr
算出缩放比例 scale = 1/dpr
创建meta以及属性
将scale值赋给initial-scale,maximum-scale
meta插入到文档中
创建屏幕大小改变重新计算函数并监听

特点:这个方案根据设备等比例缩放,每个设备显示内容一致。
缺点:当我用这套方案时,有个问题,因为监听resizeEvent,导致页面打开会先内容变大,然后再正常显示,很是影响用户体验。
参考链接

方案二(推荐):

    //获取屏幕比例
    function sreenRatio() {
        const ua = navigator.userAgent;
        const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);
        const UCversion = ua.match(/U3\/((\d+|\.){5,})/i);
        const isUCHd = UCversion && parseInt(UCversion[1].split('.').join(''), 10) >= 80;
        const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
        var dpr = window.devicePixelRatio || 1;
        if (!isIos && !(matches && matches[1] > 534) && !isUCHd) {
            // 如果非iOS, 非Android4.3以上, 非UC内核, 就不执行高清, dpr设为1;
            dpr = 1;
        }
        return dpr;
    }
    //初始化屏幕比例
    function screenRatio(baseFontSize, fontscale) {
        var ratio = sreenRatio();     
        var scale = document.createElement('meta');
        var scaleRatio = 1 / ratio;
        scale.name = 'viewport';
        scale.content = 'width=device-width,'+'initial-scale=' + scaleRatio + ', maximum-scale=' + scaleRatio + ', minimum-scale=' +
            scaleRatio + ', user-scalable=no';
        var s = document.getElementsByTagName('title')[0];
        s.parentNode.insertBefore(scale, s);
        var _baseFontSize = baseFontSize || 100;
        var _fontscale = fontscale || 1;
        document.documentElement.style.fontSize = _baseFontSize / 2 * ratio * _fontscale+'px';
    }
        if (window.screen.width >= 768) {
            screenRatio(100, 1.5);//字体放大1.5倍
        } else {
            screenRatio();
        }

特点:

  • 引用简单,布局简便
  • 根据设备屏幕的DPR,自动设置最合适的高清缩放。
  • 保证了不同设备下视觉体验的一致性。(老方案是,屏幕越大元素越大;此方案是,屏幕越大,看的越多)
  • 有效解决移动端真实1px问题(这里的1px 是设备屏幕上的物理像素)
    ps:而且不会出现方案一的问题

缺点:1.有可能会出现字体会不受控制的变大的情况,解决方法:css加上一下内容

*, *:before, *:after { max-height: 100000px }
  1. 感觉没啥问题了,然而我司测试硬生生发现一个bug -> 在某安卓设备发现在QQ上打开网页出现页面错乱。解决方法:判断如果是安卓设备,scale.content加上target-densitydpi=device-dpi

修正:

    //获取屏幕比例
    function getDpr() {
        const ua = navigator.userAgent;
        const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);
        const UCversion = ua.match(/U3\/((\d+|\.){5,})/i);
        const isUCHd = UCversion && parseInt(UCversion[1].split('.').join(''), 10) >= 80;
        const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
        var dpr = window.devicePixelRatio || 1;
        if (!isIos && !(matches && matches[1] > 534) && !isUCHd) {
            // 如果非iOS, 非Android4.3以上, 非UC内核, 就不执行高清, dpr设为1;
            dpr = 1;
        }
        return dpr;
    }
    //初始化屏幕比例
    function screenRatio(baseFontSize, fontscale) {
        var dpr = getDpr();
        var scale = document.createElement('meta');
        var scaleRatio = 1 / dpr;
        scale.name = 'viewport';
		<%/*安卓设备兼容*/%>
		if (/Android/i.test(navigator.userAgent) == true) {
			scale.content = 'width=device-width, target-densitydpi=device-dpi,'+' initial-scale=' + scaleRatio + ', maximum-scale=' + scaleRatio + ', minimum-scale=' +
	            scaleRatio + ', user-scalable=no';
		<%/*iOS设备*/%>
		} else {
			scale.content = 'width=device-width,'+'initial-scale=' + scaleRatio + ', maximum-scale=' + scaleRatio + ', minimum-scale=' +
	            scaleRatio + ', user-scalable=no';
		}
        var s = document.getElementsByTagName('title')[0];
        s.parentNode.insertBefore(scale, s);
        var _baseFontSize = baseFontSize || 100;
        var _fontscale = fontscale || 1;
        document.documentElement.style.fontSize = _baseFontSize / 2 * dpr * _fontscale+'px';
    }
   
   var isAndroid = /Android/i.test(navigator.userAgent) ? true : false;
    <%/*安卓设备不做高清放大处理*/%>
    if (window.screen.width >= 768 && !isAndroid) {
        screenRatio(null, 1.5);<%/*字体放大1.5倍*/%>
    } else {
        screenRatio();
    }

知识点补充:

像素

实际上分为两种:设备像素和CSS像素

1、设备像素(device independent pixels): 设备屏幕的物理像素,任何设备的物理像素的数量都是固定的

2、CSS像素(CSS pixels): 是为web开发者创造的,在CSS和javascript中使用的一个抽象的层,每一个CSS声明和几乎所有的javascript属性都使用CSS像素,因此实际上从来用不上设备像素 ,唯一的例外是screen.width/height

DPR

设备像素比DPR(devicePixelRatio)是默认缩放为100%的情况下,设备像素和CSS像素的比值

DPR = 设备像素 / CSS像素(某一方向上)

以iphone5为例,iphone5的CSS像素为320px568px,DPR是2,所以其设备像素为640px1136px

  • 由于DRP的存在,所以如果不做处理,会有1px线变粗问题,如果DPR为2,那css的1px起始到了设备像素中变了2px的视觉效果。
    解决办法是viewport的initial-scale设置成1/dpr 在页面初始化时缩放成1/2,也就是css的1px变成0.5px,设备像素达到1px的视觉效果(你看到的)。

viewport

一个典型的针对移动端优化的站点包含类似下面的内容:

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

width:控制 viewport 的大小,可以指定的一个值,如果 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。
height:和 width 相对应,指定高度。
initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。
maximum-scale:允许用户缩放到的最大比例。
minimum-scale:允许用户缩放到的最小比例。
user-scalable:用户是否可以手动缩放。

参考链接

git日常开发使用的指令整理

日常git命令记录

每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为commit。

git log :查看git历史记录

git log --graph :看分支合并图

git log --pretty=oneline : 查看精简的git历史记录

image

在Git中,用HEAD表示当前分支&版本,即图中的master,commit 7b035146e7db9086b9e0973ba1de6f13a99721f1

上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。

git reset --hard HEAD^ :回退到上一个版本

git reset --hard {commit id} : 本地回滚,回到某个版本,你让HEAD指向哪个版本号,你就把当前版本定位在哪。

git push -u origin master -f : 远程回滚方法之一,可能会因为权限问题报错。同时也谨慎操作,备份预备。

Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅改变HEAD从指向。

git reflog : 记录你最近的每一次命令。可解决想恢复到新版本却找不到新版本的commit id时的问题,重新查明版本号

工作区 :在电脑里能看到的目录,比如文件夹就是一个工作区

版本库(Repository) : 工作区有一个隐藏目录.git,这个是Git的版本库,里面存了很多东西,包括叫stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。

image

把文件往Git版本库里添加的过程:

1.用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
2.用git commit提交更改,实际上就是把暂存区的所有内容(可以是多次add的)提交到当前分支。

git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

git diff HEAD -- readme.txt : 可以查看工作区和版本库里面readme.txt最新版本的区别

git checkout -- readme.txt : 把readme.txt文件在工作区的修改全部撤销,也就是add前的修改

git reset HEAD <file> : 可以把暂存区的修改撤销掉(unstage),也就是已经add后的修改

git branch : 查看分支

git branch <name> : 创建分支

git checkout <name> : 切换分支

git checkout -b <name> : 创建+切换分支

git merge <name> : 合并某分支到当前分支(Fast forward模式,删除分支后,会丢掉分支信息)

git merge --no-ff -m "merge with no-ff" dev: 禁用Fast forward,在merge时生成一个新的commit,即可从从分支历史上看出分支信息

git branch -d <name> : 删除本地仓库分支

git branch -D <name>: 强行删除本地仓库分支

git push origin -d <name> : 删除远程仓库分支

git checkout . : 本地所有修改的。没有的提交的,都返回到原来的状态

git remote: 查看远程库的信息
image

git remote -v: 查看更详细的远程库信息,如果没有推送权限,就看不到push的地址。

image

场景

场景 : add 完修改到储存区后 取消撤销修改

  • 版本回退 -> git reset --hard 版本号
  • 绿字变红字(撤销add) -> git reset HEAD
  • 红字变无 (撤销没add修改) -> git checkout -- 文件 撤销该文件修改 || git checkout . 撤销全部修改

clone 远程仓库到本地后只能看到master分支

git checkout -b dev origin/dev :创建远程origin的dev分支到本地,这样,本地才有了dev分支

git push origin master/dev : 把该分支上的所有本地提交推送到远程库

git pull : 如果推送失败,先抓取远程的新提交;

提交时需pull远程仓库最新代码到本地

image

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接

git branch --set-upstream-to=origin/<branch> dev : 指定dev和origin/dev的链接:

image
image

git remote add origin https://github.com/aermin/react-scaffold.git : 本地仓库链接远程仓库

git branch -m oldName newName : 重命名本地分支

git commit --amend : 合并缓存的修改和上一次的提交

当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交

git add --all:把所有文件修改及增删添加到暂存区
git stash : 把当前工作现场贮藏起来,你的变更都保存在栈上
git stash list : 查看现有的stash

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on dev: c264051 Revert "added file_size"

git stash apply stash@{1}: 如果你想应用更早的储藏,你可以通过名字指定它。
如果你不指明,Git 默认使用最近的储藏并尝试应用它,这里差点吓尿我了,我在A banch git stash,然后切到B branch,也 git stash。接着切回A banch,执行git stash pop,发现pop的是B banch stash的内容(以为数据被覆盖而丢了),也就是stash是无论你在哪个branch都会覆盖的,默认显示所有branch中最新的stash。

git stash pop : 复现之前储存起来的工作现场

也就是当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场。

这边是因为工作区和暂存区是共用的,在各个分支里都可以看到没被stash的文件,也就是比如:在dev分支,创建一个新文件test6.txt。并add它,让它stage。这时切回master分支,你会看到这个test6.txt居然也在master分支里。但它实际上是属于dev分支的。而如果用stash代替add到stage,则master分支,test6.txt就消失了。

在工作区和暂存区的文件都可以stash,pop之后都会出现在工作区。

image

合并最近两条commit

git reset --soft "HEAD^"
git commit --amend

git cherry-pick: 其含义就是从众多的提交中选出一个提交应用在当前的工作分支中

当加一条commit却发现应该还有遗漏的修改属于这条commit,又不想重新加一条新的commit怎么办?

可以编辑补充遗漏的部分,然后把修改添加到缓存区:git add . ,然后合并缓存区的修改到上一次commit上:git commit --amend

git merge 和 git rebase ,当你在本地feature开发时,原repo的master上你的同事在此期间提交了新的commit,此时如果切回master然后pull新代码,然后切回feature,merge master,会将提交历史拷贝了一遍

image

image

用rebase解决这个问题:

git rebase master
git push origin --delete myFeature (如果之前已将commit push到 origin了,需要先删除,不然会拒绝你push,需要先pull,而pull就是fetch+merge,那rebase就没意义了)
git push origin myFeature

新手营

前提:mac 环境下

打开Terminal(终端)

创建 testgit文件夹

mkdir testgit

进入testgit文件夹

cd testgit

初始化git仓库

git init

此时输入查看目录下有哪些文件的命令ls,会发现一个文件都没有,看不到.git文件
应该用命令ls -ah,即可看见隐藏的.git文件

创建并编辑一个文件

vim test.txt

点击 i键即可开始编辑,输入点东西,然后shift键+: ,然后输入wq(w是write写入,q是quit退出),回车。

此时输入ls,即可看到生成的文件test.txt

添加文件到暂存区

git add test.txt   (或者git add .  || git add -all   这两个命令在git第二版貌似没差了)

image

提交暂存区到仓库区

git commit  -m "editor and add  a test file"

之后再次编辑test.txt

vim test.txt

这次可看见修改的状态,显示有变更的文件

git status

未完待续.....

参考:廖雪峰老师的 git教程
本地回滚,远程回滚

三栏布局: 两边固定 中间自适应 (圣杯,双飞翼,flex)

圣杯布局

<div class="wrapper">
        <div class="col main">main</div>
        <div class="col left">left</div>
        <div class="col right">right</div>
 </div>
            .wrapper {
                padding: 0 120px 0 100px;
                overflow: hidden;
            }

            .col {
                position: relative;
                float: left;
            }

            .main {
                width: 100%;
                background-color: red;
            }

            .left {
                width: 100px;
                margin-left: -100%;
                left: -100px;
                background-color: green;
            }

            .right {
                width: 120px;
                margin-left: -120px;
                right: -120px;
                background-color: blue;
            }

缺点:圣杯布局有个问题,当面板的main部分比两边的子面板宽度小的时候,布局就会乱掉。因此也就有了双飞翼布局来克服这个问题。

双飞翼布局

它与圣杯布局很像,也是全部往左浮动,但是在内容div里再嵌套一个div,设置子div的margin为左右div预留位置,左右div只设置margin负值即可实现。链接 : 这篇文章一步步借图剖析,写得很好。

相似点

1.给main设置width: 100%,占满窗口,从而自适应。

2.为了形成在一行三栏布局,给三个方块都加上浮动float: left;(注意清除浮动,因为浮动会导致父元素高度塌陷)

3.利用负margin-left把三个方块拉到一行,margin-left负多少就是往左移动多少,左边需要相对父元素的-100%,移到父元素的最左边,右边只需要移动本身宽度的负值,即可在最右边。

image

区别

1.双飞翼布局给主面板添加了一个父标签用来通过margin给子面板腾出空间。

2.圣杯采用的是padding,而双飞翼采用的margin,解决了圣杯布局的问题。

3.双飞翼布局不用设置相对布局,以及对应的left和right值。

<div class="wrapper">
        <div class="col main">
            <div class="main-wrap">main</div>
        </div>
        <div class="col left">left</div>
        <div class="col right">right</div>
</div>
            .wrapper {
                padding: 0;
                overflow: hidden;
            }
            .col {
                float: left;
            }
            .main {
                width: 100%;
                background-color: red;
            }
            .main-wrap {
                margin: 0 120px 0 100px;
            }
            .left {
                width: 100px;
                margin-left: -100%;
                background-color: green;
            }
            .right {
                width: 120px;
                margin-left: -120px;
                background-color: blue;
            }

flex布局

思路:顺着主轴依次放3列,内容在最前,通过order控制显示顺序,通过flex-grow让中间占据全部剩余空间,通过flex-basis设置左、右div的宽度。

<div class="wrapper">
            <div class="main">
                这是中间
            </div>  
            <div class="left">这是左侧</div>
            <div class="right">这是右侧</div>
</div>
    .wrapper{
        display: flex;
    }
    .main{
        background-color: red;
        flex-grow: 1;
    }
   .left{
        flex:0 1 100px;
        background-color: blue;
        order: -1;
    }
    .right{
        flex:0 1 120px;
        background-color: green;
        order: 1;
    }

缺点:兼容性。

web移动端开发总结2--调试篇(2017.10.10)

web移动端开发的调试分ios设备和安卓设备。

ios设备:
1.在ios设备上
点击 设置---Safari---高级---web检查器

image.png

2.用数据线连接mac电脑

3.ios设备上用Safari打开你要调试的网页

4.打开电脑端的safari
点击顶栏的开发---鼠标放在你的设备名称上---点击你要调试的网页

image.png

5.跳出web检查器,进行调试

image.png

浏览器输入地址到页面呈现

输入地址,解析域名,获取ip地址

当发送一个URL请求时,不管这个URL是Web页面的URL还是Web页面上每个资源的URL,浏览器都会开启一个线程来处理这个请求,同时在远程DNS服务器上启动一个DNS查询。这能使浏览器获得请求对应的IP地址。

发起TCP的连接请求

浏览器与远程Web服务器通过TCP三次握手协商来建立一个TCP/IP连接。该握手包括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在 浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。

三次握手:发送端首先发送一个带 SYN 标志的数据包给对方。接收端收到后, 回传一个带有 SYN/ACK 标志的数据包以示传达确认信息。最后,发 送端再回传一个带 ACK 标志的数据包,代表“握手”结束。若在握手过程中某个阶段莫名中断,TCP 协议会再次以相同的顺序发 送相同的数据包。

image

四次挥手:包括一个结束报文,一个应答报文,一个结束报文,一个应答报文。跟三次握手比起来就是同步-应答报文是放一起的,而`四次挥手应答报文,结束报文是分开的。

image

发起http请求

一旦TCP/IP连接建立,浏览器会通过该连接向远程服务器发送HTTP的GET请求

。远程服务器找到资源并使用HTTP响应返回该资源,值为200的HTTP响应状态表示一个正确的响应。

此时,Web服务器提供资源服务,客户端开始下载资源。

请求返回后,便进入了我们关注的前端模块

页面渲染

image

1)浏览器会解析三个东西:一个是HTML/SVG/XHTML,事实上,Webkit有三个C++的类对应这三类文档。解析这三种文件会产生一个DOM Tree。CSS,解析CSS会产生CSS规则树。Javascript,脚本,主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree.

2)解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于DOM树,因为一些像Header或display:none的东西就没必要放在渲染树中了。CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element。也就是DOM结点。也就是所谓的Frame。然后,计算每个Frame(也就是每个Element)的位置,这又叫layout和reflow过程。

3)最后通过调用操作系统Native GUI的API绘制。

HTML文档解析->dom树生成(所有)->render树生成(可见)->逐级解析dom树

浏览器在解析html文件时,会”自上而下“加载,并在加载过程中进行解析渲染。在解析过程中,如果遇到请求外部资源时,如图片、外链的CSS、iconfont等,请求过程是异步的,并不会影响html文档进行加载。

解析过程中,浏览器首先会解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)。

DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为relow;当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为repain。

页面在首次加载时必然会经历reflow和repain。reflow和repain过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少reflow和repain。

当然,我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow。

当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。所以平时的代码中,js最好是放在html文档末尾。

文章参考
浏览器的渲染原理简介
从输入url到页面展示到底发生了什么

web移动端开发总结3--抓包工具篇(Charles)( 2017.10.10 )

移动端开发过程中经常使用Charles来抓取接口(得到类似pc的chrome开发者工具的一些功能),方便调试。

这边推荐两篇教程

教程1:
mac环境下使用Charles抓包Https请求
链接

教程2:
抓包并篡改返回数据图文详解(有了这个,调试时你就不用麻烦后端给你调整接口的数据)
链接

抓包工具不仅可以调试的时候用,还有其他用途,比如抓别人网站或者app的接口,然后做个高仿demo玩玩😄

javascript数据结构--队列(2018.01.03)

队列(Queue) ,和栈非常类似,但使用不用原则,即 先进先出 。
例子:排队
排队(队列),先进先出

创建队列
(和创建栈很类似,不详细讲,可参考javascript数据结构--栈

function Queue() {

    var items = [];
    //  队列尾部添加新项
    this.enqueue = function(element){
        items.push(element);
    };
     //移除并返回队列第一项
    this.dequeue = function(){
        return items.shift();
    };

    this.front = function(){
        return items[0];
    };

    this.isEmpty = function(){
        return items.length == 0;
    };

    this.clear = function(){
        items = [];
    };

    this.size = function(){
        return items.length;
    };

    this.print = function(){
        console.log(items.toString());
    };
}

Queue类和Stack类非常类似,唯一区别是dequeue 方法和front方法的不用,由先进先出,后进先出原则不同造成。

  • 使用
var queue = new Queue(); console.log(queue.isEmpty()); //؜true
queue.enqueue("John"); 
queue.enqueue("Jack");
queue.enqueue("Camila");
queue.print();//John,Jack,Camila
queue.dequeue(); 
queue.dequeue(); 
queue.print();//Camila

添加
删除

token,Json web token(jwt)

讲Json web token 之前先来了解下什么是token,因为jwt本质就是一个token

什么token

token的意思是“令牌”,是用户身份的验证方式,最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。还可以把不变的参数也放进token,避免多次查库

什么是JSON Web Token

JSON web Token,简称JWT,本质是一个token,是一种紧凑的URL安全方法,用于在网络通信的双方之间传递。一般放在HTTP的headers 参数里面的authorization里面,值的前面加Bearer关键字和空格。除此之外,也可以在url和request body中传递。

JSON Web Token的组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名依顺序用点号(".")链接而成:1.header,2.payload,3.signature。

头部(Header)里面说明类型和使用的算法,比如:

{
  "alg": "HS256",
  "typ": "JWT"
}

说明是JWT(JSON web token)类型,使用了HMAC SHA 算法。然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

载荷(Payload)载荷就是存放有效信息的地方,含三个部分:

1.标准中注册的声明,2.公共的声明,3.私有的声明

1.标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

2.公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

3.私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

把场景2的操作描述成一个json对象。其中添加了一些其他的信息,帮助今后收到这个JWT的服务器理解这个JWT。 当然,你还可以往载荷放非敏感的用户信息,比如uid

signature

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret');//TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

应用场景:

1.浏览器将用户名和密码以post请求的方式发送给服务器。

2.服务器接受后验证通过,用一个密钥生成一个JWT。

3.服务器将这个生成的JWT返回给浏览器。

4.浏览器存储JWT并在使用时将JWT包含在authorization header里面,然后发送请求给服务器。

5.服务器可以在JWT中提取用户相关信息。进行验证。

6.服务器验证完成后,发送响应结果给浏览器。

jwt 相比 session 的有点在哪

  • 能解决csrf浅谈CSRF攻击方式
  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。

代码实例

客户端 这里引用axios

axios.interceptors.request.use(
	config => {
		const token = localStorage.getItem('userToken');
		if (token) {
			config.headers.common['Authorization'] = 'Bearer ' + token;
		}
		return config;
	},
	error => {
		return Promise.reject(error);
	}
);

后端(koa) 这里引用jsonwebtoken 这个npm包

登录时生成一个token

const jwt = require("jsonwebtoken");
const secret = require("../config").secret;

const token = jwt.sign(payload, secret, {
    expiresIn: Math.floor(Date.now() / 1000) + 24 * 60 * 60 // 一天
});

写一个jwt验证的中间件

/**
 * @file 处理jwt验证的中间件
 */

const jwt = require("jsonwebtoken");
const secret = require("../config").secret;

module.exports = async function(ctx, next) {
	// 同步验证
	const auth = ctx.get('Authorization')
	const token = auth.split(' ')[1];
	try {
		//解码取出之前存在payload的user_id 和 name
		const payload = jwt.verify(token, secret)
		ctx.user_id = payload.id;
		ctx.name = payload.name;
		await next()
	} catch (err) {
		ctx.throw(401, err)
	}
}

在路由(koa-router)这边使用这个中间件

const verify = require('../middlewares/verify');
router.post('/register', register) //注册
	.post('/login', login) //登录
	.get('/message', verify, message) // 获取首页列表信息

其他

还有一个 oauth token :一个关于授权(authorization)的开放网络标准协议。

有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。

具体看这个 理解OAuth 2.0

参考阅读&&鸣谢:

什么是 JWT -- JSON WEB TOKEN

Token 认证的来龙去脉

json web token是用来做什么的?

JSON Web Token - 在Web应用间安全地传递信息

八幅漫画理解使用JSON Web Token设计单点登录系统

js设计模式--发布订阅模式

定义:发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。

现实中的发布订阅:小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼 MM 告诉小明,不久后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。 但到底是什么时候,目前还没有人能够知道。运用发布订阅:小明离开之前,把电话号码留在 了售楼处。售楼 MM 答应他,新楼盘一推出就马上发信息通知小明。小红、小强和小龙也是一 样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼 MM 会翻开花名册, 遍历上面的电话号码,依次发送一条短信来通知他们。

发送短信通知就是一个典型的发布—订阅模式,小明、小红等购买者都是 订阅者,他们订阅了房子开售的消息。售楼处作为发布者,会在合适的时候遍历花名册上的电话 号码,依次给购房者发布消息。

好处:

购房者不用再天天给售楼处打电话咨询开售时间,在合适的时间点,售楼处作为发布者 会通知这些消息订阅者。说明发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。

购房者和售楼处之间不再强耦合在一起,当有新的购房者出现时,他只需把手机号码留 在售楼处,售楼处不关心购房者的任何情况,不管购房者是男是女还是一只猴子。 而售 楼处的任何变动也不会影响购买者,比如售楼 MM 离职,售楼处从一楼搬到二楼,这些 改变都跟购房者无关,只要售楼处记得发短信这件事情。说明发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调 用另外一个对象的某个接口。

DOM 事件

只要我们曾经在 DOM 节点上面绑定过事件函数,那我们就曾经使用过发布—订阅 模式

document.body.addEventListener( 'click', function(){
     alert(2); 
}, false );
document.body.click();// 模拟用户点击

在这里需要监控用户点击 document.body 的动作,但是我们没办法预知用户将在什么时候点 击。所以我们订阅 document.body 上的 click 事件,当 body 节点被点击时,body 节点便会向订阅 者发布这个消息。

自定义事件

代码思路:如何一步步实现发布—订阅模式?

  • 首先要指定好谁充当发布者(比如售楼处);
  • 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册);
  • 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函 数(遍历花名册,挨个发短信)。

代码

var Event = (function(){
    var clientList = {},/ /缓存列表,存放订阅者的回调函数
          listen, 
         trigger,
         remove;
    //订阅(消息)
    listen = function( key, fn ){ 
        if ( !this.clientList[ key ] ){  // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
            this.clientList[ key ] = []; 
        } 
        this.clientList[ key ].push( fn ); // 订阅的消息添加进消息缓存列表
     },  
    //发布(消息)
     trigger =  function(){  
        var key = Array.prototype.shift.call( arguments ),  // 取出消息类型
        fns = this.clientList[ key ];// 取出该消息对应的回调函数集合
        if ( !fns || fns.length === 0 ){  // 如果没有订阅该消息,则返回
            return false; 
        }
        for( var i = 0, fn; fn = fns[ i++ ]; ){ 
            fn.apply( this, arguments ); // (2) // arguments 是 trigger 时带上的参数 
        }
     }
   //取消订阅事件
   remove = function( key, fn ){
     var fns = clientList[ key ]; 
     if ( !fns ){ return false; } // 如果 key 对应的消息没有被人订阅,则直接返回
     if ( !fn ){ //如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅.也就是取消全部订阅
         fns && ( fns.length = 0 ); 
     }else{   //取消具体的订阅
          for ( var l = fns.length - 1; l >=0; l-- ){  // 反向遍历订阅的回调函数列表
               var _fn = fns[ l ]; 
               if ( _fn === fn ){  // 删除订阅者的回调函数
                    fns.splice( l, 1 );  
               } 
          } 
     }
  };
  return { 
     listen: listen, 
     trigger: trigger, 
     remove: remove 
  }
})();

测试

Event.listen( 'squareMeter88', function( price ){ // 小红订阅消息
    console.log( '价格= ' + price ); // 输出:'价格=2000000' 
});
Event.trigger( 'squareMeter88', 2000000 );// 售楼处发布消息

记录下node项目部署上线的过程及坑(2017.09.16 )

前言

这个月利用空余时间写的xmxz在修了n多bug之后,在填了不少坑之后终于把他部署到云服务器上线了。对我这个技术菜简直就是挖坑,填坑,挖坑,填坑。。。。。。现在趁还记得一些,记录一下,免得下次忘了

nodejs写爬虫,论坛系统

说到nodejs,肯定离不开异步,我在项目中用的是
promise+async/await这一套异步方案

async/await是写异步代码的新方式,以前的方法有回调函数和Promise。
async/await是基于Promise实现的,它不能用于普通的回调函数。
async/await与Promise一样,是非阻塞的。
async/await使得异步代码看起来像同步代码,这正是它的魔力所在。

了解回调函数是什么
了解异步与同步,阻塞与非阻塞
Async/Await替代Promise的6个理由
Async/Await详解

购买,部署云服务器

1.服务器购买
我买的是京东云的学生机,选的是centos7.2(国内用centos多一点)

2.服务器登陆
通过ssh方式登陆服务器
$ ssh [email protected]  //格式:ssh用户名@公网IP

3.部署nodejs / 部署nodejs
ps:部署node环境我使用NVM安装多版本

上传项目文件

我用的是FileZilla 这个ftp可视化客户端
直接去官网下载安装
然后输入主机名(你买的云服务器的公网ip) ,用户名(默认是root),密码(你设的云服务器密码)
还有端口22 。然后连接。
想上传啥直接拖拽就行了,记得先把项目里的node包删掉,不然文件数量
分分钟上万。。。。上传到猴年马月。

正确姿势->
删除node包,在云服务器中 npm i

部署mysql

如果想简单快速搞定mysql部署的可以用centos6.5
centos7以上的版本部署mysql有点麻烦
但是呢,对新鲜技术充满鸡血的我还是入坑centos7.2😂

1.确认你的系统环境

# cat /etc/redhat-release 

2.安装mysql

#yum install mysql
#yum install mysql-devel
#yum install mysql-server

如果你是centos7以上版本,你会发现安装mysql-server会失败

[root@yl-web yl]# yum install mysql-server
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.sina.cn
 * extras: mirrors.sina.cn
 * updates: mirrors.sina.cn
No package mysql-server available.
Error: Nothing to do

原因是CentOS 7 版本将MySQL数据库软件从默认的程序列表中移除,用mariadb代替了。

解决办法

# wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm
# rpm -ivh mysql-community-release-el7-5.noarch.rpm
# yum install mysql-community-server

安装成功后重启mysql服务。

# service mysqld restart

初次安装mysql,root账户没有密码。

[root@yl-web yl]# mysql -u root 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.6.26 MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.01 sec)

mysql>

设置密码方案1

mysql> set password for 'root'@'localhost' =password('password');
如 set password for 'root'@'114.67.145.235' =password('123456');
Query OK, 0 rows affected (0.00 sec)

mysql> 

不需要重启数据库即可生效。

设置密码方案2(此方案将提高mysql安全性)

sudo mysql_secure_installation

这将提示您输入默认的根密码。一旦您输入,您将需要更改它。
接下去选择yes or no参考这个连接

登录mysql

# mysql -u root -p

输入密码

如果是这样的

mysql>

则说明没问题了

最后mariadb自动替换了,将不再生效。

# rpm -qa |grep mariadb

参考:
文章1
文章2

然后你要建立,修改数据库,可以查这些语法
链接

附带一个定心丸,如果mysql安装失败了要先彻底删除mysql

用这方法-> centos下彻底删除MYSQL 和重新安装MYSQL

一个不错的教程-> 腾讯云服务器部署 Node.js 应用

另外一个看起来还可以的教程

这里我还用了pm2这个进程管理器,pm2可以用来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。(刚好外加一些模块可以让我的爬虫程序每天定时爬取)

安装

npm install -g pm2

启动

pm2 start app.js

其他pm2指令教程

最后,放上我的粗糙版网站

deprecate

hexo搭建的博客因为离开了自己放博客仓库的电脑在公司不方便使用,试试把issue当博客使用。

前言

实习快一个月了,既惊喜又惊喜,面试的web前端开发,结果导师增哥语重心长地跟我说,你的岗位是web开发,要尽快学php和node.js(数脸懵逼😂,看jd明明是web前端开发,不过公司的前端基本都是全端,前辈们好腻害😀)。

react生命周期

今天写的一个react版的滚动字幕,思路是用js操作展示内容的scrollWidth,然后用setInterval 循环调用function ,function内容大体思路是对文字内容userDOM.style.transform = 'translateX(-'+ i +'px)',每次横向左移动ipx的距离,形成滚动效果,当i>=scrollWidth的时候,将i归零,形成循环滚动。(这里操作了dom,不是很好)

这里先解释一下浏览器scrollWidth,scrollWidth,offsetWidth三种宽度的区别
情况1:
元素内无内容或者内容不超过可视区,滚动不出现或不可用的情况下。
scrollWidth=scrollWidth,两者皆为内容可视区的宽度。
offsetWidth为元素的实际宽度。
情况2:
元素的内容超过可视区,滚动条出现和可用的情况下。
scrollWidth>clientWidth。
scrollWidth为实际内容的宽度。
clientWidth是内容可视区的宽度。
offsetWidth是元素的实际宽度。

因为文字内容动态加载,还采取了随机生成用户ip和获奖内容,以便调用后端接口前展示,结果获取到的scrollWidth(元素内容的实际宽度,包括被隐藏的部分)只是内容可视区的宽度,显然是不行的,所以无法滚动全部,我们需要的是元素内容的实际宽度。为什么会出现这种情况呢?如果文字内容是静态生成的,内容写死的,并不会出现这种问题。

问题出在哪里?出在动态加载还要考虑到react的生命周期,componentWillMount(将要挂载)->render(dom渲染)->componentDidMount(已经挂载),这个滚动函数写在了componentDidMount中,也就是页面dom结构渲染完了文字内容才开始动态生成,结果scrollWidth抓取到的文字内容是空的,只能获取了内容可视区的宽度。

问题找出来了,该如何解决呢?

最简单粗暴的方式就是把动态加载文字内容的函数放在componentWillMount(将要挂载)中,在render页面之前就把该部分内容加载完毕,这样render页面的时候scrollWidth将抓取到早已加载好的文字内容了,可是这样会造成打开页面后,页面空白一会,在渲染出页面的内容来,这段空白期间就是componentWillMount加载你的数据内容去了。如果数据内容少还好说,万一量很大了,那就尴尬了,严重影响用户体验。

更好的解决办法是仍把动态加载文字内容的函数放在componentDidMount中,并在此采用window.onload 调用滚动函数,也就是等页面都渲染完了,文字内容也ok了,才会调用滚动函数(scrollWidth,setInterval,transform )。

这下,解决了生命周期以及用户体验,加载性能的问题。😀

javascript数据结构--栈(2018.01.03)

数组是计算机科学中最常用的数据结构,栈和队列类似数组,但 添加和删除元素时更为可控。

栈(stack)是一种后进先出原则的有序集合。(如一摞书,后放进去的先拿出来)
栈

function Stack() {
    //用一种数据结构保存栈里的元素,这里选择数组
    var items = []; 
    //添加一个一个或几个元素到栈顶
    this.push = function(element){
        items.push(element);
    };
    //移除栈顶的元素,同时返回被移出的元素
    this.pop = function(){
        return items.pop();
    };
   //返回栈顶的元素,不对栈做任何修改
    this.peek = function(){
        return items[items.length-1];
    };
    //栈里没元素返回true 有就返回false
    this.isEmpty = function(){
        return items.length == 0;
    };
    //返回栈里的个数
    this.size = function(){
        return items.length;
    };
    //移除栈里所有元素
    this.clear = function(){
        items = [];
    };
    //打印
    this.print = function(){
        console.log(items.toString());
    };
    //数组转字符串
    this.toString = function(){
        return items.toString();
    };
}

使用

var stack = new Stack(); console.log(stack.isEmpty()); //true
stack.push(5); 
stack.push(8);
console.log(stack.peek());// 8
stack.push(11); 
console.log(stack.size());//3
console.log(stack.isEmpty()); //false

十进制到二进制

function divideBy2(decNumber){

var remStack = new Stack(), 
      rem, 
      binaryString = '';

while (decNumber > 0){ //{1}
     rem = Math.floor(decNumber % 2); //{2}       
     remStack.push(rem); //{3}
     decNumber = Math.floor(decNumber / 2); //{4} 
}

while (!remStack.isEmpty()){ //{5} 
      binaryString += remStack.pop().toString(); 
}

return binaryString;

}

使用 (测试时记得把上面封装的Stack函数带上)

divideBy2(10);  // "1010"

十转二进制

1

1

web通讯:短轮询,长轮询,websoket

前言 : 网上写连接和轮询的文章鱼龙混杂,看了十几篇后做出自己的总结。如果有误,恳请指出。

短轮询

概念:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。

优点:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。

前端核心代码

有问题版:周期调用

setInterval(function() {
    $.get("/path/to/server", function(data, status) {
        console.log(data);
    });
}, 10000);

问题所在:在网络情况不稳定的情况下,服务器从接收请求、发送请求到客户端接收请求的总时间有可能超过10秒,而请求是以10秒间隔发送的,这样会导致接收的数据到达先后顺序与发送顺序不一致。

修正版:递归调用

首先设置10s后发起请求,当数据返回后,递归调用请求函数再隔10s发起第二次请求,以此类推。这样的话虽然无法保证两次请求之间的时间间隔为固定值,但是可以保证到达数据的顺序。

function poll() {
    setTimeout(function() {
        $.get("/path/to/server", function(data, status) {
            console.log(data);
            poll();   // 发起下一次请求
        });
    }, 10000);
}

长轮询

概念:客户端发起一个HTTP请求。服务器保持请求打开,直到有新数据可用才返回响应。当客户端收到新数据时,立即发起另一个请求,并重复该操作。这有效地模拟了服务器推送功能。

优点:在无消息的情况下不会频繁的请求。
缺点:服务器hold连接会消耗资源。
实例:WebQQ、Hi网页版、Facebook IM。

前端核心代码

function longPoll (timestamp) {
    var _timestamp;
    $.get("/path/to/server?timestamp=" + timestamp)
    .done(function(res) { //请求成功
        try {
            var data = JSON.parse(res);
            console.log(data.msg);
            _timestamp = data.timestamp;
        } catch (e) {}
    })
    .always(function() { // 请求完成  递归调用
        setTimeout(function() {
            longPoll(_timestamp || Date.now()/1000);
        }, 10000);
    });
}

后端核心代码(php版的,改天自己写个node版的)

<?php
    // 示例数据为data.txt
    $filename= dirname(__FILE__)."/data.txt";
    // 从请求参数中获取上次请求到的数据的时间戳
    $lastmodif = isset( $_GET["timestamp"])? $_GET["timestamp"]: 0 ;
    // 将文件的最后一次修改时间作为当前数据的时间戳
    $currentmodif = filemtime($filename);

    // 当上次请求到的数据的时间戳*不旧于*当前文件的时间戳,使用循环"hold"住当前连接,并不断获取文件的修改时间
    while ($currentmodif <= $lastmodif) {
        // 每次刷新文件信息的时间间隔为10秒
        usleep(10000);
        // 清除文件信息缓存,保证每次获取的修改时间都是最新的修改时间
        clearstatcache();
        $currentmodif = filemtime($filename);
    }

    // 返回数据和最新的时间戳,结束此次连接
    $response = array();
    $response["msg"] =Date("h:i:s")." ".file_get_contents($filename);
    $response["timestamp"]= $currentmodif;
    echo json_encode($response);
?>

websoket

概念:WebSocket,即 Web 浏览器与 Web 服务器之间全双工通信标准。由于是建立在 HTTP 基础上的协议,因此连接的发起方仍是客户端, 而一旦确立 WebSocket 通信连接,不论服务器还是客户端,任意一方 都可直接向对方发送报文。

为了实现 WebSocket 通信,在 HTTP 连接建立之后,需要完成一 次“握手”(Handshaking)的步骤。
image

特点(优点)

  • 推送功能。支持由服务器向客户端推送数据的推送功能。这样,服务器可直接发 送数据,而不必等待客户端的请求。
  • 减少通信量。WebSocket 的首部信息 很小,通信量相应减少了
  • 没有同源限制,客户端可以与任意服务器通信。
  • 可以发送文本,也可以发送二进制数据。

用法

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};  

参考文章地址
知乎的一个高票回答
国外讲长轮询的一篇文章,挺好的
详细讲了连接,轮询,websoket,就是后端例子不大友好
长轮询的例子借用这边
阮老师的WebSocket 教程

js面对对象(创建对象,实现继承)

创建对象

创建单个对象可以用对象字面量,object构造函数(new Object() )方法来创建,为啥还要单独拿出来讲呢?因为这些方式有个明显的缺点:使用同 一个接口创建很多对象,会产生大量的重复代码。

为了写质量更高的代码,需要学习这些创建对象模式。

创建对象是下文实现继承的基础。

工厂模式 (创建对象)

也是一种是软件工程领域一种广为人知的设计模式

思路:在一个函数内创建一个空对象,给空对象添加属性和属性值,return这个对象。然后调用这个函数并传入参数来使用。

实例

function createPerson(name, age, job){ 
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){ alert(this.name); }; 
    return o;
}
var person1 = createPerson("xm", 22); 
var person2 = createPerson("Greg", 27);
console.log(person1.name) //xm
console.log(person1.sayName()) //xm

优点:解决了创建 多个相似对象的问题
缺点:没有解决对象识别的问题(即怎样知道一个对象的类型)

构造函数模式(创建对象)

思路:创建一个构造函数,然后用new 创建构造函数的实例。

实例

function Person(name, age, job){  //按照惯例,构造函数应以一个 大写字母开头,而非构造函数应以一个小写字母开头。
    this.name = name; 
    this.age = age; 
    this.sayName = function(){ 
        console.log(this.name); 
    }; 
}

var person1 = new Person("xm", 22); 
var person2 = new Person("Greg", 27);
console.log(person1.name) //xm
console.log(person1.sayName()) //xm

优点:1.子类型构造函数中可向超类型构造函数传递参数。

2.方法都在构造函数中定义,对于属性值是引用类型的就可通过在每个实例上重新创建一遍,避免所有实例的该属性值指向同一堆内存地址,一个改其他也跟着改。
缺点:对于一些可共用的属性方法(比如这边的this.sayName)没必要都在每个实例上重新创建一遍,占用内存。(无法复用)

原型模式(创建对象)

思路:创建一个函数,给函数原型对象赋值。

具体一点就是利用函数的prototype属性指向函数的原型对象,从而在原型对象添加所有实例可共享的属性和方法。

原型图

image

实例

function Person(){ }
Person.prototype.name = "xm"; 
Person.prototype.age = 22; 
Person.prototype.sayName = function(){
     console.log(this.name); 
};
var person1 = new Person();
console.log(person1.name) //xm
console.log(person1.sayName()) //xm

优点:可以让所有对象实例共享它所包含的属性和方法(复用性)。
缺点:1.在创建子类型的实例时,不能向超类型的构造函数中传递参数。

2.如果包含引用类型值的属性,那一个实例改了这个属性(引用类型值),其他实例也跟着改变。

组合使用构造函数模式和原型模式(创建对象,推荐)

思路:构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。简单来说就是属性值是引用类型的就用构造函数模式,方法和属性能共享的就用原型模式,取精去糟。

实例

function Person(name, age){ //构造函数模式
    this.name = name; 
    this.age = age; 
    this.friends = ["aa", "bb"]; 
}
Person.prototype = {  //原型模式
    constructor : Person, 
    sayName : function(){ 
        console.log(this.name); 
    }
}
Person.prototype.hobby = {exercise:"ball"}; //原型模式

var person1 = new Person("xm", 22);
var person2 = new Person("Greg", 27)
person1.friends.push("cc");   
console.log(person1.friends);   //"aa,bb,cc"
console.log(person2.friends);   //"aa,bb"
person1.sayName = function(){console.log(this.age)};
person1.sayName();  //22
person2.sayName();  //Greg
person1.hobby.exercise = "running";
console.log(person1.hobby);  //{exercise: "running"}
console.log(person2.hobby); //{exercise: "running"}

注意&疑问:js引用类型有object,数组,函数,这里原型模式为啥object,数组会如实地一个实例改,其他跟着改;而函数却一个实例改,其他没跟着改。有知道答案的麻烦一定要告诉我下😂

优缺点:构造函数模式和原型模式的取精去糟。

继承

原型链(继承)

思路:利用原型让一个引用类型继承另一个引用类型的属性和方法。

原型链图

image

我画成这样更好理解

image

核心code

SubType.prototype = new SuperType();

实例

function SuperType(){ 
    this.colors = ["red", "blue", "green"];
    this.property = true; 
}

SuperType.prototype.getSuperValue = function(){ 
  return this.property; 
};

function SubType(){};
//继承了 SuperType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function (){ 
  return this.subproperty; 
};
//覆盖超类型中的方法(父类的原型方法不受影响,只是在子类中被覆盖)
SubType.prototype.getSuperValue = function (){ 
  return false; 
};
var instance = new SubType(); 
console.log(instance.getSuperValue());  // false
console.log(SuperType.prototype.getSuperValue); // ƒ (){ return this.property; }

var instance1 = new SubType();
var instance2 = new SubType(); 
console.log(instance1.colors); //["red", "blue", "green"]
console.log(instance2.colors);//["red", "blue", "green"]

instance1.colors.push("black"); 
console.log(instance1.colors);//["red", "blue", "green", "black"]
console.log(instance2.colors);//["red", "blue", "green", "black"]

instance1.property = 'test';
console.log(instance1.property);// 'test'
console.log(instance2.property);// true

优点:可以让所有对象实例共享它所包含的属性和方法(复用性)

缺点:
1.在创建子类型的实例时,不能向超类型的构造函数中传递参数。

2.如果包含引用类型值的属性,那一个实例改了这个属性(引用类型值),其他实例也跟着改变。

实际代码体验原型链

function SuperType(){ 
  this.property = true; 
}
SuperType.prototype.getSuperValue = function(){ 
  return this.property; 
};

function SubType(){
  this.subproperty = false; 
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function (){ 
  return this.subproperty; 
};

function Test() { 
  this.testproperty = true; 
}

Test.prototype = new SubType();
var testInstance = new Test();

testInstance.__proto__ === Test.prototype // true
testInstance.__proto__.__proto__ === SubType.prototype // true
testInstance.__proto__.__proto__.__proto__ === SuperType.prototype // true

总结:一个实例的__proto__ 等于这个实例所属的构造函数的prototype

所以解释下下面为何为true?

function Point(x, y) {
    this.x = x;
    this.y = y;
}
var myPoint = new Point();
// the following are all true
myPoint.__proto__ == Point.prototype // true
myPoint.__proto__.__proto__ == Object.prototype // true

这边是因为myPoint.proto. 等于Point.prototype,而Point.prototype是Object的实例{}, 所以等于Object.prototype

借用构造函数(继承)

思路:在子类型构造函数的内部调用超类型构造函数。

核心code

function SubType(){ //SubType继承了 SuperType
     SuperType.call(this,argument); 
}

实例

function SuperType(){ 
    this.colors = ["red", "blue", "green"]; 
}
function SubType(){ //继承了 SuperType 
    SuperType.call(this); 
}

var instance1 = new SubType(); 
instance1.colors.push("black"); 
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType(); 
console.log(instance2.colors); //"red,blue,green"

优点: 1.子类型构造函数中可向超类型构造函数传递参数。

2.方法都在构造函数中定义,对于属性值是引用类型的就可通过在每个实例上重新创建一遍,避免所有实例的该属性值指向同一堆内存地址,一个改其他也跟着改。
缺点:无法避免构造函数模式存在的问题——方法都在构造函数中定义(无法复用);在超类型的原型中定义的方法,对子类型而言也是不可见的。

组合继承

思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

实例

function SuperType(name){  //父类(构造函数)
    this.name = name;
}

SuperType.prototype.sayName = function(){  //父类的原型添加一个方法
    console.log(this.name);
}

function SubType(name, age){  //借用构造函数来实现对实例属性的继承 
    SuperType.call(this, name);    //继承实例属性 这边继承this.name = name;
    this.age = age;     //自己的属性
}

SubType.prototype = new SuperType();    //使用原型链实现对原型属性和方法的继承  这边是继承
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){ 
    alert(this.age); 
};
var instance1 = new SubType("xm", 22);
console.log(instance1.age)   // 22
console.log(instance1.name)   // xm
instance1.sayName(); //xm
instance1.sayAge(); //22

优缺点:借用构造函数继承和原型链继承的取精去糟。

css margin重叠问题及解决方法(BFC)

场景1 上下两个div的边距发生重叠,取最大的margin值

    <style>
      .div1{
          background-color: red;
          width: 100px;
          height: 100px;
          margin-bottom: 30px;
      }
      .div2{
          background-color: green;
          width: 100px;
          height: 100px;
          margin-top: 20px;
      }
    </style>
<body>
    <div class="div1"></div>
    <div class="div2"></div>
</body>

image

场景2 下面的div内嵌了很多div,margin值依然被上面的div的margin重叠 ,取margin最大值(上面的div,30px)

    <style>
      .div1{
          background-color: red;
          width: 100px;
          height: 100px;
          margin-bottom: 30px;
      }
      .div2{
          background-color: green;
          width: 100px;
          height: 100px;
          margin-top: 20px;
      }
      .div21{
          background-color: blue;
          width: 80px;
          height: 80px;
          margin-top: 20px;
      }
      .div211{
          background-color: #555;
          width: 60px;
          height: 60px;
          margin-top: 20px;
      }
    </style>
<body>
    <div class="div1"></div>
    <div class="div2">
        <div class="div21">
            <div class="div211"></div>
        </div>
    </div>
</body>

image

场景3 下面的div内嵌了很多div,margin值依然被上面的div的margin重叠 ,取margin最大值(下面最内层的div,40px)

    <style>
      .div1{
          background-color: red;
          width: 100px;
          height: 100px;
          margin-bottom: 30px;
      }
      .div2{
          background-color: green;
          width: 100px;
          height: 100px;
          margin-top: 20px;
      }
      .div21{
          background-color: blue;
          width: 80px;
          height: 80px;
          margin-top: 20px;
      }
      .div211{
          background-color: #555;
          width: 60px;
          height: 60px;
          margin-top: 40px; //改为40px
      }
    </style>
</head>
<body>
    <div class="div1"></div>
    <div class="div2">
        <div class="div21">
            <div class="div211"></div>
        </div>
    </div>
</body>

image

场景4 为了不让边距重叠,可以给子元素加一个父元素,并设置该父元素为 BFC

    <style>
      .div1{
          background-color: red;
          width: 100px;
          height: 100px;
          margin-bottom: 30px;
      }
      .div2{
          background-color: green;
          width: 100px;
          height: 100px;
          overflow: hidden;
          margin-top: 20px;
      }
      .div21{
          background-color: blue;
          width: 80px;
          height: 80px;
          margin-top: 20px;
      }
    </style>
<body>
    <div class="div1"></div>
    <div class="div2">
        <div class="div21">
        </div>
    </div>
</body>

image

BFC

BFC,块级格式化上下文,一个创建了新的BFC的盒子是独立布局的,盒子里面的子元素的样式不会影响到外面的元素。

触发至少满足下列条件中的任何一个:(常用overflow:hidden; display:flex;)

  • float的值不为none
  • position的值不为static或者relative
  • display的值为 table-cell, table-caption, inline-block, flex, 或者 inline-flex中的其中一个
  • overflow的值不为visible

也就是在外面套个bfc盒子,来防止外边距折叠。

了解BFC详细可看大漠老师的文章理解CSS中BFC

js设计模式-单例模式

单例模式:

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

例子:单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。

实现思路:用一个变量来标志当前是否已经为某个类创建 过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

image

以上是接近传统面向对象语言中的实现,单例对象从 “类”中创建而来。

JavaScript 其实是一门无类(class-free)语言,也正因为如此,生搬单例模式的概念并无 意义。在 JavaScript 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象。

js版本定义:保证一个全局对象仅有一个实例,提供给全局访问。

全局变量不是单例模式,但在 JavaScript 开发中,我们经常会把全局变量当成单例来使用。 例如:

var a = {};

当用这种方式创建对象 a 时,对象 a 确实是独一无二的。如果 a 变量被声明在全局作用域下, 则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就 满足了单例模式的两个条件。

但是全局变量存在很多问题,它很容易造成命名空间污染。在大中型项目中,如果不加以限 制和管理,程序中可能存在很多这样的变量。JavaScript 中的变量也很容易被不小心覆盖,相信 每个 JavaScript 程序员都曾经历过变量冲突的痛苦,就像上面的对象 var a = {};,随时有可能被 别人覆盖。

以下几种方式可以相对降低全局变量带来的命名污染:

1.使用命名空间

适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。
最简单的方法依然是用对象字面量的方式:

var namespace1 = { 
    a: function(){ 
        alert (1); 
    }, 
    b: function(){ 
        alert (2); 
    } 
};

2.使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:

var user = (function(){
    var __name = 'sven',
     __age = 29;
    return { getUserInfo: function(){ 
                    return __name + '-' + __age; 
                    } 
                }
})();

我们用下划线来约定私有变量__name 和__age,它们被封装在闭包产生的作用域中,外部是 访问不到这两个变量的,这就避免了对全局的命令污染。

  1. 单例模式的实际运用 (惰性单例,抽离单例管理)

code

//抽象出创建单例的方法
var getSingle = function( fn ){ 
    var result; 
    return function(){ 
        return result || ( result = fn .apply(this, arguments ) ); 
    } 
};
//创建惰性单例函数
var createLoginLayer = function(){ 
    var div = document.createElement( 'div' );
    div.innerHTML = '我是登录浮窗';
    div.style.display = 'none'; 
    document.body.appendChild( div ); 
    return div; 
};
var createSingleLoginLayer = getSingle( createLoginLayer );
//使用惰性单例函数
document.getElementById( 'loginBtn' ).onclick = function(){ 
    var loginLayer = createSingleLoginLayer(); 
    loginLayer.style.display = 'block'; 
};

内容整理自 《JavaScript设计模式与开发实践》

js数组去重(2018.02.05)

不用api

思路
1.构建一个新的数组存放结果
2.for循环中每次从原数组中取出一个元素,用这个元素循环与结果数组对比
3.若结果数组中没有该元素,则存到结果数组中

code

Array.prototype.arrUnique_for = function () { 
         let res = [this[0]]; 
         let flag = false; 
         for (let i = 1; i < this.length; i++) { 
             for (let j = 0; j < res.length; j++) { 
                 if (this[i] === res[j]) { 
                      flag = true; 
                      break;
                 }
             } 
             if (!flag) { 
               res.push(this[i]); 
             }
        flag = false; 
       }
       return res; 
}

用ES5

用indexOf的常规操作

思路
建一个空的临时数组,遍历传进来的参数(数组),并用临时数组.indexOf对数组的每一个值做判断,等于 -1表示临时数组没有这个值且收下这个值,否则表示已经有一个这个值了,拒收。
最后,临时数组就是我们要的去重后的数组。

code

function unique(arr) {
   var n = [];
   for (var i = 0; i < arr.length; i++) {
       if (n.indexOf(arr[i]) == -1) n.push(arr[i]);
   }
   return n;
 };
 var arr=[1, 2, 1, 1, '1'];
 console.log(unique(arr)); //[1, 2, "1"]

稍微优化一下

code

Array.prototype.unique = function() {
  var n = [];  //一个新的临时数组
  for (var i = 0; i < this.length; i++)  {  //遍历当前数组
      if (n.indexOf(this[i]) == -1) n.push(this[i]);    //如果当前数组的第i已经保存进了临时数组,那么跳过,否则把当前项push到临时数组里面
  }
  return n;
};
var arr=[1, 2, 1, 1, '1'];
console.log(arr.unique()); //[1, 2, "1"]

备注:

①Array.prototype 属性表示 Array 构造函数 [] 的原型 ,也就是[]是Array的一个实例, 所以本例子中 arr继承了Array.prototype的unique方法,且unique方法的this指向arr。
Array.prototype
js中 [].slice 与 Array.prototype.slice 有什么区别?

用ES6

常规操作

code

function unique(array) {
   return Array.from(new Set(array));
}
var array = [1, 2, 1, 1, '1'];
console.log(unique(array)); // [1, 2, "1"]

备注:
①Array.from() 方法从一个类似数组或可迭代对象中创建一个新的数组实例。
②ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
长这样
screenshot.png

极简操作

code

var unique = (a) => [...new Set(a)]  
var array = [1, 2, 1, 1, '1'];
unique(array)

备注:

① 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列 数组的扩展

判断js数据类型

如果让你判断一个数据是否为数组,你会怎么判断呢?

方法有很多,简单粗暴的比如判断这个数据有没有 length 属性,还有常规的比如typeof ,instanaceof ,Object.prototype.toString.call

typeof

typeof一般只能返回如下几个结果:number,boolean,string,function,object,undefined

image

缺点: 对于Array,Null等特殊对象使用typeof一律返回object,这正是typeof的局限性。

instanaceof

instanceof适用于检测对象,它是基于原型链运作的。

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。换种说法就是如果左侧的对象是右侧对象的实例, 则表达式返回true, 否则返回false 。

[1, 2, 3] instanceof Array // true 
/abc/ instanceof RegExp // true 
({}) instanceof Object // true 
(function(){}) instanceof Function // true

缺点:instanceof对基本数据类型检测不起作用,主要是因为基本数据类型没有原型链。

Object.prototype.toString.call

Object.prototype.toString.call可以检测各种数据类型,推荐使用。

Object.prototype.toString.call([]); // => [object Array] 
Object.prototype.toString.call({}); // => [object Object] 
Object.prototype.toString.call(''); // => [object String] 
Object.prototype.toString.call(new Date()); // => [object Date] 
Object.prototype.toString.call(1); // => [object Number] 
Object.prototype.toString.call(function () {}); // => [object Function] 
Object.prototype.toString.call(/test/i); // => [object RegExp] 
Object.prototype.toString.call(true); // => [object Boolean] 
Object.prototype.toString.call(null); // => [object Null] 
Object.prototype.toString.call(); // => [object Undefined]

当然,使用的时候记得在使用前定义类型而非在括号内直接定义类型。

function Animal () {}; 
Object.prototype.toString.call (Animal); // => [object Function] 
Object.prototype.toString.call (new Animal); // => [object Object]

造isArray等isType语法糖

var isString = function( obj ){ return Object.prototype.toString.call( obj ) === '[object String]'; }; 

var isArray = function( obj ){return Object.prototype.toString.call( obj ) === '[object Array]'; }; 

var isNumber = function( obj ){return Object.prototype.toString.call( obj ) === '[object Number]'; };

优化:这些函数的大部分实现都是相同的, 不同的只是 Object.prototype.toString. call( obj )返回的字符串。为了避免多余的代码,我们尝试把这些字符串作为参数提前值入 isType 函数。

var isType = function( type ){ 
    return function( obj ){ 
        return Object.prototype.toString.call( obj ) === '[object '+ type +']'; 
    } 
};

var isString = isType( 'String' ); 
var isArray = isType( 'Array' ); 
var isNumber = isType( 'Number' );

console.log( isArray( [ 1, 2, 3 ] ) ); //true

....

d4beae0bdbd7edd6d24ca31f673673c2

e9c4101f1319fb6e90f370a1bf8b4dab

the steps you take don't need to be big

they just need to take you in the right direction

step by step

前端性能优化

优化方向 优化手段
请求数量 合并脚本和样式表,CSS Sprites,按需加载资源,简化页面的设计
请求带宽 开启GZip,压缩JavaScript和CSS ,移除重复脚本,图像优化
缓存利用 使用CDN,使用外部JavaScript和CSS,添加Expires头,配置ETag,减少DNS查找,使AjaX可缓存
页面结构 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出
代码校验 避免重定向

减少HTTP请求

合并脚本和样式表(模块打包)

模块打包(合并文件)是通过合并所有脚本文件成单一脚本以及合并所有样式成单一样式表的方式来减少HTTP请求数目。

CSS Sprites

将背景图片合并到一张图片上,然后使用CSS背景图片和背景属性来呈现期望的图片片段。详细使用看这篇文章

按需加载资源

资源(特别是图片)的按需加载或者说惰性加载,可以有助于你的 Web 应用在整体上获得更好的性能。对于使用大量图片的页面来说惰性加载有着显著的三个好处:

  • 减少向服务器发出的并发请求数量(这就使得页面的其他部分获得更快的加载时间)
  • 减少浏览器的内存使用率(更少的图片,更少的内存)
  • 减少服务器端的负载

大体上的理念就是只在必要的时候才去加载图片或资源(如视频),比如在第一次被显示的时候,或者是在将要显示的时候对其进行加载。

图片具体怎么lazy load可看我的一篇文章

简化页面的设计

没必要花哨就少花哨。

请求带宽 (各种压缩?)

计算机网络的带宽是指网络可通过的最高数据率,即每秒多少比特。描述带宽时常常把“比特/秒”省略。例如,带宽是1M,实际上是1Mbps,这里的Mbps是指兆位/s。那到底什么是带宽呢?带宽的意义又是什么?为了更形象地理解带宽、位宽、时钟频率的关系,我们举个比较形象的例子,工人加工零件,如果一个人干,在大家单个加工速度相同的情况下,肯定不如两个人干的多,带宽就像是工人能够加工零件的总数量,位宽仿佛工人数量,时钟工作频率相当于加工单个零件的速度,位宽越宽,时钟频率越高则总线带宽越大,其好处也是显而易见的。

开启GZip

详细请参考这篇文章你真的了解 gzip 吗?

gzip压缩比率在3到10倍左右,可以大大节省服务器的网络带宽。而在实际应用中,并不是对所有文件进行压缩,通常只是压缩静态文件。

image

步骤:

1)浏览器请求url,并在request
header中设置属性accept-encoding:gzip。表明浏览器支持gzip。

2)服务器收到浏览器发送的请求之后,判断浏览器是否支持gzip,如果支持gzip,则向浏览器传送压缩过的内容,不支持则向浏览器发送未经压缩的内容。一般情况下,浏览器和服务器都支持gzip,response headers返回包含content-encoding:gzip。

3)浏览器接收到服务器的响应之后判断内容是否被压缩,如果被压缩则解压缩显示页面内容。

image

image

压缩JavaScript和CSS

压缩是从代码中去除不必要的字符进而实现减少尺寸然后缩短载入时间的惯用技巧。当所有注释被移除而且所有不需要的空白字符(空格、换行和Tab)被清除时,代码也就是mini化的啦。这种移除对于JavaScript来说,会改善响应时间性能,因为下载文件的尺寸被减小啦。
另外外部的脚本和样式、内联的以及代码块都应该被压缩。即使你使用gzip你的脚本和样式,使用Minification压缩这些脚本和样式仍然减少尺寸大小高达5%。随着JavaScript和CSS使用的人越来越多以及尺寸越来越大,因此通过压缩你的代码来获取savings。

gulp,webpack都能很方便地压缩。

移除重复脚本

一个页面中包含两个相同的JavaScript文件是很伤性能的。这个可能和你平常想的不一样。对美国排名前十的网站回顾可以发现:其中有两个网站包含废弃的脚本。两大因素 增加了在一个页面中出现脚本被重复的几率:团队的规模以及脚本的数量。当这件事确实发生了,重复的脚本通过创建可有可无的HTTP请求以及浪费JavaScript解析器资源的方式来损伤性能。
不必要的HTTP请求最开始出现在IE中,但是没有出现在FireFox。如果外部的脚本被引入两次而且没有被缓存起来。在页面加载期间,会产生两个HTTP请求。即使脚本被缓存起来,在用户进行页面重新载入时,额外的HTTP请求还是会出现。
另外产生浪费资源的HTTP请求,脚本进行多次计算时很耗时间的哦。不管脚本是否被缓存,多余的JavaScript执行环境还是会在IE和FireFox发生。
避免意外引入两个相同的脚本的方法是在你的模板系统中实现脚本管理模块。一般在页面中包含脚本的做法是在HTML页面中使用SCRIPT标签。

图像优化

这个不是很懂,反正就是看图片还有没有优化的空间(减小体积)。

缓存利用

这边主要参考张云龙大神的文章前端工程之CDN部署

image

ps:缓存利用 分类中保留了 添加Expires头 和 配置ETag 两项。或许有些人会质疑,明明这两项只要配置了服务器的相关选项就可以实现,为什么说它们难以解决呢?确实,开启这两项很容易,但开启了缓存后,我们的项目就开始面临另一个挑战: 如何更新这些缓存?
我之前实习的公司是用时间戳作为url修改的依据<script type="text/javascript" src="a.js?t=201404231826"></script>

使用CDN

浏览器缓存始终只是为了提升二次访问的速度,对于首次访问的加速,我们需要从网络层面进行优化,最常见的手段就是CDN(Content Delivery Network,内容分发网络)加速。通过将静态资源缓存到离用户很近的相同网络运营商的CDN节点上,不但能提升用户的访问速度,还能节省服务器的带宽消耗,降低负载。

不同地区的用户会访问到离自己最近的相同网络线路上的CDN节点,当请求达到CDN节点后,节点会判断自己的内容缓存是否有效,如果有效,则立即响应缓存内容给用户,从而加快响应速度。如果CDN节点的缓存失效,它会根据服务配置去我们的内容源服务器获取最新的资源响应给用户,并将内容缓存下来以便响应给后续访问的用户。因此,一个地区内只要有一个用户先加载资源,在CDN中建立了缓存,该地区的其他后续用户都能因此而受益。

使用外部JavaScript和CSS

如果JavaScript和CSS都会内联在HTML文档中,每次HTML文档被请求时,是能减少HTTP请求数目,但是增加了HTML文档的尺寸。
在现实世界中使用外部文件通常可以促成快响应页面产生,因为外部JavaScript和CSS文件会被浏览器缓存下来,如果JavaScript和CSS存在(浏览器缓存的)外部文件中,HTML尺寸会减少(且没有增加HTTP请求数目)。

添加Expires头,配置ETag

这部分属于强缓存和协商缓存,我之前已经一篇总结,这边不再展开。HTTP强缓存和协商缓存

减少DNS查找

域名系统(DNS)实现了域名和IP地址的映射,就像电话本实现了人名和电话号码的映射。当你将www.yahoo.com写进浏览器地址栏时,通过浏览器进行DNS解析然后返回服务器的IP地址。DNS是有代价的。给定的域名用DNS查询IP地址通常需要花费20-120毫秒。在DNS查询未完成之前浏览器不进行从这个网址任何下载。

为了更好的性能DNS查询是会缓存的。这个缓存机制会出现在专门的可缓存的服务器中(包括用户的ISP服务商或者本地的局域网),但是DNS查询在用户个人电脑上也会有缓存。

当客户端没有DNS缓存(浏览器和操作系统),网页里含有多少个独一无二的域名就要进行多少次DNS查询。独一无二的域名包括在页面中使用域名的URL、图片、脚本文件、样式、Flash等。

方法:减少唯一域名的数量就可以减少页面中的并行下载。避免DNS查询缩短响应时间,但是减少并行下载可能增加响应时间。我的意见是在至少2个但不多于4个域名情况下,将这些组件分离。这样实现在减少DNS查询和允许高程度的并行下载两种情况的折中。

下面是新浪微博的图片域名,我们可以看到他有多个域名,这样可以保证这些不同域名能够同时去下载图片,而不用排队。不过如果当使用的域名过多时,响应时间就会慢,因为不用响应域名时间不一致。

image

使AjaX可缓存

张云龙大神说:剩下的两项优化原则要做到并不容易,真正可缓存的Ajax在现实开发中比较少见。

那就日后有时间再补。

页面结构

将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

网页上的资源加载时从上网下顺序加载的,所以css放在页面的顶部能够优先渲染页面,让用户感觉页面加载很快。

当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。
加载js时会对后续的资源造成阻塞,必须得等js加载完才去加载后续的文件 ,所以就把js放在页面底部最后加载。

更详细更参考这篇文章该把JS文件放在HTML文档的那个位置

代码校验

避免重定向

最浪费性能的重定向经常发生然而web开发人员通常没有意识到。当URL中本应该有正斜杠但实际上没有正斜杠(/)的时候,就会发生上面的情况。例如,去http://astrology.yahoo.com/astrology/,但是输入了http://astrology.yahoo.com/astrology(注意少了斜杠),导致301重定向,降低用户体验。

文章参考
网站性能优化35计
有没有前端性能优化知识推荐,包括css和js?
Web性能优化
前端工程与性能优化
Web前端应该从哪些方面来优化网站?
【译】唯快不破:Web 应用的 13 个优化步骤

js图片懒加载及优化(2017.06.24)

为啥要用图片懒加载

对页面加载速度影响最大的就是图片,一张普通的图片可以达到几M的大小,而代码也许就只有几十KB。当页面图片很多时,页面的加载速度缓慢,几S钟内页面没有加载完成,也许会失去很多的用户。
所以,对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样子对于页面加载性能上会有很大的提升,也提高了用户体验。

原理

将页面中的img标签src指向一张小图片或者src为空,然后定义data-src(这个属性可以自定义命名,我才用data-src)属性指向真实的图片。src指向一张默认的图片,否则当src为空时也会向服务器发送一次请求(指向默认的一张图那就只需请求一次)。可以指向loading的地址。当载入页面时,先把可视区域内的img标签的data-src属性值赋值给src,然后监听滚动事件,加载用户即将看到的图片(利用图片出现时距离顶部的高度小于 滚动条距离顶部的高度+可视区的高度)。

image

  • ps:图片要指定宽高

关于窗口各种宽度,给出网上找的一张好图
窗口各种宽度

如果仍不是很理解,看这两篇文章

scrollWidth,clientWidth,offsetWidth的区别

JS中关于clientWidth offsetWidth scrollWidth 等的含义

图片懒加载的实现代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        img {
            display: block;
            margin-bottom: 50px;
            width: 400px;
            height: 400px;
        }
    </style>
</head>
<body>
     <img src="" data-src="http://pic.58pic.com/58pic/17/18/97/01U58PIC4Xr_1024.jpg" alt="">
    <img src="" data-src="http://ww4.sinaimg.cn/large/006y8mN6gw1fa5obmqrmvj305k05k3yh.jpg" alt="">
    <img src="" data-src="http://ww1.sinaimg.cn/large/006y8mN6gw1fa7kaed2hpj30sg0l9q54.jpg" alt="">
    <img src="" data-src="http://cover.read.duokan.com/mfsv2/download/fdsc3/p01N203pHTU7/Wr5314kcLAtVCi.jpg!t" alt="">
    <img src="" data-src="http://77fkxu.com1.z0.glb.clouddn.com/20160308/1457402219_73571.jpg" alt="">
    <img src="" data-src="http://pic1.cxtuku.com/00/16/18/b3809a2ba0f3.jpg" alt="">
    <img src="" data-src="http://img.bitscn.com/upimg/allimg/c150708/14363B06253120-6060O.jpg" alt="">
    <img src="" data-src="http://cover.read.duokan.com/mfsv2/download/fdsc3/p015trgKM7vw/H0iyDPPneOVrA4.jpg!t" alt="">
    <img src="" data-src="http://ww1.sinaimg.cn/large/006y8mN6gw1fa7kaed2hpj30sg0l9q54.jpg" alt="">
    <img src="" data-src="http://imgsrc.baidu.com/baike/pic/item/2f9cbdcc5e0bcf5c00e9283b.jpg" alt="">
    <img src="" data-src="http://ww4.sinaimg.cn/large/006y8mN6gw1fa5obmqrmvj305k05k3yh.jpg" alt="">
<script>
    (function(){
    let num = document.getElementsByTagName('img').length;
    let img = document.getElementsByTagName("img");
    let n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
    lazyload(); //页面载入完毕加载可是区域内的图片
     window.onscroll = lazyload;
    function lazyload() { //监听页面滚动事件
        let seeHeight = document.documentElement.clientHeight; //可见区域高度
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
        for (let i = n; i < num; i++) {
            // 图片未出现时距离顶部的距离大于滚动条距顶部的距离+可视区的高度
            if (img[i].offsetTop < seeHeight + scrollTop) {
                if (img[i].getAttribute("src") == "") {
                    img[i].src = img[i].getAttribute("data-src");
                }
                n = i + 1;
            }
        }
    }
    })()

</script>
</body>
</html>

demo:图片懒加载

使用节流函数进行优化

如果直接将函数绑定在scroll事件上,当页面滚动时,函数会被高频触发,这非常影响浏览器的性能。

同时还有以下场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。

1.window对象的resize、scroll事件

2.拖拽时的mousemove事件

3.射击游戏中的mousedown、keydown事件

4.文字输入、自动完成的keyup事件

解决这个问题的方法有去抖动和节流的方法

  • 去抖动原理: 当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。

不足:当我一直滚动鼠标的时候,lazyload函数就会不断被延迟,这样只有停下来的时候才会执行,那么再有些需要及时显示的情况下,就显得不那么友好了

  • 节流原理:预设一个执行周期,如果这个周期结束了都还没触发函数,那就会执行一次函数;如果这个周期还没结束就触发了函数,那定时器将重置,开始新周期。

达到了想要的效果,既没有频繁的执行也没有延迟执行

详细可看此文
关于js函数节流和去抖动

运用节流函数的图片懒加载代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        img {
            display: block;
            margin-bottom: 50px;
            width: 400px;
            height: 400px;
        }
    </style>
</head>
<body>
     <img src="" data-src="http://pic.58pic.com/58pic/17/18/97/01U58PIC4Xr_1024.jpg" alt="">
    <img src="" data-src="http://ww4.sinaimg.cn/large/006y8mN6gw1fa5obmqrmvj305k05k3yh.jpg" alt="">
    <img src="" data-src="http://ww1.sinaimg.cn/large/006y8mN6gw1fa7kaed2hpj30sg0l9q54.jpg" alt="">
    <img src="" data-src="http://cover.read.duokan.com/mfsv2/download/fdsc3/p01N203pHTU7/Wr5314kcLAtVCi.jpg!t" alt="">
    <img src="" data-src="http://77fkxu.com1.z0.glb.clouddn.com/20160308/1457402219_73571.jpg" alt="">
    <img src="" data-src="http://pic1.cxtuku.com/00/16/18/b3809a2ba0f3.jpg" alt="">
    <img src="" data-src="http://img.bitscn.com/upimg/allimg/c150708/14363B06253120-6060O.jpg" alt="">
    <img src="" data-src="http://cover.read.duokan.com/mfsv2/download/fdsc3/p015trgKM7vw/H0iyDPPneOVrA4.jpg!t" alt="">
    <img src="" data-src="http://ww1.sinaimg.cn/large/006y8mN6gw1fa7kaed2hpj30sg0l9q54.jpg" alt="">
    <img src="" data-src="http://imgsrc.baidu.com/baike/pic/item/2f9cbdcc5e0bcf5c00e9283b.jpg" alt="">
    <img src="" data-src="http://ww4.sinaimg.cn/large/006y8mN6gw1fa5obmqrmvj305k05k3yh.jpg" alt="">
<script>
    (function(){
    let num = document.getElementsByTagName('img').length;
    let img = document.getElementsByTagName("img");
    let n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
    lazyload(); //页面载入完毕加载可是区域内的图片
    function lazyload() { //监听页面滚动事件
        let seeHeight = document.documentElement.clientHeight; //可见区域高度
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
        for (let i = n; i < num; i++) {
            // 图片未出现时距离顶部的距离大于滚动条距顶部的距离+可视区的高度
            if (img[i].offsetTop < seeHeight + scrollTop) {
                if (img[i].getAttribute("src") == "") {
                    img[i].src = img[i].getAttribute("data-src");
                }
                n = i + 1;
            }
        }
    }
    采用了节流函数
        function throttle(fun, delay, time) {
    let timeout,
        startTime = new Date();
    return function() {
        let context = this,
            args = arguments,
            curTime = new Date();
        clearTimeout(timeout);
        // 如果达到了规定的触发时间间隔,触发 handler
        if (curTime - startTime >= time) {
            fun.apply(context, args);
            startTime = curTime;
            // 没达到触发间隔,重新设定定时器
        } else {
            timeout = setTimeout(fun, delay);
        }
    };
};
window.addEventListener('scroll',throttle(lazyload,500,1000));
    })()
</script>
</body>
</html>

demo:图片懒加载性能优化

如果不对的地方,恳请指出,谢谢^ ^

js字符串|数组的操作方法

字符串

split()

mdn

用法:string.split(separator,limit) ,与join相反

例子:

var a="0,1,2,3,4,5,6";
a.split(""); //["0", ",", "1", ",", "2", ",", "3", ",", "4", ",", "5", ",", "6"]

var a="0,1,2,3,4,5,6";
a.split("",3); // ["0", ",", "1"]

var a="0,1,2,3,4,5,6";
a.split(",",3);  // ["0", "1", "2"]

substr()

mdn

方法返回一个字符串中从指定位置开始到指定字符数的字符。

var str = "abcdefghij";

console.log(str.substr(1,2));   // (1,2): bc
console.log(str.substr(-3,2));  // (-3,2): hi
console.log(str.substr(-1,1));  //  返回最后字符串的一个字符  j

replace()

mdn

返回一个由替换值替换一些或所有匹配的模式后的新字符串。模式可以是一个字符串或者一个正则表达式, 替换值可以是一个字符串或者一个每次匹配都要调用的函数。

用法:str.replace(regexp|substr, newSubStr|function)

var str = 'Twas the night before Xmas...';
newstr = str.replace('Twas', 'Christmas'); //"Christmas the night before Xmas..."
//或者
var str = 'Twas the night before Xmas...';
newstr = str.replace(/xmas/i, 'Christmas'); // Twas the night before Christmas...

先更新到这,更多操作string的api看mdn的string实例

数组

splice和slice的异同点
同:
1.都是返回切割下来的数组
2.初始位置都是从0开始算
异:
1.splice 改变原数组 而slice不会
2.splice三个参数分别是初始位置,删除数量,替换的内容
而slice两个参数分别是初始位置和终点位置

var months = ['Jan', 'March', 'April', 'June'];
console.log(months.splice(1, 0, 'Feb')); // []
console.log(months); //  ["Jan", "Feb", "March", "April", "June"]
var animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2)); // ["camel", "duck", "elephant"]
console.log(animals); // ["ant", "bison", "camel", "duck", "elephant"]

ES6的类&&ES6前的继承

在ES6出来前,js并无类,与类最接近的是:创建一个构造器,然后将方法指派到 该构造器的原型上。
可以通过我之前结合高程这本书整理的文章看看,js面对对象(创建对象,实现继承)

接下来,根据代码看看两者之间的联系,在原有的基础上吸收使用ES6的类。

ES6 之前,实现自定义类型的继承是个繁琐的过程。严格的继承要求有多个步骤。

ES6前的代码实现

function Rectangle(length, width) {   // ①
  this.length = length;
  this.width = width;
  this.test = "test~" 
}

Rectangle.prototype.getArea = function() {   // ②
    return this.length * this.width;
};

Rectangle.prototype.doTest = function() {   // ②
    console.log(this.test);
};
  
function Square(length) {  // ③
    Rectangle.call(this, length, length);  //构造器继承属性
}
  
Square.prototype = Object.create(Rectangle.prototype, {   //④ 原型继承方法,且书写配置 
    constructor: {
      value:Square,
      enumerable: true,   //可枚举
      writable: true,      //可写
      configurable: true  //可配置
    }
});
 
Square.prototype.getDataFromSuperClass = function(){
  console.log(this.length ,"===", this.width);
}

var square = new Square(3);  //创建实例
console.log(square.getArea());// 9
square.doTest(); //"test~"
square.getDataFromSuperClass();//3 "===" 3
console.log(square instanceof Square);// true
console.log(square instanceof Rectangle);// true

这例子和js面对对象(创建对象,实现继承) 的例子基本一样

ES6的代码实现(类)

class Rectangle {
  constructor(length, width) {  //①
    this.length = length;
    this.width = width;
    this.test = "test~"  //在constructor定义的this.变量 都将可以在class中访问 (有木有vue的data既视感😄)
  }

  getArea() {  //②
    return this.length * this.width;  
  }

  doTest () {
    console.log(this.test);
    console.log("theWidth in super class", this.theWidth); //使用本类中的取值函数 
  }

  get theWidth() { //取值函数
    return this.width;
  }
  
}

class Square extends Rectangle {  //extends 方法相当于 ④
  constructor(length) {       
    super(length, length);   // ③  super() 用来访问父类的构造器   与 Rectangle.call(this, length, length) 相同  
  }
  
  getDataFromSuperClass(){   //因为super  所以这边可以访问到父类Rectangle的this.变量
     console.log(this.length ,"===", this.width); 
     console.log("theWidth in sub class ", this.theWidth); //使用父类的取值函数 
  }

}
var square = new Square(3);
console.log(square.getArea());// 9
square.doTest(); //"test~"  "theWidth in super class" 3
square.getDataFromSuperClass();//3 "===" 3  "theWidth in sub class " 3
console.log(square instanceof Square);// true
console.log(square instanceof Rectangle);// true

两个例子我都用序号标了,序号相同的代表着不同的写法,一样的意思

继承了其他类的类(如上文的Square)被称为派生类( derived classes )。

如果派生类指定了构造器,就需要 使用 super() ,否则会造成错误。若你选择不使用构造器, super() 方法会被自动调用, 并会使用创建新实例时提供的所有参数。

class Square extends Rectangle {// 没有构造器
}

// 等价于:

class Square extends Rectangle {
    constructor(...args) {
        super(...args);
    }
}

此例中的第二个类展示了与所有派生类默认构造器等价的写法,所有的参数都按顺序传递给 了基类的构造器。在当前需求下,这种做法并不完全准确,因为 Square 构造器只需要单个 参数,因此最好手动定义构造器

使用super()需注意

  • 你只能在派生类中使用super()。若尝试在非派生的类(即:没有使用extends关键字的类)或函数中使用它,就会抛出错误。
  • 在构造器中,你必须在访问 this 之前调用 super() 。由于 super() 负责初始化this(ES5的.call()),因此试图先访问this自然就会造成错误。
  • 唯一能避免调用 super()的办法,是从类构造器中返回一个对象。

class 的静态方法

在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。要直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用(会报错),调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法。

class Foo {
  static bar () {
    this.baz();
  }
  static baz () {  //静态成员
    console.log('hello');
  }
  baz () { //实例对象的成员, 如 new Foo();
    console.log('world');
  }
}

Foo.bar() // hello
class Foo {
  bar () {
    this.baz();
  }
  static baz () {
    console.log('hello');
  }
  baz () {
    console.log('world');
  }
}
const newFoo = new Foo();

newFoo.bar()  // world

静态方法只能访问静态成员,实例方法只能访问实例成员。
然后为啥要有静态方法呢,有些东西是不需要实例的,只要有类就存在的,比如Array.isArray(obj); 静态方法通常用于为一个应用程序创建工具函数。

未完待续....

this

var num = 1;
var obj = {
    num: 2,
    getAge: function () {
          var num = 3;
         console.log(this.num);//2 // 被obj调用 this指向obj
         function fn () {
	        console.log(this.num)
         };
	fn();//1 因为独自调用,this指向全局
    }
};
 obj.getAge()
var num = 1;
var obj = {
    num: 2,
    getAge: function () {
          var num = 3;
         console.log(this.num);//2 // 被obj调用 this指向obj
         var  fn =  ()=> {
	        console.log(this.num)
         };
	fn();//2 箭头函数修复this指向  指向词法作用域,外层作用域(obj)
    }
};
 obj.getAge()
var num = 1;
var obj = {
    num: 2,
    getAge: ()=> {
         var num = 3;
         console.log(this.num);//1 //  箭头函数修复this指向 指向词法作用域,外层作用域 (window)
         var  fn =  ()=> {
	        console.log(this.num)
         };
	fn();//1 箭头函数修复this指向 指向词法作用域,外层作用域
    }
};
 obj.getAge()

未完.......

var x=11;
var objFather={
    x:33,
   obj:{
     x:22,
     say:()=>{
       console.log(this.x);
     }
    }
}
objFather.obj.say();//11

2017

记得去年的春节,那时候已经想走前端这条路了,在一次同学聚会,喧闹的ktv中,跟软件专业的高中好友peyton讨论起了学编程这回事,给了我这个跨专业学编程的很多鼓励。

去年春节后算开始正式自学前端,至今满一年,这一路上其实还挺不容易。

一方面是起步低且晚,做为一名双非普通一本学校物理系的大三狗,除了考计算机二级临时抱佛脚看了一点C语言,其他无任何编程知识基础,基本也没啥圈内的程序猿可以交流,加上老是怕麻烦别人,索性基本自己靠搜索引擎摸索了,不过这样虽然一开始很难受,但倒也是养成了靠goole自己解决问题的能力和习惯。

另一方面是时间,因为大三课挺多的,而且为了大四能更好的实习,我还多修了两门课来凑学分,不过下学期退出了物理系的专题研究实验室,不过上学期在实验室学习的那些东西也不算作废,毕竟懂得了科研需要的寂寥和耐心。总之就是难度高的课上课听一些,难度不高的就课堂上看编程书,回宿舍就接着学,哈哈,菜并快乐着。最后通过《Head First HTML与CSS》,《JavaScript DOM编程艺术》,《JavaScript高级程序设计》,以及做百度前端IFE任务,最后算是初步入门前端了,这学期也拿了奖学金,时间管理和精力管理也稍有体会。

期间,我写了个vue项目vue-home,到现在为止获得了一百多个star,对萌新来说也是一个鼓励。

大学中最充实的一学期,之前的两年半就在社团中迷茫度日。。有点可惜。

不过接着被直系学长推荐进了一个我们学校的学生工作室(感谢toad学长,一路上帮了我很多),然后就参与接了个外包。也开始懂得了项目开发流程,前后端联调,与UI的配合。

在暑假找了家当地算不错的上市公司进行暑假实习,实习学到了很多东西,不止前端,还有node(koa),php(这个没好好学,忘了个精光)都是在这边接触的,公司前端岗位偏全栈,前端写mvc的v层和c层。从导师那边学到不少编程经验和习惯。两个多月后,HR要帮我转试用,也就是确认毕业后留下来。那时候我提出来的薪酬挺廉价的,还是被压价1/12(有点明白哈登被雷霆压价的心酸) 😂给的待遇偏低(跟后来拿到的offer比,低了快一倍)。还有就是不大满意公司的技术氛围,基本是各干各的,有点郁闷,没啥技术交流。最后我选择了放弃转试用,回学校。不过这个暑假里很感谢公司,感谢增哥和元哥的指导,我获得挺大的成长,为伪全栈之路打下了基础,而且debug能力猛进😂,感谢公司陈年老bug的锻炼。

期间,我还在空余时间(公司不加班)根据同学的需求写了个跟爬虫有关的项目 厦门校招君

接着就在10月中下旬(时间有点晚了其实)参加几家厦门公司的招聘,拿了几个offer。十月底选择了一家公司进行日常实习。在这边,业务繁多,导师太忙基本没怎么管你,我来了就马上独自负责vue的部分项目业务,跟着大家天天加班。这里技术氛围挺不错,和同事们的相处和合作很愉快,也经常技术交流。在这里,技术,业务逻辑思维和担当能力得到了不少锻炼,也学习了敏捷开发流程,其中我觉得收益匪浅的是从导师潮哥那里学到的有条理性地思考问题和解决问题,开发中注意时间管理和安排。最后, 受到了前辈们的称赞😊。

上了快三个月的班,想想毕业设计项目和论文还没写,就先回学校了,花了一个多月写了airchat这个项目,又是收获满满,学到了很多,借着js这门神奇的语言,算是真的成了一名小伪全栈。而且这段时间看了《学习javascript数据结构与算法》和《图解http》这两本书,趁机打一下基础。

总结2017:

  • 搭上了前端的列车
  • 拿了专业课的奖学金
  • 跟同学一起接了个外包,写了vue-homeairchat 两个开源项目,和一个未开源项目 厦门校招君
  • 实习半年,进步颇大
  • 认识了几个一起交流编程的朋友
  • 看了5本技术书籍
  • 懂得了如何更好地自学,自我驱动快速学习,研究技术
  • 明白了自己想要什么,该怎么做

不足之处还有很多,技术还很菜,新的一年,要加油^_^

展望2018:

  • 至少看6本技术书籍,不限于前端,还有其他与程序员基础及修养有关的技术书籍
  • 深度 -> 学好js这门语言及拓展(函数式编程,TypeScript) 看react源码
  • 广度 -> 大前端(pc,移动,桌面) 小全栈(nodejs) 进阶
  • 写一个真正有用的产品应用

多看书,多撸码,多交流。

github上如何修改和合并改别人的pull request

在本地修改pull request

场景:同学A提交了一个pr,同学B想把这个pr拉取到本地,并在这基础上进行修改,最后提交新的pr

  1. 在repo中点击这个

image

  1. 然后选择你要修改和合并的pr

image

  1. 滑到底部点击链接:command line instructions.

image

  1. 然后可看到github给出的接下去的操作

image

其实就是创建一个分支,然后pull拉取同学A远程repo中提交该pr的的分支,这时本地仓库代码就是同学A提交的那个pr时的版本的仓库代码。
接着你可以进行修改,然后按照你的开发流程最终把你的修改也提交一个pr。

image

示例

git checkout -b caffffe-fix-it 2.0
git pull https://github.com/caffffe/zendesk-cti-widget.git fix-it

一顿修改......

 git add .
git commit -m "increase timeout"
git push --set-upstream origin caffffe-fix-it

参考

Checking out pull requests locally

cookie ,session

cookie

cookie是指Web浏览器存储的少址数据。
Cookie支持跨域名访问,例如将domain属性设置为“.biaodianfu.com”,则以“.biaodianfu.com”为后缀的一切域名均能够访问该Cookie。跨域名Cookie如今被普遍用在网络中,例如Google、Baidu、Sina等。

浏览器使用

cookie可以使用 js 在浏览器直接设置(用于记录不敏感信息,如用户名)

大小和数量会有限制

如果没有设置有效期,那就是会话储存,关了浏览器就没了。

尽管浏览器对cookie做了大小限制,不过最好还是尽可能在coki中少存储信息,以免避免影响性能。

前后端协同使用

cookie数据会自动在Web浏览器和Web服务器之间传输,在服务端通使用 HTTP 协议规定的 set-cookie 来让浏览器种下cookie,每次网络请求 Request headers 中都会带上cookie。所以如果 cookie 太多太大对传输效率会有影响。

因此服务端脚本就可以 读、 写存储在客户端的 cookie的值。

前后端协同使用流程

前后端cookie

Response headers

服务器响应头

Set-Cookie 字段的属性

Set-Cookie 字段的属性

Request headers

客户端请求头

使用场景

1.自动填写用户名。比如你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。

2.自动登录。Cookie是浏览器保存信息的一种方式,可以理解为一个文件,保存到客户端了啊,服务器可以通过响应浏览器的set-cookie的标头,得到Cookie的信息。你可以给这个文件设置一个期限,这个期限呢,不会因为浏览器的关闭而消失啊。

session

session 是一种基于cookie的让服务器能识别某个用户的「机制」,当然也可以特指服务器存储的 session数据。
Session不会支持跨域名访问,仅在他所在的域名内有效。

使用场景举例:购物

当一个用户打开淘宝登录后,刷新浏览器仍然展示登录状态。然后把购物车的东西下单支付了。

刷新的时候服务器如何分辨这次发起请求的用户是刚才登录过的用户呢?下单的时候又怎么知道是哪个用户下单的呢?

鉴于 HTTP 是无状态协议,之前已认证成功的用户状态无法通过协 议层面保存下来。即,无法实现状态管理,因此即使当该用户下一次 继续访问,也无法区分他与其他的用户。

于是我们会使用 Cookie 来 管理 Session,以弥补 HTTP 协议中不存在的状态管理功能。

cookie and session

1.用户在输入用户名密码提交给服务端,服务端验证通过后会创建一个session用于记录用户的相关信息的对象,这个session对象中放有生成的sessionid,也可以放一些非机密的userinfo。session对象可保存在服务器内存中(容易产生内存泄露),生产环境一般是保存在数据库中。

存在数据库的session
上图为使用koa-session-minimal 后存在数据库的session store

2.创建session后,会把关联的session_id 通过setCookie 添加到http响应头部中。
浏览器在加载页面时发现响应头部有 set-cookie字段,就把这个cookie 种到浏览器指定域名下

3.客户端接收到从服务器端发来的 Session ID 后,会将其作为 Cookie 保存在本地。之后你发起刷新或者下单的请求时,浏览器会自动发送被种下sessionid的cookie,后端接受后去存session的地方根据sessionid查找是否有此session(有既证明处于登录状态的真实用户,否则拒绝此次请求),如果有,还可以读取登录时放的userinfo,获取用户身份(user_id)。

koa-session-minimal相关源码

需要注意

1.如果 Session ID 被第三方盗走,对方就可以伪装成你的身份进 行恶意操作了。因此必须防止 Session ID 被盗,或被猜出。为了做到 这点,Session ID 应使用难以推测的字符串,且服务器端也需要进行 有效期的管理(即使不幸被盗,之后也因有效期已过而失效),保证其安全性。

2.另外,为减轻跨站脚本攻击(XSS)造成的损失,建议事先在 Cookie 内加上 httponly 属性。

3.如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。

cookie + session 方式的局限性

  • 是存储式的有状态验证,由于一定时间内它是保存在服务器上的,当访问增多时,会较大地占用服务器的性能。

  • 如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。

  • 有安全隐患:CSRF 。因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
    浅谈CSRF攻击方式

参考阅读&&鸣谢:

Cookie 与 Session 的区别
session 、cookie、token的区别

js 闭包变量没被回收的原因

function foo() {
      var a = 2; 
      function bar() { 
            console.log( a ); 
      } 
      return bar;
} 
var baz = foo(); 
baz(); // 2

在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用,所以很 自然地会考虑对其进行回收。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用。

拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一 直存活,以供 bar() 在之后任何时间进行引用。

bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

mac使用tree(2017-04-25)

一、brew安装

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

然后一直按return键即可

二、brew使用

brew安装套件:

$ brew install tree

常用命令

$ tree -a //显示所有文件和目录。

$ tree -d //显示目录名称而非内容

$ tree -L 2 //这个命令是比较实用的,后面的数字2代表几层

js各种遍历总结(2017.11.19)

一 、普通for循环 (只能遍历数组和字符串)

可以break停止循环, 可以读写。
但是用return 需要外面套一层函数, 如图。
image

  • 遍历数组
var arr = [1,2,3,4,5];
for(var i = 0 ; i < arr.length; i++){
  console.log(i); //1 2 3 4 5 
}

不足:重复获取数组长度

优化

var arr = [1,2,3,4,5];
var len = arr.length;
for(var i = 0; i < len; i++){
  console.log(i); //1 2 3 4 5
} 

使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显。

这种方法基本上是所有循环遍历方法中性能最高的一种

  • 遍历字符串
var str = "for循环遍历字符串" ;
for (var i = 0; i < str.length; i++) {
console.log(str[i]+'=='+str.charAt(i)+'=='+str.charCodeAt(i));
//f==f==102
 o==o==111
 r==r==114
====24490
 ====29615
 ====36941
 ====21382
 ====23383
 ====31526
 ====20018
}
  • 遍历对象 不能使用

二、 for..in..循环 (可以遍历字符串、对象、数组)

这个循环很多人爱用,可以用来枚举对象的属性,但实际上,经分析测试,在众多的循环遍历方式中,它的效率是最低的(但在枚举对象属性上性能最好 via @liby的comment),所以一般枚举对象的属性的时候用它

  • 遍历数组
var arr = [2,3,4]
for (var i in arr) {
  console.log(arr[i]);//2 3 4 
}
  • 遍历对象
var obj = {name:"xiaoming",
age:"18"
}
for (var prop in obj) {
  console.log(prop);  // name age
}
for (var prop in obj) {
  console.log(obj[prop]);  // xiaoming  18 
}
  • 遍历字符串
var str  = "循环遍历字符串";
for(var i in str){
  console.log(i+"~"+str[i]);
}
//0~循
1~
2~
3~
4~
5~
6~

三、 forEach 循环(不能遍历字符串、对象)

不能break停止循环,可以读写。

没有办法中止或者跳出 forEach() 循环,除了抛出一个异常。如果你需要这样,使用 forEach() 方法是错误的。

若你需要提前终止循环,你可以使用:

简单循环
for...of 循环
Array.prototype.every()
Array.prototype.some()
Array.prototype.find()
Array.prototype.findIndex()

  • 遍历数组
var arr = [1,2,3,4,5];
arr.forEach(function(e){
	console.log(e); //1 2 3 4 5 
})
var arr = [3,4,5];
arr.forEach(function(ele,index,arr){
  console.log(ele+'~'+index);  //3 ~ 0    4~1    5~2 
  console.log(arr[index]); // 3 4 5 
});

数组自带的foreach循环,使用频率较高,实际上性能比普通for循环弱

  • forEach遍历字符串 不能使用
  • forEach遍历对象 不能使用

四、 for..of..循环 (需要ES6支持,且不能遍历对象)

对于for...of的循环,可以由break, throw continue 或return终止,但只读不能写。

  • 遍历数组
var str  = [2,3,4,5];
for (var ele of str) {
  console.log(ele); // 2  3  4  5  
}
  • 遍历对象 不能遍历,需要实现Symbol.interator接口
  • 遍历字符串
var str  = "循环遍历字符串";
for (var ele of str) {
  console.log(ele);//  循  环  遍  历  字  符  串
}

在数组的用法和forEach挺相似,都是操作数组的元素

打断一下,如果要用forEach 和 for of 遍历对象咋整?

不能直接遍历对象,但是可以结合Object.entries获取index

const object1 = {
  a: 'somestring',
  b: 42
};

for (const [key, value] of Object.entries(object1)) {
  console.log(`${key}: ${value}`);
}

Object.entries(object1).forEach(([key, value]) => {
    console.log(`${key}: ${value}`);
})

实际上也是通过 Object.entries去把对象{key, value} 转成[[key, value]], 实际上操作的还是数组
image

五、map

  • 遍历数组
function pow(x) {
    return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(results);

map是操作每一个arr的元素,如上例子中对数组的每一个元素执行pow方法(需要是个有return的function)。最后的结果仍是数组。
这种方式也是用的比较广泛的,虽然用起来比较优雅,但实际效率还比不上foreach

最后

个人喜欢需要return一个同样长度的数组的就用map,遍历对象用for in,正常其他用普通for循环就好了,性能好能读写能break return,当然不考虑局限性forEach和for of语法会更简洁

推荐阅读

js遍历方式总结
JS几种数组遍历方式以及性能分析对比

js图片预加载(2017.09.04)

继上一次的图片懒加载,这次讲下js实现图片预加载。

先上demo

demo:图片预加载

再上代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        (function () {
            function loadImages(sources, callback) {
                var count = 0,
                    images = {},
                    imgNum = 0;
                for (src in sources) {
                    imgNum++;   //imgNum =2
                }
                for (src in sources) { //src 为 img1和img2
                    images[src] = new Image();  //images = {img1:img,img2:img}
                    images[src].onload = function () { //onload 事件会在页面或图像加载完成后立即发生
                        if (++count >= imgNum) { //当次数大于等于2次时
                            callback(images);  //调用loadImages的回调函数
                        }
                    }
                    images[src].src = sources[src]; //img1-> img.src = "http://pic.58pic.com/58pic/17/18/97/01U58PIC4Xr_1024.jpg";img2同理
                }
            }
            //存储图片链接信息的关联数组  
            var sources = { //想要多少张就放多少张
                img1: "http://pic.58pic.com/58pic/17/18/97/01U58PIC4Xr_1024.jpg",
                img2: "http://ww4.sinaimg.cn/large/006y8mN6gw1fa5obmqrmvj305k05k3yh.jpg",
                img3:"http://ww1.sinaimg.cn/large/006y8mN6gw1fa7kaed2hpj30sg0l9q54.jpg"
            };
            //调用图片预加载函数,实现回调函数  
            loadImages(sources, function (images) {
                //TO-DO something  (如下面用canvas把图画出来)
                //var images = new Image();   创建一个<img>元素
                // images.src = 'myImage.png';  设置图片源地址
                var canvas = document.getElementById('canvas');
                var ctx = canvas.getContext('2d');
                //ctx.drawImage(image, dx, dy, dWidth, dHeight);
                ctx.drawImage(images.img1, 20, 20, 100, 100);
                ctx.drawImage(images.img2, 140, 20, 100, 100); 
                ctx.drawImage(images.img3, 260, 20, 100, 100); 
            });
        }());
    </script>
</head>
<body>
    <canvas id="canvas" width="1000px" height="1000px"></canvas>
</body>
</html>

可以看出代码注释很详细,
如果有些知识点还不是很清楚,请看我找的一些好的知识讲解文章,省得像我一样到处找啦;

同时,如果您觉得有点帮助,可以关注给我的github项目给个start,watch哦,我会不定期更新js的一些开发常用的知识点技能。
js开发常见技能收集->github

onload
for in 遍历
Image 元素构造器
为何使用++count
如何使用canvas创建图片
canvas的drawImage()

-------------更新 图片预加载的新方法

        function preload(arr) {
            var newimages = [];
            for (var i = 0; i < arr.length; i++) {
                newimages[i] = new Image();
                newimages[i].src = arr[i];
            }
        }

        var imgs = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg'];
        preload(imgs);

图片预加载最常用的方法就是new一个Image对象,然后将该对象的src属性设置为要加载的URL路径,这就实现了图片的预加载。

如果需要加载完图片后回调

    function preload(arr) {
            var newimages = [], loadedimages = 0;
            var postaction = function(){};
            var arr = (typeof arr != "object") ? [arr] : arr;

            function imageloadpost() {
                loadedimages++;
                if (loadedimages == arr.length) {
                    //alert("图片已经加载完成");
                    postaction(newimages);
                }
            }

            for (var i = 0; i < arr.length; i++) {
                newimages[i] = new Image();
                newimages[i].src = arr[i];
                newimages[i].onload = function() {
                    imageloadpost();
                }
                newimages[i].onerror = function() {
                    imageloadpost();
                }
            }
    }

如有错误,恳请指出指导^ ^

解决非chrome浏览器(移动端适用)后退刷新问题 (2017.08.29)

写页面时你可能会遇到这个问题,就是用Firefox,Safari,IE等非chrome浏览器,点击浏览器自带的返回键会发现不会刷新页面,因为那时js代码没有执行。

这样比如下面这种情形:
本来只建了一个二维码,新建了一个
E4294505-FFE4-473E-9316-5E32A64DB063.png

新建完用户没有点击你写的返回按钮(图中的完成按钮),而是点击了浏览器自带的返回键

图片中的chrome浏览器只是当演示用,现实中请用非chrome的去感受这个问题
比如用firefox点击打开图片示例的网站链接

736A09E5-D8C0-45E9-90CC-27F4AE5378B5.png

结果回去页面没有刷新,结果显示还是原来的样子
736A09E5-D8C0-45E9-90CC-27F4AE5378B5.png

而如果返回刷新了,会这样(用户体验会不会好些?)
9F4837FE-BF66-437F-B2D7-67FE88229866.png

奉上代码

要刷新的页面(如页面A)

<script>
    //chrome自带后退刷新,故不再次刷新
       var ua = window.navigator.userAgent; 
       var isChrome = ua.indexOf("Chrome") && window.chrome;  
       if (! isChrome) {  
    //浏览器后退刷新
            function reload() {
                setInterval(function() {   //这个定时器返回A页面会继续执行
                    if (localStorage.reload == 'true' ) {  //判断是否刷新页面
                        localStorage.setItem('reload','false');
                        location.reload()
                    }
                }, 500)
            };
        reload();
       }  

</script>

在A页面之后访问的页面(如页面B)添加一下一行代码
ps:作为A页面执行刷新功能的开关

localStorage.setItem('reload','true');

本地仓库push到github远程仓库报错

我在github上creat一个新的repo

然后把这个repo拉到我的电脑本地

然后提交了几个commit

然后要把本地库的内容推送到远程,用git push命令(期间还尝试了github客户端push)

结果报错

"Authentication failed. You may not have permission to access the repository. Open preferences and verify that you're signed in with an account that has permission to access this repository."

image

image

原因:由于远程库是空的,不能直接git push

解决办法

终端执行以下命令行

git push -u origin master

然后输入你github的username 和 password

然后以后可以直接用git push 了

js数据结构---链表

链表

概念:链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个 元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。

与数组的区别:相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。数组缺点:(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素(尽管我们已经学过的JavaScript的array类方法可以帮 我们做这些事,但背后的情况同样是这样)。
然而,链表需要使用指针,因此实现链表时需要额外注意。数组的另一个细节是可以直接访问任何位置的任何元素,而要想访问链表中间的一个元素,需要从起点(表头)开始迭代列表直到找到 所需的元素。

image

创建链表

function LinkedList() {
    let Node = function(element){
        this.element = element;
        this.next = null;
    };
    let length = 0;
    let head = null;

    this.append = function(element){
        let node = new Node(element),
            current;
        //向链表尾部追加元素
        if (head === null){ // 列表中第一个节点 (场景1:列表为空,添加的是第一个元素)
            head = node;
        } else {
            current = head; //(场景2:列表不为空,向其追加元素。)
            while(current.next){ //循环列表,直到找到最后一项
                current = current.next;
            }
            current.next = node;//找到最后一项,将其next赋为node,建立链接
        }
        length++; //更新列表的长度
    };

    //在任意位置插入元素
    this.insert = function(position, element){
        if (position >= 0 && position <= length){  //检查越界值
            let node = new Node(element),
                current = head,
                previous,
                index = 0;
            if (position === 0){   //在第一个位置添加(场景1:需要在列表的起点添加一个元素,也就是第一个位置。)
                node.next = current;
                head = node;
            } else {     //(场景2:在列表中间或尾部添加一个元素)
                while (index++ < position){    //不断地循环,直到找到要插入的位置
                    previous = current;
                    current = current.next;
                }
                node.next = current;    //插入元素
                previous.next = node;
            }
            length++; //更新列表的长度
            return true;
        } else {
            return false;
        }
    };

   //从链表中移除元素
    this.removeAt = function(position){
        if (position > -1 && position < length){//检查越界值
            let current = head,
                previous,
                index = 0;
            if (position === 0){          //移除第一项
                head = current.next;
            } else {
                while (index++ < position){  //不断地循环,直到找到要移除目标的位置
                    previous = current;
                    current = current.next;
                }
                previous.next = current.next;  //将previous与current的下一项链接起来:跳过current,从而移除它
            }
            length--;   //更新列表的长度
            return current.element;
        } else {
            return null;
        }
    };

    this.remove = function(element){
        let index = this.indexOf(element);
        return this.removeAt(index);
    };

    this.indexOf = function(element){ //indexOf方法接收一个元素的值,如果在列表中找到 它,就返回元素的位置,否则返回-1。
        let current = head,
            index = 0;
        while (current) {
            if (element === current.element) {
                return index;
            }
            index++;
            current = current.next;
        }
        return -1;
    };

    this.isEmpty = function() {
        return length === 0;
    };

    this.size = function() {
        return length;
    };

    this.getHead = function(){
        return head;
    };

    this.toString = function(){ //toString方法会把LinkedList对象转换成一个字符串
        let current = head,
            string = '';
        while (current) {
            string += current.element + (current.next ? ', ' : '');
            current = current.next;
        }
        return string;
    };

    this.print = function(){
        console.log(this.toString());
    };
}

双向链表

双向链表和普通链表的区别:在链表中, 一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素, 另一个链向前一个元素。双向链表提供了两种迭代列表的方法:从头到尾,或者反过来。我们也可以访问一个特定节 点的下一个或前一个元素。在单向链表中,如果迭代列表时错过了要找的元素,就需要回到列表 起点,重新开始迭代。这是双向链表的一个优点。

image

function DoublyLinkedList() {

    let Node = function(element){
        this.element = element;
        this.next = null;
        this.prev = null; //新增的
    };

    let length = 0;
    let head = null;
    let tail = null; //新增的  用来保存对列表最后一 项的引用的tail属性

    this.append = function(element){
        let node = new Node(element),
            current;
        if (head === null){ //在第一个位置添加
            head = node;
            tail = node; //NEW
        } else {
            //attach to the tail node //NEW
            tail.next = node;
            node.prev = tail;
            tail = node;
        }
        length++; //update size of list
    };

    this.insert = function(position, element){
        if (position >= 0 && position <= length){ //检查越界值
            let node = new Node(element),
                current = head,
                previous,
                index = 0;
            if (position === 0){ //在第一个位置添加
                if (!head){      //新增的
                    head = node;
                    tail = node;
                } else {
                    node.next = current;
                    current.prev = node; //新增的
                    head = node;
                }
            } else  if (position === length) { //最后一项 //新增的
                current = tail;     //current变量将引用最后一个元素
                current.next = node;
                node.prev = current;
                tail = node;
            } else {
                while (index++ < position){ //迭代 列表,直到到达要找的位置
                    previous = current;
                    current = current.next;
                }
                node.next = current;
                previous.next = node;
                current.prev = node; //新增的
                node.prev = previous; //新增的
            }
            length++; //更新列表的长度
            return true;
        } else {
            return false;
        }
    };

    this.removeAt = function(position){
        if (position > -1 && position < length){//检查越界值
            let current = head,
                previous,
                index = 0;
            if (position === 0){         //移除第一项
                head = current.next; // {1}
                //如果只有一项,更新tail //新增的                
                if (length === 1){ 
                    tail = null;
                } else {
                    head.prev = null; 
                }
            } else if (position === length-1){ //最后一项 //新增的
                current = tail; // {4}
                tail = current.prev;
                tail.next = null;
            } else {
                while (index++ < position){ // 迭代列表,直到到达要找的 位置
                    previous = current;
                    current = current.next;
                }
                //将previous与current的下一项链接起来——跳过current
                previous.next = current.next; 
                current.next.prev = previous; //新增的
            }
            length--;  //更新列表的长度
            return current.element;
        } else {
            return null;
        }
    };

    this.remove = function(element){
        let index = this.indexOf(element);
        return this.removeAt(index);
    };

    this.indexOf = function(element){
        let current = head,
            index = -1;
        if (element == current.element){
            return 0;
        }
        index++;
        while(current.next){
            if (element == current.element){
                return index;
            }
            current = current.next;
            index++;
        }
        if (element == current.element){
            return index;
        }
        return -1;
    };

    this.isEmpty = function() {
        return length === 0;
    };

    this. size = function() {
        return length;
    };

    this.toString = function(){
        let current = head,
            s = current ? current.element : '';
        while(current && current.next){
            current = current.next;
            s += ', ' + current.element;
        }
        return s;
    };

    this.inverseToString = function() {
        let current = tail,
            s = current ? current.element : '';
        while(current && current.prev){
            current = current.prev;
            s += ', ' + current.element;
        }
        return s;
    };

    this.print = function(){
        console.log(this.toString());
    };

    this.printInverse = function(){
        console.log(this.inverseToString());
    };

    this.getHead = function(){
        return head;
    };

    this.getTail = function(){
        return tail;
    }
}

js冒泡排序(2017-04-29)

冒泡排序是与插入排序拥有相等的运行时间,但是两种算法在需要的交换次数却很大地不同。
在最好的情况,冒泡排序需要O(n^2)次交换,而插入排序只要最多O(n)交换。
冒泡排序的实现(类似下面)通常会对已经排序好的数列拙劣地运行O(n^2),而插入排序在这个例子只需要O(n)个运算
因此很多现代的算法教科书避免使用冒泡排序,而用插入排序替换之。冒泡排序如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,也可以把最好的复杂度降低到O(n)。在这个情况,已经排序好的数列就无交换的需要。若在每次走访数列时,把走访顺序反过来,也可以稍微地改进效率。有时候称为鸡尾酒排序,因为算法会从数列的一端到另一端之间穿梭往返。

冒泡排序算法的运作如下:(两个for循环差不多搞定)

比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
由于它的简洁,冒泡排序通常被用来对于程序设计入门的学生介绍算法的概念。

function bubbleSort(arr){
if(arr.length<=1){
 return arr;
 }
 for(var j=0;j<arr.length;j++){
     for( var i = 0; i<arr.length-j;i++){
         if(arr[i] > arr[i+1]){ 
             var num = arr[i];
             arr[i] = arr[i+1];
             arr[i+1] = num;
         }
       }
      }
      
      return arr;
  }

嘿嘿, 申请使用github被占的用户名成功

由于之前的用户名大不会取,随便弄了个
现在呢,有了自己的英文名
所以 想用自己的英文名当做github用户名
可是一搜发现被占用
细看发现是个不曾活跃过的用户
于是就Report abuse
然后收到github的回复(差不多一天,速度很快呀,赞)

9e11819a66f4e04b4178cfad5e61bb28

而且,之前担心项目和小绿格会受影响,目前貌似还没发现

image

嘿嘿,开心

GitAds广告

GitAds

UI测试:从WebdriverIO and Selenium 转 Puppeteer (译)

我的动机

我在GoDaddy的一个全栈团队工作,帮助支持一系列产品:从GoDaddy的新客户门户(网站)到用于开发新Web内容的内部工具。
为了测试这些当中的每一个前端(译者注:界面和功能),我们使用了带有Selenium (译者注:Selenium 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样) 浏览器自动化的测试框架:WebdriverIO。虽然我们对setup的跨平台可配置性表示赞赏,但我们遇到了关于Selenium的一些问题。

部署浏览器Images

将Selenium浏览器整合到部署中时出现了第一个问题。我们尝试的一种方法是在部署时将Selenium浏览器下载到与代码库相同的容器中。这导致了复杂的Dockerfiles,当浏览器部署出现问题时,调试成本很高。

幸运的是,这个问题有一个简单的解决方案:Selenium预先构建的Docker容器。这些images是模块化的,维护的,随时可用的。但它带来了自己的问题。默认情况下,Docker不允许您在本地开发时查看浏览器UI,但images带有方便的调试模式以解决此问题。

CICD Flakiness

第二个痛点是没有我们的CICD管道,Selenium将导致薄脆度。(The second pain point was that Selenium was causing flakiness within our CICD pipeline. 不大好翻译....) 。 Jenkins slaves 未能连接到他们刚刚启动的浏览器容器。在某些时候,4个版本中有3个会失败。

当然,仅仅责备Selenium会有点不公平。鉴于我们的开发和CICD设置,它只是证明是一个难以维护的系统。如果我们有专门的时间让我们的管道更能适应这些故障(鉴于故障只发生在我们的CICD管道中,这(时间成本)本身就很昂贵),我们几乎可以肯定地减少了薄脆度。然而,测试套件和浏览器之间的松散耦合是一个持久的失败点,并且把时间花在尝试解决我们的CICD问题上可能是一个更好的花费时间的选择… 好吧,找另一个UI测试框架去。

来到Puppeteer

正是在这一点上,我们发现了Puppeteer,一个由Google开发的detached-head(独立头部)的UI测试框架。Puppeteer实现了Chrome devtools协议,目前只支持Google Chrome和Chromium。缺乏跨浏览器支持是一个问题,但它也允许测试框架和浏览器之间更紧密的耦合,消除了我们与WebdriverIO / Selenium的主要摩擦点。

方便的是,我们最积极开发的代码库是一个内部工具,所以Puppeteer只支持一个浏览器是可以接受的。这似乎是使用Puppeteer的绝佳机会。而Puppeteer对提高可靠性的允诺太诱人了。

那么这两个框架之间的过渡到底是什么样的呢?让我们从一个最显眼的比较点开始吧......

语法

从语法上讲,Puppeteer和WebdriverIO看起来非常相似。举例来说,每个框架的代码用于单击className 是 myLinkComponent的链接:

WebdriverIO:

await browser.waitForExist('.myLinkComponent');
await browser.click('.myLinkComponent');

Puppeteer:

await page.waitFor('.myLinkComponent');
await page.click('.myLinkComponent');

这些示例中最显着的差异(这实际上与它们的相似之处)可能是WebdriverIO使用全局浏览器常量,而Puppeteer使用在测试开始时创建的页面对象。

再举一个例子,考虑使用className 为 myTextComponent的Puppeteer和WebdriverIO代码去查找和读取文本组件:

WebdriverIO:

await browser.waitForExist('.myTextComponent');
const myText = await browser.getText('.myTextComponent');

Puppeteer:

await page.waitFor('.myTextComponent');
const myText = await page.$eval('.myTextComponent', component => component.textContent);

这里的Puppeteer示例有点不那么直截了当:没有用于抓取文本内容的实用程序,所以我们必须传递一个函数来将适当的内容写入页面的$ eval方法。不过,测试代码非常相似。

两种语法之间最重要的区别可能是您如何配置测试。大多数WebdriverIO的配置都发生在wdio.conf.js文件中(示例):

同时,Puppeteer就像一个普通的npm模块,你可以在运行它们的代码中配置测试(更简洁):

const browser = await puppeteer.launch({
  headless: false
});
const page = await browser.newPage();

这种区别突出了两种语言之间的差异:WebdriverIO允许更多种类的配置,其中Puppeteer花费更少的精力(开箱即用)。

通常,两个框架之间的语法相似性使我们可以轻松地将现有的测试套件从WebdriverIO移植到Puppeteer。

开发周期

我已经明确了我的团队对于Selenium的痛点(不好用的地方),所以我会使用我喜欢的Puppeteer。

默认情况下,Puppeteer在headless模式下运行,这意味着测试执行时不会实际打开Chrome UI窗口。对于本地开发,您通常需要禁用headless模式,以便可以在浏览器中观察测试执行情况。

Puppeteer支持许多选项,使本地开发更容易。这里有些例子:

const browser = await puppeteer.launch({
  headless: false,  // Turn on local browser UI
  devtools: true,  // Open Chrome devtools at the beginning of the test
  slowMo: 250  // Wait 250 ms between each step of execution
});
const page = await browser.newPage();

// Log browser output to console
page.on('console', (msg) => {
  console.log('console:log', ...msg.args);
})

// Handle dialogs.
page.on('dialog', (dialog) => {
  if (dialog.type() === 'alert') {
    await dialog.dismiss();
  }
});

// Take a screenshot of the page.
await page.screenshot({path: 'my-screenshot.png'});

这篇博文可以让您更详细地了解这些和其他的Puppeteer开发选项。

最重要的是,在我们采用Puppeteer后的五个月里,框架本身并没有在我们的CICD管道中造成任何瑕疵(caused no flakiness)。这是一个框架,开发人员可以确信本地传递的测试也会传递给CICD。

结论

WebdriverIO/Selenium:

  • 与浏览器的松散的耦合提供了频繁的故障点
  • 平台不可知
  • 他们网站上有大量的示例和文档

Puppeteer:

  • 没有跨平台测试
  • 更可靠的自动化测试
  • 随时可以自定义调试

简而言之,有许多应用需要支持的浏览器不仅仅是谷歌浏览器。在这些情况下,Puppeteer不是一个选择,您应该使用WebdriverIO && Selenium 或其他跨平台UI测试框架。否则,考虑Puppeteer。转换很简单,根据您团队的开发周期和CICD设置,它可能是一个对开发者更加友好且可靠的UI测试体验。

资源:

原文

UI Testing: moving from WebdriverIO and Selenium to Puppeteer

译者附带:

image

npm trends :puppeteer vs selenium vs webdriverio

本地mysql客户端连接centos的数据库(2017.10.01)

最近觉得用终端修改centos数据库有点麻烦,所以用本地mysql客户端可视化

进入centos的数据库

mysql -u root -h localhost -p

使用数据库

use mysql;

赋予远程权限

GRANT ALL PRIVILEGES ON . TO 'root'@'%' IDENTIFIED BY 'password' WITH GRANT OPTION;

让权限立即生效

flush privileges;

--解释
其中root表示用户名,%表示所有的电脑都可以连接,也可以设置某个ip地址运行连接,password表示密码

然后在本地的mysql客户端连接

airchat 打包上线小记

前言

本次开发我用了三个git分支,分别是主分支master ,开发分支dev , 线上分支online,
如果你要fork到你的本地跑,最好是fork master分支 。

然后最好把node升级到v8.0.0版本,以免版本问题造成一些报错。

步骤

1.购买,部署配置云主机

这里你需要完成购买一个云主机(我选的是centos7.2,不同版本部署方式可能不一样),登陆你的云主机,部署nodejs,部署mysql,安装pm2

如果你有了云主机部署配置经验请略过,没有的话可以参考下我的一篇文章 记录下node项目部署上线的过程及坑

这边不再细讲

2.修改些master分支的代码,然后打包前端代码

  • a: 修改webpack的路径配置

image

  • b: 把soket链接的ip改成你云主机的ip

image

  • c: 注释掉axios.defaults.baseURL

image

ps: b和c 两个步骤是根据我上线时遇到的bug,如果你们有更好的解决方法,恳请告知。

  • d:执行以下代码进行打包
npm run build

打包你的前端代码,生成一个dist文件夹,这里放着前端打包生成的静态资源。

image

3.添加koa2静态资源管理

网页存储在服务器,是静态资源的形式存在的,你可以把静态资源托管到其他的服务器上(比如github静态服务器),也可以和后端文件放在同一个服务器,让koa2去管理静态资源。

以下是步骤

a: 切换到server 文件夹 然后下载koa-static中间

cd server
npm install koa-static -s

image

b: 在 airchat/server/index.js 文件中添加以下几行代码

const static = require('koa-static') //静态资源服务插件
const path = require('path') //路径管理


// 配置静态资源
const staticPath = '../dist'
app.use(static(
	path.join(__dirname, staticPath)
))

4.在云主机中添加你的数据库

这边默认你已经完成了步奏1在云主机中对数据库的部署。然后你可以用多种方式添加你本地sql的文件到你云主机的数据库中。

这边讲下我是用的方式

我本地下了一个mysql的客户端,方便操作使用。(mac环境下)
image

接着用本地mysql客户端连接centos云主机的数据库,具体怎么操作可参考我的这篇文章本地mysql客户端连接centos云主机的数据库

然后就可以在本地操作云主机的数据库了
image
把sql文件import进来即可

5. 上传你的项目文件到云主机

上传项目文件到云主机我用的是FileZilla 这个ftp可视化客户端,去官网下载安装,然后输入主机名(你买的云服务器的公网ip) ,用户名(默认是root),密码(你设的云服务器密码),还有端口22 。然后连接。
想上传啥直接拖拽就行了,记得先把项目里的node包删掉,不然文件数量
分分钟上万。。。。上传到猴年马月。

image
image

正确姿势 -> 删除node包,在云服务器中的airchat/ 路径 和 airchat/server中执行

npm i

6.运行你的项目

步骤1中你安装好了pm2(npm install -g pm2

这里只需要在终端执行(路径 server/ )

pm2 start index.js

这时在浏览器输入你云主机的ip或者对应的域名,即可看到你部署的线上网站。

关于自执行函数(立即执行函数)(2017.04.26)

什么是自执行函数?

一下有三种写法

1.最前最后加括号

(function(){alert(1);}()); 

这种方法好处是能提醒阅读代码的人,这段代码是一个整体。 坏处是前面的代码行后记得加分号,不然会报错。如:

var a=1 
(function(){alert(1);}()); 

2.function外面加括号

(function(){alert(1);})(); 

这种做法比方法1少了一个代码整体性的好处.

3.function前面加运算符,常见的是!与void 。

!function(){alert(1);}(); 
void function(){alert(2);}();

显然,加上“!”或“+”等运算符,写起来是最简单的。加上“void ”要敲五下键盘,但是听说有一个好处是,比加"!"少一次逻辑运算。

网上的图

  • 首先声明一个匿名函数 function(){alert('我是匿名函数')}。
  • 然后在匿名函数后面接一对括号 (),调用这个匿名函数。

自执行函数的作用

创建一个独立的作用域,这个作用域里面的变量,外面访问不到(即避免「变量污染」)

例子来看一个著名的面试题:

var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
  liList[i].onclick = function(){
    alert(i) // 为什么 alert 出来的总是6,而不是0、1、2、3、4、5
  }
}

为什么 alert 的总是 6 呢?

因为 i 是贯穿整个作用域的,而不是给每个li 分配了一个i,如下:

也是网上的图

那么怎么解决这个问题呢?

用立即执行函数给每个li创造一个独立作用域即可(当然还有其他办法):

var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
  !function(ii){
    liList[ii].onclick = function(){
      alert(ii) // 0、1、2、3、4、5
    }
  }(i)
}

在立即执行函数执行的时候,i 的值被赋值给 ii,此后 ii 的值一直不变。i 的值从 0 变化到 5,对应 6 个立即执行函数,这 6 个立即执行函数里面的 ii 「分别」是 0、1、2、3、4、5。

(整理自网络)

JS数组的各种操作方法(2017.12.21)

先来个经典数组操作题:

求斐波那契数列的前20 个数字。已知斐波那契数列中第一个数字是1 ,
第二个是2 ,从第三项开始,每一项都等于前两项之和

var fibonacci = []; 
fibonacci[0] = 1;
fibonacci[1] = 2; 
for(var i = 2; i < 20; i++){
fibonacci[i] = fibonacci[i-1] + fibonacci[i-2]; 
}
console.log(fibonacci); //[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946]

数组增删

push unshift shift pop

var numbers = [0,1,2,3,4,5,6,7,8,9];
numbers[numbers.length] = 10; //插入尾
numbers.push(11);//插入尾
numbers.push(12, 13); //插入尾
numbers.unshift(-2); //插入头
numbers.unshift(-4, -3);//插入头
numbers.shift();//删除并返回第一个数
numbers.pop(); // 删除并返回最后一个数
console.log(numbers);  //[-3, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,]

通过push和pop方法,就能用数组来模拟栈
通过shift和unshift方法,就能用数组模拟基本的队列数据结构

  • 二维数组
var averageTemp = [];
averageTemp[0] = [72,75,79,79,81,81];
averageTemp[1] = [81,79,75,75,73,72];

function printMatrix(myMatrix) {
for (var i=0; i<myMatrix.length; i++){
for (var j=0; j<myMatrix[i].length; j++){
console.log(myMatrix[i][j]);
}
}
}

printMatrix(averageTemp); //72 75 79 79 81 81 81 79 75 75 73 72

三维就三个for循环,以此类推。

数组迭代方法

数组迭代方法

数组迭代方法

  • 数组合并
var zero = 0;
var positiveNumbers = [1,2,3];
var negativeNumbers = [-3,-2,-1];

console.log(negativeNumbers.concat(zero, positiveNumbers))//-3 -2 -1 0 1 2 3
console.log(negativeNumbers.concat(positiveNumbers))//-3 -2 -1 1 2 3
  • 迭代器函数
var isEven = function (x) {
// 如果x是2的倍数,就返回true
console.log(x);
return (x % 2 == 0) ? true : false;
// 也可以写成return (x % 2 == 0) ? true : false
};
var numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

[every]数组numbers的第一个元素是1,它不是2的倍数, 因此isEven 函数返回false,然后every执行结束。

numbers.every(isEven); // 1 false  

[some] some方法和every的行为类似,不过some方法会迭代数组的每个元素,直到函数返回true:

numbers.some(isEven); //1 2 true

[forEach]如果要迭代整个数组,可以用forEach方法。它和使用for循环的结果相同:

numbers.forEach(function(x){
console.log((x % 2 == 0));
}); // [false, true, false, true, false, true, false, true,
false, true, false, true, false, true, false]

[map]JavaScript还有两个会返回新数组的遍历方法。第一个是map:

var myMap = numbers.map(isEven); //:[false, true, false, true, false, true, false, true,false, true, false, true, false, true, false]

[filter]。它返回的新数组由使函数返回true的元素组成:

var evenNumbers = numbers.filter(isEven);//[2, 4, 6, 8, 10, 12, 14]

[reduce]reduce方法接收一个函数作为参数,这个函数有四个参数:previousValue、currentValue、index和array。这个函数会返回一个将被叠加到累加器的值,reduce方法停止执行后会返回这个累加器。对一个数组中的所有元素求和:

numbers.reduce(function(previous, current, index){
return previous + current;
}); //120

slice

概念:

slice() 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。原始数组不会被修改。

实例

//如果不传入参数二,那么将从参数一的索引位置开始截取,一直到数组尾
var a=[1,2,3,4,5,6];
var b=a.slice(0,3);  //[1,2,3]
var c=a.slice(3);    //[4,5,6]
 
//如果两个参数中的任何一个是负数,array.length会和它们相加,试图让它们成为非负数,举例说明:
//当只传入一个参数,且是负数时,length会与参数相加,然后再截取
var a=[1,2,3,4,5,6];
var b=a.slice(-1);  //[6]
 
//当只传入一个参数,是负数时,并且参数的绝对值大于数组length时,会截取整个数组
var a=[1,2,3,4,5,6];
var b=a.slice(-6);  //[1,2,3,4,5,6]
var c=a.slice(-8);  //[1,2,3,4,5,6]
 
//当传入两个参数一正一负时,length也会先于负数相加后,再截取
var a=[1,2,3,4,5,6];
var b=a.slice(2,-3);  //[3]
 
//当传入一个参数,大于length时,将返回一个空数组
var a=[1,2,3,4,5,6];
var b=a.slice(6);  //[]

另外一种用法:
slice 方法可以用来将一个类数组(Array-like)对象/集合转换成一个新数组。你只需将该方法绑定到这个对象上。 一个函数中的 arguments 就是一个类数组对象的例子。

实例

function list() {
  console.log(arguments); //Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  console.log(Array.prototype.slice.call(arguments)) ; //[1, 2, 3]
}

list(1, 2, 3); 

splice

概念:
splice() 方法通过删除现有元素或添加新元素来更改一个数组的内容。

用法:array.splice(start,deleteCount,item...)

实例:

var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];

myFish.splice(2, 0, 'drum'); // 在索引为2的位置插入'drum'
// myFish 变为 ["angel", "clown", "drum", "mandarin", "sturgeon"]

myFish.splice(2, 1); // 从索引为2的位置删除一项(也就是'drum'这一项)
// myFish 变为 ["angel", "clown", "mandarin", "sturgeon"]

join

概念:
将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。作用与split相反。
join() 方法,不会改变数组!

实例:

var arr = ["0", "1", "2"];
var b = arr.join("");// "012" 

还有我写的另外一篇 js各种遍历方法总结

Nginx反向代理centos的80端口(2017.10.13)

最近换了个新的云主机,重新配置了下centos的环境。记录下Nginx反向代理centos的80端口的流程。

HTTP请求是80端口,但是在Linux上非root权限是无法使用1024以下端口的,并且因为安全原因,最好不要使用root权限登录服务器,所以无法直接用node.js程序监听80端口。因此我们需要使用Nginx给node.js做反向代理,将80端口指向应用程序监听的端口(如node.js默认的3000端口)。

  1. 添加Nginx仓库

yum install epel-release

2.下载Nginx

yum install nginx

3.启用nginx服务

service nginx start

4.添加开机启动

systemctl enable nginx

5.修改Nginx配置文件

vi /etc/nginx/nginx.conf

6.进入配置文件后修改下

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name www.hxvin.com,hxvin.com;   /#修改这一行(写上你绑定的域名)
       root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
proxy_pass http://127.0.0.1:4000;  # 添上这一行(端口号写你nodejs运行的端口号)
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

7.测试配置文件是否能够正确运行

nginx -t

[root@jdu4e00u53f7 ~]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

出现这样,证明配置成功

8.重启nginx

service nginx restart

现在直接在浏览器中输入我们配置的域名就可以访问我们的项目了。

ps:如果你用的云主机是国内的,那么你的域名必须先备案才能访问,不然只能域名加后端端口号访问了,如www.hxvin.com:4000

动手实现一个redux

概念

  • 一个app有一个store,一个store管理着一个全局state
  • createStore 传入reducer,返回getState, dispatch, subscribe
  • action是一个至少有type这个键的对象,可以写一个creactActioner 函数去return生成action对象
  • createStore.dispatch(action) 根据action这个对象去更新state
  • dispatch是一个函数,内部有将执行reducer函数
  • reducer也是一个函数,传入state,action, 输出一个新的state . (switch case return….)

demo链接

qq20180719-212609-hd

<div id = 'title'></div>
<input type="button" id = "changeTheme" value="变成蓝色主题">
//实现一个createStore
function createStore(reducer) { // 参数是reducer函数(纯函数)
  let state = null;
  const listeners = [];
  const subscribe = listener => listeners.push(listener); // 观察者模式,订阅一次,每次dispatch时都会执行传入的这个函数
  const getState = () => state;
  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener()); // 订阅监听函数
  };
  dispatch({}); // 初始化 state 也就是执行reducer函数,由于没传state,state被默认值替换( {themeName: 'Red Theme',themeColor: 'red'})
  return { getState, dispatch, subscribe };
}

// 写一个reducer
function themeReducer(state, action) {
  if (!state) {
    return { // 若没传state 则返回一个默认的state对象
      themeName: 'Red Theme',
      themeColor: 'red'
    };
  }
  switch (action.type) {
    case 'UPATE_THEME_NAME':
      return { ...state, themeName: action.themeName };
    case 'UPATE_THEME_COLOR':
      return { ...state, themeColor: action.themeColor };
    default:
      return state;
  }
}

// 把reducer传入createStore 生成 store`
const store = createStore(themeReducer); // 这边也初始化了state

// 渲染页面的代码
function renderApp(state) {
  const titleDOM = document.getElementById('title');
  titleDOM.innerHTML = state.themeName;
  titleDOM.style.color = state.themeColor;
}

// 监听数据变化重新渲染页面
store.subscribe(() => renderApp(store.getState()));// 让每次dispatch时都会执行传入的这个函数,渲染页面

// 首次渲染页面
renderApp(store.getState());


// 后面可以随意 dispatch  action了,页面自动更新

//creactActioner  action生成函数
const updateThemeName = () => ({
  type: 'UPATE_THEME_NAME',
  themeName: 'Blue Theme'
});
const updateThemeColor = () => ({
  type: 'UPATE_THEME_COLOR',
  themeColor: 'blue'
});

document.getElementById('changeTheme').onclick = () => {
  store.dispatch(updateThemeName());
  store.dispatch(updateThemeColor());
  //这样也行
  //store.dispatch({ type: 'UPATE_THEME_NAME', themeName: 'Blue Theme' });
  //store.dispatch({ type: 'UPATE_THEME_COLOR', themeColor: 'blue' });
};

参考:react小书

js浅拷贝与深拷贝(2017.09.04)

深浅拷贝区别

深拷贝和浅拷贝只针对像 Object, Array 这样的复杂对象(引用类型)的。

复制引用(引用类型)的拷贝方法称之为浅拷贝,也因为直接复制引用类型,导致新旧对象共用一块内存地址,会互相影响,具体看例子

深拷贝就是指完全的拷贝一个对象,将原对象的各个属性递归复制下来。这样即使嵌套了对象,两者也相互分离。

浅拷贝

var shallowCopy = function(obj) {
    if (typeof obj !== 'object') return;      // 只拷贝对象
    var newObj = obj instanceof Array ? [] : {};     // 根据obj的类型判断是新建一个数组还是对象
    for (var key in obj) {      // 遍历obj,并且判断是obj的属性才拷贝
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

var obj = { a:1, arr: [2,3] };
var shallowObj = shallowCopy(obj);
shallowObj.arr[1] = 5;
console.log(obj.arr[1])  // 5
shallowObj.a = 5;
console.log(obj.a) // 1 

备注:

①hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性。Object.prototype.hasOwnProperty(),;
②instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。换种说法就是如果左侧的对象是右侧类的实例, 则表达式返回true, 否则返回false 。

原理

遍历对象,然后把属性和属性值都放在一个新的对象

具体:判断是参数否为对象,是数组还是object;用 for in 遍历所传的参数对象,并用 if (arg.hasOwnProperty(prop)) 忽略掉继承属性,然后赋值给临时创建的对象,最后返回此对象。

深拷贝

实现一个深拷贝在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数就OK了

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

var obj = { a:1, arr: [2,3] };
var shallowObj = shallowCopy(obj);
shallowObj.arr[1] = 5;
console.log(obj.arr[1])  // 3
shallowObj.a = 5;
console.log(obj.a) // 1 

性能问题

尽管使用深拷贝会完全的克隆一个新对象,不会产生副作用,但是深拷贝因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。

本文参考地址

待续:

https://www.zhihu.com/question/23031215
https://yanhaijing.com/javascript/2018/10/10/clone-deep/
https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript
https://segmentfault.com/a/1190000002789651

图解http--了解web及网络基础(2018.01.04)

使用 HTTP 协议访问 Web

根据 Web 浏览器地址栏中指定的 URL,Web 浏览器从 Web 服务器端获取文件资源(resource)等信 息,从而显示出 Web 页面。

Web 是建立在 HTTP 协议上通 信的。

网络基础 TCP/IP

通常使用的网络(包括互联网)是在 TCP/IP 协议族的基础上运作 的。而 HTTP 属于它内部的一个子集

TCP/IP 是互联网相关的各类协议族的总称

TCP/IP 的分层管理

TCP/IP 协议族里重要的一点就是分层。TCP/IP 协议族按层次分别分 为以下 4 层:应用层、传输层、网络层和数据链路层。

  • 应用层
    应用层决定了向用户提供应用服务时通信的活动。
    TCP/IP 协议族内预存了各类通用的应用服务。比如,FTP(File Transfer Protocol,文件传输协议)和 DNS(Domain Name System,域 名系统)服务就是其中两类。
    HTTP 协议也处于该层。

  • 传输层
    传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据 传输。

  • 网络层
    网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计 算机,并把数据包传送给对方。

  • 链路层
    用来处理连接网络的硬件部分。

TCP/IP 通信传输流

利用 TCP/IP 协议族进行网络通信时,会通过分层顺序与对方进行通 信。发送端从应用层往下走,接收端则往应用层往上走。
发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该 层所属的首部信息。反之,接收端在层与层传输数据时,每经过一层 时会把对应的首部消去。

与 HTTP 关系密切的协议 : IP、TCP 和 DNS

负责传输的 IP 协议

IP(Internet Protocol)网际协议位于网络层。
有人会把“IP”和“IP 地址”搞混,“IP”其实是一种协议的名称。

IP 协议的作用是把各种数据包传送给对方。而要保证确实传送到对方 那里,则需要满足各类条件。其中两个重要的条件是 IP 地址和 MAC 地址

IP 地址指明了节点被分配到的地址,MAC 地址是指网卡所属的固定 地址。

IP 间的通信依赖 MAC 地址。在网络上,通信的双方在同一局域网 (LAN)内的情况是很少的,通常是经过多台计算机和网络设备中转 才能连接到对方。而在进行中转时,会利用下一站中转设备的 MAC 地址来搜索下一个中转目标。

确保可靠性的 TCP 协议

TCP 位于传输层,提供可靠的字节流服务。
TCP 协议为了更容易传送大数据才把数据分割,而且 TCP 协议能够 确认数据最终是否送达到对方。

为了准确无误地将数据送达目标处,TCP 协议采用了三次握手

握手过程中使用了 TCP 的标志:SYN和ACK(acknowledgement,确认的意思)

发送端首先发送一个带 SYN 标志的数据包给对方。接收端收到后, 回传一个带有 SYN/ACK 标志的数据包以示传达确认信息。最后,发 送端再回传一个带 ACK 标志的数据包,代表“握手”结束。

负责域名解析的 DNS 服务

DNS(Domain Name System,域名系统)服务是和 HTTP 协议一样位于应用层的 协议。它提供域名到 IP 地址之间的解析服务。

DNS 协议提供通过域名 查找 IP 地址,或逆向从 IP 地址反查域名的服务。

各种协议与 HTTP 协议的关系

一图胜千言

URI 和 URL

与 URI(统一资源标识符)相比,我们更熟悉 URL(Uniform Resource Locator,统一资源定位符)。
URL 正是使用 Web 浏览器等 访问 Web 页面时需要输入的网页地址。

统一资源标识符

URI 用字符串标识某一互联网资源,而 URL 表示资源的地点(互联 网上所处的位置)。可见 URL 是 URI 的子集。

URI 格式

绝对 URI 的格式

使用 http: 或 https:

等协议方案名获取访问资源时要指定协议类型。不 区分字母大小写,最后附一个冒号(:)。
也可使用 data: 或 javascript: 这类指定数据或脚本程序的方案名。

登录信息(认证)

指定用户名和密码作为从服务器端获取资源时必要的登录信息(身份 认证)。此项是可选项。

服务器地址

使用绝对 URI 必须指定待访问的服务器地址。地址可以是类似 hackr.jp 这种 DNS 可解析的名称,或是 192.168.1.1 这类 IPv4 地址 名,还可以是 [0:0:0:0:0:0:0:1] 这样用方括号括起来的 IPv6 地址名。

服务器端口号

指定服务器连接的网络端口号。此项也是可选项,若用户省略则自动 使用默认端口号。

带层次的文件路径

指定服务器上的文件路径来定位特指的资源。这与 UNIX 系统的文件目录结构相似。 查了下,macos和Linux两者都是从UNIX来的。

查询字符串

针对已指定的文件路径内的资源,可以使用查询字符串传入任意参 数。此项可选。 片段标识符 使用片段标识符通常可标记出已获取资源中的子资源(文档内的某个 位置)。

HTTP强缓存和协商缓存

Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。

浏览器缓存也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage 等等。这里只讨论 HTTP 缓存相关内容。

在具体了解 HTTP 缓存之前先来明确几个术语:

  • 命中缓存 : 可以执行强缓存(200)或者协商缓存(304)
  • 缓存命中率:从缓存中得到数据的请求数与所有请求数的比率。理想状态是越高越好。
  • 过期内容:超过设置的有效时间,被标记为“陈旧”的内容。通常过期内容不能用于回复客户端的请求,必须重新向源服务器请求新的内容或者验证缓存的内容是否仍然准备。
  • 验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
  • 失效:失效就是把内容从缓存中移除。当内容发生改变时就必须移除失效的内容。

浏览器缓存主要是 HTTP 协议定义的缓存机制。HTML meta 标签,例如

<META HTTP-EQUIV="Pragma" CONTENT="no-store">

含义是让浏览器不缓存当前页面。但是代理服务器不解析 HTML 内容,一般应用广泛的是用 HTTP 头信息控制缓存。

HTTP 头信息控制缓存

大致分为两种:强缓存和协商缓存。强缓存如果命中缓存不需要和服务器端发生交互,而协商缓存不管是否命中都要和服务器端发生交互,强制缓存的优先级高于协商缓存。

Expires 如果命中,返回的from cache 不会产生http请求;而eTag,Last-Modified如果命中返回的是304。

匹配流程(已有缓存的情况下):

image

强缓存

可以理解为无须验证的缓存策略。对强缓存来说,响应头中有两个字段 Expires/Cache-Control 来表明规则。

image

Expires

概念:Expires 指缓存过期的时间,超过了这个时间点就代表资源过期。

缺点:Expires 是一个时间点,但服务端和客户端时间不统一,会造成提前过期或者过期还在使用的问题。并且 Expires 是 HTTP/1.0 的标准。略过时。

应用 HTTP/1.1 版本的缓存服务器遇到同时存在 Expires 首部字段的情 况时,会优先处理 max-age 指令,而忽略掉 Expires 首部字段。而 HTTP/1.0 版本的缓存服务器的情况却相反,max-age 指令会被忽略

Cache-Control

Cache-Control 可以由多个字段组合而成,主要有以下几个取值:

  1. max-age 缓存服务器里该资源缓存的最大有效期,单位是s。打个滑稽的比喻,你(客户端)去商店买泡面(发http请求资源),泡面上包装上(浏览器缓存服务器)写了保质期12个月(max-age),告诉你有没有过期( 缓存服务器判定该资源缓存是否过期),从而不用真的以身试吃泡面(缓存服务器转发请求给源服务器)就知道能不能吃(要不要返回缓存)

image
当客户端发送的请求中包含 max-age 指令时,如果判定缓存资源的缓存时间数值比指定时间的数值更小,那么客户端就接收缓存的资源。 另外,当指定 max-age 值为 0,那么缓存服务器通常需要将请求转发 给源服务器。

当服务器返回的响应中包含 max-age 指令时,缓存服务器将不对资源 的有效性再作确认,而 max-age 数值代表资源保存为缓存的最长时 间。

image

在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

image

  1. s-maxage 同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。

  2. public 表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。

  3. private 表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。

  4. no-cache
    客户端发送的请求中如果包含 no-cache 指令,则表示客户端将不会接收缓存过的响应,“中间”的缓存服务器必须把客户端请求转发 给源服务器。
    如果服务器返回的响应中包含 no-cache 指令,那么缓存服务器不能对资源进行缓存。源服务器以后也将不再对缓存服务器请求中提出的资 源有效性进行确认,且禁止其对响应资源进行缓存操作。

  5. no-store 禁止缓存,每次请求都要向服务器重新获取数据。

image

协商缓存

缓存的资源到期了,并不意味着资源内容发生了改变,如果和服务器上的资源没有差异,实际上没有必要再次请求。客户端和服务器端通过某种验证机制验证当前请求资源是否可以使用缓存。

浏览器第一次请求数据之后会将数据和响应头部的缓存标识存储起来。再次请求时会带上存储的头部字段,服务器端验证是否可用。如果返回 304 Not Modified,代表资源没有发生改变可以使用缓存的数据,获取新的过期时间。反之返回 200 就相当于重新请求了一遍资源并替换旧资源。

If-Modified-Since(Request Header)/ Last-modified(Response Header)

Last-modified: 服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。

image

image

注意:如果响应头中有 Last-modified 而没有 Expire 或 Cache-Control 时,浏览器会有自己的算法来推算出一个时间缓存该文件多久,不同浏览器得出的时间不一样,所以 Last-modified 要记得配合 Expires/Cache-Control 使用。

If-None-Match(Request Header)/ Etag(Response Header)

(巧记:连起来既If Not Match Etag => response的Etag标签不匹配了 该重新返回新资源了)

由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag: abcd,之后的请求中带上 If-None-Match: abcd。服务器检查 ETag,一致时命中缓存返回304,不一致时进行Last-modified/ If-Modified-Since 的判断。

image

last-modified 和 Etag 区别

  • 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。
  • Etag比last-modified date更具有弹性,例如某个文件在1秒内修改了10次,Etag可以综合Inode(文件的索引节点(inode)数),MTime(修改时间)和Size来精准的进行判断,避开UNIX记录MTime只能精确到秒的问题。 Last-modified 只能精确到秒。
  • 一些资源的最后修改时间改变了,但是内容没改变,使用 Last-modified 看不出内容没有改变。
  • Etag 的精度比 Last-modified 高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag 进行 Conditional Request。

注意:实际使用 ETag/Last-modified 要注意保持一致性,做负载均衡和反向代理的话可能会出现不一致的情况。计算 ETag 也是需要占用资源的,如果修改不是过于频繁,看自己的需求用 Cache-Control 是否可以满足。

304 状态码

该状态码表示客户端发送附带条件(指采用 GET 方法的请求报文中包含 If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since 中任一首部)的请求时,服务器端允许请求访问资源,但未满足条件的情况。

image

实际应用

回到实际应用上来,首先要明确哪些内容适合被缓存哪些不适合。

考虑缓存的内容:

  • css样式文件
  • js文件
  • logo、图标
  • html文件
  • 可以下载的内容
  • 一些不应该被缓存的内容:
  • 业务敏感的 GET 请求

可缓存的内容又分为几种不同的情况:

不经常改变的文件:使用强缓存。 给 max-age 设置一个较大的值,一般设置 max-age=31536000

比如引入的一些第三方文件、打包出来的带有 hash 后缀 css、js 文件。一般来说文件内容改变了,会更新版本号、hash 值,相当于请求另一个文件。

标准中规定 max-age 的值最大不超过一年,所以设成 max-age=31536000。至于过期内容,缓存区会将一段时间没有使用的文件删除掉。

可能经常需要变动的文件:使用协商缓存。 Cache-Control: no-cache / max-age=0

比如入口 index.html 文件、文件内容改变但名称不变的资源。选择 ETag 或 Last-Modified 来做验证,在使用缓存资源之前一定会去服务器端做验证,命中缓存时会比第一种情况慢一点点,毕竟还要发请求进行通信。

注意: 这里只描述了最基本的思路,实际使用 HTTP 缓存需要后端配合配置,具体情况具体对待,而且各方的实现并不一定完全按照标准来的,踩踩坑更健康。

Reference

HTTP 缓存机制一二三
《图解HTTP》

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.