Javascript中的this的全面解析

——《你不知道的Javascript(上卷)》总结

整   理:肖雅君

时   间:2016-10-21

说   明:Javascript中的this的全面解析

this关键字的js最复杂的机制,是一个很特别的关键字,被自动定义在所有的函数作用域中,但是实际使用的时候很难说出this到底指向什么。下面将分2部分介绍this,每一部分都有各自解析的重点和主要内容。

一、this的含义和误区
1.为什么使用this
2.两种常见的对this的误解
3.this到底是什么

二、this的深入解析
1.什么是调用位置
2.绑定规则
(1)默认绑定
(2)隐式绑定
(3)显式绑定
(4)new绑定
3.this词法

【this的含义和误区】

一、为什么使用this
书中通过两段代码的对比来解释为什么要使用this,第一段代码如下:这段代码在不同的上下文对象(you和me)中重复使用函数identify()和speak(),不用针对不同的对象编写不同版本的函数:
function identify(){
return this.name.toUpperCase();
}
function speak(){
var greeting = "Hello, I am" + " " +identify.call(this);
console.log(greeting);
}
var me = {name:"Jay"};
var you = {name:"reader"};

identify.call(me);//JAY
identify.call(you);//READER
speak.call(me);//Hello, I am JAY
speak.call(you);//Hello, I am READER

如果不使用this,那就需要给identify()和speak()显示传入一个上下文对象:

function identify(cxt){
return cxt.name.toUpperCase();
}
function speak(cxt){
var greeting = "Hello, I am" + " " +identify(cxt);
console.log(greeting);
}
var me = {name:"Jay2"};
var you = {name:"reader2"};

identify(you);//READER2
speak(me);//Hello, I am JAY2

对比发现:this提供了一种更好的方式在隐式“传递”一个对象引用。因为随着模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱。因此,通过使用this可以将API设计的冯家简洁,并且易于复用。

二、两种常见的对于this的误解

1-误解1:指向函数本身

把this理解成指向函数本身,这种推断是按照英语语法的角度来推断的。
常见的在函数内部引用自身的情况有:递归或者是一个在第一次被调用后自己解除绑定的事件处理器。新手会认为:既然可以把函数看作一个对象,那就可以在调用函数时存储状态(属性的值)。
下面来通过记录分析函数foo被调用的次数来证明下this并不是指向的函数本身:

function foo(num){
console.log("foo:" + num);
this.count++;//记录foo被调用的次数
}
foo.count = 0;
for(var i = 0; i < 10; i++){
if(i>5){
foo(i);
}
}
//foo:6
//foo:7
//foo:8
//foo:9
console.log(foo.count); //0

为什么会是0呢?
foo()函数中的console.log语句产生了4条输出,证明foo()确实被调用了4次,但是foo.count仍然是0,所以,从字面上理解,this指向函数本身是错的。那么,问题的原因是什么呢?
foo()函数是在全局作用域下执行的,this在这段代码中其实指向window,并这段代码再无意中创建了一个全局变量count,他的值是NaN。
那么为什么ths的行为和预期的不一致呢?书中提供了三种解决方法,其中前两种方法,回避了this的含义和工作原理。代码如下:

方法一:运用作用域(词法作用域)方法,该方法解决了我们遇到的问题,但是没有直面this.

function foo(num){
console.log("foo" + num);
data.count++;//记录foo被调用的次数
}
var data = {count:0};

for (var i = 0; i < 10; i++){
if(i > 5){
foo(i);
}
}
//foo6
//foo7
//foo8
//foo9
console.log(data.count);//4

===========================拓展分界线===========================

补充:这边涉及到一个作用域的问题,js中所说的词法作用域其实就是静态作用域,我随便扒了一段代码,来解释下js里的词法作用域和其他语言的作用域的区别。

比如说,在c#中有一段代码:
static void Main(string[] args)
{
if(true)
{
int num = 10;
}
Console.Write(num);
}
//报错: 因为上下文中不存在num这个变量。因为这边的作用域是用花括号决定的,叫做块级作用域。

换种方式:
static void Main(string[] args)
{
if(true)
{
int num = 10;
System.Console.WriteLine(num);
}
}
而在js中:
if(true) {
var num = 10;
}
alert(num);
//10

弹出框10,所以在js中变量的作用范围是怎么限定的呢?

1.函数限定变量作用域:
在JS中,只有函数可以限定一个变量的作用范围。
也就是说,在JS中,在函数里面定义的变量,可以在函数里面被访问,但是在函数外无法访问。看如下代码:
var func = function() {
var num = 10;
};
try{
alert(num);
}catch( e ){
alert( e );
};
//抛出异常:num is not defined

这段代码再执行的时候,会抛出异常:num is not defined;因为num是在函数里面定义的,在函数外则不能使用。也就是说,在函数里面定义的变量在函数外不能使用,在函数内可随意使用,即使是在赋值之前。如下代码:(注:调试的时候在浏览器控制台调试不会抛出异常,会正常弹出10,需在本地测试。)

var func = function(){
alert(num);
var num = 10;
alert(num);
};
try{
func();
}catch(e){
alert(e);
}
这段代码运行后,不会抛出错误,会产生两次弹窗,一次undefined一次10(下文解释为什么,这里面涉及到“变量名提升和函数名提升”的概念)。从这里可以看出,变量只有在函数中可以被访问,同理在改函数中的函数也可以访问。

2.子域访问父域
之前说了,函数可以限定变量的作用域,那么在函数中的函数就可以成为该作用已的子域。在子域中的代码可以访问到父域中的变量,代码如下:
var func = function(){
var num = 10;
var sub_func = function(){
alert(num);
};
sub_func();
};
func();//10
这段代码的执行结果是10,可看出子欲可访问父域中的变量num。但是子域中访问父域的代码也是有条件的,如下:

var func = function(){
var num = 10;
var sub_func = function(){
var num = 20;
alert(num);
}
sub_func();
}
func();//20
执行结果是20,此时的num输出的是子域里的值。

由此可见js中的变量访问规律:js解释器现在当前的作用域中搜索是否有该变量的定义,如果有,则使用这个变量;如果无,则到父级中去寻找。层层往上,直到顶级作用域,如果顶级中也不存在,则抛出异常“变量未定义”。
综合如下:
(function() {
var num = 10;
(function() {
var num = 20;
(function(){
alert(num);
})()
})();
})();

//这段代码执行后打印出20. 如果将"var num = 20;"去掉,那么打印的就是10. 同样,如果再去掉"var num = 10",那么就会出现未定义的错误。

3.变量名提升和函数名提升
变量名提升:
var num = 10;
var func = function() {
alert(num);
var num = 20;
alert(num);
};
func();
//undefined 20

像这样将代码定义在后面,而在前面使用的情况在Js中很常见,如:
var num = 10;
var func = function(){
var num;// 感觉就是这里已经定义了,但是没有赋值一样
alert(num);
var num = 20;
alert(num);
};
func();
这种现象就被称为变量名提升。

同样有函数名提升:
var func = function() {
alert("调用外面的函数");
};

var foo = function() {
func();
var func = function() {
alert("调用内部的函数");
};
func();
};

===========================拓展分界线END===========================

方法二 :创建一个指向函数对象的词法标识符(变量)来引用它。同样该方法仍旧回避了this的问题。
function foo(num){
console.log("foo: " + num);
foo.count++; // foo指向它自身
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count);// 4

方法三: 因为this在foo函数执行时指向了别处,所以可强制this指向foo函数。
function foo(num){
console.log("foo: " + num);
this.count++;
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo.call(foo, i); //使用call()可以确保this指向函数本身
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count);// 4

2-误解2:指向函数作用域

第二种常见的误解是:this指向函数作用域。这个问题有点复杂,因为在某种情况下它是正确的,但在其他情况下他却是错误的。
但一定要明白,this在任何情况下都不指向函数的作用域,在javascript内部作用域和对象确实很相似,可见的标识符都是他的属性,但作用域“对象”无法通过JavaScript代码访问,它存在于JavaScript引擎内部。
function foo(){
var a = 2;
this.bar();
}
function bar(){
console.log(this.a);
}
foo();
// undefined
这段代码试图通过this联通foo()和bar()的词法作用域,从而让bar()可以访问foo()作用域的变量a。这是错误的。

三、this到底是个什么鬼
排除以上的几种误解,可以得出以下结论:
1.this是在运行时进行绑定的,并不是在编写的时候绑定的。
2.this的绑定和函数的声明的位置没有关系,只取决于函数的调用方式。

具体细节是:当一个函数被调用的时候,会创建一个活动记录,这个活动记录会包含一些信息,如:函数在哪里被调用、函数的调用方式、传入的参数等等,而this就算这个记录的一个属性,会在函数执行的时候被用到。

四、总结
1.随着程序使用的模式越来越复杂,显示传递上下文对象会让代码显得很乱,因此通过this隐式传递可以将API设计的更加简洁并且利于复用。
2.this既不指向函数自身,也不指向函数的作用域。
3.this实际上是函数被调用时发生的绑定,它的指向完全取决于函数在哪里被调用。

关于变量的作用域的小结:
在js中词法作用域又叫做静态作用域,作用域的访问规则可以画成一个作用域链:
1) 作用域链就是对象的数组
2) 全部script是0级链,每个对象占一个位置
3) 凡是看到函数延伸一个链出来,一级级展开
4) 访问首先看当前函数,如果没有定义往上一级链检查
5) 如此往复,直到0级链

【this的深入解析】

调用位置
调用位置:就是函数在代码中被调用的位置(而不是声明位置)。要想回答htis到底引用的什么,只有仔细分析调用位置才能回答这个问题。
而分析条用位置最重要的就是分析调用栈。
调用栈:就是为了到达当前执行位置所调用的所有的函数。
function baz(){
//当前调用栈是:baz
//因此,当前调用位置是全局作用域
console.log("baz");
bar();//bar的调用位置
}
function bar(){
//当前的调用栈是:baz -> bar
//因此,当前的调用位置是在baz中
console.log("bar");
foo();//foo的调用位置
}
function foo(){
//当前的调用栈是baz -> bar -> foo
//因此,当前的调用位置是在baz中
console.log("foo");
}
baz();//baz的调用位置

绑定规则
思路:通过找到函数的调用位置,然后判断需要应用规则中的哪一条,便可决定this的绑定对象。关于this的绑定规则主要有一下四种:
(1)默认绑定
(2)隐式绑定
(3)显式绑定
(4)new绑定

1.默认绑定
默认绑定的典型类型是:独立函数调用。如下代码:
function foo(){
console.log(this.a);
}
var a = 2;
foo();//2
调用foo()函数的时候,函数应用了默认绑定,this指向全局对象window(这是在非严格模式下,若是在严格模式下会报错),所以this.a被解析成了全局变量a。所以,可在不使用任何修饰的函数引用进行调用,只能使用默认绑定,无法应用其他规则。

2.隐式绑定
隐式绑定的常见形式上在调用位置具有上下文对象,或者说被某个对象拥有或者包含。如下:
function foo(){
console.log(this.a);
}
var obj = {a:2, foo:foo};

obj.foo(); //2
这里的函数是foo()是预先定义好的,然后将其添加为obj对象的引用属性。调用位置使用obj上下文对象来引用函数,因此可以说函数被调用的时候obj对象“拥有”或者“包含”它。无论你怎么称呼这种模式,当foo()被调用时,它的前面确实加上了obj的引用,当函数引用上下文对象时,隐式绑定规则就会把函数调用中的this绑定到这个上下文对象。所以,this.a和obj.a是一样的。

另一个需要注意的点是:对象属性引用链中只有最后一层或者说最顶层会影响调用位置。
function foo(){
console.log(this.a);
}
var obj1 = {a:1, obj2:obj2};
var obj2 = {a:100, foo:foo};
obj1.obj2.foo();//100

隐式丢失
隐式绑定最常见的问题就是隐式丢失 -> 隐式绑定的函数会丢失绑定对象。也就是说它会应用“默认绑定”,而把this绑定到全局对象或者undefined上,取决于是否是严格模式。【*1】
function foo(){
console.log(this.a);
}
var obj = {a:2, foo:foo}

var bar = obj.foo;//函数别名
var a = "global";//a是全局对象的属性
bar();//"global"

虽然bar是obj.foo的一个引用,但实际上,他引用的是foo函数本身,因此,此时的bar()其实是一个不带任何修饰的函数调用,因此,它应用了“默认绑定”。

===========================拓展分界线===========================
*1-备注:js中函数调用的两种方法:
function test(a){
alert("你输入的是:" + a);
}

//方法一:直接调用
test("臣妾做不到啊!");

//方法二:函数赋值给变量
var mytst = test;
mytst("贱人就是矫情");//用变量来调用函数

===========================拓展分界线END===========================

一种更微妙、更常见的并且更出乎意料的情况发生在传入回调函数时:
function foo(){
console.log(this.a);
}
function callBack(fn){
//fn其实引用的是foo
fn();//调用位置
}
var obj = {a:2, foo:foo}
var a = "global";
callBack(obj.foo);//"global"

参数传递其实就是一种隐式赋值,这句话可以用下面的两段代码来详细的讲解:
(1)代码一
var a = 1;
function fn(){
alert(a);
a = 2;
}
fn();
alert(a);
(2)代码二
var a = 1;
function fn(a){
alert(a);
a = 2;
}
fn();
alert(a);

在第一段代码中:
首先,在全局作用域中,先通过变量提升,找到了标识符a和函数fn,a此时有个默认的undefined。然后,在执行阶段,程序先将变量a赋值为1,紧接着执行函数fn(),此时在函数域中,依旧应用变量提升的规则,但是什么都没有找到,紧接着执行函数内的代码alert(a),因为在函数中并没有找到变量a。所以,通过作用域链向上层的父级作用域中查找,我们找到了a,并且此时a的值已经被赋值为1,所以alert(a)的结果是1。下一句:a = 2,注意,a前面没有关键字var ,即:这里的a是全局的,也就说在执行a =2 时,它修改的是全局作用域中a 的值,所以alert(a)时,值自然为2。

在第二段代码中:
同样通过变量提升,找到标识符a和函数fn,a此时的默认值也是undefined。开始执行,a 先被赋值1,然后函数执行,这里与第一段代码的不同处在于,在函数fn中传入了参数a,那么这么做的结果就是:在函数域先运用变量提升的规则的时候,不会像第一段代码中那样,什么都找不到,而相当于定义了一个值为undefined(调用的时候没有传入参数)的变量a,所以执行函数中的alert(a)时,结果为undefined,而不会像第一种情况听过作用域链向上查找。因为本函数中已经找到了a,只不过是以参数的形式传入的。同理代码(a = 2)会修改a的值,即在函数域中,a的值现在为2(可以去尝试在函数中最后面alert一下a的值)。而在函数外执行alert(a),我们得到的结果便是1,因为该句代码是在全局中执行的,即会在全局中去查找变量a,而不会去访问函数域中的a。这也是因为,在JavaSceipt中子作用域可以访问父作用域而反过来却不行的规则。

回到this绑定丢失的话题上,上面讲这么多,其实就是想说明:参数传递其实就是一种隐式赋值,参数传递其实就是一种隐式赋值,参数传递其实就是一种隐式赋值。重要的事情三遍。
按照上面的方式来解析代码:在执行callBack(obj.foo)时,在函数作用域通过变量提升,找到了参数fn,它的默认值是undefined,然后将参数传入,其实相当于(var fn = obj.foo),这就是与前面的将其直接赋值给一个变量对等上了,然后再执行fn(),应用“默认绑定”,此时的this已经不指向obj了,而是指向window(严格模式)。

如果把函数传入内置的函数而不是传入自己声明的函数,会发生什么呢?
function foo(){
console.log(this.a);
}
var objg = {a:2, foo:foo};
var a = "global";

setTimeout(obj.foo, 1000);//"global"

结果还是一样的。
因为javascript环境中内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn,dalay){
//等待delay秒
fn();//调用位置
}
回调函数丢失this绑定的情况是非常常见的,并且还有一种情况this的行为会出乎我们意料:调用回调函数的函数可能会修改this。由于无法控制回调函数的执行方式,因此就没有办法控制调用位置得到期望的绑定。

那么如何通过固定this来“修复”这个问题呢?
即:如何解决this的丢失问题。

显示绑定
在隐式绑定中,我们必须在一个对象的内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接绑定到这个对象上。那么,如果我们不想在每个对象内部包含函数引用,而想在每个对象上强制调用函数,该怎么做呢?

这时就需要call(绑定this,其他参数...)和apply(绑定this,其他参数...) 这两个方法了。这两个方法的第一个参数都是给this准备的,不同之处在于其他参数的形式上,他们两的其他参数对比如下:
call(绑定this,"参数1","参数2","参数3","参数4");
apply(绑定this,["参数1","参数2","参数3","参数4"]);

===========================拓展分界线===========================
注:跑题之apply和call
apply的其他参数是以数组序列形式存在的,它会在执行时将其解析成单个的参数再依次的传递到调用的函数中,这有什么用处呢?加入我们有一个数组:
var arr = [1,2,3,4,5,6];
现在我要找到其中的最大值,当然这里有很多方法了。既然这里讲到apply那么我们就用apply方法来解决这个问题。如果想要找到一组数中最大的一个,有一个简单的方法,使用Math.max(...)。但是,该方法并不能找出一个数组中的最大值,也就是说:
Math.max(1,2,3,4,5); // 可以找到最大值5
Math.max([1,2,3,4,5]); // NAN这就不行了,因为不接受以数组作为参数

我们的做法就是通过:
Math.max.apply(null, [1,2,3,4,5]); //得到数组中的最大值5

===========================拓展分界线END===========================

通过call()和apply()这两种方法我们可以显式的绑定this到指定的对象:
function foo(){
console.log(this.a);
}
var obj = {a: 2}
foo.call(obj);//2

这两个方法是如何工作的呢? 它们的第一个参数是一个obj对象, 它们会把这个对象obj绑定到this,接着在调用函数foo时指定这个this。 因为你可以直接指定this 的绑定对象,也就是obj, 因此我们称之为显式绑定。也就是说:在调用 foo 时强制把它的 this 绑定到 obj 上

但是,显式绑定仍旧无法解决this丢失绑定的问题。

1.硬绑定
显式绑定的一个变种可以解决这个问题。
function foo(){
console.log(this.a);
}
var obj = {a: 2}
var bar = function(){
foo.call(obj);
}
bar();// 2

setTimeout(bar, 100); // 2

// 硬绑定的 bar 不可能再修改它的 this
bar.call(window); // 2

看看它是如何工作的:我们创建了一个函数bar(),并在他的内部手动调用foo.call(obj)。因此,强制把foo的this绑定到了obj,无论之后如何调用函数bar,它总会手动在obj上调用foo。这样的形式我们称之为硬绑定。

硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值:
function foo(something){
console,log(this.a, something);
return this.a + something;
}
var obj = {a: 2}

var bar = function(){
return foo.apply(obj, arguments);
}
var b = bar(3); //2, 3
console.log(b); //5

硬绑定是一种非常常用的模式,所以ES5提供了内置的方法Function.prototype.bind,它的用法如下:
function foo(something){
console,log(this.a, something);
return this.a + something;
}
var obj = {
a:2
}
var bar = foo.bind(obj);
var b = bar(3); //2, 3
console.log(b); //5

bind(..) 会返回一个硬编码的新函数, 它会把参数设置为 this 的上下文并调用原始函数。

2. API调用的“上下文”
第三方库的许多函数, 以及 JavaScript 语言和宿主环境中许多新的内置函数, 都提供了一
个可选的参数, 通常被称为“ 上下文”( context), 其作用和 bind(..) 一样, 确保你的回调
函数使用指定的 this。
举例来说;
function foo(el){
console.log(el, this.id);
}
var obj = {id: "awesome"};
// 调用 foo(..)时把this绑定到obj
[1, 2, 3].forEach(foo, obj ); // 1 awesome 2 awesome 3 awesome

这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定, 这样你可以少些一些代码。

new 绑定
第四条规则,也是最后一条规则,在讲解他之前我们首先要澄清一个非常常见的关于javascript中函数和对象的误解。在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用new初始化类是会调用类中的构造函数。通常的形式是这样:
someThinges = new MyClass(...)
javascript中也有个new操作符,但javascript中的new操作符的机制与面向类的语言完全不同。

首先我们重新定义一下JavaScrit中的“构造函数”。
在Javascript中,构造函数只是一些使用new操作符时被调用的函数。它并不会属于某个类,也不会实例化一个类。实际上它甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。

举例来说,思考一下Number()作为构造函数时的行为,ES5.1中这样描述它:

Number构造函数
当Number在new表达式中被调用时,它是一个构造函数:它会初始化新建的对象。
所以,包括内置对象函数在内的所有函数都可以用new来调用,这种函数被称为构造函数调用,这有个非常细微的区别:实际上并不存在所为的“构造函数”,只有对于函数的“构造调用”。使用new来调用函数,会自动执行下面的操作:
(1)创建一个全新的对象
(2)这个新对象会被执行[[原型]]连接(之后会细说)
(3)这个新对象会绑定到函数调用的this
(4)如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个对象。

如下代码:
function foo(a){
this.a = a
}
var bar = new foo(2);
console.log(bar) // foo {a: 2}
console.log(bar.a); //2

使用new 来调用foo(...)时,我们会构造一个新的对象,并把它绑定到foo(...)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法。我们称之为new绑定。

箭头函数
我们之前介绍的四条规则已经可以包含所有正常是有的函数。但是在ES6中介绍了一种无法使用这些规则的特殊函数类型:箭头函数
箭头函数不是使用function关键字定义的,而是使用“ => ”定义。箭头函数不使用this的四种标准规则,而是根据外层作用域(函数或全局)来决定this。

最终小结:
如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到后就可以顺序应用下面这四条规则来判断this的绑定对象:
判断this:
1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo()
2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。
var bar = foo.call(obj2)
3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
var bar = obj1.foo()
4. 如果都不是的话, 使用默认绑定。 如果在严格模式下, 就绑定到 undefined, 否则绑定到全局对象。
var bar = foo()

另:ES6中的箭头函数不会使用四条标准的绑定规则,而是根据词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到了什么),这其实和ES6之前代码中的self = this 机制一样。

如何配置测试环境,支持多分支开发

标签: 运维 测试环境搭建 apache


编 写:袁 亮
时 间:2016-09-05
说 明:如何配置测试环境,支持多分支开发

一、目的

1、业务开发,多人合作的时候,需要开启多个分支
2、每个分支的开发、调试、访问需要能跟其他分支独立

二、分支独立需要考虑如下问题

1、需要配置独立的访问规则,来支持各个分支
    要求:访问规格更改不需要改动程序
2、配置文件,每个分支可以自行配置,也可以使用通用的
    包含配置:
        数据库、缓存、静态文件等等
    解决方案:
        SetEnv CI_ENV development
        development配置目录不要放在svn里,通过svn ignore忽略
3、图片、文件上传
    解决办法:所有上传,走统一的接口,返回上传之后的地址即可,各个分支一样
4、api接口
    解决办法:在各自分支里,调用不同的api分支

三、访问规则配置方案

1、方案一:
    实施:将分支名以目录的形式放在测试hosts之后,比如shopdev的配置
    优点:新建分支很简单,在相应目录下建个分支目录即可
    缺点:因为多了一层目录,会导致整个的访问规则都不一样了,代码中需要做相应的兼容判断,比如出现这种:
        http://shopdev.ci123.com/svn/yungou/webroot/index_yiqigou.php/item-5287.html?store_id=120&share_uid=406259
        或者直接/目录开头的访问等等

2、方案二:
    实施:将分支名以泛域名解析的形式,隐藏到hosts里
    优点:对程序不用做任何改动,访问规则完全兼容(ps:代码里写死了绝路路径的这种自己挖坑的另算)
    缺点:测试域名一般不是实际解析的域名,也意味着没有走DNS解析的过程,只能通过hosts来实现
        而hosts并不具备解析功能,隐藏泛解析这种hosts是做不了的,只能每个分支自己加hosts
        ps:如果嫌加hosts麻烦,可以考虑将测试域名做实际解析
            或者在公司内部自建DNS(bind),缓存非测试域名,测试域名自己做解析
            比较麻烦,没太大意义,暂时不考虑
    其他:
        直接使用nginx+fastcgi会更简单,只是我们使用的apache为主,因此是配置的nginx+apache的方式

四、方案二实施

1、nginx 配置泛域名解析
    server_name通配符即可

2、apache支持泛域名解析(rewrite方式)
    1.1 ServerAlias 通配符实现泛解析
    1.2 rewrite 实现将分支名转为相应的目录名
    1.3 框架本身的rewrite重写
    1.4 静态文件rewrite重写

3、hosts 处理
    需要使用的分支,自行添加hosts

五、yungou.ws测试机最终使用

1、svn 创建分支,checkout到/opt/ci123/www/html/seller_shop下
    ps:分支名只支持大小写字母、数组、_组成的字符串
2、本地添加hosts
    192.168.0.249 分支名.yungou.ws
3、浏览器访问 (一些老旧浏览器需要强刷或者重启)
    分支名.yungou.ws

六、apache rewrite 概述

RewriteEngine On

# 将所有非static静态文件的请求,全部达到webroot的index中,框架本身的rewrite要求,将分支名嵌入进去
RewriteCond %{HTTP_HOST} [\w_]+.yungou.ws
RewriteCond %{REQUEST_URI} !static.*
#将hosts放到rewrite匹配源中,需要其中的分支名作为后续使用 跟下一条规则关联
RewriteRule ^(.+) %{HTTP_HOST}$1 [C] 
RewriteRule ([\w_]+).yungou.ws(.*) /$1/webroot/index.php/$2 [L]

# static 静态文件目录不rewrite,直接访问
RewriteCond %{HTTP_HOST} [\w_]+.yungou.ws
RewriteCond %{REQUEST_URI} static.*
RewriteRule ^(.+) %{HTTP_HOST}$1 [C]
RewriteRule ([\w_]+).yungou.ws(.*) /$1/webroot/$2 [L]

附:参考文档

1、nginx泛解析域名实现多级域名多个同时绑定
2、nginx泛域名解析实现二级域名多域名
3、Apache 2.2 + Tomcat 泛域名解析 动态二级域名
4、使apache解析域名到目录的方法
5、Apache的Rewrite规则详细介绍
6、Apache Module mod_rewrite
7、解析无限个二级域名的方法

如何使用CasperJs抓取商品数据

标签: 爬虫 casperjs phantomjs


编 写:袁 亮
时 间:2016-08-30
说 明:如何使用CasperJs抓取商品数据

一、目的

抓取天猫、淘宝、京东的相应商品数据
抓取商品名、价格、轮播图、详情、产品属性等数据

二、方案选择

1、情况分析
    1.1 对应的页面数据很多都是后加载的
    1.2 页面请求很多,想找到其对应的ajax请求比较麻烦
    1.3 ajax请求的地址是服务端生成的,不能通过简单的拼接来生成
    1.4 对正则水平要求较高,很多请求地址是写在js里的

2、可选方案
    2.1 php等语言,直接发钱http请求,然后通过正则匹配等方式,找到响应数据源
        curl、phpquery、Snopy等等 
    2.2 使用浏览器实际访问,然后获取浏览器的最终结果数据
        phantomJS
        casperJS + phantomJS
        python + selenium + phantomJS
3、选用方案 casperJS + phantomJS
    3.1 纯JS,方便前后端使用
    3.2 Casper封装的比较友好、文档齐全,比较好用
        特别是针对后加载的元素,之间waitForSelector的方法非常方便
        可以引入jquery等自己的js文件,方便dom操作
    3.3 获取数据,只需要在浏览器打开的时候,找到对应的节点选择器即可
    3.4 安装部署方便

三、简单例子

var casper = require('casper').create();
casper.start('http://casperjs.org/', function() {
    this.echo(this.getTitle());
});

casper.thenOpen('http://phantomjs.org', function() {
    this.echo(this.getTitle());
});
casper.run();

四、环境部署 (非源码安装)

1、安装phantomjs
    1.1 下载安装
        wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
    1.2 解压
        tar jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2
    1.3 做软连接,放在环境变量里,比如(注意权限问题,如果需要apache调用,更要注意)
        ln -s /opt/ci123/phantomjs/bin/phantomjs /usr/local/sbin/
    1.4 测试运行
        phantomjs --version

2、安装casperjs
    2.1 选择相应版本下载
        wget https://github.com/casperjs/casperjs/archive/1.1.3.zip
    2.2 解压
        unzip 1.1.3.zip 
    2.3 做软连接,放在环境变量里,比如(注意权限问题,如果需要apache调用,更要注意)
        ln -s /opt/ci123/casper/bin/casperjs /usr/bin/casperjs
    2.4 测试是否正常
        casperjs --version

五、实际使用

1、apache调用php
2、php根据url调用不同的CasperJS,传入要抓取的url
3、Casper输出json数据
4、php使用exec调用,并获取casper的输出
5、php将相应的json输出返回apache或者存入数据库等等
演示代码:
    192.168.0.249 
    /opt/ci123/www/html/yuanliang/casper/fetch

六、踩过的坑

1、抓取https的时候,249上一直失败
    原因:
        249上的ssl配置应该问题,导致抓取部分https网站的时候,一直报错
    解决办法:
        a:CasperJS需要较高版本才能用
            casperjs --ignore-ssl-errors=yes --ssl-protocol=any xxx.js
            phantomjs --ssl-protocol=any xxx.js
        b:升级249上的ca证书或者openssl版本

2、抓取速度太慢
    原因:
        这个工具本质是一个无界面的浏览器,因此里面的所有资源都跟浏览器一样,会去加载,图片等资源较多的时候会卡
    解决办法:
        设置不加载图片 loadImages:false
3、clientScripts 注入远程jquery文件无效
    原因:
        只能加载本地的js文件,不能加载远程的js文件
    解决办法:
        可以下载到本地,或者使用remoteScripts来加载,这个可以使用远程文件
        不过使用本地文件比较好,少一次http请求,速度会快很多
4、淘宝的后加载数据一直获取不到
    原因:
        之前https的出问题的时候,以为是ua有问题,所以设置了一个chrome的ua,但是实际上内核不一样
        导致淘宝本身的js执行失败,数据加载不出来
    解决办法:
        去掉ua设置即可
5、天猫的后加载数据(详情)一直加载不出来,报语法错误
    原因:
        天猫的代码中,如何屏幕大小超过1260,会执行一段JS,这段JS会出错
    解决办法:
        设置屏幕宽度小于1260即可
6、waitForSelector获取某个数据,超时,导致后面都不执行了
    原因:
        默认的timeout处理函数,会直接将整个进程中止,后面的所有代码都不执行
    解决办法:
        如果可以接受部分字段抓取不到,则可以在waitForSelector中自行设置timeout处理函数,不中止
        如果需要全部抓取才算结束,可以在timeout的时候触发一个报警等来通知
7、根据浏览器的元素定位,返回不了相应的数据
    原因:
        浏览器最终呈现的元素,有些跟phantomJs返回的不一样
        比如图片后加载,这是在页面滚动到一定位置之后才会触发的
    解决办法:
        如图片后加载这种,数据其实已经返回了,只是存在其他地方,可以返回对应的html,然后看数据存储在哪个字段上
        如果有些是点击、滚动等之后,从服务端获取的数据,那就需要模拟浏览器的点击滚动等事件,然后获取,比较少见
8、执行获取页面数据的时候,失败报错
    原因:
        页面代码执行在不同的沙箱中,在CasperJS本身是读取不到的
    解决办法:
        均在this.evaluate函数中执行,在内部可用操作所有的DOM,跟页面执行JS效果一样
9、天猫商品详情图片一直抓取不对
    原因:
        图片本身是后加载的,之前数据是放在data-ks-lazyload中,后加载执行之后,这个data被干掉了
        直接取src也会有问题,因为有的时候取数据的时候,后加载js还没执行到
        所以导致一会好一会坏
    解决办法:
        先取data-ks-lazyload,如果没有,则去src数据

七、注意事项

1、debug的时候,可用通过将当前页面截图出来,方便查看加载到什么情况了
    也可以将完整的html输出到文件里查看
2、打印json数据,方便跟php等交互
    打印数据
    casper.then(function(){
        require('utils').dump(data);
    });
3、注意很多异步操作,很多时候,执行效果会跟预期不一样
4、当Casper运行结果跟预期不一样的时候,可以考虑直接写相应的phantomJs做对应小块功能来调试
5、淘宝url等在命令行传参会出错,需要用单引号包含起来

八、参考资料

1、CasperJS 官方文档
    http://docs.casperjs.org/en/latest/index.html
2、phantomJS 文档
    http://phantomjs.org/documentation/
3、PhantomJS基础及示例
    http://imweb.io/topic/560b402ac2317a8c3e08621c
4、casperjs模拟登陆https页面获取当前面地址后发现为about:blank问题排查
    http://www.bubuko.com/infodetail-1018663.html
5、 Linux使用curl访问https站点时报错汇总
    http://www.ipcpu.com/2014/12/curl-https-error/

HTML5 history pushState/replaceState操纵浏览器的历史记录

整   理:晋 哲

时   间:2016-06-16

说   明:HTML5引进了history pushState()/replaceState()方法,可以操纵浏览器的历史记录

HTML5引进了history.pushState()方法和history.replaceState()方法,它们可以逐条地添加和修改历史记录条目。这些方法可以协同window.onpopstate事件一起工作。

一、pushState()方法
pushState()有三个参数:一个状态对象、一个标题(现在会被忽略),一个可选的URL地址。
状态对象(state object):一个JavaScript对象,设置状态对象之后会将其拷贝到popstate事件的state属性中,然后在popstate事件中可以通过获取state属性与创建的新历史记录条目关联。
标题(title):FireFox浏览器目前会忽略该参数,所以暂时可以传一个空字符串。
地址(URL):新的历史记录条目的地址。该参数是可选的,不指定的话则为文档当前URL。

二、replaceState()方法
history.replaceState()操作类似于history.pushState(),不同之处在于replaceState()方法会修改当前历史记录条目而并非创建新的条目。

三、popstate事件
每当激活的历史记录发生变化时都会触发popstate事件。如果被激活的历史记录条目是由pushState所创建,或是被replaceState方法影响到的,popstate事件的状态属性将包含历史记录的状态对象的一个拷贝。

实例:选项卡切换结合浏览器的前进与后退
普通的选项卡切换局部页面,点击“后退”会直接跳转到上一个页面;现结合history.pushState()方法和popstate事件,可以实现选项卡页面之间浏览器历史记录的前进与后退。

简易总结实现步骤:
1、使用pushState()方法添加一条新的历史记录条目,这时只是改变了浏览器URL内容;
2、每激活一次历史记录(比如点击后退),就会触发一次popstate事件,事件中获取的state属性就会随之改变,在这种状态下设置对应页面切换的代码即可。

示例代码:
history_pic

Ajax加载的页面同样存在无法前进与后退的状况,同理实现。

参考代码:
1、Ajax与HTML5 history pushState/replaceState实例-张鑫旭
http://www.zhangxinxu.com/wordpress/?p=3432
2、动漫酷网站
http://dmku.net
3、pjax是对ajax + pushState的封装
https://github.com/welefen/pjax

PHP empty、isset的差异

PHP empty、isset的差异:
1.isset():
参数           返回
''            true
0             true
false          true
null($p=null)     false
参数未定义($p)    false
其他           true

总结:如果参数存在(非NULL)则返回 TRUE,否则返回 FALSE(包括未定义)。
参数值设置为:null,返回false;
注意,isset对于NULL值变量,特殊处理。

2.empty():

参数           返回
''            true
0             true
false          true
null($p=null)     true
参数未定义($p)    true
其他           false

总结: 如果参数是非空或非零的值,则 empty() 返回 false。
未定义以及没有任何属性的对象都将被认为是空的,如果参数为空,则返回 true。

Highcharts数据可视化图表库

1.简介

   Highcharts是一款纯javascript编写的图表库,能够很简单便捷的在Web网站或Web应用中添加交互性的图表,Highcharts目前支持多达18种不同类型的图表。
在线演示:http://www.hcharts.cn/demo/index.php
Api文档:http://www.hcharts.cn/api/index.php
中文教程:http://www.hcharts.cn/docs/index.php

2.highcharts优势

  • 兼容性
    Highcharts支持目前所有的现代浏览器,包括IE6 +、ios、Android。Highcharts在标准浏览器中使用SVG技术渲染图形,在遗留的IE浏览器中使用VML技术来绘图。
  • 开源免费
    针对个人用户及非商业用途免费,并提供源代码下载,可任意修改。商业用途需要购买许可。
  • 纯Javascript
    Highcharts完全基于本地浏览器技术,不需要任何插件(例如Flash、java),不需要安装任何服务器环境或动态语言库支持,只需要两个js文件即可运行。
  • 图表类型丰富
    Highcharts目前支持直线图、曲线图、面积图、曲线面积图、面积范围图、曲线面积范围图、柱状图、柱状范围图、条形图、饼图、散点图、箱线图、气泡图、误差线图、漏斗图、仪表图、瀑布图、雷达图,共18种类型图表,其中很多图表可以集成在同一个图形中形成综合图。
  • 动态性
    提供丰富的API接口,方便在创建图表后对图表的任意点、线和文字等进行增加、删除和修改操作。
  • 图表导出和打印功能
    可以将Highcharts图表导出为PNG、JPG、PDF和SVG格式文件或直接在网页上打印出来。
  • 图表缩放
    可以设置图表的缩放,让你更方便查看图表数据。
  • 支持外部数据加载
    Highcharts支持多种数据形式,可以是Javascript数组、json文件、json对象和表格数据等。

3.安装使用方法

(1)页面引入jquery文件和highcharts.js文件
<script src="http://cdn.hcharts.cn/jquery/jquery-1.8.3.min.js"></script>
<script src="http://cdn.hcharts.cn/highcharts/highcharts.js"></script>
(2)创建放置图表的元素
<div id="container" style="min-width:800px;height:400px"></div>
(3)调用插件方法
$(function(){
   $('#container').highcharts({
       //配置参数
   });
});

4.基本组成

   通常情况下,Highcharts包含标题(Title)、坐标轴(Axis)、数据列(Series)、数据提示框(Tooltip)、图例(Legend)、版权信息(Credits)等,高级的还包括导出功能按钮(Exporting)、标示线(PlotLines)、标示区域(PlotBands)等。
Highcharts基本组成部分如下图所示:
图片1
Title
图表标题,包含标题和副标题(subTitle),其中副标题是非必须的。
Axis
坐标轴,包含x轴(xAxis)和y轴(yAxis)。通常情况下,x轴显示在图表的底部,y轴显示在图表的左侧。多个数据列可以共同使用同一个坐标轴,为了对比或区分数据,Highcharts提供了多轴的支持。
Series
数据列。图表上一个或多个数据系列,比如曲线图中的一条曲线,柱状图中的一个柱形。
Tooltip
数据提示框。当鼠标悬停在某点上时,以框的形式提示该点的数据,比如该点的值,数据单位等。数据提示框内提示的信息完全可以通过格式化函数动态指定。
Legend
图例。用不同形状、颜色、文字等 标示不同数据列,通过点击标示可以显示或隐藏该数据列。
Credits
图表版权信息。显示在图表右下方的包含链接的文字,默认是Highcharts官网地址。通过指定credits.enabled=false即可不显示该信息。
Exporting
导出功能按钮。通过引入exporting.js即可增加图表导出为常见文件功能。
PlotLines
标示线。可以在图表上增加一条标示线,比如平均值线等。
PlotBands
标示区域。可以在图表添加不同颜色的区域带,标示出明显的范围区域。

5.折线图实现代码

89

6.图表效果展示

(1)折线图
1
(2)柱状图
4
(3)饼图
5
(4)3D柱状图
3

ECharts数据可视化图表库

一、使用体验
ECharts所提供的常规的图表样式简洁大方,默认的颜色配置不错,也可以自定义,丰富的配置项设置可交互,可个性化定制。

官网提供了很详细的入手文档,主要是配置项的熟悉使用。
官网:http://echarts.baidu.com
echarts-pic1

二、引用方式
1、最简单的引用方式是使用 script 标签引入,官网还提供了“在线构建”的选项,可以按需生成简化版js文件。
<script src="echarts.min.js">
echarts-pic4

2、HTML只需要一个具备高宽的dom容器。
<div id="container" style="width:600px;height:400px;"></div>

3、通过 echarts.init 方法初始化一个 echarts 实例并通过 setOption 方法生成图表,option指定图表的配置项和数据即可。
var mychart = echarts.init(document.getElementById('container'));
mychart.setOption(option);
echarts-pic2

三、配置项设置
ECharts提供了“配置项手册”,树状图的形式展现内部属性方法,并搭配示例说明。方面查询、理解和使用,入手起来很快。
配置项设置相对于搭建图表框架,设置完之后可以延续使用,每次只需调取不同数据进行展示。
echarts-pic3

四、细节功能
1、保存为图片,并可以设置保存的像素比;当前图表所用数据列表形式展现;
tool1
tool3
2、图表形式的切换,如折线图和柱状图因为数据形式相同可以切换展示;
tool2
3、多图表之间的切换、联动;
4、数据的动态更新,数据的改变驱动图表展现的改变;
5、数据区域缩放、拖动等交互组件;
6、地理坐标系展示,提供了世界、中国、各省地图的js和json引入方式;
echarts-pic6
7、各类主题样式的选择下载;
echarts-pic5
8、异步数据的加载和更新。
echarts-pic7

异步数据加载示例
http://shopdev.ci123.com/svn/jinzhe/webroot/echarts/echarts_ajax.php

ReactiveCocoa实现验证码倒计时

有时候需要时间验证码倒计时,以前在没有使用rac的时候我是这么写的:

 __block int timeout = [time intValue];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
    if(timeout<=0){
        dispatch_source_cancel(_timer);
        dispatch_async(dispatch_get_main_queue(), ^{
            // 倒计时完成
            [self.codeButton setTitle:@"获取验证码" forState:UIControlStateNormal];
            self.codeButton.enabled = YES;
        });
    }else{
        int seconds = timeout;
        NSString *strTime = [NSString stringWithFormat:@"%.2d", seconds];
        dispatch_async(dispatch_get_main_queue(), ^{
           // 更新button上的倒计时
             [self.codeButton setTitle:[NSString stringWithFormat:@"%@秒后重新获取",strTime] forState:UIControlStateNormal];
           self.codeButton.enabled = NO;
        });
        timeout--;
    }
});
dispatch_resume(_timer);

用GCD这么写比较蛋疼,更好的办法,肯定有的,但是因为项目的原因没有再研究,直到用到了ReactiveCocoa后,我不由得思考,如何更简单的实现这个功能:

  • 将倒计时本身给抽取出来;
  • 将按钮的enabled给独立出来;

    @weakify(self);
    static NSInteger number = 0;
    RACSignal *timerSignal = [[[RACSignal interval:1.0f onScheduler:[RACScheduler mainThreadScheduler]] map:^id(NSDate *date){
        @strongify(self);
        if (--number <= 0) {
            [self.codeButton setTitle:@"获取验证码" forState:UIControlStateNormal];
            return @YES;
        }else{
            [self.codeButton setTitle:[NSString stringWithFormat:@"%d秒后可重新获取", (int)number] forState:UIControlStateNormal];
            return @NO;
        }
    }] takeUntilBlock:^BOOL(id x){
        return number <= 0;
    }];
    
    // 验证码点击
    self.regView.codeButton.rac_command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        number = kCountDownSeconds;
        return timerSignal;
    }];
    

好处一目了然。

iOS博客问答摘录

最近在阅读大神Casa Taloyum博客,发现他不仅文章写得好,还尽心尽力的回复每一个人的评论,每篇文章评价都上百条,一条条看下来,受益匪浅,不仅有初学者的问题,也有开发遇到瓶颈的探讨,作者都一一解答,我就摘抄了一部分,让大家分享。

1、什么时候添加和删除notification?

答:

  • 根据最小权力原则,我们倾向于优先放在展示周期去监听事件。
    ViewController的展示周期是小于ViewController的生命周期的,所以一般如果能在展示周期完成的监听事件的需求,就不会放到生命周期中去做。除非展示周期搞不定的,才会把监听扩大到生命周期。

2、如果一个ViewController 有很多的业务,视图也比较复杂,该怎么拆分呢?我想把业务的处理和页面跳转抽取出来,放到一个category里面,这样viewController可以减少很多代码,但是这个category貌似没有复用的价值。
另外,如果UITableView里面有很多不一样的cell,如何重构代码才能使cell的逻辑简化呢?我尝试用工厂模式去解决,但是发现每个cell需要的model参数都差不多,无法通过model去区分,而通过indexPath去区分的话又不方便重用,只能是一个页面适用。

答:

  • 一般是按照业务角色来拆分业务模块,这需要你对业务有很好的抽象能力。首先,用Category来做对象功能拆分这个思路是没错的,但是对于拆分ViewController来说,拆分更加偏重的是对业务的抽象,然后设立角色,这样才能做到可复用,所以category的思路在这种场景下是不适用的。category只是把大对象变多个小对象而已,它适合拆分那种本身就已经抽象程度比较高、可复用性比较高的底层对象,而不适合用来拆分业务。

  • 独立出DataSource成一个对象,DataSource事实上就可以理解为一个factory,然后DataSource根据Controller给的指示(通过设置DataSource属性也好,通过方法穿参数也行)去生产当前需要的Cell
    继续阅读iOS博客问答摘录

solr使用进阶

快速入门主要讲的是solr管理界面,并且已经利用给好的例子做简单的搜索。
接下要做的是利用数据库是数据来建议搜索。
 
索引mysql的数据 要怎么做?
官方文档也没有详细的说明,主要是修改solrconfig.xml和schema.xml
 
一、先看下路径问题:
为什么要先看路径,是因为有些需要自己去设置。

002oYysygy70MdCwKYa0a&690

bin 常用命令脚本

contrib 各种jar包

dist 各种jar包

server web服务器

solr 未来创建的core会在该目录下

configsets  solr配置集,新建的core可以从这里拷贝配置

二、创建一个搜索实例 

1、{solr安装路径}/server/solr/新建一个文件夹命名为test

2、拷贝{solr安装路径}/server/solr/configsets/sample_techproducts_configs中的conf文件夹到test目录下

3、在后台采用如下配置,然后点击【add core】按钮完成搜索实例的添加

4、solr5.3下自带db、mail、rss、solr、tika实例

目录结构
002oYysygy70MdNZO1593&690
三、配置分词
目前sphinx用的是mmseg分词,而solr支持的分词支持较多。

1、导入smartcn的jar包

在{solr安装路径}/server/solr/test/conf/solrconfig.xml加入如下代码

<lib dir="${solr.install.dir:../../../..}/contrib/analysis-extras/lucene-libs/" regex=".*smartcn.*\.jar"></lib>

2、配置分词器

在{solr安装路径}/server/solr/test/conf/schema.xml加入如下代码

<fieldType name="text_cn" class="solr.TextField" positionIncrementGap="100">
      <analyzer> 
          <tokenizer class="org.apache.lucene.analysis.cn.smart.SmartChineseSentenceTokenizerFactory"/>
          <filter class="solr.LowerCaseFilterFactory"/>     
          <filter class="org.apache.lucene.analysis.cn.smart.SmartChineseWordTokenFilterFactory"/>           
      </analyzer>
    </fieldType>

在{solr安装路径}/server/solr/test/conf/schema.xml加入如下代码

效果如图

002oYysygy70MdPaUWi3e&690

分词效果:这里的好处就是可以直接界面测试。
之前遇到的一个梗,就是123456qq
分词的结果一般都是123456和qq,所以搜123456q是搜不到的
002oYysygy70MdQ9T74ea&690
具体的分词效果,后期会再验证。这里只讲怎么配置。
四、配置导入功能

1、导入相关jar包

①mysql的jar包 

导入下载地址:https://dev.mysql.com/downloads/connector/j/

将jar放置到{solr安装路径}/dist目录下

在{solr安装路径}/server/solr/test/conf/solrconfig.xml加入如下代码

<lib dir="${solr.install.dir:../../../..}/dist/" regex="mysql.*\.jar" />

②dataimporthandler包在{solr安装路径}/server/solr/test/conf/solrconfig.xml加入如下代码

<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-dataimporthandler-.*\.jar" />
2、配置handler

在{solr安装路径}/server/solr/test/conf/solrconfig.xml加入如下代码

<requestHandler name="/dataimport" class="solr.DataImportHandler">
    <lst name="defaults">
      <str name="config">db-data-config.xml</str>
    </lst>
  </requestHandler>

3、配置数据源

在{solr安装路径}/server/solr/test/conf/下新建db-data-config.xml,配置如下:

002oYysygy70MdSCNgg44&690

002oYysygy70MdCwKYa0a&690

002oYysygy70MdSCNgg44

zzz

五、效率问题


第一次(本地)

002oYysygy70MdY8RkRc1&690

第二次(10w)

002oYysygy70MdZ0ivo12&690

第三次(100w)

002oYysygy70MdZX6nb0d&690

根据sphinx的记录
sphinx效率是5-8w docs/sec

solr是基于java单纯执行速度上比C写的sphinx慢
 
六、其他问题
1、导入不全的问题
选取的字段不能为空 如create_date为空,导入终止
2、solr的优势
sphinx的rotate选项可以动态更新索引
3、sphinx比solr建立索引的效率更快