Home > Web Front-end > JS Tutorial > javascript functions_javascript skills

javascript functions_javascript skills

WBOY
Release: 2016-05-16 19:26:26
Original
1061 people have browsed it

javascript的函数

子乌注:一篇相当不错的function入门文章,个人感觉相当经典。

词语翻译列表
function:函数(Function未翻译)
declare:定义
assign:指派,分配
functionbody:函数体(就是函数的内容)
object:对象
property:属性
unnamed:匿名(在这里没翻译成未命名)
object oriented programming:面相对相编程
class:类(比如后面的class data type我翻译成类数据类型)
pointer:指针
reassign:重新分配
nest:嵌套
feature:功能,特性
local/global:局部/全局
blueprint:蓝图(?)
user defined:用户自定义
instance:实例
prototype:原型(除了标题都不翻译)
internal:内部
constructor:构造器
duplication:复制

函数:定义

有以下这些方法可以定义一个函数。所有这些都是有效的,但是它们在后台如何实现的则有一些差别。

常用的写法

一般大家都用这个写法来定义一个函数:

<span title="代码">functionName([parameters]){functionBody};</span>

Example D1:

Language:javascript, parsed in: 0.005 seconds, using GeSHi 1.0.7.12
  1. function add(a, b)
  2. {                     
  3.   return a+b;
  4. }                     
  5. alert(add(1,2));        // 结果 3
  6.  

运行示例

当我们这么定义函数的时候,函数内容会被编译(但不会立即执行,除非我们去调用它)。而且,也许你不知道,当这个函数创建的时候有一个同名的对象也被创建。就我们的例子来说,我们现在有一个对象叫做“add”(要更深入了解,看底下函数:对象节。)

匿名函数

我们也可以通过指派一个变量名给匿名函数的方式来定义它。

Example D2

Language:javascript, parsed in: 0.006 seconds, using GeSHi 1.0.7.12
  1. var add=function(a, b)
  2. {                     
  3.   return a+b;
  4. }                     
  5. alert(add(1,2));        // 结果 3
  6.  

运行示例

这个代码和前一个例子做了同样的事情。也许语法看起来比较奇怪,但它应该更能让你感觉到函数是一个对象,而且我们只是为这个对指派了一个名称。可以把它看做和<span title="代码">var myVar=[1,2,3]</span>一样的语句。以这种方式声明的函数内容也一样会被编译。

当我们指派一个这样的函数的时候,我们并不一定要求必须是匿名函数。在这里,我作了和ExampleD2一样的事情,但我加了函数名“theAdd”,而且我可以通过调用函数名或者是那个变量来引用函数。

Example D2A

Language:javascript, parsed in: 0.008 seconds, using GeSHi 1.0.7.12
  1. var add=function theAdd(a, b)
  2. {                     
  3.   return a+b;
  4. }                     
  5. alert(add(1,2));           // 结果 3
  6. alert(theAdd(1,2));        // 结果也是 3
  7.  

运行示例

使用这种方式来定义函数在面向对象编程中是很有用的,因为我们能像底下这样使一个函数成为一个对象属性

Language:javascript, parsed in: 0.005 seconds, using GeSHi 1.0.7.12
  1. var myObject=new Object();
  2. myObject.add=function(a,b){return a+b}
  3. // myObject 现在有一个叫做“add”的属性(或方法)
  4. // 而且我能够象下面这样使用它
  5. myObject.add(1, 2);
  6.  

new

我们也能够通过使用运算符new来定义一个函数。

这是一个最少见的定义函数的方式并且并不推荐使用这种方式除非有特殊的理由(可能的理由见下)。语法如下:

<span title="代码">varName=new Function([param1Name, param2Name,...paramNName], functionBody);</span>

Example D3:

Language:javascript, parsed in: 0.004 seconds, using GeSHi 1.0.7.12
  1. var add=new Function("a", "b", "return a+b;");
  2. alert(add(3,4));        // 结果 7
  3.  

运行示例

我在这里有两个参数叫做ab,而函数体返回ab的和。请注意<span title="代码">new Function(...)</span>使用了大写F,而不是小写f。 这就告诉javascript,我们将要创建一个类型是Function的对象。 还要注意到,参数名和函数体都是作为字符串而被传递。我们可以随心所欲的增加参数,javascript知道函数体会是右括号前的最后一个字符串(如果没有参数,你能够只写函数体)。你没必要将所有东西都写在一行里(使用<span title="代码">\</span>或者使用字符串连接符<span title="代码">+</span>来分隔长代码)。<span title="代码">\</span>标记告诉JavaScript在下一行查找字符串的其余部分。例子如下:

Example D4

Language:javascript, parsed in: 0.007 seconds, using GeSHi 1.0.7.12
  1. // 注意 "+"
  2. // 和 "\"的不同用法
  3. var add=new Function("a", "b",
  4.   "alert" +                     
  5.   "('adding '+a+' and ' +b);\
  6.    return a+b;");
  7. alert(add(3,4));        // 结果 7
  8.  

javascript:exampleD4();

采用这种方式定义函数会导致函数并没被编译,而且它有可能会比用其它方式定义的函数要慢。至于为什么,看一下这个代码:

Example D5

Language:javascript, parsed in: 0.022 seconds, using GeSHi 1.0.7.12
  1. function createMyFunction(myOperator)
  2. {
  3.   return new Function("a", "b", "return a" + myOperator + "b;");
  4. }
  5.  
  6. var add=createMyFunction("+");                // 创建函数 "add"
  7. var subtract=createMyFunction("-");           // 创建函数 "subtract"
  8. var multiply=createMyFunction("*");           // 创建函数 "multiply"
  9. // test the functions
  10. alert("加的结果="+add(10,2));                  // 结果是 12
  11. alert("减的结果="+subtract(10,2));             // 结果是 8
  12. alert("乘的结果="+multiply(10,2));             // 结果是 20
  13. alert(add);
  14.  

运行示例

这个有趣的例子创建了三个不同的function,通过实时传递不同的参数来创建一个新Function。因为编译器没法知道最终代码会是什么样子的,所以<span title="代码">new Function(...)</span>的内容不会被编译。那这有什么好处呢?嗯,举个例子,如果你需要用户能够创建他们自己的函数的时候这个功能也许很有用,比如在游戏里。我们也许需要允许用户添加“行为”给一个“player”。但是,再说一次,一般情况下,我们应该避免使用这种形式,除非有一个特殊的目的。

函数:对象

函数是javascript中的一种特殊形式的对象。它是第一个类数据类型。这意味着我们能够给它增加属性。这里有一些需要注意的有趣观点:

对象的创建

就像刚才提及的,当我们定义一个函数时,javascript实际上在后台为你创建了一个对象。这个对象的名称就是函数名本身。这个对象的类型是function。在下面的例子,我们也许不会意识到这一点,但我们实际上已经创建了一个对象:它叫做Ball

Example 1

Language:javascript, parsed in: 0.006 seconds, using GeSHi 1.0.7.12
  1. function Ball()       // 也许看起来有点奇怪,但是这个声明
  2. {                     // 创建了一个叫做Ball的对象
  3.   i=1;
  4. }                     
  5. alert(typeof Ball);     // 结果 "function"
  6.  

运行示例

我们甚至能将这个对象的内容打印出来而且它会输出这个函数的实际代码,Example 2: 点击 alert(Ball);来看看Ball的内容。

属性的添加

我们能够添加给Object添加属性,包括对象function。因为定义一个函数的实质是创建一个对象。我们能够“暗地里”给函数添加属性。比如,我们这里定义了函数Ball,并添加属性callsign

Language:javascript, parsed in: 0.007 seconds, using GeSHi 1.0.7.12
  1. function Ball()       // 也许看起来有点奇怪,但是这个声明
  2. {                     // 创建了一个叫做Ball的对象,而且你能够
  3. }                     // 引用它或者象下面那样给它增加属性
  4. Ball.callsign="The Ball"; // 给Ball增加属性
  5. alert(Ball.callsign); // 输出 "The Ball"
  6.  

运行示例

指针

因为function是一个对象,我们能够为一个function分配一个指针。如下例,变量ptr指向了对象myFunction

Language:javascript, parsed in: 0.005 seconds, using GeSHi 1.0.7.12
  1. function myFunction(message)
  2. {
  3.   alert(message);
  4. }
  5. var ptr=myFunction;  // ptr指向了myFunction
  6. ptr("hello");        // 这句会执行myFunction:输出"hello"
  7.  

运行示例

我们能够运行这个函数,就好像这个函数名已经被指针名代替了一样。所以在上面,这行<span title="代码">ptr("hello"); </span><span title="代码">myFunction("hello");</span>的意义是一样的。

指向函数的指针在面向对象编程中相当有用。例如:当我们有多个对象指向同一个函数的时候(如下):

Example 4A

Language:javascript, parsed in: 0.017 seconds, using GeSHi 1.0.7.12
  1. function sayName(name)
  2. {
  3.   alert(name);
  4. }
  5. var object1=new Object();      // 创建三个对象
  6. var object2=new Object();
  7. var object3=new Object();
  8. object1.sayMyName=sayName;       // 将这个函数指派给所有对象
  9. object2.sayMyName=sayName;
  10. object3.sayMyName=sayName;
  11. object1.sayMyName("object1");    // 输出 "object1"
  12. object2.sayMyName("object2");    // 输出 "object2"
  13. object3.sayMyName("object3");    // 输出 "object3"
  14.  

运行示例

上例图示

因为只有指针被保存(而不是函数本身),当我们改变函数对象自身的时候,所有指向那个函数的指针都会发生变化。我们能够在底下看到:

Example 5:

Language:javascript, parsed in: 0.016 seconds, using GeSHi 1.0.7.12
  1. function myFunction()
  2. {
  3.   alert(myFunction.message);
  4. }
  5. myFunction.message="old";
  6. var ptr1=myFunction;                 // ptr1 指向 myFunction
  7. var ptr2=myFunction;                 // ptr2 也指向 myFunction
  8.  
  9. ptr1();         // 输出 "old"
  10. ptr2();                              // 输出 "old"
  11.  
  12. myFunction.message="new";
  13.  
  14. ptr1();         // 输出 "new"
  15. ptr2();                              // 输出 "new"
  16.  

运行示例

指针的指向

我们能够在一个函数创建之后重新分配它,但是我们需要指向函数对象本身,而不是指向它的指针。在下例中,我将改变myfunction()的内容。

Example 6:

Language:javascript, parsed in: 0.007 seconds, using GeSHi 1.0.7.12
  1. function myFunction()
  2. {
  3.   alert("Old");
  4. }
  5. myFunction(); // 输出 "Old"
  6. myFunction=function()
  7. {
  8.   alert("New");
  9. };
  10. myFunction(); // 输出 "New"
  11.  

运行示例

旧函数哪里去了??被抛弃了。

上例图示

如果我们需要保留它,我们可以在改变它之前给它分配一个指针。

Example 6A:

Language:javascript, parsed in: 0.008 seconds, using GeSHi 1.0.7.12
  1. function myFunction()
  2. {
  3.   alert("Old");
  4. }
  5. var savedFuncion=myFunction;
  6. myFunction=function()
  7. {
  8.   alert("New");
  9. };
  10. myFunction();    // 输出 "New"
  11. savedFuncion()// 输出 "Old"
  12.  

运行示例

上例图示

不过要小心,象下面这样的例子并不会有作用,因为是创建了另一个叫做myFunctionPtr的函数而不是修改它。

Example 6B:

Language:javascript, parsed in: 0.009 seconds, using GeSHi 1.0.7.12
  1. function myFunction()
  2. {
  3.   alert("Old");
  4. }
  5. var savedFunc=myFunction;
  6. savedFunc=function()
  7. {
  8.   alert("New");
  9. };
  10. myFunction();            // 输出 "Old"
  11. savedFunc();             // 输出 "New"
  12.  

运行示例

内嵌函数

我们还能够在一个函数中嵌套一个函数。下例,我有一个叫做getHalfOf的函数,而在它里面,我有另一个叫做calculate的函数。

Example 7

Language:javascript, parsed in: 0.015 seconds, using GeSHi 1.0.7.12
  1. function getHalfOf(num1, num2, num3)     
  2. {
  3.   function calculate(number)
  4.   {
  5.     return number/2;
  6.   }
  7.  
  8.   var result="";
  9.   result+=calculate(num1)+" ";
  10.   result+=calculate(num2)+" ";
  11.   result+=calculate(num3);
  12.   return result;
  13. }         
  14. var resultString=getHalfOf(10,20,30);
  15. alert(resultString);         // 输出 "5 10 15"
  16.  

运行示例

你只能在内部调用嵌套的函数。就是说,你不能这么调用:<span title="代码">getHalfOf.calculate(10)</span>,因为calculate只有当外部函数(getHalfOf())在运行的时候才会存在。这和我们前面的讨论一致(函数会被编译,但只有当你去调用它的时候才会执行)。

调用哪个函数?

你也许正在想命名冲突的问题。比如,下面哪一个叫做calculate的函数会被调用?

Example 8

Language:javascript, parsed in: 0.017 seconds, using GeSHi 1.0.7.12
  1. function calculate(number)
  2. {
  3.   return number/3;
  4. }
  5.  
  6. function getHalfOf(num1, num2, num3)     
  7. {
  8.   function calculate(number)
  9.   {
  10.     return number/2;
  11.   }
  12.  
  13.   var result="";
  14.   result+=calculate(num1)+" ";
  15.   result+=calculate(num2)+" ";
  16.   result+=calculate(num3);
  17.   return result;
  18. }         
  19. var resultString=getHalfOf(10,20,30);
  20. alert(resultString);         // 输出 "5 10 15"
  21.  

运行示例

在这个例子中,编译器会首先搜索局部内存地址,所以它会使用内嵌的calculate函数。如果我们删除了这个内嵌(局部)的calculate函数,这个代码会使用全局的calculate函数。

函数:数据类型及构造函数

让我们来看看函数的另一个特殊功能--这让它和其它对象类型截然不同。一个函数能够用来作为一个数据类型的蓝图。这个特性通常被用在面向对象编程中来模拟用户自定义数据类型(user defined data type)。使用用户自定义数据类型创建的对象通常被成为用户自定义对象(user defined object)

数据类型

在定义了一个函数之后,我们也同时创建了一个新的数据类型。这个数据类型能够用来创建一个新对象。下例,我创建了一个叫做Ball的新数据类型。

Example DT1

Language:javascript, parsed in: 0.004 seconds, using GeSHi 1.0.7.12
  1. function Ball()
  2. {
  3. }
  4. var ball0=new Ball(); // ball0 现在指向一个新对象
  5.  
  6. alert(ball0);         // 输出 "Object",因为 ball0 现在是一个对象
  7.  

运行示例

这样看来,<span title="代码">ball0=new Ball()</span>作了什么?new关键字创建了一个类型是Object的新对象(叫做ball0)。然后它会执行<span title="代码">Ball()</span>,并将这个引用传给ball0(用于调用对象)。下面,你会看到这条消息:“creating new Ball”,如果<span title="代码">Ball()</span>实际上被运行的话。

Example DT2

Language:javascript, parsed in: 0.009 seconds, using GeSHi 1.0.7.12
  1. function Ball(message)
  2. {
  3.   alert(message);
  4. }
  5. var ball0=new Ball("creating new Ball")// 创建对象并输出消息
  6. ball0.name="ball-0";                      // ball0现在有一个属性:name
  7. alert(ball0.name);                        // 输出 "ball-0"
  8.  

运行示例

我们可以把上面这段代码的高亮的一行看做是底下的代码高亮处的一个简写:

Language:javascript, parsed in: 0.010 seconds, using GeSHi 1.0.7.12
  1. function Ball(message)
  2. {
  3.   alert(message);
  4. }
  5. var ball0=new Object();
  6. ball0.construct=Ball;
  7. ball0.construct("creating new ball")// 执行 ball0.Ball("creating..");
  8. ball0.name="ball-0";                     
  9. alert(ball0.name);         
  10.  

运行示例

这行代码<span title="代码">ball0.construct=Ball</span>Example 4中的<span title="代码">ptr=myFunction</span>语法一致。

如果你还是不明白这行的含义那就回过头再复习一下Example 4。注意:你也许考虑直接运行<span title="代码">ball0.Ball("...")</span>,但是它不会起作用的,因为ball0并没有一个叫做Ball("...")的属性,并且它也不知道你究竟想作些什么。

添加属性

当我们象上面那样使用关键字new创建一个对象的时候,一个新的Object被创建了。我们可以在创建之后给这个对象添加属性(就好像我在上面那样添加属性name。而接下来的问题就是如果我们创建了这个对象的另外一个实例,我们得象下面那样再次给这个新对象添加这个属性。)

Example DT3 (creates 3 ball objects)

Language:javascript, parsed in: 0.013 seconds, using GeSHi 1.0.7.12
  1. function Ball()
  2. {
  3. }
  4. var ball0=new Ball(); // ball0 现在指向了类型Ball的一个新实例
  5. ball0.name="ball-0"// ball0 现在有一个属性"name"
  6.  
  7. var ball1=new Ball();
  8. ball1.name="ball-1";
  9.  
  10. var ball2=new Ball();
  11.  
  12. alert(ball0.name);    // 输出 "ball-0"
  13. alert(ball1.name);    // 输出 "ball-1"
  14. alert(ball2.name);    // 哦,我忘记给ball2添加“name”了!
  15.  

我忘记给ball2添加属性name了,如果在正式的程序中这也许会引发问题。有什么好办法可以自动增加属性呢?嗯,有一个:使用this关键字。this这个词在function中有特别的意义。它指向了调用函数的那个对象。让我们看看下面的另一个示例,这时候我们在构造函数中添加上这些属性:

Example DT4

Language:javascript, parsed in: 0.010 seconds, using GeSHi 1.0.7.12
  1. function Ball(message, specifiedName)
  2. {
  3.   alert(message);
  4.   this.name=specifiedName;               
  5. }
  6. var ball0=new Ball("creating new Ball", "Soccer Ball")
  7. alert(ball0.name);                   // prints "Soccer Ball"
  8.  

运行示例

请记住:是new关键字最终使得构造函数被执行。在这个例子中,它将会运行<span title="代码">Ball("creating new Ball", "Soccer Ball");</span>而关键字this将指向ball0

因此,这行:<span title="代码">this.name=specifiedName</span>变成了<span title="代码">ball0.name="Soccer Ball"</span>

它主要是说:给ball0添加属性name,属性值是<span title="代码">Soccer Ball</span>

我们现在只是添加了一个name属性给ball0,看起来和上一个例子中所做的很象,但却是一个更好更具扩展性的方法。现在,我们可以随心所欲的创建许多带有属性的ball而无需我们手动添加它们。而且,人们也希望创建的Ball对象能够清晰的看懂它的构造函数并且能够轻松找出Ball的所有属性。让我们添加更多属性到Ball里。

Example DT5

Language:javascript, parsed in: 0.026 seconds, using GeSHi 1.0.7.12
  1. function Ball(color, specifiedName, owner, weight)
  2. {
  3.   this.name=specifiedName;               
  4.   this.color=color;
  5.   this.owner=owner;
  6.   this.weight=weight;
  7. }
  8. var ball0=new Ball("black/white", "Soccer Ball", "John", 20)
  9. var ball1=new Ball("gray", "Bowling Ball", "John", 30)
  10. var ball2=new Ball("yellow", "Golf Ball", "John", 55)
  11. var balloon=new Ball("red", "Balloon", "Pete", 10)
  12.  
  13. alert(ball0.name);                        // 输出 "Soccer Ball"
  14. alert(balloon.name);                      // 输出 "Balloon"
  15. alert(ball2.weight);                      // 输出 "55"
  16.  

运行示例

嘿!使用面向对象术语,你能够说Ball是一个拥有如下属性的对象类型:name, color, owner, weight。

将对象赋给属性

我们并没被限制只能添加形如字符串或者数字之类的简单数据类型作为属性。我们也能够将对象赋给属性。下面,supervisorEmployee的一个属性.

Example DT6

Language:javascript, parsed in: 0.021 seconds, using GeSHi 1.0.7.12
  1. function Employee(name, salary, mySupervisor)
  2. {
  3.   this.name=name;               
  4.   this.salary=salary;
  5.   this.supervisor=mySupervisor;
  6. }
  7. var boss=new Employee("John", 200);
  8.  
  9. var manager=new Employee("Joan", 50, boss)
  10. var teamLeader=new Employee("Rose", 50, boss)
  11.  
  12. alert(manager.supervisor.name+" is the supervisor of "+manager.name);
  13. alert(manager.name+"\'s supervisor is "+manager.supervisor.name)
  14.  

会输出什么呢?运行示例

就像你在上面这个例子中看到的那样,managerteamLeader都有一个supervisor属性,而这个属性是类型Employee的一个对象。

将函数作为属性

任何类型的对象都可以作为一个属性,回忆一下前面的Example 4(不是Example DT4),函数也是一个对象。所以你可以让一个函数作为一个对象的一个属性。下面,我将添加两个函数getSalaryaddSalary

Example DT7

Language:javascript, parsed in: 0.022 seconds, using GeSHi 1.0.7.12
  1. function Employee(name, salary)
  2. {
  3.   this.name=name;               
  4.   this.salary=salary;
  5.  
  6.   this.addSalary=addSalaryFunction;
  7.  
  8.   this.getSalary=function()
  9.                  {
  10.                    return this.salary;
  11.                  };
  12. }
  13. function addSalaryFunction(addition)
  14. {
  15.   this.salary=this.salary+addition;
  16. }
  17.  
  18. var boss=new Employee("John", 200000);
  19. boss.addSalary(10000);                    // boss 长了 10K 工资……为什么老板工资可以长这么多:'(
  20. alert(boss.getSalary());                  // 输出 210K……为什么默认工资也那么高……:'(
  21.  

运行示例

addSalarygetSalary演示了几种将函数赋给属性的不同方法。如果你记得我们最开始的讨论;我讨论了三种声明函数的不同方式。所有那些在这里都是适用的,但是上面展示的两个最常用。

让我们看看有什么不同。下面,注意一下9-12行的代码。当这部分代码执行的时候,函数getSalary被声明。如前面数次提到的,一个函数声明的结果是一个对象被创建。所以这时候boss被创建(接下来的高亮行),而boss里有一个getSalary属性。

Language:javascript, parsed in: 0.022 seconds, using GeSHi 1.0.7.12
  1. function Employee(name, salary)
  2. {
  3.   this.name=name;               
  4.   this.salary=salary;
  5.  
  6.   this.addSalary=addSalaryFunction;
  7.  
  8.   this.getSalary=function()
  9.                  {
  10.                    return this.salary;
  11.                  };
  12. }
  13. function addSalaryFunction(addition)
  14. {
  15.   this.salary=this.salary+addition;
  16. }
  17.  
  18. var boss=new Employee("John", 200000);
  19. var boss2=new Employee("Joan", 200000);
  20. var boss3=new Employee("Kim", 200000);
  21.  
  22.  

当你创建这个对象的更多实例时(boss2boss3),每一个实例都有一份getSalary代码的单独拷贝;而与此相反,addSalary则指向了同一个地方(即addSalaryFunction)。

看看下面的代码来理解一下上面所描述的内容。

Example DT8

Language:javascript, parsed in: 0.036 seconds, using GeSHi 1.0.7.12
  1. function Employee(name, salary)
  2. {
  3.   this.name=name;               
  4.   this.salary=salary;
  5.  
  6.   this.addSalary=addSalaryFunction;
  7.   this.getSalary=function()
  8.                  {
  9.                    return this.salary;
  10.                  };
  11. }
  12. function addSalaryFunction(addition)
  13. {
  14.   this.salary=this.salary+addition;
  15. }
  16.  
  17. var boss1=new Employee("John", 200000);
  18. var boss2=new Employee("Joan", 200000);
  19. // Add attributes to the getSalary function object
  20. boss1.getSalary.owner="boss1";
  21. boss2.getSalary.owner="boss2";
  22. alert(boss1.getSalary.owner); // Output "boss1 "
  23. alert(boss2.getSalary.owner); // Output "boss2 "
  24. // If two objects point to the same function object, then
  25. //Both the above two outputs should be "boss2".
  26. // Add attributes to the addSalary function object
  27. boss1.addSalary.owner="boss1";
  28. boss1.addSalary.owner="boss2";
  29. alert(boss1.addSalary.owner); // Output "boss2 "
  30. alert(boss2.addSalary.owner); // Output "boss2 "
  31. // Because both objects point to the same function, (Ziwu’s note: the original text is not pointing to the same function, suspected to be a typo)
  32. //When modifying one of them, it will affect all instances (so both output "boss2").

Run the example

Maybe not important stuff, but here are some conclusions about running inline functions like getSalary above: 1) Requires more storage space to store the object (because every object instance will has its own getSalary code copy); 2) javascript needs more time to construct this object.

Let’s rewrite this example to make it more efficient.

Example DT9

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template