当前位置: 首页 > news >正文

网站建设及运行情况介绍/搜狗站长推送工具

网站建设及运行情况介绍,搜狗站长推送工具,个人网站 建设,大陆人去香港做义工网站原创作者: abruzzi 阅读:388次 评论:2条 更新时间:2010-05-13 第九章 函数式的Javascript 要说JavaScript和其他较为常用的语言最大的不同是什么,那无疑就是JavaScript是函数式的语言,函数式语言的特点如下: 函数为第一等的元素&a…

原创作者: abruzzi   阅读:388次   评论:2条   更新时间:2010-05-13    

 

第九章 函数式的Javascript

要说JavaScript和其他较为常用的语言最大的不同是什么,那无疑就是JavaScript是函数式的语言,函数式语言的特点如下:

函数为第一等的元素,即人们常说的一等公民。就是说,在函数式编程中,函数是不依赖于其他对象而独立存在的(对比与Java,函数必须依赖对象,方法是对象的方法)。

函数可以保持自己内部的数据,函数的运算对外部无副作用(修改了外部的全局变量的状态等),关于函数可以保持自己内部的数据这一特性,称之为闭包。我们可以来看一个简单的例子:

 

 

 

Js代码

var outter = function(){       var x = 0;       return function(){          return x++;       }   }       var a = outter();   print(a());   print(a());       var b = outter();   print(b());   print(b());  
var outter = function(){    var x = 0;    return function(){       return x++;    }} var a = outter();print(a());print(a()); var b = outter();print(b());print(b());

 

 

运行结果为:

 

0
1
0
1

 

变量a通过闭包引用outter的一个内部变量,每次调用a()就会改变此内部变量,应该注意的是,当调用a时,函数outter已经返回了,但是内部变量x的值仍然被保持。而变量b也引用了outter,但是是一个不同的闭包,所以b开始引用的x值不会随着a()被调用而改变,两者有不同的实例,这就相当于面向对象中的不同实例拥有不同的私有属性,互不干涉。

由于JavaScript支持函数式编程,我们随后会发现JavaScript许多优美而强大的能力,这些能力得力于以下主题:匿名函数,高阶函数,闭包及柯里化等。熟悉命令式语言的开发人员可能对此感到陌生,但是使用lisp, scheme等函数式语言的开发人员则觉得非常亲切。

9.1匿名函数

匿名函数在函数式编程语言中,术语成为lambda表达式。顾名思义,匿名函数就是没有名字的函数,这个是与日常开发中使用的语言有很大不同的,比如在C/Java中,函数和方法必须有名字才可以被调用。在JavaScript中,函数可以没有名字,而且这一个特点有着非凡的意义:

 

 

 

Js代码

function func(){       //do something   }       var func = function(){       //do something   }  
function func(){    //do something} var func = function(){    //do something}

 

 

这两个语句的意义是一样的,它们都表示,为全局对象添加一个属性func,属性func的值为一个函数对象,而这个函数对象是匿名的。匿名函数的用途非常广泛,在JavaScript代码中,我们经常可以看到这样的代码:

 

 

 

Js代码

var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});   print(mapped);  
var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});print(mapped);

 

 

应该注意的是,map这个函数的参数是一个匿名函数,你不需要显式的声明一个函数,然后将其作为参数传入,你只需要临时声明一个匿名的函数,这个函数被使用之后就别释放了。在高阶函数这一节中更可以看到这一点。

9.2高阶函数

通常,以一个或多个函数为参数的函数称之为高阶函数。高阶函数在命令式编程语言中有对应的实现,比如C语言中的函数指针,Java中的匿名类等,但是这些实现相对于命令式编程语言的其他概念,显得更为复杂。

9.2.1 JavaScript中的高阶函数

         Lisp中,对列表有一个map操作,map接受一个函数作为参数,map对列表中的所有元素应用该函数,最后返回处理后的列表(有的实现则会修改原列表),我们在这一小节中分别用JavaScript/C/Java来对map操作进行实现,并对这些实现方式进行对比:

 

 

Js代码

  1. Array.prototype.map = function(func /*, obj */){  
  2.     var len = this.length;  
  3.     //check the argument  
  4.     if(typeof func != "function"){  
  5.        throw new Error("argument should be a function!");  
  6.     }  
  7.      
  8.     var res = [];  
  9.     var obj = arguments[1];  
  10.     for(var i = 0; i < len; i++){  
  11.        //func.call(), apply the func to this[i]  
  12.        res[i] = func.call(obj, this[i], i, this);  
  13.     }  
  14.      
  15.     return res;  
  16. }  
Array.prototype.map = function(func /*, obj */){    var len = this.length;    //check the argument    if(typeof func != "function"){       throw new Error("argument should be a function!");    }       var res = [];    var obj = arguments[1];    for(var i = 0; i < len; i++){       //func.call(), apply the func to this[i]       res[i] = func.call(obj, this[i], i, this);    }       return res;}

 

 

我们对JavaScript的原生对象Array的原型进行扩展,函数map接受一个函数作为参数,然后对数组的每一个元素都应用该函数,最后返回一个新的数组,而不影响原数组。由于map函数接受的是一个函数作为参数,因此map是一个高阶函数。我们进行测试如下:

 

 

 

Js代码

function double(x){       return x * 2;   }       [1, 2, 3, 4, 5].map(double);//return [2, 4, 6, 8, 10]  
function double(x){    return x * 2;} [1, 2, 3, 4, 5].map(double);//return [2, 4, 6, 8, 10]

 

 

应该注意的是double是一个函数。根据上一节中提到的匿名函数,我们可以为map传递一个匿名函数:

 

 

 

Js代码

var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});   print(mapped);  
var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});print(mapped);

 

 

这个示例的代码与上例的作用是一样的,不过我们不需要显式的定义一个double函数,只需要为map函数传递一个“可以将传入参数乘2并返回”的代码块即可。再来看一个例子:

 

Js代码

  1. [  
  2.     {id : "item1"},  
  3.     {id : "item2"},  
  4.     {id : "item3"}  
  5. ].map(function(current){  
  6.     print(current.id);  
  7. });  
[    {id : "item1"},    {id : "item2"},    {id : "item3"}].map(function(current){    print(current.id);});

将会打印:

 

 

item1
item2
item3

 

 

也就是说,这个map的作用是将传入的参数(处理器)应用在数组中的每个元素上,而不关注数组元素的数据类型,数组的长度,以及处理函数的具体内容。

9.2.2 C语言中的高阶函数

C语言中的函数指针,很容易实现一个高阶函数。我们还以map为例,说明在C语言中如何实现:

 

 

Js代码

  1. //prototype of function  
  2. void map(int* array, int length, int (*func)(int));  
//prototype of functionvoid map(int* array, int length, int (*func)(int));

 

 

map函数的第三个参数为一个函数指针,接受一个整型的参数,返回一个整型参数,我们来看看其实现:

 

 

Js代码

  1. //implement of function map  
  2. void map(int* array, int length, int (*func)(int)){  
  3.     int i = 0;  
  4.     for(i = 0; i < length; i++){  
  5.        array[i] = func(array[i]);  
  6.     }  
  7. }  
//implement of function mapvoid map(int* array, int length, int (*func)(int)){    int i = 0;    for(i = 0; i < length; i++){       array[i] = func(array[i]);    }}

 

 

我们在这里实现两个小函数,分别计算传入参数的乘2的值,和乘3的值,然后进行测试:

 

 

 

Js代码

int twice(int num) { return num * 2; }   int triple(int num){ return num * 3; }       //function main   int main(int argc, char** argv){       int array[5] = {1, 2, 3, 4, 5};       int i = 0;       int len = 5;           //print the orignal array       printArray(array, len);           //mapped by twice       map(array, len, twice);       printArray(array, len);           //mapped by twice, then triple       map(array, len, triple);       printArray(array, len);           return 0;   }  
int twice(int num) { return num * 2; }int triple(int num){ return num * 3; } //function mainint main(int argc, char** argv){    int array[5] = {1, 2, 3, 4, 5};    int i = 0;    int len = 5;     //print the orignal array    printArray(array, len);     //mapped by twice    map(array, len, twice);    printArray(array, len);     //mapped by twice, then triple    map(array, len, triple);    printArray(array, len);     return 0;}

 

 

运行结果如下:

 

 

1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

 

 

应该注意的是map的使用方法,如map(array, len, twice)中,最后的参数为twice,而twice为一个函数。因为C语言中,函数的定义不能嵌套,因此不能采用诸如JavaScript中的匿名函数那样的简洁写法。

         虽然在C语言中可以通过函数指针的方式来实现高阶函数,但是随着高阶函数的“阶”的增高,指针层次势必要跟着变得很复杂,那样会增加代码的复杂度,而且由于C语言是强类型的,因此在数据类型方面必然有很大的限制。

9.2.3 Java中的高阶函数

Java中的匿名类,事实上可以理解成一个教笨重的闭包(可执行单元),我们可以通过Java的匿名类来实现上述的map操作,首先,我们需要一个对函数的抽象:

 

 

Java代码

  1. interface Function{  
  2.    int execute(int x);  
  3. }  
    interface Function{       int execute(int x);    }

 

我们假设Function接口中有一个方法execute,接受一个整型参数,返回一个整型参数,然后我们在类List中,实现map操作:

 

 

Java代码

  1. private int[] array;  
  2.   
  3. public List(int[] array){  
  4.    this.array = array;  
  5. }  
  6.   
  7. public void map(Function func){  
  8.    for(int i = 0, len = this.array.length; i < len; i++){  
  9.        this.array[i] = func.execute(this.array[i]);   
  10.    }  
  11. }  
    private int[] array;       public List(int[] array){       this.array = array;    }       public void map(Function func){       for(int i = 0, len = this.array.length; i < len; i++){           this.array[i] = func.execute(this.array[i]);        }    }

 

map接受一个实现了Function接口的类的实例,并调用这个对象上的execute方法来处理数组中的每一个元素。我们这里直接修改了私有成员array,而并没有创建一个新的数组。好了,我们来做个测试:

 

 

Java代码

  1. public static void main(String[] args){  
  2.    List list = new List(new int[]{1, 2, 3, 4, 5});  
  3.    list.print();  
  4.    list.map(new Function(){  
  5.        public int execute(int x){  
  6.           return x * 2;  
  7.        }  
  8.    });  
  9.    list.print();  
  10.     
  11.    list.map(new Function(){  
  12.        public int execute(int x){  
  13.           return x * 3;  
  14.        }  
  15.    });  
  16.    list.print();  
  17. }  
    public static void main(String[] args){       List list = new List(new int[]{1, 2, 3, 4, 5});       list.print();       list.map(new Function(){           public int execute(int x){              return x * 2;           }       });       list.print();             list.map(new Function(){           public int execute(int x){              return x * 3;           }       });       list.print();    }

 

 

同前边的两个例子一样,这个程序会打印:

 

 

1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

 

 

灰色背景色的部分即为创建一个匿名类,从而实现高阶函数。很明显,我们需要传递给map的是一个可以执行execute方法的代码。而由于Java是命令式的编程语言,函数并非第一位的,函数必须依赖于对象,附属于对象,因此我们不得不创建一个匿名类来包装这个execute方法。而在JavaScript中,我们只需要传递函数本身即可,这样完全合法,而且代码更容易被人理解。

 

 

9.3闭包与柯里化

闭包和柯里化都是JavaScript经常用到而且比较高级的技巧,所有的函数式编程语言都支持这两个概念,因此,我们想要充分发挥出JavaScript中的函数式编程特征,就需要深入的了解这两个概念,我们在第七章中详细的讨论了闭包及其特征,闭包事实上更是柯里化所不可缺少的基础。

9.3.1柯里化的概念

闭包的我们之前已经接触到,先说说柯里化。柯里化就是预先将函数的某些参数传入,得到一个简单的函数,但是预先传入的参数被保存在闭包中,因此会有一些奇特的特性。比如:

 

 

 

Js代码

var adder = function(num){       return function(y){          return num + y;         }   }       var inc = adder(1);   var dec = adder(-1);  
var adder = function(num){    return function(y){       return num + y;      }} var inc = adder(1);var dec = adder(-1);

 

 

这里的inc/dec两个变量事实上是两个新的函数,可以通过括号来调用,比如下例中的用法:

 

 

Js代码

  1. //inc, dec现在是两个新的函数,作用是将传入的参数值(+/-)1  
  2. print(inc(99));//100  
  3. print(dec(101));//100  
  4.    
  5. print(adder(100)(2));//102  
  6. print(adder(2)(100));//102  
//inc, dec现在是两个新的函数,作用是将传入的参数值(+/-)1print(inc(99));//100print(dec(101));//100 print(adder(100)(2));//102print(adder(2)(100));//102

 

9.3.2柯里化的应用

根据柯里化的特性,我们可以写出更有意思的代码,比如在前端开发中经常会遇到这样的情况,当请求从服务端返回后,我们需要更新一些特定的页面元素,也就是局部刷新的概念。使用局部刷新非常简单,但是代码很容易写成一团乱麻。而如果使用柯里化,则可以很大程度上美化我们的代码,使之更容易维护。我们来看一个例子:

 

 

Js代码

  1. //update会返回一个函数,这个函数可以设置id属性为item的web元素的内容  
  2. function update(item){  
  3.     return function(text){  
  4.        $("div#"+item).html(text);  
  5.     }  
  6. }  
  7.    
  8. //Ajax请求,当成功是调用参数callback  
  9. function refresh(url, callback){  
  10.     var params = {  
  11.        type : "echo",  
  12.        data : ""  
  13.     };  
  14.    
  15.     $.ajax({  
  16.        type:"post",  
  17.        url:url,  
  18.        cache:false,  
  19.        async:true,  
  20.        dataType:"json",  
  21.        data:params,  
  22.         
  23.        //当异步请求成功时调用  
  24.        success: function(data, status){  
  25.            callback(data);  
  26.        },  
  27.         
  28.        //当请求出现错误时调用  
  29.        error: function(err){  
  30.            alert("error : "+err);  
  31.        }  
  32.     });  
  33. }  
  34.    
  35. refresh("action.do?target=news", update("newsPanel"));  
  36. refresh("action.do?target=articles", update("articlePanel"));  
  37. refresh("action.do?target=pictures", update("picturePanel"));  
//update会返回一个函数,这个函数可以设置id属性为item的web元素的内容function update(item){    return function(text){       $("div#"+item).html(text);    }} //Ajax请求,当成功是调用参数callbackfunction refresh(url, callback){    var params = {       type : "echo",       data : ""    };     $.ajax({       type:"post",       url:url,       cache:false,       async:true,       dataType:"json",       data:params,             //当异步请求成功时调用       success: function(data, status){           callback(data);       },             //当请求出现错误时调用       error: function(err){           alert("error : "+err);       }    });} refresh("action.do?target=news", update("newsPanel"));refresh("action.do?target=articles", update("articlePanel"));refresh("action.do?target=pictures", update("picturePanel"));

 

 

其中,update函数即为柯里化的一个实例,它会返回一个函数,即:

 

Js代码

  1. update("newsPanel") = function(text){  
  2.     $("div#newsPanel").html(text);  
  3. }  
update("newsPanel") = function(text){    $("div#newsPanel").html(text);}

由于update(“newsPanel”)的返回值为一个函数,需要的参数为一个字符串,因此在refresh的Ajax调用中,当success时,会给callback传入服务器端返回的数据信息,从而实现newsPanel面板的刷新,其他的文章面板articlePanel,图片面板picturePanel的刷新均采取这种方式,这样,代码的可读性,可维护性均得到了提高。

9.4一些例子

9.4.1函数式编程风格

通常来讲,函数式编程的谓词(关系运算符,如大于,小于,等于的判断等),以及运算(如加减乘数等)都会以函数的形式出现,比如:

 

 

Java代码

  1. a > b  
a > b

 

通常表示为:

 

 

Java代码

  1. gt(a, b)//great than  
gt(a, b)//great than

 

因此,可以首先对这些常见的操作进行一些包装,以便于我们的代码更具有“函数式”风格:

 

 

 

Js代码

function abs(x){ return x>0?x:-x;}   function add(a, b){ return a+b; }   function sub(a, b){ return a-b; }   function mul(a, b){ return a*b; }   function div(a, b){ return a/b; }   function rem(a, b){ return a%b; }   function inc(x){ return x + 1; }   function dec(x){ return x - 1; }   function equal(a, b){ return a==b; }   function great(a, b){ return a>b; }   function less(a, b){ return a<b; }   function negative(x){ return x<0; }   function positive(x){ return x>0; }   function sin(x){ return Math.sin(x); }   function cos(x){ return Math.cos(x); }  
function abs(x){ return x>0?x:-x;}function add(a, b){ return a+b; }function sub(a, b){ return a-b; }function mul(a, b){ return a*b; }function div(a, b){ return a/b; }function rem(a, b){ return a%b; }function inc(x){ return x + 1; }function dec(x){ return x - 1; }function equal(a, b){ return a==b; }function great(a, b){ return a>b; }function less(a, b){ return a<b; }function negative(x){ return x<0; }function positive(x){ return x>0; }function sin(x){ return Math.sin(x); }function cos(x){ return Math.cos(x); }

 

 

如果我们之前的编码风格是这样:

 

Js代码

  1. // n*(n-1)*(n-2)*...*3*2*1  
  2. function factorial(n){  
  3.     if(n == 1){  
  4.         return 1;  
  5.     }else{  
  6.         return n * factorial(n - 1);  
  7.     }  
  8. }  
// n*(n-1)*(n-2)*...*3*2*1function factorial(n){    if(n == 1){        return 1;    }else{        return n * factorial(n - 1);    }}

 

 

在函数式风格下,就应该是这样了:

 

 

 

Js代码

function factorial(n){       if(equal(n, 1)){           return 1;       }else{           return mul(n, factorial(dec(n)));       }   }  
function factorial(n){    if(equal(n, 1)){        return 1;    }else{        return mul(n, factorial(dec(n)));    }}

 

 

函数式编程的特点当然不在于编码风格的转变,而是由更深层次的意义。比如,下面是另外一个版本的阶乘实现:

 

 

Js代码

  1. /* 
  2.  *  product <- counter * product 
  3.  *  counter <- counter + 1 
  4.  * */  
  5.    
  6. function factorial(n){  
  7.     function fact_iter(product, counter, max){  
  8.         if(great(counter, max)){  
  9.             return product;  
  10.         }else{  
  11.             fact_iter(mul(counter, product), inc(counter), max);  
  12.         }  
  13.     }  
  14.    
  15.     return fact_iter(1, 1, n);  
  16. }  
/* *  product <- counter * product *  counter <- counter + 1 * */ function factorial(n){    function fact_iter(product, counter, max){        if(great(counter, max)){            return product;        }else{            fact_iter(mul(counter, product), inc(counter), max);        }    }     return fact_iter(1, 1, n);}

 

 

虽然代码中已经没有诸如+/-/*//之类的操作符,也没有>,<,==,之类的谓词,但是,这个函数仍然算不上具有函数式编程风格,我们可以改进一下:

 

 

Js代码

function factorial(n){       return (function factiter(product, counter, max){          if(great(counter, max)){              return product;          }else{              return factiter(mul(counter, product), inc(counter), max);          }       })(1, 1, n);   }       factorial(10);  
function factorial(n){    return (function factiter(product, counter, max){       if(great(counter, max)){           return product;       }else{           return factiter(mul(counter, product), inc(counter), max);       }    })(1, 1, n);} factorial(10);

 

 

通过一个立即运行的函数factiter,将外部的n传递进去,并立即参与计算,最终返回运算结果。

9.4.2 Y-结合子

提到递归,函数式语言中还有一个很有意思的主题,即:如果一个函数是匿名函数,能不能进行递归操作呢?如何可以,怎么做?我们还是来看阶乘的例子:

 

 

 

Js代码

function factorial(x){       return x == 0 ? 1 : x * factorial(x-1);     }  
function factorial(x){    return x == 0 ? 1 : x * factorial(x-1);  }

 

 

factorial函数中,如果x值为0,则返回1,否则递归调用factorial,参数为x减1,最后当x等于0时进行规约,最终得到函数值(事实上,命令式程序语言中的递归的概念最早即来源于函数式编程中)。现在考虑:将factorial定义为一个匿名函数,那么在函数内部,在代码x*factorial(x-1)的地方,这个factorial用什么来替代呢?

lambda演算的先驱们,天才的发明了一个神奇的函数,成为Y-结合子。使用Y-结合子,可以做到对匿名函数使用递归。关于Y-结合子的发现及推导过程的讨论已经超出了本部分的范围,有兴趣的读者可以参考附录中的资料。我们来看看这个神奇的Y-结合子:

 

 

 

Js代码

var Y = function(f) {     return (function(g) {       return g(g);     })(function(h) {       return function() {         return f(h(h)).apply(null, arguments);       };     });   };  
var Y = function(f) {  return (function(g) {    return g(g);  })(function(h) {    return function() {      return f(h(h)).apply(null, arguments);    };  });};

 

 

我们来看看如何运用Y-结合子,依旧是阶乘这个例子:

 

 

 

Js代码

var factorial = Y(function(func){       return function(x){          return x == 0 ? 1 : x * func(x-1);       }   });       factorial(10);  
var factorial = Y(function(func){    return function(x){       return x == 0 ? 1 : x * func(x-1);    }}); factorial(10);

 

 

或者:

 

 

Js代码

  1. Y(function(func){  
  2.     return function(x){  
  3.        return x == 0 ? 1 : x * func(x-1);  
  4.     }  
  5. })(10);  
Y(function(func){    return function(x){       return x == 0 ? 1 : x * func(x-1);    }})(10);

 

 

不要被上边提到的Y-结合子的表达式吓到,事实上,在JavaScript中,我们有一种简单的方法来实现Y-结合子:

 

 

Js代码

  1. var fact = function(x){  
  2.    return x == 0 : 1 : x * arguments.callee(x-1);  
  3. }  
  4.   
  5. fact(10);  
    var fact = function(x){       return x == 0 : 1 : x * arguments.callee(x-1);    }       fact(10);

 

或者:

 

 

Js代码

  1. (function(x){  
  2.    return x == 0 ? 1 : x * arguments.callee(x-1);  
  3. })(10);//3628800  
    (function(x){       return x == 0 ? 1 : x * arguments.callee(x-1);    })(10);//3628800

 

其中,arguments.callee表示函数的调用者,因此省去了很多复杂的步骤。

9.4.3其他实例

下面的代码则颇有些“开发智力”之功效:

 

 

Js代码

  1. //函数的不动点  
  2. function fixedPoint(fx, first){  
  3.     var tolerance = 0.00001;  
  4.     function closeEnough(x, y){return less( abs( sub(x, y) ), tolerance)};  
  5.     function Try(guess){//try 是javascript中的关键字,因此这个函数名为大写  
  6.         var next = fx(guess);  
  7.         //print(next+" "+guess);  
  8.         if(closeEnough(guess, next)){  
  9.             return next;  
  10.         }else{  
  11.             return Try(next);  
  12.         }  
  13.     };  
  14.     return Try(first);  
  15. }  
//函数的不动点function fixedPoint(fx, first){    var tolerance = 0.00001;    function closeEnough(x, y){return less( abs( sub(x, y) ), tolerance)};    function Try(guess){//try 是javascript中的关键字,因此这个函数名为大写        var next = fx(guess);        //print(next+" "+guess);        if(closeEnough(guess, next)){            return next;        }else{            return Try(next);        }    };    return Try(first);}
 

 

 

 

Js代码

  1. // 数层嵌套函数,  
  2. function sqrt(x){  
  3.     return fixedPoint(  
  4.         function(y){  
  5.             return function(a, b){ return div(add(a, b),2);}(y, div(x, y));  
  6.         },  
  7.         1.0);  
  8. }  
  9.    
  10. print(sqrt(100));  
// 数层嵌套函数,function sqrt(x){    return fixedPoint(        function(y){            return function(a, b){ return div(add(a, b),2);}(y, div(x, y));        },        1.0);} print(sqrt(100));

 

 

fiexedPoint求函数的不动点,而sqrt计算数值的平方根。这些例子来源于《计算机程序的构造和解释》,其中列举了大量的计算实例,不过该书使用的是scheme语言,在本书中,例子均被翻译为JavaScript。

转载于:https://www.cnblogs.com/TDYToBaby/archive/2010/06/12/1757322.html

http://www.lbrq.cn/news/970561.html

相关文章:

  • 嘉兴网站推广公司/百度推广登录手机版
  • 徐州市铜山新区建设局网站/垂直搜索引擎
  • 途牛的旅游网站是谁做的/企业策划咨询公司
  • 楚雄做网站建设的公司/网站优化排名易下拉效率
  • 怎么做一网站/网站搭建公司
  • 网站维护大概要多久/卖链接的网站
  • 建筑公司网站设计模板/网络营销的认识
  • 网站网络架构/全国新冠疫情最新消息
  • wordpress添加新php页面/seo教程 百度网盘
  • 外贸网站营销建站/b站新人视频怎么推广
  • 外链数是网站反向链接码/个人博客
  • 河南住房和城乡建设厅一体化平台网站/必应bing搜索引擎
  • 建设法律法规文本查询网站/郑州seo招聘
  • 经济与政府网站建设/深圳百度地图
  • 今日新闻头条内容/网站怎么做优化排名
  • 品辰设计的网站谁做的/教育机构加盟
  • 响应式网站 外贸/小吃培训机构排名前十
  • 分类网站有哪些/长沙好的seo外包公司
  • 网站建设实训日志/网络营销个人总结
  • 就业网站哪个靠谱/线上网络推广怎么做
  • 四川专门做招聘酒的网站/网络营销专业技能
  • 做网站产品图片素材/关键词优化最好的方法
  • 网站出现转站怎么办/seo 怎么做到百度首页
  • 阿里巴巴网站域名建设/nba西部排名
  • 网站产品要如何做详情/广州百度seo排名
  • 常德网站公司/百度一下你就知道官网
  • 一个人只做网站的流程/最近七天的新闻重点
  • 扬中市新闻网站/seo软件优化工具软件
  • 设计网站页面的作文/seo站内优化包括
  • 龙岩app定制/seo托管公司
  • 分布式系统高可用性设计 - 监控与日志系统
  • 鸿蒙系统账号与签名内容整理
  • YAML 自动化用例中 GET vs POST 请求的参数写法差异
  • 鸿蒙网络编程系列58-仓颉版TLS数字证书查看及验签示例
  • 2025年中国品牌全球化发展分析:中国品牌在社交渠道、电商平台及官网流量方面显著增长
  • 【亲测有效】ubuntu20.04服务器新建用户+vnc配置教程