首页

编程小知识之 JavaScript 文件读取

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

本文介绍了两种在 JavaScript 读取文件的方法

文件读取在当今的编程语言中应该算是一项比较 trivial 的工作了,语言的标准库一般都会帮助我们做好文件抽象与读写缓存,我们仅需要熟悉和运用相关的 API 即可.

但是 JavaScript 由于安全性的原因,一直以来都没有提供相关的文件读取接口,但有时我们确实需要读取本地文件内容,下面是两种可能的读取方法.

1. 使用 HTML5 中的 FileReader
HTML5 引入的 FileReader 可以帮助我们读取本地文件,但是有一个限制,就是我们不能直接使用文件路径的方式来访问文件,而是首先需要用户选择文件(通过文件选择或者拖动文件至网页等方式)

代码还是比较简单的,首先你需要在 html 文件中添加文件选择表单:

<input type="file" id="file-input" />
<div id="file-content" />
1
2
接着就可以在 JavaScript 中进行(用户选择的)文件读取了:

function readSingleFile(e) {
    // file from "file-input"
    var file = e.target.files[0];
    if (!file) {
      return;
    }
  
    // create FileReader
    var reader = new FileReader();
  
    // load callback
    reader.onload = function(e) {
      var contents = e.target.result;
    
      // process file contents here
      // ...
    
    };
  
    // do read
    reader.readAsText(file);
}

// add file input change listener
var fileInput = document.getElementById("file-input");
fileInput.addEventListener("change", readSingleFile, false);

2. 使用 XMLHttpRequest
XMLHttpRequest 一般用于实现 Ajax,通过他我们也可以实现本地文件的读取(但是有同源等限制),示例代码如下:

var contents = "";

var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
    if (xmlhttp.status == 200 && xmlhttp.readyState == 4) {
        contents = xmlhttp.responseText;
        
        // process file contents here
        // ...
      
    }
};

// send read request
xmlhttp.open("GET","abc.txt",true);
xmlhttp.send();

更多资料
http://researchhubs.com/post/computing/javascript/open-a-local-file-with-javascript.html
https://www.html5rocks.com/en/tutorials/file/dndfiles/
https://www.liaoxuefeng.com/wiki/1022910821149312/1023022332902400

JavaScript 获取窗口属性

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

获取窗口属性

  • 查看滚动条的滚动距离 
    • window.pageXOffset/pageYOffset 
      • IE8及IE8以下不兼容
    • document.body/documentElement.scrollLeft/scrollTop 
      • 兼容性比较混乱,同时取两个值相加,因为不可能存在两个同时有值
    • 封装兼容性方法,求滚动轮滚动离getScrollOffset()

为了解决兼容性的问题,我们来封装一个函数:

<script type="text/javascript">
    function getScrollOffset() {
        if(window.pageXOffset) { x : window.pageXoffset, y : window.pageYoffset }
        else{
            return { x : document.body.scrollLeft + document.documentElement.scrollLeft, y : document.body.scrollTop + document.documentElement.scrollTop,
            }
        }
    }
</script>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 查看视口的尺寸 
    • window.innerWidth/innerHeight 
      • IE及IE8以下不兼容
    • document.documentElement.clientWidth/clientHeight 
      • 标准模式下,任意浏览器都兼容
    • document.body.clientWidth/clientHeight 
      • 适用于怪异模式(向后兼容)下的浏览器
    • 封装兼容性方法,返回浏览器视口尺寸getViewportOffset()

为了解决兼容性的问题,我们来封装一个函数:

<script type="text/javascript"> function getSViewportOffset() { if(window.innerWidth) { return {
                w : window.innerWidth,
                h : window.innerHeight
            }
        }else{ if(document.compatMode ==="BackCompat") { return {
                    w : document.body.clienWidth,
                    h : document.body.clientHeight
                }
            }else{ return {
                    w : document.documentElement.clientWidth,
                    h : document.documrntElement.clientHeight
                }
            }
    }
</script>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 查看元素的几何尺寸

    • domEle.getBoundingClientRect();
    • 兼容性很好
    • 该方法返回一个对象,对象里面有left,top,right,bottom等属性。left和top代表该元素左上角的X和Y坐标,right和bottom代表元素右下角的X和Y坐标。
    • height和width属性老版本IE不显示(人为解决:分别相减一下就能得出)
    • 返回的结果并不是”实时的”
  • 让滚动条滚动

    • window上有三个方法
    • scroll(x,y)在x轴、y轴上滚动的位置,scrollTo(x,y) 
      让滚动条滚动到当前位置,而不是累加距离(这两种方法是完全一样的)
    • scrollBy();累加滚动距离
    • 三个方法功能类似,用法都是将x,y坐标传入。即实现让滚动条滚动到当前位置。
    • 区别:scrollBy()会在之前的数据基础之上做累加。
    • eg:利用scroll()页面定位功能。
    • eg:利用scrollBy()快速阅读功能。

练习: 
做一个小阅读器,会自动翻页。

<!DOCTYPE html> <html> <head> <title>Document</title> </head> <body> 文本内容 <div style="width:100px;height:100px;background-color:orange;color:#fff;font-size:40px;text-align:center;line-height:100px;position:fixed;bottom:200px;right:50px;opcity:0.5;">start</div> <div style="width:100px;height:100px;background-color:orange;color:green;font-size:40px;text-align:center;line-height:100px;position:fixed;bottom:50px;right:50px;opcity:0.5;">stop</div> </body> <script type="text/javascript"> var start = document.getElement.getElementsByTagName('div')[0]; var stop = document.getElement.getElementsByTagName('div')[1]; var timer = 0; var key = true; //加锁,防止连续点start产生累加加速 start.onclick = function() { if(key) {
            timer = setInterval(function() { window.scollBy(0,10);
            },100);
            key = false;
        }
    }
    stop.onclick = function() { clearInterval(timer);
        key = true;
    } </script>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

由‘==’和‘===’引出的js的隐式转换问题

周周

‘==’和‘===’都是Javascript中的比较运算符,都是比较运算符两边是否相等。对于‘==’和‘===’的区别,大家也都知道:

  ‘==’仅仅是比较运算符两边的数值是否相等,如果数值相等则返回true;‘===’不仅会判断运算符两边的数值是否相等,并且还会判断两边的类型是否相等,只有数值和类型都相等才会返回true。虽然知道以上的判断依据已经能解决绝大数此类问题,但是如果往其中深究来说,会有同学问:在比较的时候‘===’先判断类型,如果类型不同就直接返回false,这个没什么问题。但是如果是‘==’比较两个不同类型的数据时,具体是怎么进行计算判断的呢?

 既然是不同类型进行比较,肯定最终参与比较的结果必须是同一个类型的,因此JS会存在一个隐式转换的问题,并且很多JS的隐式转换很难通过console.log()等方法直观的观察到,因此很多初学者会对JS的隐式转换感到疑惑。

 首先让我们回忆一下,咱们的JS中一共有哪些数据类型?

       六大数据类型
       基本数据类型(简单数据类型)
       number 数值型(NaN)
       string 字符串
       boolean 布尔型
       undefined 未定义
       null 空引用
       引用数据类型(复杂数据类型)
       object

       JS基础中,我们学习到咱们的JS中一共有六种数据类型,分为基本数据类型(简单数据类型)和引用数据类型(复杂数据类型),不同类型的值进行比较的时候,存在隐式转换的问题,咱们通过‘==’来验证一下JS隐式转换的情况。

       1.我们首先来看看下列的语句计算结果:

console.log(NaN==true);//false
console.log(NaN==false);//false
console.log(NaN==0);//false
console.log(NaN==1);//false
console.log(NaN==NaN);//false

       由上面的例子可以看出,NaN属于Number数据类型中一个特殊情况,如果‘==’两边同为Number数据类型的数字,很直观的可以看出值是否相同一眼就可以看出结果,但是作为Number类型的特殊情况,NaN在进行比较的时候,也会有特殊的结果:如果 x 或 y 中有一个为 NaN,则返回 false;

       2.我们继续看看下列的语句计算结果:

console.log(null == undefined); //true(特殊情况)---------------------------------
console.log(null == ''); //false
console.log(undefined == ''); //false

      在上述例子中,引出了一个null,null是一个简单数据类型,它的意义就是一个空应用,但是你如果通过console.log(typeof null) 来打印结果的时候却发现,结果竟然是object?此时你可能会怀疑人生,然后疯狂的翻阅之前学习的资料,因为object明明是一个复杂数据类型,怎么会在判断null这个简单数据类型的类型时打印出来呢?其实,这个问题属于一个历史问题。咱们学习的JS在发展过程中是通过ECMAScript来确定规范的,每年都会有新的规定和规范提出,在JS的发展过程中,null一开始的作用就是用来指向一个空地址,让开发者在创建数据的时候,先用null赋值给还未给值的对象用于标准初始化。但是其实咱们开发过程中很少用到,但是这个仍作为规范留了下来。又因为typeof是根据数据的前几位判断数据类型的,null相当于空指针,前几位是地址的格式,所以判断结果就为object。又因为undefined值是派生自null值的,因此ECMA-262规定对他们的相等测试要返回true。所以这一情况判断的条件为:如果 x 与 y 皆为 null 或 undefined 中的一种类型,则返回 true(null == undefined // true);否则返回 false(null == 0 // false);

       3.请看下列例子:

console.log(true == '123'); //false
console.log(true == '1'); //true
console.log(false == '0'); //true
 
console.log(true == !0); //true
 
console.log([] == []); //false
console.log([] == ![]); //true 比较地址 ------------------------------------------------
var a = c = [];
var b = [];
console.log(a == b); //false
console.log(a == !b); //true
console.log(a == c); //true
 
console.log(Boolean([]) == true); //true
console.log(Number([]) == 0); //true
console.log(Number(false) == 0); //true

       其实比较的逻辑为:如果 x,y 类型不一致,且 x,y 为 String、Number、Boolean 中的某一类型,则将 x,y 使用 Number 函数转化为 Number 类型再进行比较;

      使用Number函数可以将其他的数据类型转变为Number类型,这一同为Number类型的数据,对比起来就会变得十分简单。值得注意的是在上述的例子中,两个空数组进行比较,结果返回的结果仍然为false,这个是怎么回事呢?其实这个很好理解,因为数组也是对象的一种,是复杂数据类型,所以用变量储存对象时储存的其实是地址。对象的内容相同,但是储存在堆区的位置不同,所以地址也是不同的,所以在判断的时候返回的是false。

      其实在JS中还有很多的隐式转换情况,以上只是针对于‘==’的隐式转换情况,对于这些问题,在实际开发过程中,需要作为开发者不断的学习和积累,这也是咱们作为开发者的一个要求之一。

原生js的ajax请求

周周

传统方法的缺点:

      传统的web交互是用户触发一个http请求服务器,然后服务器收到之后,在做出响应到用户,并且返回一个新的页面,,每当服务器处理客户端提交的请求时,客户都只能空闲等待,并且哪怕只是一次很小的交互、只需从服务器端得到很简单的一个数据,都要返回一个完整的HTML页,而用户每次都要浪费时间和带宽去重新读取整个页面。这个做法浪费了许多带宽,由于每次应用的交互都需要向服务器发送请求,应用的响应时间就依赖于服务器的响应时间。这导致了用户界面的响应比本地应用慢得多。

什么是ajax

       ajax的出现,刚好解决了传统方法的缺陷。AJAX 是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

XMLHttpRequest 对象

       XMLHttpRequest对象是ajax的基础,XMLHttpRequest 用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。目前所有浏览器都支持XMLHttpRequest

方法
描述
abort()
停止当前请求
getAllResponseHeaders() 
 把HTTP请求的所有响应首部作为键/值对返回
getResponseHeader("header")
返回指定首部的串值
open("method","URL",[asyncFlag],["userName"],["password"])
建立对服务器的调用。method参数可以是GET、POST或PUT。url参数可以是相对URL或绝对URL。这个方法还包括3个可选的参数,是否异步,用户名,密码
send(content)
向服务器发送请求
setRequestHeader("header", "value") 
把指定首部设置为所提供的值。在设置任何首部之前必须先调用open()。设置header并和请求一起发送 ('post'方法一定要 )
五步使用法:

       1.创建XMLHTTPRequest对象
       2.使用open方法设置和服务器的交互信息
       3.设置发送的数据,开始和服务器端交互
       4.注册事件
       5.更新界面

下面给大家列出get请求和post请求的例子

get请求:      

       //步骤一:创建异步对象
       var ajax = new XMLHttpRequest();
       //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
       ajax.open('get','getStar.php?starName='+name);
       //步骤三:发送请求
        ajax.send();
       //步骤四:注册事件 onreadystatechange 状态改变就会调用
        ajax.onreadystatechange = function () {
       if (ajax.readyState==4 &&ajax.status==200) {
       //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
       console.log(xml.responseText);//输入相应的内容
         }
        } 

post请求:

       //创建异步对象  
       var xhr = new XMLHttpRequest();
       //设置请求的类型及url
       //post请求一定要添加请求头才行不然会报错
       xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
       xhr.open('post', '02.post.php' );
       //发送请求
       xhr.send('name=fox&age=18');
       xhr.onreadystatechange = function () {
       // 这步为判断服务器是否正确响应
       if (xhr.readyState == 4 && xhr.status == 200) {
              console.log(xhr.responseText);
             }
        };    

为了方便使用,我们可以把他封装进方法里面,要用的时候,直接调用就好了

       function ajax_method(url,data,method,success) {
       // 异步对象
       var ajax = new XMLHttpRequest();

      // get 跟post  需要分别写不同的代码
      if (method=='get') {
          // get请求
          if (data) {
              // 如果有值
              url+='?';
              url+=data;
          }else{

         }      

       // 设置 方法 以及 url
            ajax.open(method,url);

           // send即可
           ajax.send();
        }else{
             // post请求
             // post请求 url 是不需要改变
             ajax.open(method,url);

            // 需要设置请求报文
           ajax.setRequestHeader("Content-type","application/x-www-form-urlencoded");

           // 判断data send发送数据
          if (data) {
            // 如果有值 从send发送
                ajax.send(data);
          }else{
               // 木有值 直接发送即可
              ajax.send();
            }
         }     

       // 注册事件
       ajax.onreadystatechange = function () {
       // 在事件中 获取数据 并修改界面显示
            if (ajax.readyState==4&&ajax.status==200) {
                // console.log(ajax.responseText);

                // 将 数据 让 外面可以使用
               // return ajax.responseText;

               // 当 onreadystatechange 调用时 说明 数据回来了
              // ajax.responseText;

              // 如果说 外面可以传入一个 function 作为参数 success
              success(ajax.responseText);
             }
         }
      }

懒加载封装实现

周周

1.什么是懒加载?
         当访问一个页面的时候,先把img元素背景图片路径替换成一张替代图片的路径(这样就只需请求一次,占位图),将图片的真实路径存储在img自定义属性中,只有当图片出现在浏览器的可视区域内时,才设置图片正真的路径,让图片显示出来。这就是图片懒加载。
2.为什么要用懒加载?
       很多页面,内容很丰富,页面很长,图片较多。比如说各种商城页面。这些页面图片数量多,而且比较大,少说百来K,多则上兆。要是页面载入就一次性加载完毕,提高首屏加载速度,可以减轻服务器压力,节约流量,用户体验好。
3.懒加载实现封装?

    lazyLoad由四个函数组成,init(初始化函数),checkShow(判断图片是否加载),shouldShow(将要展示的图片),showImg(展示图片)。

(1)初始化函数(init)  由于滚动事件太消耗性能,所以用定时器替换,不是滚动就触发,而是滚动后200毫秒后触发。

                var timer;
                function init(){
                    $(window).on("scroll",function(){
                        if(timer){
                            clearTimeout(timer);
                        }
                        timer = setTimeout(function(){
                            checkShow();  //
                        },200);
                    });
                }

(2)判断”图片是否加载“(checkshow)函数,如果图片有isload属性,就说明图片已经加载过了,直接return。如果图片没有isload属性,进入将要展示图片shouldshow函数

                function checkShow(){
                    $lazyLoad.each(function(){
                        $cur = $(this);
                        if($cur.attr('isLoaded')){
                            return;
                        }
                        if(shouldShow($cur)){
                            showImg($cur);
                        }
                    });
                }

(3)将要展示图片shouldshow函数,获取屏幕可视宽度,滚动高度,要展示的元素到文档的高度,如果元素到文档的高度小于屏幕的可视高度加上滚动高度,说明元素已在可视区内,返回true,否则返回false。

               function shouldShow ($node){
                    var scrollH = $(window).scrollTop(),
                        windowH = $(window).height(),
                        top = $node.offset().top;
                    if(top < windowH + scrollH){
                        return true;
                    } else {
                        return false;
                    }

                }

(4)“展示图片”函数,将元素的src属性替换为自定义属性data-src(真正图片的地址)。

                function showImg ($node){
                    $node.find("img").attr("src",$node.data("src"));
                    $node.attr("isLoaded",true);
                }

(5)函数返回一个对象

              return {
                        init : init
           }

      这样就实现懒加载封装了!

js中常见的位置属性-offset,scroll,client系列

周周

前言
       Javascript中的offset、scroll、client系列都是比较常用的坐标属性,也是比较容易混淆的知识点。
offset家族
       offset家族一般在节点对象里面使用。
       offsetParent
             1.    如果当前元素的父级元素没有进行css定位(position为absolute或relative),offsetParent为body。
             2.    如果当前元素的父级元素中有css定位(position为absolute或relative),offsetParent取最近的那个父级元素。
       offsetLeft/Top计算规则:
             标准流、浮动、非脱标定位
              offsetLeft = 自己的margin+offsetParent的margin、padding、border
              脱标定位
              offsetLeft = 自己的left + margin-left
        注意:与stlye.left的区别
              offsetLeft只可读,不可写。也就是说,通过offsetLeft只能获取元素的左偏移值,而无法去设置元素的左偏移值。
               stlye.left可读可写,但是通过style.left获取元素的偏移值,是一个带单位的字符串,例如“100px”。

            (offsetTop同理)

        offsetWidth(和offsetHeight:

        其实就是一个元素的实际宽度 = width+padding+border

client家族】

        clientWidth (clientHeight) = width+padding

              该属性指的是元素的可视部分宽度和高度

              假如元素有padding有滚动,且滚动是显示的

              clientWidth = width + padding - 滚动轴宽度

       clientTop(clientLeft):

             这一对属性是用来读取元素的border的宽度和高度的

             clientTop = border-top 的 border-width

             clientLeft = border-left 的 border-width

【scroll家族】

150537.jpg.png

  如上图所示
       scrollWidth(和scrollHeight
          无滚动轴时:scrollWidth = clientWhidth = width + padding
          有滚动轴时:scrollWidth = 实际内容的宽度 + padding
       scrollTop(和scrollLeft
           这对元素是可读写的,指的是当元素其中的内容超出其宽高的时候,元素被卷起的宽度和高度
【事件里面的clientXoffsetXscreenX


  • event.clientX:设置或获取鼠标指针位置相对于当前窗口的 x 坐标,其中客户区域不包括窗口自身的控件和滚动条
  • event.clientY:设置或获取鼠标指针位置相对于当前窗口的 y 坐标,其中客户区域不包括窗口自身的控件和滚动条
  • vent.offsetX:设置或获取鼠标指针位置相对于触发事件的对象的 x 坐标
  • event.offsetY:设置或获取鼠标指针位置相对于触发事件的对象的 y 坐标
  • event.screenX 设置或获取获取鼠标指针位置相对于用户屏幕的 x 坐标。
  • event.screenY设置或获取鼠标指针位置相对于用户屏幕的 y 坐标。


【window系列】
  • window.innerHeight指的是浏览器窗口显示html文档的可视区域的高度
  • window.outerHeight指的是浏览器窗口的高度 ,包括了工具栏,地址栏等等高度

       window.screen包含了屏幕的信息
  • window.screen.width   电脑屏幕的整个宽度
  • window.screen.availWidth   电脑屏幕除去菜单条之后的宽度
  • window.screen.left   浏览器窗口的左上角距离电脑屏幕最左侧的距离




常用正则表达式20例

周周

   总结了工作中可能经常会用到的各类正则表达式的写法。
   方便以后进行查找,减少工作量。

   1. 校验基本日期格式

var reg1 = /^\d{4}(\-|\/|\.)\d{1,2}\1\d{1,2}$/;
var reg2 = /^(^(\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2}$)|(^\d{4}年\d{1,2}月\d{1,2}日$)$/;

   2. 校验密码强度
   密码的强度必须是包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间。

var reg = /^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$/;

   3. 校验中文 

   字符串仅能是中文。

var reg = /^[\\u4e00-\\u9fa5]{0,}$/; 4. 由数字、26个英文字母或下划线组成的字符串 var reg = /^\\w+$/; 5. 校验E-Mail 地址
同密码一样,下面是E-mail地址合规性的正则检查语句。
var reg = /[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?/; 6. 校验身份证号码
下面是身份证号码的正则校验。15 或 18位。
15位: var reg = /^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$/; 18位: var reg = /^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$/; 7. 校验日期
“yyyy-mm-dd” 格式的日期校验,已考虑平闰年。 var reg = /^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/; 8. 校验金额
金额校验,到2位小数。 var reg = /^[0-9]+(.[0-9]{2})?$/; 9. 校验手机号
下面是国内 13、15、18开头的手机号正则表达式。(可根据目前国内收集号扩展前两位开头号码) var reg = /^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$/;    10. 判断IE的版本
   IE目前还没被完全取代,很多页面还是需要做版本兼容,下面是IE版本检查的表达式。

var reg = /^.*MSIE [5-8](?:\\.[0-9]+)?(?!.*Trident\\\/[5-9]\\.0).*$/;

      11. 校验IP-v4地址

var reg = /\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b/;

     12. 校验IP-v6地址

var reg = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/;

      13. 检查URL的前缀
   应用开发中很多时候需要区分请求是HTTPS还是HTTP,通过下面的表达式可以取出一个url的前缀然后再逻辑判断。

if (!s.match(
    /^[a-zA-Z]+:\/\//
  )) {
  s =
    'http://' +
    s;

}

      14. 提取URL链接
   下面的这个表达式可以筛选出一段文本中的URL。

var reg = /^(f|ht){1}(tp|tps):\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/;    15. 文件路径及扩展名校验
   验证 windows下文件路径和扩展名(下面的例子中为.txt文件)
var reg = /^([a-zA-Z]\\:|\\\\)\\\\([^\\]+\\)*[^\\/:*?"<>|]+\\.txt(l)?$/;       16. 提取Color Hex Codes
   有时需要抽取网页中的颜色代码,可以使用下面的表达式。
var reg = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; 17. 提取网页图片
   假若你想提取网页中所有图片信息,可以利用下面的表达式。
var reg = /\\< *[img][^\\>]*[src] *= *[\\"\']{0,1}([^\\"\'\ >]*)/;    18. 提取页面超链接
   提取html中的超链接。

var reg = /(<a\\s*(?!.*\\brel=)[^>]*)(href="https?:\/\/)((?!(?:(?:www\\.)?'.implode('|(?:www\\.)?', $follow_list).'))[^"]+)"((?!.*\\brel=)[^>]*)(?:[^>]*)>/;

   19. 查找CSS属性
   通过下面的表达式,可以搜索到相匹配的CSS属性。

var reg = /^\\s*[a-zA-Z\\-]+\\s*[:]{1}\\s[a-zA-Z0-9\\s.#]+[;]{1}/;

   20. 抽取注释
   如果你需要移除HMTL中的注释,可以使用如下的表达式。

var reg = /<!--(.*?)-->/;

10种最常见的Javascript错误

周周

以下是 JavaScript 错误 Top 10:

  3a8ccf12-f663-38d7-bca8-6178415d9875.png

       为了便于阅读,我们将每个错误描述都缩短了。接下来,让我们深入到每一个错误,来确定什么会导致它,以及如何避免创建它。 
1. Uncaught TypeError: Cannot read property 
        如果你是一个 JavaScript 开发人员,可能你看到这个错误的次数比你敢承认的要多(LOL…)。当你读取一个未定义的对象的属性或调用其方法时,这个错误会在 Chrome 中出现。 您可以很容易的在 Chrome 开发者控制台中进行测试(尝试)。

       发生这种情况的原因很多,但常见的一种是在渲染 UI 组件时对于状态的初始化操作不当。
        我们来看一个在真实应用程序中发生的例子:我们选择 React,但该情况也同样适用于 Angular、Vue 或任何其他框架。

Javascript代码  

class Quiz extends Component {
  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }
  render() {
    return (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}
这里有两件重要的事情要实现:

  • 组件的状态(例如 this.state)从 undefined 开始。
  • 当异步获取数据时,不管它是在构造函数componentWillMount还是componentDidMount中获取的,组件在数据加载之前至少会呈现一次,当 Quiz 第一次呈现时,this.state.items 是未定义的。 这又意味着 ItemList 将 items 定义为 undefined,并且在控制台中出现错误 - “Uncaught TypeError: Cannot read property ‘map’ of undefined”。

       这很容易解决。最简单的方法:在构造函数中用合理的默认值来初始化 state。

Javascript代码


class Quiz extends Component {
  // Added this:
  constructor(props) {
    super(props);
    // Assign state itself, and a default value for items
    this.state = {
      items: []
    };
  }
  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }
  render() {
    return (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}

       在你的应用程序中的具体代码可能是不同的,但我们希望我们已经给你足够的线索,以解决或避免在你的应用程序中出现的这个问题。如果还没有,请继续阅读,因为我们将在下面覆盖更多相关错误的示例。
        2. TypeError: ‘undefined’ is not an object
        这是在 Safari 中读取属性或调用未定义对象上的方法时发生的错误。您可以在 Safari Developer Console 中轻松测 试。这与 1 中提到的 Chrome 的错误基本相同,但 Safari 使用了不同的错误消息提示语。

      3.TypeError: null is not an object
        这是在 Safari 中读取属性或调用空对象上的方法时发生的错误。 您可以在 Safari Developer Console 中轻松测试。

有趣的是,在 JavaScript 中,null 和 undefined 是不一样的,这就是为什么我们看到两个不同的错误信息。undefined 通常是一个尚未分配的变量,而 null 表示该值为空。 要验证它们不相等,请尝试使用严格的相等运算符 ===:  

       在现实世界的例子中,这种错误可能发生的一种场景是:如果在加载元素之前尝试在 JavaScript 中使用元素。 因为 DOM API 对于空白的对象引用返回值为 null。
        任何执行和处理 DOM 元素的 JS 代码都应该在创建 DOM 元素之后执行。 JS 代码按照 HTML 中的规定从上到下进行解释。 所以,如果 DOM 元素之前有一个标签,脚本标签内的 JS 代码将在浏览器解析 HTML 页面时执行。 如果在加载脚本之前尚未创建 DOM 元素,则会出现此错误。
        在这个例子中,我们可以通过添加一个事件监听器来解决这个问题,这个监听器会在页面准备好的时候通知我们。 一旦 addEventListener被触发,init() 方法就可以使用 DOM 元素。

Html代码

<script>
  function init() {
    var myButton = document.getElementById("myButton");
    var myTextfield = document.getElementById("myTextfield");
    myButton.onclick = function() {
      var userName = myTextfield.value;
    }
  }
  document.addEventListener('readystatechange', function() {
    if (document.readyState === "complete") {
      init();
    }
  });
</script>
<form>
  <input type="text" id="myTextfield" placeholder="Type your name" />
  <input type="button" id="myButton" value="Go" />
</form>

4. (unknown): Script error 
       当未捕获的 JavaScript 错误(通过window.onerror处理程序引发的错误,而不是捕获在try-catch中)被浏览器的跨域策略限制时,会产生这类的脚本错误。 例如,如果您将您的 JavaScript 代码托管在 CDN 上,则任何未被捕获的错误将被报告为“脚本错误” 而不是包含有用的堆栈信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则将不允许进行通信。 
        要获得真正的错误消息,请执行以下操作: 
        1. 发送 ‘Access-Control-Allow-Origin’ 头部 
        将 Access-Control-Allow-Origin 标头设置为 * 表示可以从任何域正确访问资源。 如有必要,您可以将域替换为您的域:例如,Access-Control-Allow-Origin:www.example.com。 但是,处理多个域会变得棘手,如果你使用 CDN,可能由此产生更多的缓存问题会让你感觉到这种努力并不值得。 在这里看到更多。 
         这里有一些关于如何在各种环境中设置这个头文件的例子: 
         在 JavaScript 文件所在的文件夹中,使用以下内容创建一个 .htaccess 文件:
代码

       Header add Access-Control-Allow-Origin "*"  

       将 add_header 指令添加到提供 JavaScript 文件的位置块中:
代码

       location ~ ^/assets/ {  add_header Access-Control-Allow-Origin *;  }  

        将以下内容添加到您为 JavaScript 文件提供资源服务的后端: 

代码 

        rspadd Access-Control-Allow-Origin:\ *  

        在 <script> 中设置 crossorigin="anonymous"

在您的 HTML 代码中,对于您设置了Access-Control-Allow-Origin header 的每个脚本,在 script 标签上设置crossorigin =“anonymous”。在脚本标记中添加 crossorigin 属性之前,请确保验证上述 header 正确发送。 在Firefox 中,如果存在crossorigin属性,但Access-Control-Allow-Origin头不存在,则脚本将不会执行。 

5. TypeError: Object doesn’t support property

       这是您在调用未定义的方法时发生在 IE 中的错误。 您可以在 IE 开发者控制台中进行测试。

fcb1e309-0293-3836-a8e5-e6e361a28add.png

       这相当于 Chrome 中的 “TypeError:”undefined“ is not a function” 错误。 是的,对于相同的逻辑错误,不同的浏览器可能具有不同的错误消息。 
        对于使用 JavaScript 命名空间的 Web 应用程序,这是一个 IE l浏览器的常见的问题。 在这种情况下,99.9% 的原因是 IE 无法将当前名称空间内的方法绑定到 this 关键字。 例如:如果你 JS 中有一个命名空间 Rollbar 以及方法 isAwesome 。 通常,如果您在 Rollbar 命名空间内,则可以使用以下语法调用isAwesome方法:
Javascript代码 

this.isAwesome();  

        Chrome,Firefox 和 Opera 会欣然接受这个语法。 另一方面 IE,不会。 因此,使用 JS 命名空间时最安全的选择是始终以实际名称空间作为前缀。

6. TypeError: ‘undefined’ is not a function
        当您调用未定义的函数时,这是 Chrome 中产生的错误。 您可以在 Chrome 开发人员控制台和 Mozilla Firefox 开发人员控制台中进行测试。 63e43ce7-2048-3c44-9d80-3e8a27b71a56.png

       随着 JavaScript 编码技术和设计模式在过去几年中变得越来越复杂,回调和关闭中的自引用范围也相应增加,这是这种/那种混淆的相当常见的来源。
       考虑这个代码片段:
Javascript代码

function testFunction() {
  this.clearLocalStorage();
  this.timer = setTimeout(function() {
    this.clearBoard();    // what is "this"?
  }, 0);
};

执行上面的代码会导致以下错误:“Uncaught TypeError:undefined is not a function”。 你得到上述错误的原因是,当你调用setTimeout()时,实际上是调用window.setTimeout()。 因此,在窗口对象的上下文中定义了一个传递给setTimeout()的匿名函数,该函数没有clearBoard()方法。
一个传统的,旧浏览器兼容的解决方案是简单地将您的 this 保存在一个变量,然后可以由闭包继承。 例如:
Javascript代码 

function testFunction () {
  this.clearLocalStorage();
  var self = this;   // save reference to 'this', while it's still this!
  this.timer = setTimeout(function(){
    self.clearBoard(); 
  }, 0);

};

或者,在较新的浏览器中,可以使用bind()方法传递适当的引用:
Javascript代码

function testFunction () {
  this.clearLocalStorage();
  this.timer = setTimeout(this.reset.bind(this), 0);  // bind to 'this'
};
function testFunction(){
    this.clearBoard();    //back in the context of the right 'this'!
};

7. Uncaught RangeError: Maximum call stack 

        这是 Chrome 在一些情况下会发生的错误。 一个是当你调用一个不终止的递归函数。您可以在 Chrome 开发者控制台中进行测试。

                     5cadac78-96ee-3a2f-ad3f-493cceae8e0a.png

      此外,如果您将值传递给超出范围的函数,也可能会发生这种情况。 许多函数只接受其输入值的特定范围的数字。 例如:Number.toExponential(digits) 和 Number.toFixed(digits) 接受 0 到 20 的数字,Number.toPrecision(digits) 接受 1 到 21 的数字。
Javascript代码

var a = new Array(4294967295);  //OK
var b = new Array(-1); //range error
var num = 2.555555;
document.writeln(num.toExponential(4));  //OK
document.writeln(num.toExponential(-2)); //range error!
num = 2.9999;
document.writeln(num.toFixed(2));   //OK
document.writeln(num.toFixed(25));  //range error!
num = 2.3456;
document.writeln(num.toPrecision(1));   //OK
document.writeln(num.toPrecision(22));  //range error!

8. TypeError: Cannot read property ‘length’

        这是 Chrome 中发生的错误,因为读取未定义变量的长度属性。 您可以在 Chrome 开发者控制台中进行测试。
      您通常会在数组中找到定义的长度,但是如果数组未初始化或者变量名称在另一个上下文中隐藏,则可能会遇到此错误。让我们用下面的例子来理解这个错误。
Javascript代码

var testArray = ["Test"];
function testFunction(testArray) {
    for (var i = 0; i < testArray.length; i++) {
      console.log(testArray[i]);
    }
}
testFunction(); 当你用参数声明一个函数时,这些参数变成了函数作用域内的本地参数。这意味着即使你函数外有名为 testArray 的变量,在一个函数中具有相同名字的参数也会被视为本地参数。

        您有两种方法可以解决您的问题:
        1. 删除函数声明语句中的参数(事实上你想访问那些声明在函数之外的变量,所以你不需要函数的参数):

var testArray = ["Test"];
/* Precondition: defined testArray outside of a function */
function testFunction(/* No params */) {
    for (var i = 0; i < testArray.length; i++) {
      console.log(testArray[i]);
    }
}
testFunction();

      2. 用声明的数组调用该函数:

var testArray = ["Test"];
function testFunction(testArray) {
   for (var i = 0; i < testArray.length; i++) {
      console.log(testArray[i]);
    }
}
testFunction(testArray); 9. Uncaught TypeError: Cannot set property 
        当我们尝试访问一个未定义的变量时,它总是返回 undefined,我们不能获取或设置任何未定义的属性。 在这种情况下,应用程序将抛出 “Uncaught TypeError: Cannot set property”。 
       如果测试对象不存在,错误将会抛出 “Uncaught TypeErrorUncaught TypeError: Cannot set property”。

10. ReferenceError: event is not defined 
当您尝试访问未定义的变量或超出当前范围的变量时,会引发此错误。
       如果在使用事件处理系统时遇到此错误,请确保使用传入的事件对象作为参数。像 IE 这样的旧浏览器提供了一个全局变量事件,但并不是所有浏览器都支持。像 jQuery 这样的库试图规范化这种行为。尽管如此,最好使用传入事件处理函数的函数。
function myFunction(event) {
    event = event.which || event.keyCode;
    if(event.keyCode===13){
       alert(event.keyCode);
    }
}

结论 
        我们希望你学到了新的东西,可以避免将来的错误,或者本指南帮助你解决了头痛的问题。 
        尽管如此,即使有最佳实践,生产中也会出现意想不到的错误。能够查看影响用户的错误,并拥有快速解决问题的好工具,这一点非常重要。

20个最常用的javascript方法函数收集

周周

     收集了一些比较常用的JavaScript函数

     1、字符串长度截取函数

     function cutstr(str,len){

           var temp,icount = 0,patrn = /[^x00-\xff]/,strre=" ";

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

                if (icount<len-1){

                    temp =str.substr(i,1);

                    if (patrn.exec(temp)==null){

                       icount =icount+1    

                  }else {

                     icount =icount+2

                 }

                    strre+=temp

              } else {

                   break;

               }

          }

           returen strre+"...";

      }

     2、替换全部

     String.prototype.replaceAll = function(s1,s2) {

            return this.replace(new RegExp(s1,"gm"),s2);

     }

     3、清除空格

     function trim = function() {

            var reExtraSpace = /^\s*(.*?)\s+$/;

            return this.replace(reExtraSpace,"$1");

     }

     4、清除左空格/右空格

      function  ltrim(s){

            return s.replace(/^(\s*| *)/,"");

      }

     function rtrim(s) {

           return s.replace(/(\s*| *)$/,"");

      }

      5、判断是否以某个字符串开头

      String.prototype.startWith = function(s) {

            return this.indexOf(s)==0;

      }

       6、判断是否以某个字符串结束 

      String.prototype.endWith = function(s) {

            var d = this.length - s.length;

            return (d >= 0 && this.lastIndexOf(s)==d);

        }

        7、转义HTML标签

       function HtmlEncode(text) {

             return text.replace(/&/g,'&').replace(/\"/g,' " ').replace(/</g,'<').replace(/>/g,'>');

       }

        8、时间日期格式转换

       Date.prototype.Format = function(formatStr){

             var str = formatStr;

             var week =['日','一','二','三','四','五','六'];

             str = str.replace(/yyyy|YYYY/,this.getFullYear());

             str = str.replace(/yy|YY,(this.getYear()%100)>9?(this.getYear()%100).toString():'0'+(this.getYear()%100));

             str = str.replace(/MM/,(this.getMonth()+1)>9?(this.getMonth()+1).toString():'0'+this.getMonth()+1));

             str = str.replace(/w|W/g,week[(this.getDay()];

             str = str.replace(/dd|DD/,(this.getDate()>9?this.getDate().toString():'0'+this.getDate());

             str = str.replace(/d|D/g,this.getDate());

             str = str.replace(/hh|HH/,this.getHours()>9?(this.getHours().toString():'0'+this.getHours());

             str = str.replace(/h|H/g,this.getHours());

             str = str.replace(/mm/,this.getMinutes()>9?this.getMinutes().toString():'0'+this.getMinutes());

             str = str.replace(/m/g,this.getMinutes());

             str = str.replace(/ss|SS/,this.getSeconds()>9?this.getSeconds().toString():'0'+this.getgetSeconds());

             str = str.replace(/s|S/g,this.getSeconds());

            return str;

        }

        9、判断是否为数字类型

        function isDigit (value){

             var patrn = /^[0-9]*$/;

             if (patrn.exec(value) == null || value=="" ) {

                   return false;  

             } else {

                  return true;

            }

        }

       10、设置cookie

       function setCookie(name,value,Hours){

              var d =new Date();

              var offset =8;

              var utc = d.getTime()+(d.getTimezoneOffset()*60000);

              var nd = utc+(3600000*offset);

              var exp = new Date(nd);

              exp.setTime(exp.getTime()+Hours*60*60*1000);

              document.cookie = name+"="+escape(value)+";domain=360doc.com;"

       }

       获取cookie值

       function getCookie(name){

             var arr = document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)"));

             if (arr!=null){

               return unescape(arr[2]);

            }else {

             return null;

             }

       }

      11、加入收藏夹

       function AddFavorite(sURL,sTitle) {

            try{

               window.external.addFavorite(sURL,sTitle);

         }catch(e){

           try{

               window.sidebar.addPanel(sTitle,sURL,"");

            }catch(e){

                  alert("加入收藏夹失败,请使用Ctrl+D进行添加");

             }

          }

       }

      12、设为首页

      function setHomepage(){

             if(document.all){

               document.body.style.behavior='url(#default#homepage)';

               document.body.setHomePage('http://w3cboy.com')

         }else if (window.sidebar){

              if(window.netscape){

                 try{

                    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect")

              }catch(e){

                 alert("该操作被浏览器拒绝,如果想启用该功能,请在地址栏内输入about:config,然后将

                 项 signed.applets.codebase_principal_support 值该为true");

             }

           }

               var prefs = Components.classes['@mozilla.org/preferences- service;1'].getService(Components.interfaces.nsIPrefBranch); 

               prefs.setCharPref('browser.startup.homepage', 'http://w3cboy.com');

         }

       }

       13、加载样式文件

             function LoadStyle(url) {

                 try { 

                     document.createStyleSheet(url); 

                    } 

                   catch(e) { 

                      var cssLink = document.createElement('link');

                      cssLink.rel = 'stylesheet'; 

                      cssLink.type = 'text/css'; 

                      cssLink.href = url; 

                     var head = document.getElementsByTagName('head')[0]; 

                     head.appendChild(cssLink);

                    } 

                 }

            14、返回脚本内容

            function evalscript(s) { 

                   if(s.indexOf('<script') == -1) return s; 

                   var p = /<script[^\>]*?>([^\x00]*?)<\/script>/ig;

                   var arr = []; 

                   while(arr = p.exec(s)) { 

                   var p1 = /<script[^\>]*?src=\"([^\>]*?)\"[^\>]*?(reload=\"1\")?(?:charset=\"([\w\-]+?)\")?><\/script>/i; 

                   var arr1 = []; 

                   arr1 = p1.exec(arr[0]); 

                   if(arr1) { 

                         appendscript(arr1[1], '', arr1[2], arr1[3]);

                    } else { 

                         p1 = /<script(.*?)>([^\x00]+?)<\/script>/i; 

                         arr1 = p1.exec(arr[0]); 

                         appendscript('', arr1[2], arr1[1].indexOf('reload=') != -1);

                          } 

                       } 

                      return s; 

                  }

            15、清除脚本内容

             function stripscript(s) { 

                      return s.replace(/<script.*?>.*?<\/script>/ig, ''); 

              }

              16、动态加载脚本文件

             function appendscript(src, text, reload, charset) {

                      var id = hash(src + text); 

                      if(!reload && in_array(id, evalscripts)) return; 

                      if(reload && $(id)) { $(id).parentNode.removeChild($(id));
                }

                evalscripts.push(id);

                var scriptNode = document.createElement("script"); 

                scriptNode.type = "text/javascript"; 

                scriptNode.id = id; 

                scriptNode.charset = charset ? charset : (BROWSER.firefox ? document.characterSet : document.charset); 

               try { 

                    if(src) { 

                           scriptNode.src = src; 

                           scriptNode.onloadDone = false; 

                           scriptNode.onload = function () { 

                                       scriptNode.onloadDone = true;

                                      JSLOADED[src] = 1; 

                            }; 

                         scriptNode.onreadystatechange = function () {

                         if((scriptNode.readyState == 'loaded' || scriptNode.readyState == 'complete') && !scriptNode.onloadDone) { 

                                   scriptNode.onloadDone = true; JSLOADED[src] = 1; 

                                  } 

                               }; 

                          } else if(text){ 

                                scriptNode.text = text; 

                         } 

                         document.getElementsByTagName('head')[0].appendChild(scriptNode); 

                     } catch(e) {} 

                   }

           17、返回按ID检索的元素对象
           
function $(id) {

                    return !id ? null : document.getElementById(id); 

             }

           18、跨浏览器绑定事件

           function addEventSamp(obj,evt,fn){ 

                    if(!oTarget){

                           return;

                     } if (obj.addEventListener) { 

                              obj.addEventListener(evt, fn, false);

                         }else if(obj.attachEvent){ 

                             obj.attachEvent('on'+evt,fn); 

                           }else{

                               oTarget["on" + sEvtType] = fn; 

                            }

                    }

             19、跨浏览器删除事件

                function delEvt(obj,evt,fn){ if(!obj){

                        return;

               } if(obj.addEventListener){

                      obj.addEventListener(evt,fn,false); 

                 }else if(oTarget.attachEvent){ 

                    obj.attachEvent("on" + evt,fn); 

                 }else{ 

                     obj["on" + evt] = fn; 

                  } 

               }

            20、元素添加on方法

            Element.prototype.on = Element.prototype.addEventListener;

             NodeList.prototype.on = function (event, fn) {、

                     []['forEach'].call(this, function (el) { 

                           el.on(event, fn); 

                      });

                      return this; 

               };






你不知道的JavaScript:有趣的setTimeout

周周

     话不多说,先上代码

     for(var j=0;j<10;J++){

         setTimeout(function(){console.log(j);},5000)

     }

     看到这三行代码,你也许会不耐烦道:又要讲闭包?要吐槽了好么?别急,让我们先来思考一下,这段代码在浏览器中的执行结果是什么?

     <!-- more -->

     甲:顺序打印0到9?

     乙:这题我见过,打印十个10!

     哪个答案正确?

     执行结果显示,浏览器打印出十个10,貌似乙对了,但是如果你足够细心,你会发现几个问题:为什么会循环打印十个10,而不是0到9?

     从结果来看,for循环执行完跳出之后,才开始执行setTimeout(所以j才等于10),为什么不是每次迭代都执行一次setTimeout呢?

1、为什么会循环打印十个10?

      许多人习惯用第二个问题中的执行结果来回答这个问题:“for循环执行完毕跳出之后才开始执行setTimeout,所以才打印了十个10”。这样的答案,只能说是既应付了自己,又应付了别人。其实,要解答第一个问题,首先要解答第二个问题。

2、为什么不是每一次迭代都执行一次setTimeout?

     大家都知道,JavaScript在ES6出现以前,是没有块状作用域的,这就意味着,在for循环中用var定义的变量j,其实是属于全局的,那其实整个全局作用域中只有一个j,每次for循环都是更新这个j。

       那么现在的关键问题在于,为什么整个for循环会先于setTimeout执行,而不是我们正常理解的,一次迭代执行一次。这就涉及到了JavaScript的核心特性:单线程。

       JavaScript设计的初衷,是浏览器用来与用户进行交互和DOM操作的,这就决定了它必须是单线程的。设想JavaScript同时有两个线程,一个线程在DOM节点内添加内容,一个线程删除该节点,浏览器就会出现混乱。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成为了这门语言的核心特征,将来也不会改变。

      单线程就意味着,所有任务需要排队,前一个任务结束,才会执行下一个任务,如果前一个任务耗时很长,后一个任务就不得不一直等着。

      为了优化单线程的性能,JavaScript将任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程,而进入“任务队列(task queue)”的任务,只用主线程中的同步任务执行完毕,异步任务才会进入执行队列执行。只要主线程空了,就会去读取“任务队列”,这就是JavaScript的运作机制。这个过程会不断重复。

       而setTimeout就被JavaScript定义为异步任务。每次for循环的迭代,都将setTimeout中的回调函数加入任务队列等待执行。也就是说,只有同步任务中的for循环完全结束,主线程中才会去任务列表中找到尚未执行的十个setTimeout(十次迭代)回调函数并顺序执行(先进先出)。而此时,j已经经过循环结束变成了10,所以此时主线程执行的,是十个一模一样的打印i的回调函数,即打印十个10,。至此完美回答了第一和第二个问题,文章开头的代码与下面的代码其实是等价的:

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

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

            setTimeout(console.log(i),5000);

}

       小小的一个setTimeout,牵扯出了很多JavaScript的深层次问题,可见JavaScript还有许多地方是值得深入探究的。



日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档