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

经常被面试官考的JavaScript数据类型知识你真的懂吗?

本文中讲解的内容面试题引入js中的数据类型js弱类型语言js中的强制转换规则js转换规则不同场景应用js中的数据类型判断NaN相关总结toString与String的一些误区文章篇

    640?wx_fmt=gif

本文中讲解的内容

640?wx_fmt=jpeg

面试题引入

js中的数据类型

js弱类型语言

js中的强制转换规则

js转换规则不同场景应用

js中的数据类型判断

NaN相关总结

toString与String的一些误区


文章篇幅较长, 建议收藏或者关注公众号, 方便日后翻阅

前言

面试了几个开发者,他们确实做过不少项目,能力也是不错的,但是发现Javascript基础并不好,于是决定写一下这篇javascrip数据类型相关的基础文章,其实也不仅仅是因为面试了他们,之前自己在面试的时候,也曾经被虐过,面试官说过的最深刻的一句话我到现在都记得。

基础很重要,只有基础好才会很少出bug,大多数的bug都是基础不扎实造成的。

这里给出两道我们公司数据类型基础相关的面试题和答案,如果都能做对并且知道为什么(可以选择忽略本文章):

640?wx_fmt=png

640?wx_fmt=png


本篇文章会以一个面试官问问题的角度来进行分析讲解

js中的数据类型

面试官问:说一说Javascript中有哪些数据类型?

Javascript 中共有七种内置数据类型,包括基本类型对象类型

基本类型

基本类型分为以下六种:

注意:

  1. string 、number 、boolean 和 null  undefined 这五种类型统称为原始类型(Primitive),表示不能再细分下去的基本类型

  2. symbol是ES6中新增的数据类型,symbol 表示独一无二的值,通过 Symbol 函数调用生成,由于生成的 symbol 值为原始类型,所以 Symbol 函数不能使用 new 调用;

  3. null 和 undefined 通常被认为是特殊值,这两种类型的值唯一,就是其本身。

对象类型

对象类型也叫引用类型,array和function是对象的子类型。对象在逻辑上是属性的无序集合,是存放各种值的容器。对象值存储的是引用地址,所以和基本类型值不可变的特性不同,对象值是可变的。

js弱类型语言

面试官问:说说你对Javascript是弱类型语言的理解?

Javascript 是弱类型语言,而且Javascript 声明变量的时候并没有预先确定的类型, 变量的类型就是其值的类型,也就是说变量当前的类型由其值所决定,夸张点说上一秒种的String,下一秒可能就是个Number类型了,这个过程可能就进行了某些操作发生了强制类型转换。虽然弱类型的这种不需要预先确定类型的特性给我们带来了便利,同时也会给我们带来困扰。为了能充分利用该特性就必须掌握类型转换的原理,

js中的强制转换规则

面试官问:Javascript中强制类型转换是一个非常易出现bug的点,知道强制转换时候的规则吗?

注:规则最好配合下面什么时候发生转换使用这些规则看效果更佳。

ToPrimitive(转换为原始值)

ToPrimitive对原始类型不发生转换处理,只针对引用类型(object)的,其目的是将引用类型(object)转换为非对象类型,也就是原始类型。

ToPrimitive 运算符接受一个值,和一个可选的 期望类型作参数。ToPrimitive 运算符将值转换为非对象类型,如果对象有能力被转换为不止一种原语类型,可以使用可选的 期望类型 来暗示那个类型。

转换后的结果原始类型是由期望类型决定的,期望类型其实就是我们传递的type。直接看下面比较清楚。 ToPrimitive方法大概长这么个样子具体如下。

/*** @obj 需要转换的对象* @type 期望转换为的原始数据类型,可选*/ToPrimitive(obj,type)
ToPrimitive(obj,type)

type不同值的说明
  1. 先调用obj的toString方法,如果为原始值,则return,否则第2步

  2. 调用obj的valueOf方法,如果为原始值,则return,否则第3步

  3. 抛出TypeError 异常

  1. 调用obj的valueOf方法,如果为原始值,则返回,否则下第2步

  2. 调用obj的toString方法,如果为原始值,则return,否则第3步

  3. 抛出TypeError 异常

  1. 该对象为Date,则type被设置为String

  2. 否则,type被设置为Number

Date数据类型特殊说明:

对于Date数据类型,我们更多期望获得的是其转为时间后的字符串,而非毫秒值(时间戳),如果为number,则会取到对应的毫秒值,显然字符串使用更多。 其他类型对象按照取值的类型操作即可。

ToPrimitive总结

ToPrimitive转成何种原始类型,取决于type,type参数可选,若指定,则按照指定类型转换,若不指定,默认根据实用情况分两种情况,Date为string,其余对象为number。那么什么时候会指定type类型呢,那就要看下面两种转换方式了。

toString

Object.prototype.toString()

toString() 方法返回一个表示该对象的字符串。

每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。

这里先记住,valueOf() 和 toString() 在特定的场合下会自行调用。

valueOf

Object.prototype.valueOf()方法返回指定对象的原始值。

Javascript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是我们很少需要自己调用此函数,valueOf 方法一般都会被 Javascript 自动调用。

不同内置对象的valueOf实现:

对照代码会更清晰一些:

var str = new String('123');console.log(str.valueOf());//123var num = new Number(123);console.log(num.valueOf());//123var date = new Date();console.log(date.valueOf()); //1526990889729var bool = new Boolean('123');console.log(bool.valueOf());//truevar obj = new Object({valueOf:()=>{ return 1}})console.log(obj.valueOf());//1new String('123');
console.log(str.valueOf());//123

var num = new Number(123);
console.log(num.valueOf());//123

var date = new Date();
console.log(date.valueOf()); //1526990889729

var bool = new Boolean('123');
console.log(bool.valueOf());//true

var obj = new Object({valueOf:()=>{
return 1
}})
console.log(obj.valueOf());//1

Number

Number运算符转换规则:

注意:对象这里要先转换为原始值,调用ToPrimitive转换,type指定为number了,继续回到ToPrimitive进行转换(看ToPrimitive)。

String

String 运算符转换规则

注意:对象这里要先转换为原始值,调用ToPrimitive转换,type就指定为string了,继续回到ToPrimitive进行转换(看ToPrimitive)。

String(null) // 'null'String(undefined) // 'undefined'String(true) // 'true'String(1) // '1'String(-1) // '-1'String(0) // '0'String(-0) // '0'String(Math.pow(1000,10)) // '1e+30'String(Infinity) // 'Infinity'String(-Infinity) // '-Infinity'String({}) // '[object Object]'String([1,[2,3]]) // '1,2,3'String(['koala',1]) //koala,1null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(1) // '1'
String(-1) // '-1'
String(0) // '0'
String(-0) // '0'
String(Math.pow(1000,10)) // '1e+30'
String(Infinity) // 'Infinity'
String(-Infinity) // '-Infinity'
String({}) // '[object Object]'
String([1,[2,3]]) // '1,2,3'
String(['koala',1]) //koala,1

Boolean

ToBoolean 运算符转换规则

除了下述 6 个值转换结果为 false,其他全部为 true:

  1. undefined

  2. null

  3. -0

  4. 0或+0

  5. NaN

  6. ''(空字符串)

假值以外的值都是真值。其中包括所有对象(包括空对象)的转换结果都是true,甚至连false对应的布尔对象new Boolean(false)也是true

Boolean(undefined) // falseBoolean(null) // falseBoolean(0) // falseBoolean(NaN) // falseBoolean('') // falseBoolean({}) // trueBoolean([]) // trueBoolean(new Boolean(false)) // trueundefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false

Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

js转换规则不同场景应用

面试官问:知道了具体转换成什么的规则,但是都在什么情况下发生什么样的转换呢?

什么时候自动转换为string类型

'2' + 1 // '21''2' + true // "2true"'2' + false // "2false"'2' + undefined // "2undefined"'2' + null // "2null"1 // '21'
'2' + true // "2true"
'2' + false // "2false"
'2' + undefined // "2undefined"
'2' + null // "2null"

//toString的对象var obj2 = { toString:function(){ return 'a' }}console.log('2'+obj2)//输出结果2a//常规对象var obj1 = { a:1, b:2}console.log('2'+obj1);//输出结果 2[object Object]//几种特殊对象'2' + {} // "2[object Object]"'2' + [] // "2"'2' + function (){} // "2function (){}"'2' + ['koala',1] // 2koala,1
var obj2 = {
toString:function(){
return 'a'
}
}
console.log('2'+obj2)
//输出结果2a

//常规对象
var obj1 = {
a:1,
b:2
}
console.log('2'+obj1);
//输出结果 2[object Object]

//几种特殊对象
'2' + {} // "2[object Object]"
'2' + [] // "2"
'2' + function (){} // "2function (){}"
'2' + ['koala',1] // 2koala,1

对下面'2'+obj2详细举例说明如下:

  1. 左边为string,ToPrimitive原始值转换后不发生变化

  2. 右边转化时同样按照ToPrimitive进行原始值转换,由于指定的type是number,进行ToPrimitive转化调用obj2.valueof(),得到的不是原始值,进行第三步

  3. 调用toString() return 'a'

  4. 符号两边存在string,而且是+号运算符则都采用String规则转换为string类型进行拼接

  5. 输出结果2a

对下面'2'+obj1详细举例说明如下:

  1. 左边为string,ToPrimitive转换为原始值后不发生变化

  2. 右边转化时同样按照ToPrimitive进行原始值转换,由于指定的type是number,进行ToPrimitive转化调用obj2.valueof(),得到{ a: 1, b: 2

  3. 调用toString() return [object Object]

  4. 符号两边存在string,而且是+号运算符则都采用String规则转换为string类型进行拼接

  5. 输出结果2[object Object]

代码中几种特殊对象的转换规则基本相同,就不一一说明,大家可以想一下流程。

注意:不管是对象还不是对象,都有一个转换为原始值的过程,也就是ToPrimitive转换,只不过原始类型转换后不发生变化,对象类型才会发生具体转换。

string类型转换开发过程中可能出错的点:

var obj = { width: '100'};obj.width + 20 // "10020"
width: '100'
};

obj.width + 20 // "10020"

预期输出结果120 实际输出结果10020

什么时候自动转换为Number类型

注意:null转为数值时为0,而undefined转为数值时为NaN。

判断等号也放在Number里面特殊说明

== 抽象相等比较与+运算符不同,不再是String优先,而是Nuber优先。 下面列举x == y的例子

  1. 如果x,y均为number,直接比较 没什么可解释的了

1 == 2 //false2 //false

  1. 如果存在对象,ToPrimitive() type为number进行转换,再进行后面比较

var obj1 = { valueOf:function(){ return '1' }}1 == obj1 //true//obj1转为原始值,调用obj1.valueOf()//返回原始值'1'//'1'toNumber得到 1 然后比较 1 == 1[] == ![] //true//[]作为对象ToPrimitive得到 '' //![]作为boolean转换得到0 //'' == 0 //转换为 0==0 //true
valueOf:function(){
return '1'
}
}
1 == obj1 //true
//obj1转为原始值,调用obj1.valueOf()
//返回原始值'1'
//'1'toNumber得到 1 然后比较 1 == 1
[] == ![] //true
//[]作为对象ToPrimitive得到 ''
//![]作为boolean转换得到0
//'' == 0
//转换为 0==0 //true

  1. 存在boolean,按照ToNumber将boolean转换为1或者0,再进行后面比较

//boolean 先转成number,按照上面的规则得到1 //3 == 1 false//0 == 0 true3 == true // false'0' == false //true
//3 == 1 false
//0 == 0 true
3 == true // false
'0' == false //true

4.如果x为string,y为number,x转成number进行比较

//'0' toNumber()得到 0 //0 == 0 true'0' == 0 //true
//0 == 0 true
'0' == 0 //true

什么时候进行布尔转换

条件部分的每个值都相当于false,使用否定运算符后,就变成了true

if ( !undefined && !null && !0 && !NaN && !'') { console.log('true');} // true//下面两种情况也会转成布尔类型expression ? true : false!! expressionundefined
&& !null
&& !0
&& !NaN
&& !''
) {
console.log('true');
} // true

//下面两种情况也会转成布尔类型
expression ? true : false
!! expression

js中的数据类型判断

面试官问:如何判断数据类型?怎么判断一个值到底是数组类型还是对象?

三种方式,分别为 typeof、instanceof 和 Object.prototype.toString()

typeof

通过 typeof操作符来判断一个值属于哪种基本类型。

typeof 'seymoe' // 'string'typeof true // 'boolean'typeof 10 // 'number'typeof Symbol() // 'symbol'typeof null // 'object' 无法判定是否为 nulltypeof undefined // 'undefined'typeof {} // 'object'typeof [] // 'object'typeof(() => {}) // 'function''seymoe' // 'string'
typeof true // 'boolean'
typeof 10 // 'number'
typeof Symbol() // 'symbol'
typeof null // 'object' 无法判定是否为 null
typeof undefined // 'undefined'

typeof {} // 'object'
typeof [] // 'object'
typeof(() => {}) // 'function'

上面代码的输出结果可以看出,

  1. null 的判定有误差,得到的结果 如果使用 typeof,null得到的结果是object

  2. 操作符对对象类型及其子类型,例如函数(可调用对象)、数组(有序索引对象)等进行判定,则除了函数都会得到 object 的结果。

综上可以看出typeOf对于判断类型还有一些不足,在对象的子类型和null情况下。

instanceof

通过 instanceof 操作符也可以对对象类型进行判定,其原理就是测试构造函数的  prototype 是否出现在被检测对象的原型链上。

[] instanceof Array // true({}) instanceof Object // true(()=>{}) instanceof Function // trueArray // true
({}) instanceof Object // true
(()=>{}) instanceof Function // true

注意:instanceof 也不是万能的。 举个例子:

let arr = []let obj = {}arr instanceof Array // truearr instanceof Object // trueobj instanceof Object // true
let obj = {}
arr instanceof Array // true
arr instanceof Object // true
obj instanceof Object // true

在这个例子中,arr 数组相当于 new Array() 出的一个实例,所以 arr.proto === Array.prototype,又因为 Array 属于 Object 子类型,即 Array.prototype.proto === Object.prototype,所以 Object 构造函数在 arr 的原型链上。所以 instanceof 仍然无法优雅的判断一个值到底属于数组还是普通对象。

还有一点需要说明下,有些开发者会说 Object.prototype.proto === null,岂不是说 arr instanceof null 也应该为 true,这个语句其实会报错提示右侧参数应该为对象,这也印证 typeof null 的结果为 object 真的只是Javascript中的一个 bug 。

Object.prototype.toString() 可以说是判定 Javascript 中数据类型的终极解决方法了,具体用法请看以下代码:

Object.prototype.toString.call({}) // '[object Object]'Object.prototype.toString.call([]) // '[object Array]'Object.prototype.toString.call(() => {}) // '[object Function]'Object.prototype.toString.call('seymoe') // '[object String]'Object.prototype.toString.call(1) // '[object Number]'Object.prototype.toString.call(true) // '[object Boolean]'Object.prototype.toString.call(Symbol()) // '[object Symbol]'Object.prototype.toString.call(null) // '[object Null]'Object.prototype.toString.call(undefined) // '[object Undefined]'Object.prototype.toString.call(new Date()) // '[object Date]'Object.prototype.toString.call(Math) // '[object Math]'Object.prototype.toString.call(new Set()) // '[object Set]'Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'Object.prototype.toString.call(new Map()) // '[object Map]'Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'// '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(() => {}) // '[object Function]'
Object.prototype.toString.call('seymoe') // '[object String]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'

Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'

我们可以发现该方法在传入任何类型的值都能返回对应准确的对象类型。用法虽简单明了,但其中有几个点需要理解清楚:

NaN相关总结

NaN的概念

NaN 是一个全局对象的属性,NaN 是一个全局对象的属性,NaN是一种特殊的Number类型

什么时候返回NaN (开篇第二道题也得到解决)

一些例子:

Infinity / Infinity; // 无穷大除以无穷大Math.sqrt(-1); // 给任意负数做开方运算'a' - 1; // 算数运算符与不是数字或无法转换为数字的操作数一起使用'a' * 1;'a' / 1;parseInt('a'); // 字符串解析成数字parseFloat('a');Number('a'); //NaN'abc' - 1 // NaNundefined + 1 // NaN//一元运算符(注意点)+'abc' // NaN-'abc' // NaNInfinity; // 无穷大除以无穷大
Math.sqrt(-1); // 给任意负数做开方运算
'a' - 1; // 算数运算符与不是数字或无法转换为数字的操作数一起使用
'a' * 1;
'a' / 1;
parseInt('a'); // 字符串解析成数字
parseFloat('a');

Number('a'); //NaN
'abc' - 1 // NaN
undefined + 1 // NaN
//一元运算符(注意点)
+'abc' // NaN
-'abc' // NaN

误区

toString和String的区别

toString()可以将数据都转为字符串,但是null和undefined不可以转换。

console.log(null.toString())//报错 TypeError: Cannot read property 'toString' of nullconsole.log(undefined.toString())//报错 TypeError: Cannot read property 'toString' of undefinednull.toString())
//报错 TypeError: Cannot read property 'toString' of null

console.log(undefined.toString())
//报错 TypeError: Cannot read property 'toString' of undefined

toString()括号中可以写数字,代表进制

二进制:.toString(2);

八进制:.toString(8);

十进制:.toString(10);

十六进制:.toString(16);

String()可以将null和undefined转换为字符串,但是没法转进制字符串

console.log(String(null));// nullconsole.log(String(undefined));// undefinedString(null));
// null
console.log(String(undefined));
// undefined




640?wx_fmt=png

Javascript中的闭包这一篇就够了

JS中的for循环——你可能不知道的点。

一道面试题引发的事件循环深入思考

公司要求会使用框架vue,面试题会被问及哪些?


640?wx_fmt=jpeg


觉得本文对你有帮助?请分享给更多人

640?wx_fmt=jpeg


真的对你有帮助点个在看哦,亲!


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 本文讨论了微软的STL容器类是否线程安全。根据MSDN的回答,STL容器类包括vector、deque、list、queue、stack、priority_queue、valarray、map、hash_map、multimap、hash_multimap、set、hash_set、multiset、hash_multiset、basic_string和bitset。对于单个对象来说,多个线程同时读取是安全的。但如果一个线程正在写入一个对象,那么所有的读写操作都需要进行同步。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • Vue3中setup函数的参数props和context配置详解
    本文详细介绍了Vue3中setup函数的参数props和context的配置方法,包括props的接收和配置声明,以及未通过props进行接收配置时的输出值。同时还介绍了父组件传递给子组件的值和模板的相关内容。阅读本文后,读者将对Vue3中setup函数的参数props和context的配置有更深入的理解。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • MPLS VP恩 后门链路shamlink实验及配置步骤
    本文介绍了MPLS VP恩 后门链路shamlink的实验步骤及配置过程,包括拓扑、CE1、PE1、P1、P2、PE2和CE2的配置。详细讲解了shamlink实验的目的和操作步骤,帮助读者理解和实践该技术。 ... [详细]
  • 本文介绍了如何在Azure应用服务实例上获取.NetCore 3.0+的支持。作者分享了自己在将代码升级为使用.NET Core 3.0时遇到的问题,并提供了解决方法。文章还介绍了在部署过程中使用Kudu构建的方法,并指出了可能出现的错误。此外,还介绍了开发者应用服务计划和免费产品应用服务计划在不同地区的运行情况。最后,文章指出了当前的.NET SDK不支持目标为.NET Core 3.0的问题,并提供了解决方案。 ... [详细]
author-avatar
落叶野
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有