Phantomjs+Nodejs+Mysql数据抓取(1.数据抓取)


概要:
 
这篇博文主要讲一下如何使用Phantomjs进行数据抓取,这里面抓的网站是太平洋电脑网估价的内容。主要是对电脑笔记本以及他们的属性进行抓取,然后在使用nodejs进行下载图片和插入数据库操作。

先进行所有页面的内容进行抓取

var page =require('webpage').create();
var address='http://product.pconline.com.cn/server/';
var fs = require('fs');
var mypath = 'version/server/server.txt';
var count = 2;
var pageSize=0;
  phantom.outputEncoding="gbk";
  page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko";


function loadController(status){
  loadComputerList(address);
}

function loadComputerList(url){
  console.log('loading '+url);

  page.onLoadFinished = function loadListsucc(status){
    console.log("loadlistSucc ["+url+"] =======================Status:"+status);
  };

  page.open(url,function(status){
    setTimeout(function(){
      console.log(status);
      var content='';
      content = page.evaluate(function(){
      var cont='';
      var listComputer = document.querySelectorAll('div.item-title>h3>a');
      var listPrice =document.querySelectorAll('div.price');
      for(var j=0;jvar computer = listComputer[j].innerText;
          var price = listPrice[j].innerText;
          var url = listComputer[j];
          cont += computer+'/t/t价格:'+price+','+url+'/r/n';
      }
      return cont;
    });
    console.log(content);
    console.log('========== write to file !============');
    try{
      fs.write(mypath, content, 'a');
    }catch(e){
      console.log(e);
    }
    console.log('========== begin loading next page!============');
    var nextUrl = page.evaluate(function(){
          var url = '';
          var next =  document.querySelectorAll('div.pager a[class=page-next]');
          var cont = '';
          url = next[0];
          cont += url;
          return cont;
                });
        console.log(nextUrl);

    if(count else{
        console.log(count);
    phantom.exit();
    }
    }, 100);
  });
}

page.open(address,function(status){
  // page.onLoadFinished = loadController;
  page.render('computer.jpeg');
  pageSize = page.evaluate(function(){
    var cont='';
    var size =document.querySelector('div.pager>em>i').innerText;
    cont += size;
    return cont;
  });
  console.log(pageSize);
  loadController(status);
});

上面部分代码可以直接抓取到

http://product.pconline.com.cn/server/

这个页面中所有分页的信息

下面进行代码分析:

page.open(address,function(status){
  // page.onLoadFinished = loadController;
  page.render('computer.jpeg');
  pageSize = page.evaluate(function(){
    var cont='';
    var size =document.querySelector('div.pager>em>i').innerText;
    cont += size;
    return cont;
  });
  console.log(pageSize);
  loadController(status);
});

这部分代码是Phantomjs的入口,也是我们进行数据抓取的开始部分。

 var size =document.querySelector('div.pager>em>i').innerText;

这里抓到的信息是所有页面的页数,用来作为循环判断的次数依据

然后观察代码就可以发现从入口结束之后就跳转到了LoadContriller函数中去,然后再调用loadComputerList这个函数,然后就可以进行数据抓取了

我们再看一下

 var listComputer = document.querySelectorAll('div.item-title>h3>a');
 var listPrice =document.querySelectorAll('div.price');

这两段代码,就是我们要抓取的电脑URL以及价格的信息。 
再抓到我们想要的信息之后,我们再对其进行拼接

for(var j=0;jlistComputer.length;j++){
          var computer = listComputer[j].innerText;
          var price = listPrice[j].innerText;
          var url = listComputer[j];
          cont += computer+'/t/t价格:'+price+','+url+'/r/n';
      }

然后获得一行具有基本信息的电脑属性。 
然后接下来的工作就是要把这些信息进行存储,我们这里因为不能直接存入数据库,所以要先存入文本中,代码如下:

var fs = require('fs');


try{
      fs.write(mypath, content, 'a');
    }catch(e){
      console.log(e);
    }

再PhantomJS中有API种有相应的读写文件讲解,这里就不多说了,上述代码就是在请求获得之后,将我们拼接好的内容写入文件中,采用的方式是’a’是添加的意思

经过上述过程,我们已经能够将第一个页面中的所有基本信息抓下来了,接下来的问题就是我们该如何跳转到下一个页面中,去抓取接下来的内容

代码如下:

var nextUrl = page.evaluate(function(){
          var url = '';
          var next =  document.querySelectorAll('div.pager a[class=page-next]');
          var cont = '';
          url = next[0];
          cont += url;
          return cont;
                });
        console.log(nextUrl);

    if(count console.log(nextUrl);
      count++;
      console.log(count);
      loadComputerList(nextUrl);
    }else{
        console.log(count);
    phantom.exit();
    }

这里面获取下一个页面Url用到的js语句是:

var next =  document.querySelectorAll('div.pager a[class=page-next]');

朋友们可以使用开发者工具去该网站中看一下点击下一页按钮对应的dom节点是什么,然后就明白这段代码的含义了

再这里面,我们获取了下一页的按钮之后,还需要进行判断现在的循环次数,由于下一页的按钮是一直存在的,我们并不能通过判断是否为空来结束任务,所以我这里用了一个比较蠢得办法来解决这个问题。

抓取所有页面的内容就基本上完成了,这段脚本代码比较简单,如果需要抓同一个网站,只需要修改两部分就可以了,一个是address这个入口,还有就是写文件的路径。

抓取详细信息

再上面我们已经抓到了一些基本信息了,但是页面中并没有为我们提供比如电脑cpu,内存,显卡这些内容,所以我们的抓取工作并没有完成。那么接下来的工作就是要通过我们刚才有抓到的url进入到电脑商品的详细信息页面中去,然后再抓下我们所需要的详细信息。 
代码如下:

var page =require('webpage').create();
var address='http://product.pconline.com.cn/server/';
var fs = require('fs');
var mypath='version/Server/server_page.txt';
var stream = null;
var steams = null;
var K=1;
var line ='';
var cate ='';
var url = '';
var dragPath='version/Server/server_detail.txt';
phantom.outputEncoding="gbk";
page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko";



function start(url){
  console.log(url);
  page.open(url,function(status){
    setTimeout(function(){
    if(status == 'success'){
    console.log('open success!');
    console.log('==========begin work!=============');
    stream = page.evaluate(function(){
        var name = document.querySelector('.pro-tit>h1').innerText;
        name = name.replace('参数','');
        var listComputer = document.querySelectorAll('div.pannel>ul .title');
        var listParameter = document.querySelectorAll('[itemid]');
        var price = document.querySelector('.r-price').innerText;
        price = price.replace(//n/g,'');
        var cont= name+'|&|'+price+'|&|产品特性:';
        for(var j=0;jvar computer = listComputer[j].innerText;
            computer = computer.replace(' ','');
            cont += computer+' ';
          }
        for( var j = 0;jvar parameter = listParameter[j].innerText;
          parameter = parameter.replace(//n/g,'');
          parameter = parameter.replace('/t',' ');
          if(j1){
              cont += parameter+"|&|";
            }else{
              cont += parameter+'';
            }
          }

          return cont+'/r/n';
    });
    try{
      fs.write(dragPath, stream, 'a');
    }catch(e){
      console.log(e);
    }
    console.log(stream);
  }else{
    console.log('page open fail!');
  }
    before();
            }, 100);
  });
}

function readFile(status){
    streams = fs.open(mypath,'r');
    before();
}

function before(){
  console.log('=========work in befor==========='+K);
  K++;
  if(!streams.atEnd()){
    console.log('=========work in befor get Next Line===========');
        line = streams.readLine();
        cate = line.split(',');
    console.log(cate[1]);
    var pcUrl = cate[1].replace('.html','_detail.html');
    console.log(pcUrl);
        start(pcUrl);
    }else{
    console.log('end!!!!!!!!!!!!');
    phantom.exit();
    }

}




page.open(address,function(status){

  readFile(status);

})

我们继续来分析下代码,Phantomjs的开始入口我们就不讲了,每次启动phantomjs都是由这个入口开始,然后再到我们想要的操作中去。

function readFile(status){
    streams = fs.open(mypath,'r');
    before();
}

这里我们成功打开文件,并且把文件中的内容缓存到了streams中去,这里设置的是全局变量,所以直接跳到before这个函数中去

function before(){
  console.log('=========work in befor==========='+K);
  K++;
  if(!streams.atEnd()){
    console.log('=========work in befor get Next Line===========');
        line = streams.readLine();
        cate = line.split(',');
    console.log(cate[1]);
    var pcUrl = cate[1].replace('.html','_detail.html');
    console.log(pcUrl);
        start(pcUrl);
    }else{
    console.log('end!!!!!!!!!!!!');
    phantom.exit();
    }

}

这里面进行的操作主要是在抓文件前,我们需要对我们刚才读进来的内容进行分析,比如:

line = streams.readLine();
cate = line.split(',');
var pcUrl = cate[1].replace('.html','_detail.html');

这三部分,首先就是实现了逐行读取的功能,将每一行的内容读出来,然后通过分隔符获得Url,这里由于我们获得的url并不是我们要的详细信息url,所以我们要进行拼接。

http://product.pconline.com.cn/server/lenovo/514943.html
http://product.pconline.com.cn/server/lenovo/514943_detail.html

这里提供两段实例,读者可以进去看一下,就明白我们为什么要这么拼接url了

stream = page.evaluate(function(){
        var name = document.querySelector('.pro-tit>h1').innerText;
        name = name.replace('参数','');
        var listComputer = document.querySelectorAll('div.pannel>ul .title');
        var listParameter = document.querySelectorAll('[itemid]');
        var price = document.querySelector('.r-price').innerText;
        price = price.replace(//n/g,'');
        var cont= name+'|&|'+price+'|&|产品特性:';
        for(var j=0;jvar computer = listComputer[j].innerText;
            computer = computer.replace(' ','');


概要:
 
这篇博文主要讲一下如何使用Phantomjs进行数据抓取,这里面抓的网站是太平洋电脑网估价的内容。主要是对电脑笔记本以及他们的属性进行抓取,然后在使用nodejs进行下载图片和插入数据库操作。

先进行所有页面的内容进行抓取

var page =require('webpage').create();
var address='http://product.pconline.com.cn/server/';
var fs = require('fs');
var mypath = 'version/server/server.txt';
var count = 2;
var pageSize=0;
  phantom.outputEncoding="gbk";
  page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko";


function loadController(status){
  loadComputerList(address);
}

function loadComputerList(url){
  console.log('loading '+url);

  page.onLoadFinished = function loadListsucc(status){
    console.log("loadlistSucc ["+url+"] =======================Status:"+status);
  };

  page.open(url,function(status){
    setTimeout(function(){
      console.log(status);
      var content='';
      content = page.evaluate(function(){
      var cont='';
      var listComputer = document.querySelectorAll('div.item-title>h3>a');
      var listPrice =document.querySelectorAll('div.price');
      for(var j=0;jvar computer = listComputer[j].innerText;
          var price = listPrice[j].innerText;
          var url = listComputer[j];
          cont += computer+'/t/t价格:'+price+','+url+'/r/n';
      }
      return cont;
    });
    console.log(content);
    console.log('========== write to file !============');
    try{
      fs.write(mypath, content, 'a');
    }catch(e){
      console.log(e);
    }
    console.log('========== begin loading next page!============');
    var nextUrl = page.evaluate(function(){
          var url = '';
          var next =  document.querySelectorAll('div.pager a[class=page-next]');
          var cont = '';
          url = next[0];
          cont += url;
          return cont;
                });
        console.log(nextUrl);

    if(count else{
        console.log(count);
    phantom.exit();
    }
    }, 100);
  });
}

page.open(address,function(status){
  // page.onLoadFinished = loadController;
  page.render('computer.jpeg');
  pageSize = page.evaluate(function(){
    var cont='';
    var size =document.querySelector('div.pager>em>i').innerText;
    cont += size;
    return cont;
  });
  console.log(pageSize);
  loadController(status);
});

上面部分代码可以直接抓取到

http://product.pconline.com.cn/server/

这个页面中所有分页的信息

下面进行代码分析:

page.open(address,function(status){
  // page.onLoadFinished = loadController;
  page.render('computer.jpeg');
  pageSize = page.evaluate(function(){
    var cont='';
    var size =document.querySelector('div.pager>em>i').innerText;
    cont += size;
    return cont;
  });
  console.log(pageSize);
  loadController(status);
});

这部分代码是Phantomjs的入口,也是我们进行数据抓取的开始部分。

 var size =document.querySelector('div.pager>em>i').innerText;

这里抓到的信息是所有页面的页数,用来作为循环判断的次数依据

然后观察代码就可以发现从入口结束之后就跳转到了LoadContriller函数中去,然后再调用loadComputerList这个函数,然后就可以进行数据抓取了

我们再看一下

 var listComputer = document.querySelectorAll('div.item-title>h3>a');
 var listPrice =document.querySelectorAll('div.price');

这两段代码,就是我们要抓取的电脑URL以及价格的信息。 
再抓到我们想要的信息之后,我们再对其进行拼接

for(var j=0;jlistComputer.length;j++){
          var computer = listComputer[j].innerText;
          var price = listPrice[j].innerText;
          var url = listComputer[j];
          cont += computer+'/t/t价格:'+price+','+url+'/r/n';
      }

然后获得一行具有基本信息的电脑属性。 
然后接下来的工作就是要把这些信息进行存储,我们这里因为不能直接存入数据库,所以要先存入文本中,代码如下:

var fs = require('fs');


try{
      fs.write(mypath, content, 'a');
    }catch(e){
      console.log(e);
    }

再PhantomJS中有API种有相应的读写文件讲解,这里就不多说了,上述代码就是在请求获得之后,将我们拼接好的内容写入文件中,采用的方式是’a’是添加的意思

经过上述过程,我们已经能够将第一个页面中的所有基本信息抓下来了,接下来的问题就是我们该如何跳转到下一个页面中,去抓取接下来的内容

代码如下:

var nextUrl = page.evaluate(function(){
          var url = '';
          var next =  document.querySelectorAll('div.pager a[class=page-next]');
          var cont = '';
          url = next[0];
          cont += url;
          return cont;
                });
        console.log(nextUrl);

    if(count console.log(nextUrl);
      count++;
      console.log(count);
      loadComputerList(nextUrl);
    }else{
        console.log(count);
    phantom.exit();
    }

这里面获取下一个页面Url用到的js语句是:

var next =  document.querySelectorAll('div.pager a[class=page-next]');

朋友们可以使用开发者工具去该网站中看一下点击下一页按钮对应的dom节点是什么,然后就明白这段代码的含义了

再这里面,我们获取了下一页的按钮之后,还需要进行判断现在的循环次数,由于下一页的按钮是一直存在的,我们并不能通过判断是否为空来结束任务,所以我这里用了一个比较蠢得办法来解决这个问题。

抓取所有页面的内容就基本上完成了,这段脚本代码比较简单,如果需要抓同一个网站,只需要修改两部分就可以了,一个是address这个入口,还有就是写文件的路径。

抓取详细信息

再上面我们已经抓到了一些基本信息了,但是页面中并没有为我们提供比如电脑cpu,内存,显卡这些内容,所以我们的抓取工作并没有完成。那么接下来的工作就是要通过我们刚才有抓到的url进入到电脑商品的详细信息页面中去,然后再抓下我们所需要的详细信息。 
代码如下:

var page =require('webpage').create();
var address='http://product.pconline.com.cn/server/';
var fs = require('fs');
var mypath='version/Server/server_page.txt';
var stream = null;
var steams = null;
var K=1;
var line ='';
var cate ='';
var url = '';
var dragPath='version/Server/server_detail.txt';
phantom.outputEncoding="gbk";
page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko";



function start(url){
  console.log(url);
  page.open(url,function(status){
    setTimeout(function(){
    if(status == 'success'){
    console.log('open success!');
    console.log('==========begin work!=============');
    stream = page.evaluate(function(){
        var name = document.querySelector('.pro-tit>h1').innerText;
        name = name.replace('参数','');
        var listComputer = document.querySelectorAll('div.pannel>ul .title');
        var listParameter = document.querySelectorAll('[itemid]');
        var price = document.querySelector('.r-price').innerText;
        price = price.replace(//n/g,'');
        var cont= name+'|&|'+price+'|&|产品特性:';
        for(var j=0;jvar computer = listComputer[j].innerText;
            computer = computer.replace(' ','');
            cont += computer+' ';
          }
        for( var j = 0;jvar parameter = listParameter[j].innerText;
          parameter = parameter.replace(//n/g,'');
          parameter = parameter.replace('/t',' ');
          if(j1){
              cont += parameter+"|&|";
            }else{
              cont += parameter+'';
            }
          }

          return cont+'/r/n';
    });
    try{
      fs.write(dragPath, stream, 'a');
    }catch(e){
      console.log(e);
    }
    console.log(stream);
  }else{
    console.log('page open fail!');
  }
    before();
            }, 100);
  });
}

function readFile(status){
    streams = fs.open(mypath,'r');
    before();
}

function before(){
  console.log('=========work in befor==========='+K);
  K++;
  if(!streams.atEnd()){
    console.log('=========work in befor get Next Line===========');
        line = streams.readLine();
        cate = line.split(',');
    console.log(cate[1]);
    var pcUrl = cate[1].replace('.html','_detail.html');
    console.log(pcUrl);
        start(pcUrl);
    }else{
    console.log('end!!!!!!!!!!!!');
    phantom.exit();
    }

}




page.open(address,function(status){

  readFile(status);

})

我们继续来分析下代码,Phantomjs的开始入口我们就不讲了,每次启动phantomjs都是由这个入口开始,然后再到我们想要的操作中去。

function readFile(status){
    streams = fs.open(mypath,'r');
    before();
}

这里我们成功打开文件,并且把文件中的内容缓存到了streams中去,这里设置的是全局变量,所以直接跳到before这个函数中去

function before(){
  console.log('=========work in befor==========='+K);
  K++;
  if(!streams.atEnd()){
    console.log('=========work in befor get Next Line===========');
        line = streams.readLine();
        cate = line.split(',');
    console.log(cate[1]);
    var pcUrl = cate[1].replace('.html','_detail.html');
    console.log(pcUrl);
        start(pcUrl);
    }else{
    console.log('end!!!!!!!!!!!!');
    phantom.exit();
    }

}

这里面进行的操作主要是在抓文件前,我们需要对我们刚才读进来的内容进行分析,比如:

line = streams.readLine();
cate = line.split(',');
var pcUrl = cate[1].replace('.html','_detail.html');

这三部分,首先就是实现了逐行读取的功能,将每一行的内容读出来,然后通过分隔符获得Url,这里由于我们获得的url并不是我们要的详细信息url,所以我们要进行拼接。

http://product.pconline.com.cn/server/lenovo/514943.html
http://product.pconline.com.cn/server/lenovo/514943_detail.html

这里提供两段实例,读者可以进去看一下,就明白我们为什么要这么拼接url了

stream = page.evaluate(function(){
        var name = document.querySelector('.pro-tit>h1').innerText;
        name = name.replace('参数','');
        var listComputer = document.querySelectorAll('div.pannel>ul .title');
        var listParameter = document.querySelectorAll('[itemid]');
        var price = document.querySelector('.r-price').innerText;
        price = price.replace(//n/g,'');
        var cont= name+'|&|'+price+'|&|产品特性:';
        for(var j=0;jvar computer = listComputer[j].innerText;
            computer = computer.replace(' ','');
            cont += computer+' ';
          }
        for( var j = 0;jvar parameter = listParameter[j].innerText;
          parameter = parameter.replace(//n/g,'');
          parameter = parameter.replace('/t',' ');
          if(j1){
              cont += parameter+"|&|";
            }else{
              cont += parameter+'';
            }
          }

          return cont+'/r/n';
    });

这部分代码就是我们要获取详细信息的代码了,读者可以研究一下,其实原理就是找到节点,然后取出来,进行拼接,最后获得一个详细的信息,实例:

联想ThinkServer TS130 S1225/2G/500O|&|¥5417|&|产品特性:产品型号 TS130 S1225/2G/500O|&|产品类型 塔式|&|产品结构 4U|&|CPU系列 至强处理器E3系列,Intel|&|CPU核心 四核|&|总线规格 DMI 5GT/s|&|CPU型号 E3-1225|&|CPU主频 3.1GHz|&|三级缓存 6M|&|标配CPU数目 1个|&|主板插槽 1×PCIE 2.0 x161×PCIE 2.0 x12×PCI 32/33|&|内存类型 DDR3|&|标配内存 2G|&|最大内存容量 32G|&|硬盘接口类型 SATAⅢ|&|标配硬盘 500G|&|最大硬盘容量 4TB|&|硬盘转速 7200转|&|硬盘阵列 Raid 0,Raid 1|&|光驱 DVD-ROM光驱|&|显示芯片 集成显卡|&|网卡 双端口千兆网卡|&|工作环境 工作温度:10℃-35℃,工作湿度:10%-80%|&|存储环境 储存温度:-40℃-70℃,储存湿度:10%-90%|&|电源 1个80PLUS单电源|&|最大功率 280W|&|操作系统 Windows 2003 R2 SP2简体中文标准版(32位/64位) Windows 2003 R2 SP2简体中文企业版(32位/64位)Windows Server 2008 简体中文基础版(64位) Windows Server 2008 简体中文标准版(32位/64位)Windows Server 2008 简体中文企业版(32位/64位)Windows Server 2008 R2 简体中文基础版(64位)Windows Server 2008 R2 简体中文标准版(64位) Windows Server 2008 R2 简体中文企业版(64位) Windows Small Business Server 2011 Essential Windows XP 简体中文专业版,SP2Windows Vista Business 简体中文商业版Windows 7简体中文专业版(32位/64位)Windows 7简体中文旗舰版(32位/64位)|&|尺寸 406×377×174mm

上面这部分就是我们获取到的详细的电脑信息内容,然后再拼接完信息之后,我们要做的就是写入文件中去,这里和上面是相似的,我就不重复了。

cont += computer+' '; } for( var j = 0;jvar parameter = listParameter[j].innerText; parameter = parameter.replace(//n/g,''); parameter = parameter.replace('/t',' '); if(j1){ cont += parameter+"|&|"; }else{ cont += parameter+''; } } return cont+'/r/n'; });

这部分代码就是我们要获取详细信息的代码了,读者可以研究一下,其实原理就是找到节点,然后取出来,进行拼接,最后获得一个详细的信息,实例:

联想ThinkServer TS130 S1225/2G/500O|&|¥5417|&|产品特性:产品型号 TS130 S1225/2G/500O|&|产品类型 塔式|&|产品结构 4U|&|CPU系列 至强处理器E3系列,Intel|&|CPU核心 四核|&|总线规格 DMI 5GT/s|&|CPU型号 E3-1225|&|CPU主频 3.1GHz|&|三级缓存 6M|&|标配CPU数目 1个|&|主板插槽 1×PCIE 2.0 x161×PCIE 2.0 x12×PCI 32/33|&|内存类型 DDR3|&|标配内存 2G|&|最大内存容量 32G|&|硬盘接口类型 SATAⅢ|&|标配硬盘 500G|&|最大硬盘容量 4TB|&|硬盘转速 7200转|&|硬盘阵列 Raid 0,Raid 1|&|光驱 DVD-ROM光驱|&|显示芯片 集成显卡|&|网卡 双端口千兆网卡|&|工作环境 工作温度:10℃-35℃,工作湿度:10%-80%|&|存储环境 储存温度:-40℃-70℃,储存湿度:10%-90%|&|电源 1个80PLUS单电源|&|最大功率 280W|&|操作系统 Windows 2003 R2 SP2简体中文标准版(32位/64位) Windows 2003 R2 SP2简体中文企业版(32位/64位)Windows Server 2008 简体中文基础版(64位) Windows Server 2008 简体中文标准版(32位/64位)Windows Server 2008 简体中文企业版(32位/64位)Windows Server 2008 R2 简体中文基础版(64位)Windows Server 2008 R2 简体中文标准版(64位) Windows Server 2008 R2 简体中文企业版(64位) Windows Small Business Server 2011 Essential Windows XP 简体中文专业版,SP2Windows Vista Business 简体中文商业版Windows 7简体中文专业版(32位/64位)Windows 7简体中文旗舰版(32位/64位)|&|尺寸 406×377×174mm

上面这部分就是我们获取到的详细的电脑信息内容,然后再拼接完信息之后,我们要做的就是写入文件中去,这里和上面是相似的,我就不重复了。

vue 2.0 开发实践总结之疑难篇

续上一篇文章:vue2.0 开发实践总结之入门篇 ,如果没有看过的可以移步看一下。 

 

本篇文章目录如下:

1.  vue 组件的说明和使用

2.  vuex在实际开发中的使用

3.  开发实践总结

 

1.  vue 组件的说明和使用

一个组件实质上是一个拥有预定义选项的一个 Vue 实例

在header组件内部允许外部使用,需要导出属性,有2种导出方法

1.  默认导出(不用命名)

1 export default {
2   data () {
3     return {
4       msg: 'header'
5     }
6   }
7 }

以上代码实际上会自动生成一个 new vue

在父组件中导入

1 import Header from './components/header'

 

2.直接在任何变量或者函数前面加上一个关键字

1 export const sqrt = Math.sqrt;

在父组件中导入

1 import sqrt from './components/header';

 

引用一个组件  

import Header from './components/header'

在该组件中定义

1  export default{
2     data: function () {},   //data一定要是返回一个函数
3     components: {
4       comHeader: Header    //声明组件
5     }
6 }

在template中使用

1 <template>
2   <div class="com-app">
3     <com-header></com-header>   //注意,html不区分大小写,所以需要将  comHeader 写成 com-header 
4   </div>
5 </template>

 

一个vue对象通常包括下面几个属性

1 data:      //vue对象的数据
2 methods:  //vue对象的方法
3 watch:    //对象监听的方法
4 computed:  //计算逻辑放到computed中
5 created:   //属性已绑定,dom未生成,一般在这里进行ajax处理以及页面初始化处理

 

2. vuex

vue 2.0 开发实践总结之疑难篇

 

通过尤大大这张图,我们很清楚的看到,所有的数据流都是单向的,并且actions只能通过分发mutations来修改 store 实例的状态

像一些全局信息通用,比如 header内容的渲染,是否显示,loading 什么时候显示,什么时候隐藏,以及接口api的固定值,都写在store记录组件的state。
 1 const store = new Vuex.Store({
 2   state: {
 3     comm: {
 4       loading: false,  //是否显示loading
 5       apiUrl: 'http://www.sherlochao.com:9091/photosharing/',  //接口base url
 6       imgUrl: 'http://www.sherlochao.com:9091/filebase',    //图片base url
 7       indexConf: {
 8         isFooter: true, // 是否显示底部
 9         isSearch: true, // 是否显示搜索
10         isBack: false,  // 是否显示返回
11         isShare: false, // 是否显示分享
12         title: ''   // 标题
13       }
14     }
15   }
16 })

在mutations中改变state状态

 1 const store = new Vuex.Store({
 2 mutations: {
 3     //loading的显示
 4     isLoading: (state, status) => {
 5       state.comm.loading = status
 6     },
 7     //修改header的信息
 8     changeIndexConf: (state, data) => {
 9       Object.assign(state.comm.indexConf, data)
10     }
11 })

e.g 在 header.vue 中 控制是否显示

 1 export default {
 2     data: function () {
 3       return {}
 4     },
 5     computed: {
 6       isShowSearch: function () {
 7         return this.$store.state.comm.indexConf.isSearch   //获取vuex里面 state 状态值
 8       },
 9       title: function () {
10         return this.$store.state.comm.indexConf.title
11       },
12       isBack: function () {
13         return this.$store.state.comm.indexConf.isBack
14       }
15     }
16 }

template代码

 1 <template>
 2   <div class="header">
 3     <div v-show="isShowSearch"></div>
 4     <div class="title" v-show="!isShowSearch">
 5       <a v-show="isBack" class="back t-icon" @click="goBack"><span
 6         class="iconfont icon icon-xiangzuojiantou"></span></a>
 7       <p>{{title}}</p>
 8     </div>
 9   </div>
10 </template>

在其他地方控制 header 是否显示, e.g: 详情页面

 1 export default {   
 2    created: function () {
 3       vm.$store.commit('changeIndexConf', {
 4         isFooter: false,
 5         isSearch: false,
 6         isBack: true,
 7         isShare: true,
 8         title: '详情页'
 9       })    
10     }
11  }

  

3.开发实践总结

1. vue-router

由于整个项目list组件很多地方公用,并且‘我的收藏’,‘搜索结果页面’,‘我的圈子’,仅仅只是从  /search/own 到 /search/star
此时,原来的组件实例会被复用,意味着组件的生命周期钩子不会再被调用
解决方法:  复用组件时,想对路由参数的变化作出响应的话,可以简单地 watch(监测变化)  对象
1 export default {
2   watch: {
3     '$route' (to, from) {
4       // 对路由变化作出响应...
5     }
6   }
7 }

 

2.判断是否登陆

进入个人信息页面,由于需要判断是否已登陆,此时由 router 进行一个拦截,具体代码如下

1 router.beforeEach(function (to,from,next) {
2   var userMsg = localStorage.getItem('userMsg')
3   if(to.path === '/home'){
4     if(!userMsg){
5       next({ path: '/login' })
6     }
7   }
8   next()
9 })

 

3.常用api

1. 点击事件获取当前对象

event.target , this为vue 对象
 
2. 和jquery类似获取当前dom对象
1 <input type="submit" disabled="canSubmit" ref="isSubmit" @click="register" value="立即注册" class="button"/>
1 this.$refs.isSubmit.removeAttribute('disabled')   //使用this.$refs 获取当前dom

 

其他常见的api  可移步   vue2.0官方文档 

如果在阅读中有发现任何错误或者有更好的建议,请联系我,谢谢!

原创文章,转发请注明来源!

 

读高性能JavaScript编程 第三章

第三章  DOM Scripting 

  1. 最小化 DOM 访问,在 JavaScript 端做尽可能多的事情。 
  2.   在反复访问的地方使用局部变量存放 DOM 引用. 
  3. 小心地处理 HTML 集合,因为他们表现出“存在性”,总是对底层文档重新查询。将集合的 length 属性缓
  4. 存到一个变量中,在迭代中使用这个变量。如果经常操作这个集合,可以将集合拷贝到数组中。
  5.    如果可能的话,使用速度更快的 API,诸如 querySelectorAll()和 firstElementChild。 
  6.    注意重绘和重排版;批量修改风格,离线操作 DOM 树,缓存并减少对布局信息的访问。 
  7.   动画中使用绝对坐标,使用拖放代理。 
  8.    使用事件托管技术最小化事件句柄数量。 
  9. 上面八条建议都是抄的。

dom 文档对象模型 类似的还有 bom 浏览器对象模型 。

要使用一个 对象首先要知道该对象存在的位置,比如 document 对象 它并不是被定义在 ECMAScript 中而是 dom 中。所以 本来你每次访问 document就很慢。

引用:

  文档对象模型(DOM)是一个独立于语言的,使用 XML和 HTML 文档操作的应用程序接口(API)。
在浏览器中,主要与 HTML 文档打交道,在网页应用中检索 XML 文档也很常见。DOM APIs 主要用于访
问这些文档中的数据。

  尽管 DOM 是与语言无关的 API,在浏览器中的接口却是以 JavaScript 实现的。客户端大多数脚本程序

与文档打交道,DOM 就成为 JavaScript 代码日常行为中重要的组成部分。

  浏览器通常要求 DOM 实现和 JavaScript 实现保持相互独立。 例如, 在 Internet Explorer中, 被称为 JScript

的 JavaScript实现位于库文件 jscript.dll中,而 DOM 实现位于另一个库 mshtml.dll(内部代号 Trident)。

两个独立的部分以功能接口连接就会带来性能损耗,这就是为什么会有上面8条建议。

注意:

  1、html集合的存在性

eg:document.getElementsByName()  返回一个类数组对象,这个对象就是 HTML集合。

eg:

 // an accidentally infinite loop 
var alldivs = document.getElementsByTagName_r('div'); 
for (var i = 0; i < alldivs.length; i++) { 
  document.body.appendChild(document.createElement('div')) 
} 

    说明:这段代码看上去只是简单地倍增了页面中 div元素的数量。它遍历现有 div,每次创建一个新的 div并附

加到 body上面。但实际上这是个死循环,因为循环终止条件 alldivs.length 在每次迭代中都会增加,它反
映出底层文档的当前状态。

其实这里还有第二个问题,如果循环里没有做任何操作,这是一个正常的循环,但它还是存在性能问题,因为目标是一个HTML集合。

简单说就是每次循环 都会访问 length 属性,而length属性的值是通过查询文档的操作得到的。每获取一次length都会重新查询一次文档,以保证获取到的数据是最新的,上面有一个现成的例证。更要命的是查询文档这个操作是天生就慢的。原因就在上面,有一段话可以加深印象:

  一个很形象的比喻是把 DOM 看成一个岛屿,把 JavaScript(ECMAScript)看成另一个岛屿,两者之间以一座收费桥连接(参见 John Hrvatin,微软,MIX09,http://videos.visitmix.com/MIX09/T53F)。每次 ECMAScript 需要访问 DOM 时,你需要过桥,交一次“过桥费”。

所以,在循环的时候 把 length缓存到一个变量里最好。

 

如果把 alldivs 复制到一个数组内 例如 :

 function toArray(coll) { 
  for (var i = 0, a = [], len = coll.length; i < len; i++) { 
    a[i] = coll[i]; 
  } 
  return a; 
} 

可以更优的解决问题,如果你需要在循环中多次操作item的话(建议4)。 同时 len = coll.length 这样写是非常值得学习的,但是在C#中就不晓得有没有益了。

  2、使用速度更快的api,这是一种更优的替代解决方案 eg:  var elements = document.getElementById(‘menu’).getElementsByTagName_r(‘a’);  elements =

toArray(elements); 和 var elements = document.querySelectorAll('#menu a'); 
建议5:
这两个函数都是 DOM 节点的属性,所以你可以使用 document.querySelector('.myclass')来查询整个文档中的节点,或者使用 elref.querySelector('.myclass')在子树中进行查询,其中 elref是一个 DOM 元素的引用。

3、 重绘和重拍版引用原文的解释:

  当浏览器下载完所有页面 HTML标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据
结构:    A DOM tree       表示页面结构   A render tree  表示 DOM 节点如何显示 。

  渲染树中为每个需要显示的 DOM 树节点存放至少一个节点(隐藏 DOM 元素在渲染树中没有对应节
点)。渲染树上的节点称为“框”或者“盒”,符合 CSS 模型的定义,将页面元素看作一个具有填充、边距、
边框和位置的盒。一旦 DOM 树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素了。

  当 DOM 改变影响到元素的几何属性(宽和高)——例如改变了边框宽度或在段落中添加文字,将发生
一系列后续动作——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变
受到影响。浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完
成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。

  不是所有的 DOM 改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。
在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。

 

例子:(有可能根本不会出现,只为分析问题使用)
 // setting and retrieving styles in succession 
var computed,     
tmp = '', 
    bodystyle = document.body.style; 
if (document.body.currentStyle) { // IE, Opera 
  computed = document.body.currentStyle; 
} else { // W3C 
  computed = document.defaultView.getComputedStyle(docume
} 
// inefficient way of modifying the same property 
// and retrieving style information right after 
bodystyle.color = 'red'; 
tmp = computed.backgroundColor; 
bodystyle.color = 'white'; 
tmp = computed.backgroundImage; 
bodystyle.color = 'green'; 
tmp = computed.backgroundAttachment; 

PK: 

bodystyle.color = 'red'; 
bodystyle.color = 'white'; 
bodystyle.color = 'green'; 
tmp = computed.backgroundColor; 
tmp = computed.backgroundImage; 
tmp = computed.backgroundAttachment; //winner
bodystyle.color 虽然和 computed.backgroundColor等三个属性无关,但是 浏览器仍要刷新渲染队列并重排版,注意computed被查询了。(如果把computed缓存到变量里就不会有这种情况)
这也是为什么 后者PK胜出了。
还有一种情况更加具有画面感,
 var el = document.getElementById('mydiv'); 
el.style.borderLeft = '1px'; 
el.style.borderRight = '2px'; 
el.style.padding = '5px'; 

这段代码使浏览器排版了三次。只不过速度比较快感官上只改变了一次,但是可以想象到绘制三次的画面。

(注意:现在大多数浏览器都优化了这种情况,只排版一次。) 

如同下面代码一样:

 var el = document.getElementById('mydiv'); 
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;'; 

 el.style.cssText += '; border-left: 1px;';  //or

el.className = 'active'; //or

  离线dom 很好理解,就是在 操作dom的时候不产生 重绘、重排,操作完成后一次性 ‘提交’。

离线dom的三种方式: 1、隐藏 and 操作 and 显示 eg:

 var ul = document.getElementById('mylist'); 
ul.style.display = 'none'; 
appendDataToElement(ul, data); 
ul.style.display = 'block'; 

2、创建并更新一个文档片断,然后附加或替换,eg:

 var fragment = document.createDocumentFragment(); 
appendDataToElement(fragment, data); 
document.getElementById('mylist').appendChild(fragment); 

3、制作副本,替换原节点eg:

 var old = document.getElementById('mylist'); 
var clone = old.cloneNode(true); 
appendDataToElement(clone, data); 
old.parentNode.replaceChild(clone, old); 

缓存并减少对布局信息的访问就是类似 于提取局部变量,只不过布局信息这类数据的访问影响比较大。

4、动画

  1. 使用绝对坐标定位页面动画的元素,使它位于页面布局流之外。
  2. 比如一个扩大和缩小的动画,当扩大使其覆盖原有布局,重绘这一部分而不会重排、重绘一大部分页面。
  3. 动画结束时,重新定位。 其他元素是一起被复位的。

5、事件托管技术最小化事件句柄数量

这里说自己的理解有可能是错误的,暂时先这样理解了:

首先 每个事件都有三个阶段: 

• Capturing
捕获
• At target
到达目标
• Bubbling
冒泡

在onload(或DOMContentReady) 事件里  会让 很多元素和很多事件句柄 挂接/附加 (attached) ,而 有一些挂接是不必的。比如说一些根本不会被点到的按钮。

引用:

一个简单而优雅的处理 DOM 事件的技术是事件托管。它基于这样一个事实:事件逐层冒泡总能被父元
素捕获。采用事件托管技术之后,你只需要在一个包装元素上挂接一个句柄,用于处理子元素发生的所有
事件。