经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
ES2015+ 常用新特性一口气看个够
来源:cnblogs  作者:by.Genesis  时间:2021/6/28 9:34:01  对本文有异议

ES2015 也叫 ES6,区别只是一个是以发布的年份来命名,一个是以版本号来命名

从那以后组织每年都会发布一个新版本,根据这个规则,ES2016 === ES7... ES2020 === ES11

但通常我习惯将 ES2015 及其后续版本统称为 ES2015+

变量声明

ES2015 增加了两个声明变量标识符的关键字,letconst,两者都支持块级作用域,并且在声明之前不能访问

凡是不需要重新赋值的变量标识符都可以使用 const 关键字来声明

其余需要重新赋值的变量就使用 let 关键字来声明,像循环计数器之类的

  1. {
  2. const arr = ['a', 'b', 'c', 'd', 'e']
  3. const length = arr.length
  4. for (let i = 0; i < length; i++) {
  5. //
  6. }
  7. }
  8. // 块之外无法访问

对象字面量

对象字面量的简写形式以及计算属性

  1. const foo = 1
  2. const obj = {
  3. foo, // 属性简写,等同于 foo: foo,
  4. bar() {
  5. // 方法简写
  6. },
  7. // 计算属性
  8. ['na' + 'me']: 'by.Genesis',
  9. __proto__: 原型
  10. }

这些特性都可以简化原本的代码

__proto__ 用来 get/set 原型,不过并不推荐使用,应该使用 Object.getPrototypeOf(o)Object.setPrototypeOf(o, proto)

箭头函数

箭头(=>)就是一种函数简写方式,同时提供一些有用的特性

  1. // 当函数有且仅有一个参数的时候可以省略参数的圆括号
  2. ;[1, 2, 3].forEach(item => { console.log(item) })
  3. // 当函数体内只有一条语句的时候可以省略函数体的花括号,同时隐式返回该条语句
  4. const sum = (x, y) => x + y
  5. // 如果隐式返回的是一个对象字面量,为了消除歧义,可以使用一对圆括号包裹对象字面量
  6. const pos = (x, y) => ({ x: x + 1, y: y * 2 })
  7. // 词法 this
  8. const obj = {
  9. name: 'by.Genesis',
  10. showName() {
  11. setTimeout(() => {
  12. console.log(this.name) // obj.showName() this === obj
  13. }, 300)
  14. }
  15. }
  16. // 立即执行箭头函数表达式
  17. ;(() => {
  18. alert(101)
  19. })()

class

类(class)就是传统的构造函数基于原型继承的语法糖

  1. class Person {
  2. constructor(name, age) {
  3. this.name = name
  4. this.age = age
  5. }
  6. update() {
  7. // 方法
  8. }
  9. }
  10. // 继承
  11. class Student extends Person {
  12. constructor(name, age, grade) {
  13. super(name, age)
  14. this.grade = grade
  15. }
  16. update() {
  17. // 调用父类的方法
  18. super.update()
  19. }
  20. get foo() {
  21. // getter
  22. }
  23. set foo() {
  24. // setter
  25. }
  26. static baz() {
  27. // 静态方法通过 Student.baz() 调用
  28. }
  29. }
  30. const s1 = new Student('by.Genesis', 20, 2)

同函数一样,类也可以作为表达式赋值给一个变量,或者作为参数传给函数,甚至从函数中返回

  1. const Person = class {
  2. //
  3. }

无论是用类声明还是表达式,都需要先定义,然后再使用,不会提升,不会提升

Symbol

符号(Symbol)是一种新的原始类型,其没有字面量形式

符号可以分为3类,普通符号,全局符号和众所周知的符号(well-known Symbol)

  1. // 创建 Symbol,不需要 new
  2. // 传入的参数作为该 Symbol 的描述符
  3. const name = Symbol('name')
  4. // 将 Symbol 用作对象的 key
  5. // 只能使用可计算属性名的方式
  6. const o = {
  7. [name]: 'by.Genesis'
  8. }
  9. // 通过 typeof 操作符判断值类型
  10. typeof Symbol('name') === 'symbol'
  11. // 符号值是唯一的,就算在创建时传入了相同的参数,得到的符号也不是同一个
  12. Symbol('name') !== Symbol('name')
  13. // 不过在全局符号注册表中同一个 key 返回的是同一个符号
  14. Symbol.for('name') === Symbol.for('name')
  15. // 获取符号描述符
  16. Symbol('name').description === 'name'

对象的符号属性无法通过传统的方法遍历出来,需要的时候可以使用 Object.getOwnPropertySymbols() 方法获取参数对象中所有符号属性组成的数组

  1. Object.getOwnPropertySymbols(o) // [Symbol(name)]

除此之外,还有一些 众所周知的符号(well-known Symbol),这类符号的作用是暴露一些 JavaScript 内部操作

  1. // 当数组作为 concat 参数时,默认会被展开
  2. const arr = [4, 5, 6]
  3. ;[1, 2, 3].concat(arr) // [1, 2, 3, 4, 5, 6]
  4. // 可以修改此行为让数组参数不展开
  5. arr[Symbol.isConcatSpreadable] = false
  6. ;[1, 2, 3].concat(arr) // [1, 2, 3, [4, 5, 6]]
  7. // 也可以让类数组对象展开
  8. ;[1, 2, 3].concat({
  9. [Symbol.isConcatSpreadable]: true,
  10. length: 3,
  11. 0: 4,
  12. 1: 5,
  13. 2: 6
  14. }) // [1, 2, 3, 4, 5, 6]

Promise

Promise 主要用来表示一个未来值

  1. // 创建一个 promise
  2. const p = new Promise((resolve, reject) => {
  3. setTimeout(resolve, 3000, 'by.Genesis')
  4. })
  5. // promise resolve 时执行
  6. p.then(res => {
  7. console.log(res) // 'by.Genesis'
  8. })
  9. // 总是会执行,无论 resolve 还是 reject
  10. p.finally(() => console.log('finally'))
  11. // 立即创建一个 fulfilled 的 promise
  12. const p2 = Promise.resolve('101真狗')
  13. // 立即创建一个 rejected 的 promise
  14. const p3 = Promise.reject(404)
  15. // promise reject 时执行
  16. p3.catch(err => {
  17. console.log(err) // 404
  18. })
  19. // 等待一组 promise 全部 resolve
  20. // 一旦有一个为 rejected 则立即 reject
  21. Promise.all([p2, p3])
  22. // 获取一组 promise 中最快的那一个,无论 resolve 还是 reject
  23. Promise.race([p2, p3])
  24. // 等待一组 promise 全部 settled,无论 resolve 还是 reject
  25. Promise.allSettled([p2, p3])
  26. // 获取一组 promise 中最快 resolve 的那一个
  27. // 只有当全部都为 rejected 的时候 reject
  28. Promise.any([p2, p3])

迭代器和生成器

当一个对象拥有一个 next 方法,并且调用该方法时可以得到一个包含 donevalue 两个属性的结果对象,那么这就是一个迭代器(Iterator)

  1. iterator = {
  2. next() {
  3. return {
  4. done: false,
  5. value: 10
  6. }
  7. }
  8. }

其中 done 为 Boolean 类型,表示该迭代器是否已经迭代完毕

生成器(Generator)是一种特殊函数,声明的时候在 function 关键字和函数名中间多了个星号(*)

生成器内通过 yield 关键字返回值

  1. function *g() {
  2. yield 1
  3. yield 2
  4. yield 3
  5. }
  6. // 调用生成器可以得到一个迭代器
  7. iterator = g()
  8. // 调用迭代器的 next 方法执行生成器内部代码并得到结果对象
  9. iterator.next() // { value: 1, done: false }

当一个对象具有特殊的符号 [Symbol.iterator] 方法,并且该方法返回一个迭代器的时候,那么这个对象就是一个可迭代对象(Iterable)

  1. iterable = {
  2. *[Symbol.iterator]() { // 这里同时使用了对象方法简写,计算属性以及生成器
  3. yield 1
  4. yield 2
  5. yield 3
  6. }
  7. }

String,Array,Map,Set,NodeList 等等都是可迭代对象,迭代器自身也是可迭代对象,迭代器的 [Symbol.iterator] 方法返回自身

生成器可以通过 yield* 委托给其它可迭代对象

  1. iterable = {
  2. *[Symbol.iterator]() {
  3. yield 1
  4. yield* [2, 3]
  5. }
  6. }

可迭代对象可以使用 for of 语法遍历

  1. for (let v of iterable) {
  2. console.log(v) // 1 2 3
  3. }

异步函数

异步函数(Async function)在函数前面添加一个 async 关键字,其内部可以使用 await 关键字

await 表达式可以将其后面的 Promise resolve 的值提取出来

  1. async function fn() {
  2. const x = await Promise.resolve(101)
  3. return x
  4. }
  5. // 执行异步函数也返回一个 Promise
  6. fn().then(res => console.log(res)) // 101

异步函数就是生成器和 Promise 语法糖

异步迭代

当一个迭代器的 next 方法返回一个 Promise,并且该 Promise resolve 后可以得到一个包含 donevalue 两个属性的结果对象,那么这个迭代器就是一个异步迭代器(Async Iterator)

  1. asyncIterator = {
  2. next() {
  3. return Promise.resolve({
  4. done: false,
  5. value: 10
  6. })
  7. }
  8. }

将异步函数和生成器结合到一起,就是异步生成器(Async Generator),其内部可以同时使用 awaityield 关键字

  1. async function *g() {
  2. yield 1
  3. const a = await new Promise(resolve => {
  4. setTimeout(resolve, 3000, 2)
  5. })
  6. yield a
  7. yield Promise.resolve(a + 1)
  8. }
  9. // 执行异步生成器返回一个异步迭代器
  10. asyncIterator = g()

当一个对象具有特殊的符号 [Symbol.asyncIterator] 方法,并且该方法返回一个异步迭代器的时候,那么这个对象就是一个异步可迭代对象(Async Iterable)

  1. asyncIterable = {
  2. async *[Symbol.asyncIterator]() {
  3. yield 1
  4. const a = await new Promise(resolve => {
  5. setTimeout(resolve, 3000, 2)
  6. })
  7. yield a
  8. yield Promise.resolve(a + 1)
  9. }
  10. }

异步可迭代对象使用 for await of 语法遍历

  1. ;(async () => {
  2. for await (let v of asyncIterable) {
  3. console.log(v) // 1 2 3
  4. }
  5. })()

await 应该放到异步函数中

Map & Set

Map 是包含键值对(key-value)的有序集合,其中 key 可以是 任意类型 (是任意类型,包括引用类型甚至 DOM 元素都可以作为 Map 的 key)

  1. // 创建一个 Map
  2. const m = new Map([['a', 1], ['b', 2]])
  3. // 添加值,如果已存在就是修改
  4. m.set('c', 3)
  5. // 获取值
  6. m.get('b') // 2
  7. // 判断值
  8. m.has('b') // true
  9. // 获取长度
  10. m.size // 3
  11. // 删除值
  12. m.delete('b')
  13. m.has('b') // false
  14. m.size // 2
  15. // 清空
  16. m.clear()
  17. m.size // 0

Set 就是一组不重复值的有序集合

  1. // 创建一个 Set
  2. const s = new Set([1, 2])
  3. // 添加值
  4. s.add(3)
  5. s.add(1) // 该值已存在,集合保持不变
  6. // 除了没有获取值的方法,剩下的和 Map 一致

Map 和 Set 都可以通过 forEach 方法遍历其中的值

  1. set.forEach(handler => handler())

可以把 Set 看作是 key 和 value 为同一个值的特殊 Map,也可以认为 Set 是只有 key

Map 和 Set 遍历顺序和添加时的顺序是一致的,因此都是有序集合

WeakSet & WeakMap

弱版本只能用来存放引用类型

WeakMap 只对其 key 有类型要求,而 value 可以是任意类型

弱版本不是可迭代对象,不能遍历,也没有 size 属性,也不能用 clear 方法清空集合,只具备最基本的添加,删除等方法

弱版本是弱引用,其优势就是利于垃圾回收

解构

按照一定模式从对象或者可迭代对象中提取值

  1. // 可迭代对象解构
  2. // let 声明对变量 a, b, c 都生效
  3. let [a, b, c] = [1, 2]
  4. a === 1
  5. b === 2
  6. c === undefined
  7. // 交换值
  8. ;[a, b] = [b, a] // a = 2, b = 1
  9. // 数组可以解构任意可迭代对象,包括字符串
  10. // 解构时也可以跳过一些不需要的值
  11. ;[, , c] = '123' // c = '3'
  12. // 对象属性解构
  13. { x, y, z: { w } } = { x: 3, y: 4, z: { w: 5 } }
  14. x === 3
  15. y === 4
  16. w === 5

默认值

在声明函数参数或者解构的时候都可以指定一个默认值,当对应的值为 undefined 的时候,就会使用这个默认值

  1. // 函数参数默认值
  2. const sum = (x, y = 4) => x + y
  3. sum(3) === 7
  4. // 迭代器解构的默认值
  5. const [a, b = 2] = [1]
  6. a === 1
  7. b === 2
  8. // 对象解构的默认值
  9. const { name = 'by.Genesis' } = { age: 18 }
  10. name === 'by.Genesis'
  11. // 函数参数和对象解构一起使用
  12. const fn = ({ height = 18, width = 36 } = {}) => {}
  13. fn() // height = 18, width = 36
  14. fn({ height: 36 }) // height = 36, width = 36
  15. fn({ height: 36, width: 18 }) // height = 36, width = 18

Spread & Rest

可迭代对象均可使用展开(Spread)运算符(...)展开为独立的值,这些值可以作为函数的参数或放到数组中

  1. // 展开可迭代对象作为函数参数
  2. Math.max(...[5, 20, 10]) === 20
  3. // 展开可迭代对象到一个数组中
  4. const arr = [...new Set([1, 2, 2, 3])] // [1, 2, 3]
  5. // 展开可迭代对象到一个数组中
  6. const newArr = [1, ...[2, 3], ...'45'] // [1, 2, 3, '4', '5']

而普通对象也可以展开其属性,放到另一个对象中,这和 Object.assign 方法作用类似

  1. // 展开对象属性到另一个对象中
  2. const o = {
  3. a: 1,
  4. ...{
  5. b: 2,
  6. c: 3
  7. }
  8. } // o = { a: 1, b: 2, c: 3 }
  9. const o2 = Object.assign({ a: 1 }, { b: 2, c: 3 })

和展开相反,多个值可以使用收集(Rest)运算符(...)打包成一个数组,或者多个对象属性打包成一个对象

  1. // 函数剩余参数打包成一个数组
  2. const fn = (x, ...y) => y.length
  3. fn(2, 5, 7, 11) === 3 // x = 2, y = [5, 7, 11]
  4. // 可迭代对象剩余值打包成一个数组
  5. const [a, ...b] = new Set([2, 5, 7, 11])
  6. a === 2
  7. // b = [5, 7, 11]
  8. // 对象剩余属性打包成一个对象
  9. const { a, ...o } = {
  10. a: 1,
  11. b: 2,
  12. c: 3
  13. } // o = { b: 2, c: 3 }

收集运算符只能用于最后一个标识符

模板字符串

模板字符串就是功能更强大的字符串,它支持多行以及插值

在模板字符串中插值使用 ${} 花括号里面可以插入表达式,表达式甚至可以是另一个模板字符串

  1. const str = `
  2. <ul>
  3. ${lists.map(item => {
  4. return `<li>${item.user} is ${item.age} years old.</li>`
  5. }).join('')}
  6. </ul>
  7. `

标签模板

  1. const username = 'by.Genesis'
  2. const age = 18
  3. const str = tag`${username} is ${age} years old.`
  4. // tag 就是一个函数
  5. // 第一个参数为字符串按插值分割而成的数组
  6. // 后面的参数为插值表达式的值
  7. // 可以自行处理字符串逻辑
  8. function tag(template, ...substitutions) {
  9. console.log(template) // ['', ' is ', ' years old.']
  10. console.log(substitutions) // ['by.Genesis', 18]
  11. return substitutions[0] + template[1] + 'handsome'
  12. }
  13. str === 'by.Genesis is handsome'

代理和反射

代理(Proxy)就是为一个目标对象生成一个代理,当对这个代理对象执行一些操作的时候,就会触发对应的拦截器,在拦截器中可以自行定义操作和返回的值,或者用反射(Reflect)执行元操作,每个代理方法都有对应的反射方法

  1. const obj = {}
  2. const proxy = new Proxy(obj, {
  3. get(target, key) {
  4. // 属性取值
  5. if (key === 'name') {
  6. // 自定义返回值
  7. return 'by.Genesis'
  8. } else {
  9. // 用反射还原操作
  10. return Reflect.get(target, key)
  11. }
  12. },
  13. set() { 属性赋值 },
  14. has() { in 操作符 },
  15. deleteProperty() { 删除属性 },
  16. getPrototypeOf() { 获取原型 },
  17. setPrototypeOf() { 设置原型 },
  18. defineProperty() { Object.defineProperty },
  19. getOwnPropertyDescriptor() { Object.getOwnPropertyDescriptor },
  20. preventExtensions() { Object.preventExtensions },
  21. isExtensible() { Object.isExtensible },
  22. ownKeys() { Object.keys, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.assign },
  23. enumerable() { for in 循环 },
  24. apply() { 函数普通调用 },
  25. construct() { new 方式调用函数 }
  26. })
  27. proxy.name === 'by.Genesis'
  28. obj.name === undefined

以上是这些拦截器以及对应的触发条件

逻辑运算

Nullish coalescing Operator

JavaScript 里面的假值(Falsy)有 null, undefined, 0, '', NaN, false,除假值外都为真值(Truthy)

而空值(Nullish)只有 nullundefined,当该运算符左侧为空值时返回右侧

  1. null ?? 1 // 1
  2. undefined ?? 1 // 1
  3. 0 ?? 1 // 0
  4. 0 || 1 // 1

Optional chaining

链式操作时,当中间某个值是 null 或者 undefined 就会报错,而这个操作符可以让链式操作更安全

  1. const o = {}
  2. o.p.q // Uncaught TypeError: Cannot read property 'q' of undefined
  3. o.p?.q // undefined

逻辑赋值

  1. // 逻辑或赋值
  2. a ||= b // 当 a 为假值时赋值
  3. a || a = b
  4. // 逻辑与赋值
  5. a &&= b // 当 a 为真值时赋值
  6. a && a = b
  7. // 逻辑空赋值
  8. a ??= b // 当 a 为空值时赋值
  9. a ?? a = b

模块

在 ES 模块(Modules) 问世之前,已经有各种定义模块的规范了,比如 AMD,CommonJS 等,ES 模块提供语言层面的支持

使用 export 关键字导出模块,使用 import 关键字导入模块

  1. // 可以同时导出多个具名模块
  2. export const sum = (x, y) => x + y
  3. export const name = 'by.Genesis'
  4. // 导入具名模块时名称必须和导出时一致
  5. // 另外可以使用 as 关键字指定别名
  6. import { sum, name as username } from './example.js'
  7. // 也可以先声明再导出,导出时也可以使用 as 关键字指定别名
  8. // 指定别名后,导入的时候就需要使用这个别名了
  9. export { sum, name as username }
  10. // 一个模块只允许有一个默认导出
  11. export default { name: 'by.Genesis' }
  12. // 导入默认模块可以任意命名
  13. import o from './example.js'
  14. // 同时导入默认模块和具名模块
  15. import o, { sum } from './example.js'
  16. // 全部导入并指定一个别名,所有模块都会成为这个别名的属性
  17. import * as m from './example.js'
  18. m.default // 默认模块是 default 属性
  19. m.sum // 具名模块就是自己的名字
  20. // 从另一个模块中导入再导出
  21. export * from './another.js'
  22. // 直接导入,不指定任何命名
  23. import './example.js'
  24. // 甚至还可以不导出任何东西,仅仅只是执行一些代码而已

数字

  1. // 非无穷
  2. Number.isFinite(101) // true
  3. Number.isFinite(NaN) // false
  4. // 安全整数
  5. Number.isSafeInteger(Number.MAX_SAFE_INTEGER) === true
  6. // 二进制数字 0b 开头(binary),八进制数字 0o 开头(octonary)
  7. // 前面是数字零,后面是字母,大小写都可以,但是为了便于区别,建议使用小写
  8. 0b1001 === 0o11
  9. // 新增指数运算符(两个乘号),主要是给指数运算一个正儿八经的运算符,而不是去调用方法
  10. 2 ** 3 === Math.pow(2, 3)
  11. 2 ** 3 === 2 * 2 * 2
  12. // 数字中可以添加下划线分割数字,增加数字的可读性
  13. 123_4567_8889 === 12345678889

BigInt

大整数用来表示安全整数范围之外的整数,数字字面量后面添加一个 n

  1. const num = 9007199254740992n
  2. typeof num === 'bigint'

字符串方法

  1. // 重复几次
  2. 'xyz'.repeat(3) // 'xyzxyzxyz'
  3. // 判断开头
  4. 'http://xyz.io/'.startsWith('http') === true
  5. // 判断结尾
  6. 'avator.jpg'.endsWith('.jpg') === true
  7. // 判断包含
  8. 'xyz'.includes('yz') === true
  9. // 首尾填充,第一个参数为填充后长度,第二个参数为填充字符串
  10. '2'.padStart(2, '0') // '02'
  11. // 字符串已经达到长度则不填充
  12. '12'.padStart(2, '0') // '12'
  13. // 首尾去空白
  14. ' xyz '.trimStart() === 'xyz '
  15. ' xyz '.trimLeft() === 'xyz '
  16. ' xyz '.trimEnd() === ' xyz'
  17. ' xyz '.trimRight() === ' xyz'
  18. // 字符串全部替换
  19. 'xyx'.replaceAll('x', 'z') === 'zyz'
  20. // replace 方法只会替换一次
  21. 'xyx'.replace('x', 'z') === 'zyx'
  22. // 多次替换需要使用全局正则
  23. 'xyx'.replace(/x/g, 'z') === 'zyz'

数组方法

静态方法

  1. // 创建只有一个数字值的数组
  2. Array.of(3) // [3]
  3. // 构造函数只会创建长度为传入数字的稀疏数组
  4. new Array(3) // [empty × 3]
  5. // 将类数组或者可迭代对象转换为数组
  6. Array.from($('.modal'))
  7. Array.from({
  8. length: 5
  9. }).map((item, index) => index + 1) // [1, 2, 3, 4, 5]

实例方法

  1. // 填充数组,可以传入一个起始索引
  2. new Array(3).fill('x', 1) // [empty, 'x', 'x']
  3. // 包含判断,可以传入一个起始索引
  4. [NaN, 1, 2].includes(NaN) === true
  5. [NaN, 1, 2].includes(NaN, 1) === false
  6. // 数组中查找元素,返回找到的元素
  7. [1, 2, NaN].find(item => item !== item) // NaN
  8. // 查找元素索引,返回找到元素的索引
  9. ['x', 'y', NaN].findIndex(item => item !== item) === 2
  10. // 复制到指定位置
  11. // 这是一个变异方法,直接在原数组上进行修改
  12. // Array#copyWithin(target, start, ?end)
  13. [1, 2, 3, 4, 5, 6].copyWithin(3, 0, 3) // [1, 2, 3, 1, 2, 3]
  14. // 扁平化
  15. [1, [2, [3, [4]]]].flat(2) // [1, 2, 3, [4]]
  16. // 不知道到底有多少层?那就无限大好了
  17. [1, [2, [3, [4]]]].flat(Infinity) // [1, 2, 3, 4]
  18. // flatMap 相当于 map + flat(1)
  19. // 会自动扁平化一层
  20. // 这个方法可以让返回的数组变长,这是普通 map 无法合理办到的
  21. [1, 2, 3, 4].flatMap(x => [x, x * x]) // [1, 1, 2, 4, 3, 9, 4, 16]

其它方法

  1. // 对象比较
  2. Object.is(NaN, NaN) // true
  3. Object.is(0, -0) // false
  4. // Object.keys() 补充方法
  5. Object.values({ x: 1, y: 2 }) // [1, 2]
  6. Object.entries({ x: 1, y: 2 }) // [['x', 1], ['y', 2]]
  7. Object.fromEntries([['x', 1], ['y', 2]]) // { x: 1, y: 2 }
  8. // 获取对象全部自身属性描述
  9. Object.getOwnPropertyDescriptors({ x: 1, y: 2 }) // { x: { value: 1, writable: true, enumerable: true, configurable: true }, y: { value: 2, ... } }

原文链接:http://www.cnblogs.com/xyzhanjiang/p/13530369.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号