热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《JavaScript高等程序设计》(第3版)读书笔记第7章函数表达式

定义函数表达式的体式格局有两种:函数声明。它的主要特性就是函数声明提拔(functiondeclarationhoisting)即在实行代码之前会先读取函数声明。这就意味着可以把函
  • 定义函数表达式的体式格局有两种:

    • 函数声明。它的主要特性就是 函数声明提拔(function declaration hoisting) 即在实行代码之前会先读取函数声明。这就意味着可以把函数声明放在挪用它的语句背面。
    • 函数表达式。

// 函数声明
function functionName(params) {
...
}

// 函数表达式有几种差别的体式格局,下面是最罕见的一种
var functiOnName= function(params) {
...
}

  • 上面这类情势看起来好像是通例的变量赋值语句。而右侧这类情势建立的函数叫做 匿名函数 (anonymous function)(有时刻也叫 拉姆达函数 lambda),因为function关键字背面没有标识符。
  • 函数表达式与其他表达式一样,在运用前必需先赋值,不然会致使失足。

sayHi(); // 毛病,函数还不存在
var sayHi = function () {
console.log('Hi!');
};

  • 外表上看,下面的代码没有题目,conditiontrue时,运用一个定义,不然运用另一个定义。现实上,在ECMAScript中属于无效语法,Javascript引擎会尝试修正毛病,将其转换为合理状况。但题目是浏览器尝试修正毛病的做法并不一致。大多数浏览器会返回第二个声明,疏忽condition的值;Firefox会在condition为true的时刻返回第一个声明。因而这类做法很风险,不应该涌如今你的代码中。

// 不要如许做!
if (condition) {
function sayHi() {
console.log('Hi!');
}
} else {
function sayHi() {
console.log('Yo!');
}
}

  • 上述代码改成函数表达式就没有题目

// 可以如许做
var sayHi;
if (condition) {
sayHi = function() {
console.log('Hi!');
}
} else {
sayHi = function() {
console.log('Yo!');
}
}

  • 可以建立函数再赋值给变量,也就能把函数作为其他函数的返回值。createComparisonFunction() 就返回了一个匿名函数。

function createComparisonFunction (propertyName) {
return function (object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}

  • 在把函数当作值来运用的状况下,都可以运用匿名函数。不过,这并非匿名函数唯一的用处。

递归

  • 递归函数是在一个函数经由过程名字挪用自身的状况下组成的

// 典范的递归阶乘函数
function factorial (num) {
if (num <= -1) {
return 1;
} else {
return num * factorial(num - 1);
}
}

  • 虽然递归阶乘函数外表看没有什么题目,但下面的代码却能够致使它失足

// 把factorial() 函数保留在变量anotherFactorial中
var anotherFactorial = factorial;
// 将factorial设置为null
// 如今指向原始函数的援用只剩下anotherFactorial
factorial = null;
// 原始函数必需实行factorial()
// 但factorial不再是函数,所以致使失足
anotherFactorial(4); // throw error!

  • 可以运用 arguments.callee (指向正在实行函数的指针)完成函数的递归挪用

// 非严厉形式
function factorial (num) {
if (num <= -1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}

  • 但在严厉形式下不能经由过程剧本接见 arguments.callee,会致使失足。可以运用定名函数表达式来杀青雷同的效果

var factorial = (function f(num) {
if (num <= -1) {
return 1;
} else {
return num * f(num - 1);
}
})

闭包

  • 匿名函数闭包 是两个观点,轻易殽杂。 闭包 是指有权接见另一个函数作用域中的变量的函数。
  • 建立闭包的罕见体式格局,就是在一个函数内部建立另一个函数,仍以前面的 createComparisonFunction() 函数为例

function createComparisonFunction (propertyName) {
return function (object1, object2) {
// 下面两行代码接见了外部函数中的变量propertyName
// 纵然这个内部函数被返回了,而且是在其他处所被挪用了
// 它依旧可以接见变量 propertyName
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}

  • 上述例子,纵然内部函数被返回了,在其他处所挪用,它依旧可以接见propertName。因为这个内部函数的作用域链中包括 createComparisonFunction() 的作用域。要搞清晰个中细节,必需从明白函数被挪用的时刻都邑发作什么入手。
  • 第4章引见过 作用域链。当某个函数被 挪用 时会发作以下事变:

    • 建立一个 实行环境(execution context) 及响应的 作用域链
    • 运用 arguments 和其他定名参数的值来初始化函数的 运动对象(activation object)
    • 构成作用域链。外部函数的运动对象一直处于第二位,外部函数的外部函数的运动对象处于第三位&#8230;直至作为作用域尽头的全局实行环境
    • 函数实行过程当中,为读写变量的值,就须要在作用域链中查找变量。

function compare(value1, value2) {
if (value1 return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);

  • 背景的每一个实行环境都有一个示意变量的对象——变量对象。全局环境的变量对象一直存在,而像compare()函数如许的部分环境的变量对象,则只在函数实行的过程当中存在。

    • 在建立 compare() 函数时,会建立一个预先包括全局变量对象的作用域链,这个作用域链被保留在内部的[[scope]]属性中。
    • 当挪用 compare() 函数时,会为函数建立一个实行环境,然后经由过程复制函数的[[scope]]属性中的对象构建起实行环境的作用域链。
    • 今后,又有一个运动对象(在此作为变量对象运用)被建立并被推入实行环境作用域链的前端。
  • 关于本例, compare() 函数的实行环境而言,其作用域链中包括两个变量对象:

    • 当地运动对象
    • 全局运动对象
  • 作用域链本质上是一个指向变量对象的指针列表,它只援用但不现实包括变量对象。
  • 不管什么时刻在函数中接见一个变量时,就会从作用域链中搜刮具有响应名字的变量。一般而言,当函数实行终了后,部分运动对象就会被烧毁,内存中近保留全局作用域(全局实行环境的变量对象)。
  • 然则闭包的状况有所差别

function createComparisonFunction (propertyName) {
return function (object1, object2) {
// 下面两行代码接见了外部函数中的变量propertyName
// 纵然这个内部函数被返回了,而且是在其他处所被挪用了
// 它依旧可以接见变量 propertyName
// 即为 createComparisonFunction 的运动对象
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}
// 建立比较函数
// 挪用了 createComparisonFunction() 要领
// 建立了 createComparisonFunction 的运动对象
// 返回内部的匿名函数 保留在 compareNames
// createComparisonFunction 实行终了
// 但它的运动对象仍被 内部匿名函数援用,所以运动对象依旧存在,不会烧毁
var compareNames = createComparisonFunction("name");
// 此时result挪用了 保留在 compareNames 的匿名函数
// 该匿名函数坚持了对 createComparisonFunction 运动对象的援用
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
// 纵然 compareNames 实行终了,createComparisonFunction 运动对象依旧存在
// 须要手动消弭对匿名函数的援用(以便开释内存)
compareNames = null;

  • 起首,建立的比较函数被保留在变量compareNames中,而经由过程将其设置为null消弭援用,就即是关照渣滓接纳例程将其消弭。跟着匿名函数的作用域链被烧毁,其他作用域链(除了全局作用域)也都可以安全地的烧毁了。图7-2展现了挪用compareNames()的过程当中发生的作用域链之间的关联

《《Javascript高等程序设计》(第3版)读书笔记 第7章 函数表达式》

  • 因为闭包会照顾包括它的函数的作用域,因而会比其他函数占用更多的内存。过分运用闭包能够致使内存占用过量,我们发起读者只在相对必要时再斟酌运用闭包。虽然像V8等优化后的Javascript引擎会尝试接纳被闭包占用的内存,但请照样要郑重运用。

闭包与变量

  • 作用域链的这类设置机制,引出了一个值得注重的副作用,即闭包只能获得包括函数中任何变量的末了一值。别忘了闭包所保留的是全部变量对象,而不是某个特别的变量。

function createFunctions() {
var result = new Array();
for (var i=0; i <10; i++) {
// 赋值给数组元素的是匿名函数自身,不是详细的值
// 所以在 createFunctions() 实行终了后,挪用数组内的函数,返回的是变量i的值
// 而变量i在实行终了后,即是 10
result[i] = function() {
// 返回指向变量 i 的指针
return i;
};
}
return result;
}

  • 这个函数会返回一个 函数数组。外表上看result里的每一项函数都应该返回本身的索引值。但现实上每一个函数返回的都是10
  • 因为每一个函数的作用域中都保留着createFunctions()函数的运动对象,所以它们援用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每一个函数都援用着保留变量i的同一个变量对象,所以在每一个函数内部i的值都是10.
  • 可以经由过程建立另一个匿名函数强迫让闭包的行动相符预期

function createFunctions() {
var result = new Array();
for (var i=0; i <10; i++) {
// 此时返回的里层匿名函数挪用了外层匿名函数的 num
// 里层匿名函数建立并返回了一个接见 num 的闭包
// 如此一来 result 数组中的每一个函数都有本身的num变量副本
result[i] = function(num) {
// 返回建立的另一个匿名函数
return function() {
return num;
};
}(i);
}
return result;
}

关于this对象

  • 在闭包中运用this对象也能够会致使一些题目。this对象是在运转时基于函数的实行环境绑定的:

    • 在全局函数中,this即是window
    • 当函数被作为某个对象的要领挪用时,this即是谁人对象。
    • 匿名函数的实行环境具有全局性,因而其this对象一般指向window(经由过程call() apply()转变函数的实行环境的状况除外)。但有时刻因为变成写闭包的体式格局差别,这一点能够不会那末显著

var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
// 在非严厉形式下
object.getNameFunc()(); // "The Window"

  • 每一个函数在被挪用时都邑自动获得两个特别变量: thisarguments。内部函数在搜刮这两个变量时,只会搜刮到其运动对象为止,因而永久不能够直接接见外部函数中的这两个变量(这一点经由过程图7-2可以看的清晰)。
  • 不过,把外部作用域中的this对象保留在一个闭包可以接见到的变量里,就可以让闭包接见该对象了。

var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
var that = this;
return function() {
return that.name;
};
}
};
object.getNameFunc()(); // "My Object"

  • 在几种特别状况下,this的值能够会不测的转变。

var name = "The Window";
var object = {
name: "My Object",
getName: function() {
return this.name;
}
};
// this 指向 object
object.getName(); // "My Object"
// 加上了括号,看似在援用一个函数
// 但 (object.getName) 和 object.getName 的定义是雷同的
// 所以这行代码与上面的代码无异
(object.getName)(); // "My Object"
// 非严厉形式
// 赋值语句会返回 object.getName 的匿名函数
// 相当于将匿名函数在全局环境下运转
(object.getName = object.getName)(); // "The Window"

  • 第三行代码先实行了一条赋值语句,然后再挪用赋值后的效果。因为这个赋值表达式的值是函数自身,所以this的值不能获得保持,效果就返回了 &#8220;The Window&#8221; 。

内存走漏

  • 因为IE9之前的版本对JScript对象和COM对象运用差别的渣滓网络例程(第4章引见过),因而闭包在IE的这些版本中会致使一些特别的题目。详细来说,假如闭包的作用域链中保留着一个HTML元素,那末就意味着该元素将没法被烧毁

function assignHandler() {
var element = document.getElementById("someElement");
element.Onclick= function() {
console.log(element.id);
};
}

  • 以上代码建立了一个作为element元素处置惩罚顺序的闭包,而这个闭包则又建立了一个轮回援用(事宜将在第13章议论)。因为匿名函数保留了一个对assignHandler()的运动对象的援用,因而就会致使没法削减element的援用数。只需匿名函数存在,element的援用数至少是1。

// 以下修正可以防止这个题目
function assignHandler() {
var element = document.getElementById("someElement");
var id = element.id;
element.Onclick= function() {
console.log(id);
};
element = null;
}

  • 闭包中援用包括函数的全部运动对象,而个中包括着element。纵然闭包不直接援用element,包括函数的运动对象也依旧会保留一个援用。因而有必要把element变量设置为null

模拟块级作用域

  • Javascript没有块级作用域。这意味着在块语句中定义的变量,现实上是在包括函数中而非语句中建立的。

function outputNumerbs(cout) {
for (var i=0; i console.log(i);
}
console.log(i); // 计数
}

  • 在Java, C++等语言中,变量i只会在for轮回的语句块中有定义,轮回一旦完毕,变量i就会被烧毁。但是在Javascript中,变量i是定义在outputNumbers()的运动对象中的,因而从它有定义最先,就可以在函数内部到处接见它。纵然像下面如许毛病的从新声明变量也不会转变值。

function outputNumerbs(cout) {
for (var i=0; i console.log(i);
}
var i; // 从新声明变量
console.log(i); // 计数
}

  • Javascript历来不会通知你是不是屡次声清楚明了同一个变量,碰到这类状况,它只会对后续的声明置若罔闻(不过,它会实行后续声明中的变量初始化)。
  • 匿名函数可以用来模拟块级作用域并防止这个题目。用作块级作用域(一般称为 私有作用域 )的匿名函数的语法以下:

(function() {
// 这里是块级作用域
...
})()

// 罕见的代码片断
// 定义了一个函数,然后马上挪用它
var someFunction = function() {
// 这里是块级作用域
...
};
someFunction();

  • 那这里假如将函数名也去掉呢?答案是不可,会致使失足。因为Javascriptfunction关键字当作一个函数声明的最先,而函数声明背面不能跟圆括号。(函数表达式可以)

function() {
// 这里是块级作用域
...
}() // 失足!

  • 要将函数声明转换成函数表达式,只需表面包裹圆括号即可

(function() {
// 这里是块级作用域
...
}())

  • 不管在什么处所,只需暂时须要一些变量,就可以运用私有作用域

function outputNumbers(cout) {
// 这里是一个闭包,匿名函数可以接见 cout
(function () {
for (var i=0; i console.log(i);
}
})();
// 在这里挪用变量 i 会报错
console.log(i); // throw error
}

  • 这类手艺常常在全局作用域中被用在函数外部,从而限定向全局作用域中增加过量的变量和函数。

(function() {
var now = new Date();
if (now.getMonth() == 0 && now.gettDate() == 1) {
console.log('Haapy new year!");
}
})();

  • 这类做法可以削减闭包占用的内存,因为没有指向匿名函数的援用。只需函数实行终了,就可以马上烧毁其作用域链了。

私有变量

  • 严厉来说,Javascript中没有私有成员的观点;一切对象属性都是共有的。不过却是有一个私有变量的观点。
  • 任安在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部接见这些变量。
  • 假如在函数内部建立一个闭包,那末闭包经由过程本身的作用域链可以接见这些私有变量。应用这一点,我们就可以建立用于接见私有变量的公有要领。
  • 有权接见私有变量和私有函数的公有要领称为 特权要领(privileged method) 。有两种建立特权要领的体式格局:

    • 在组织函数中定义特权要领(静态私有变量)
    • 模块形式
  • 组织函数中定义,基础的形式以下

// 组织函数Person
// 入参 name 是它的私有变量
function Person(name) {
this.getName = function() {
return name;
};
this.setName = function(value) {
name = value;
};
}
var person = new Person("Nicholas");
console.log(person.getName()); // "Nicholas"
person.setName("Greg");
console.log(person.getName()); // "Greg"

  • 这类形式有一个瑕玷,那就是必需运用组织函数来到达目标,而第6章议论过,组织函数形式确实定是针对每一个实例都邑建立出一样的一组新要领
  • 而运用静态私有变量来完成特权要领就可以防止这个题目。

静态私有变量

(function() {
// 私有变量
var privateVariable = 10;
// 私有函数
function privateFunction() {
return false;
}
// 组织函数
// 这里没有运用var操作符,自动建立全局变量
// 严厉形式下不能运用
MyObject = function() {};
// 公有/特权要领
MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction();
};
})();

  • 公有要领是在原型上定义的,防止了反复建立要领的状况。
  • 须要注重的是,这个形式在定义组织函数时没有运用函数声明,而是运用了函数表达式。函数声明只能建立部分函数,但那并非我们想要的。
  • 这个特权要领,作为一个闭包,老是保留着对包括作用域的援用。

(function() {
var name = "";
Person = function(value) {
name = value;
};
Person.prototype.getName = function() {
return name;
};
Person.prototype.setName = function (value) {
name = value;
};
})();
var person1 = new Person("Nicholas");
console.log(person1.getName()); // "Nicholas"
person1.setName("Greg");
console.log(person1.getName()); // "Greg"
var person2 = new Person("Michael");
console.log(person1.getName()); // "Michael"
console.log(person2.getName()); // "Michael"

  • 这个例子中的Person组织函数与getName() setName() 要领一样,都有权接见私有变量name
  • name变成了一个静态的、由一切实例同享的属性。
  • 以这类体式格局建立静态私有变量会因为运用原型而增长代码复用,但每一个实例都没有本身的私有变量。
  • 多查找作用域链中的一个条理,就会在肯定水平上影响查找速率。而这恰是运用闭包和私有变量的一个显著的不足之处。

模块形式

  • 模块形式经由过程为单例增加私有变量和特权要领使其获得加强
  • 这类形式在须要对单例举行某些初始化,同时又须要保护其私有变量时异常有效

var application = function() {
// 私有变量和函数
var compOnents= new Array();
// 初始化
components.push(new BaseComponent());
// 大众
return {
getComponentCuont: function() {
return components.length;
},
registerComponent: function(component) {
if (typeof compOnent== "object") {
components.push(component)
}
}
};
}();

  • 在WEB应用顺序中,常常须要运用一个单例来治理应用顺序级的信息。这个简朴的例子建立了一个用于治理组件的application对象。
  • 简而言之,假如必需建立一个对象并以某些数据对其举行初始化,同时还要公然一些可以接见这些私有数据的要领,那末就可以运用模块形式。
  • 以模块形式建立的每一个单例都是Object的实例,因为终究要经由过程一个对象字面量来示意他。

加强的模块形式

  • 加强的模块形式合适那些单例必需是某种范例的实例,同时还必需增加某些属性和(或)要领对其加以加强的状况。
  • 假如前述例子中的application对象必需是BaseComponent的实例,可以以下代码

var application = function() {
// 私有变量和函数
var compOnents= new Array();
// 初始化
components.push(new BaseComponent());
// 建立 application 的一个部分副本
var app = new BaseComponent();
// 大众接口
app.getCompOnentCuont= function() {
return components.length;
};
app.registerCompOnent= function(component) {
if (typeof compOnent== "object") {
components.push(component);
}
};
// 返回这个副本
return app;
}();

推荐阅读
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 开发笔记:计网局域网:NAT 是如何工作的?
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了计网-局域网:NAT是如何工作的?相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了获取关联数组键的列表的方法,即使用Object.keys()函数。同时还提到了该方法在不同浏览器的支持情况,并附上了一个代码片段供读者参考。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 一、什么是闭包?有什么作用什么是闭包闭包是定义在一个函数内部的函数,它可以访问父级函数的内部变量。当一个闭包被创建时,会关联一个作用域—— ... [详细]
author-avatar
lao6345790
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有