Skip to content

ES6

ES6 是 JavaScript 现代化的里程碑版本,引入了大量新特性提升了开发效率。ES6 面试题的考察重点包括

  • let 和 const 的作用域
  • 箭头函数
  • 解构赋值
  • 模板字符串
  • Promise
  • 迭代器与生成器
  • 模块化导入与导出
  • 类与继承
  • Set 和 Map 数据结构的使用。

1. let、const、var的区别

(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

  • 内层变量可能覆盖外层变量
  • 用来计数的循环变量泄露为全局变量

(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。

(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

区别varletconst
是否有块级作用域×✔️✔️
是否存在变量提升✔️××
是否添加全局属性✔️××
能否重复声明变量✔️××
是否存在暂时性死区×✔️✔️
是否必须设置初始值××✔️
能否改变指针指向✔️✔️×

2. const对象的属性可以修改吗

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。

但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

3. 如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以 new 一个箭头函数会报错。。

原理: new操作符的实现步骤如下:

  1. 创建一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 构造指向构造函数中的代码函数中的this指向该对象(也就是为这个对象添加属性和方法)
  4. 返回新的对象

所以,上面的第二、三步,箭头函数都是没有办法执行的。

4. 箭头函数与普通函数的区别

  1. 定义方式:箭头函数使用箭头(=>)语法,省略了 function 关键字。括号与箭头之间不能换行
  2. 参数处理:如果没有参数,直接写空括号;如果只有一个参数,箭头函数可以省略括号;如果多个参数,需要括号包裹,逗号隔离参数
  3. 函数体:如果函数体只有一条语句,箭头函数可以省略花括号和 return 关键字。如果箭头函数的函数体只有一条语句并且不需要返回值(最常见是调用一个函数),可以给这条语句前面加一个 void 关键字。
  4. 箭头函数没有自己的 this 对象,而是从其作用域链的上一层继承 this。‌箭头函数中 this 的指向在它被定义的时候就已经确定了。
  5. 箭头函数的 this 指向不能通过 call、apply、bind 等方法改变。
  6. 箭头函数不可以当作构造函数,不可以对箭头函数使用 new 命令。如果使用 new 关键字调用则会抛出错误。
  7. 其他特性:箭头函数没有自己的 arguments 对象,没有原型 prototype,不能用作 Generator 函数,不能使用 yeild 关键字,没有 super。

5. 箭头函数的this指向哪⾥?

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。

可以⽤Babel理解⼀下箭头函数:

js
// ES6 
const obj = { 
  getArrow() { 
    return () => { 
      console.log(this === obj); 
    }; 
  } 
}

转化后:

js
// ES5,由 Babel 转译
var obj = { 
   getArrow: function getArrow() { 
     var _this = this; 
     return function () { 
        console.log(_this === obj); 
     }; 
   } 
};

6. 扩展运算符的作用及使用场景

(1)对象扩展运算符

对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

js
let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }

上述方法实际上等价于:

js
let bar = { a: 1, b: 2 };
let baz = Object.assign({}, bar); // { a: 1, b: 2 }

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

js
let bar = {a: 1, b: 2};
let baz = {...bar, ...{a:2, b: 4}};  // {a: 2, b: 4}

利用上述特性就可以很方便的修改对象的部分属性。在redux中的reducer函数规定必须是一个纯函数reducer中的state对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。

需要注意:扩展运算符对对象实例的拷贝属于浅拷贝

(2)数组扩展运算符

数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

js
console.log(...[1, 2, 3])
// 1 2 3
console.log(...[1, [2, 3, 4], 5])
// 1 [2, 3, 4] 5

下面是数组的扩展运算符的应用:

  • 将数组转换为参数序列
js
function add(x, y) {
  return x + y;
}
const numbers = [1, 2];
add(...numbers) // 3
  • 复制数组
js
const arr1 = [1, 2];
const arr2 = [...arr1];

要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。

  • *合并数组 如果想在数组内合并数组,可以这样:
js
const arr1 = ['two', 'three'];
const arr2 = ['one', ...arr1, 'four', 'five'];
// ["one", "two", "three", "four", "five"]
  • 扩展运算符与解构赋值结合起来,用于生成数组
js
const [first, ...rest] = [1, 2, 3, 4, 5];first // 1rest  // [2, 3, 4, 5]

需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

js
const [...rest, last] = [1, 2, 3, 4, 5];         // 报错const [first, ...rest, last] = [1, 2, 3, 4, 5];  // 报错
  • 将字符串转为真正的数组
js
[...'hello']    // [ "h", "e", "l", "l", "o" ]
  • 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组

比较常见的应用是可以将某些数据结构转为数组:

js
// arguments对象
function foo() {
  const args = [...arguments];
}

用于替换es5中的Array.prototype.slice.call(arguments)写法。

  • 使用Math函数获取数组中特定的值
js
const numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
Math.max(...numbers); // 9

7. 对对象与数组的解构的理解

解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。

1.数组的解构

在解构数组时,以元素的位置为匹配条件来提取想要的数据的:

js
const [a, b, c] = [1, 2, 3]

最终,a、b、c分别被赋予了数组第0、1、2个索引位的值:

数组里的0、1、2索引位的元素值,精准地被映射到了左侧的第0、1、2个变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取:

2.对象的解构

对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:

js
const stu = {   
	name: 'Bob',  
	age: 24 
}

假如想要解构它的两个自有属性,可以这样:

js
const { name, age } = stu

注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的:

js
const { age, name } = stu

8. 如何提取高度嵌套的对象里的指定属性?

有时会遇到一些嵌套程度非常深的对象:

js
const school = {
   classes: {
      stu: {
         name: 'Bob',
         age: 24,
      }
   }
}

像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它:

js
const { name } = school

显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构:

js
const { classes } = school
const { stu } = classes
const { name } = stu
name // 'Bob'

但是还有一种更标准的做法,可以用一行代码来解决这个问题:

js
const { classes: { stu: { name } }} = school
       
console.log(name)  // 'Bob'

可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。

9. 对 rest 参数的理解

扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组

js
function mutiple(...args) {
  let result = 1;
  for (var val of args) {
    result *= val;
  }
  return result;
}
mutiple(1, 2, 3, 4) // 24

这里,传入 mutiple 的是四个分离的参数,但是如果在 mutiple 函数里尝试输出 args 的值,会发现它是一个数组:

js
function mutiple(...args) {
  console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]

这就是 … rest运算符的又一层威力了,它可以把函数的多个入参收敛进一个数组里。这一点经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。

11. ES6中模板语法与字符串处理

ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事情:

js
var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1]

仅仅几个变量,写了这么多加号,还要时刻小心里面的空格和标点符号有没有跟错地方。但是有了模板字符串,拼接难度直线下降:

js
var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`

字符串不仅更容易拼了,也更易读了,代码整体的质量都变高了。这就是模板字符串的第一个优势——允许用${}的方式嵌入变量。但这还不是问题的关键,模板字符串的关键优势有两个:

  • 在模板字符串中,空格、缩进、换行都会被保留
  • 模板字符串完全支持“运算”式的表达式,可以在${}里完成一些计算

基于第一点,可以在模板字符串里无障碍地直接写 html 代码:

js
let list = `
	<ul>
		<li>列表项1</li>
		<li>列表项2</li>
	</ul>
`;
console.log(message); // 正确输出,不存在报错

基于第二点,可以把一些简单的计算和调用丢进 ${} 来做:

js
function add(a, b) {
  const finalString = `${a} + ${b} = ${a+b}`
  console.log(finalString)
}
add(1, 2) // 输出 '1 + 2 = 3'

除了模板语法外, ES6中还新增了一系列的字符串方法用于提升开发效率:

(1)存在性判定

在过去,当判断一个字符/字符串是否在某字符串中时,只能用 indexOf > -1 来做。现在 ES6 提供了三个方法:includesstartsWithendsWith,它们都会返回一个布尔值来告诉你是否存在。

  • includes:判断字符串与子串的包含关系:
js
const son = 'haha' 
const father = 'xixi haha hehe'
father.includes(son) // true
  • startsWith:判断字符串是否以某个/某串字符开头:
js
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
  • endsWith:判断字符串是否以某个/某串字符结尾:
js
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true

(2)自动重复

可以使用 repeat 方法来使同一个字符串输出多次(被连续复制多次):

js
const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3) 
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;

12.ES6 有哪些新特性?

  1. let 和 const:具有块级作用域,let 用来声明变量可重新赋值,const 用来声明常量不可再次赋值。
  2. 箭头函数:新的函数声明方式,语法简洁。
  3. 模版字符串:字符串插值功能,可定义多行字符串。
  4. 解构赋值:是一种 JavaScript 表达式,‌它允许从数组或对象中提取属性或值,‌并将这些值赋给其他变量。‌
  5. 默认参数:函数参数可设置默认值。
  6. 扩展运算符:可以将数组展开为逗号分隔的参数序列,或者合并多个对象或数组。
  7. 类与模块:通过 class 关键字定义类,使用 import 和 export 来导入和导出模块。
  8. Promise:用于处理异步操作。
  9. Symbol 和迭代器:提供了一种新的原始数据类型和自定义迭代行为的方式。
  10. 新的数据结构:Map、Set。
  11. 其他:对象属性简写,属性和方法简写,提升了 JavaScript 的编码效率和可读性。

13.ES6 新增的 Symbol 基础数据类型有什么作用?

  1. 用作对象属性名:可以确保属性名称的唯一性。
  2. 防止属性冲突:可以避免在不同模块不同库之间发生属性命名冲突。
  3. 内置 Symbol:ES6 提供了一些内置的 Symbol,如 Symbol.iteratorSymbol.match 等。
  4. 全局注册表:用 Symbol.for() 方法创建的 Symbol 会被放入一个全局注册表中。通过 Symbol.keyFor() 可以在注册表中检测值是否存在。
  5. 迭代器和生成器:Symbol 可以用于自定义对象的迭代行为,如:Symbol.iterator 方法,可以使对象可迭代。

14.ES Module 与 CommonJS 模块方案有什么异同?

这是一个非常经典、且每一位 JavaScript 开发者都必须跨过去的“坎”!搞懂这两个规范,能帮你避开开发中 80% 莫名其妙的引入报错。

简单来说,它们都是 JavaScript 的模块化规范。也就是规定了:我们该如何把几万行的代码拆分成不同的小文件,并且如何让这些文件互相引用和沟通。


1. 它们分别是什么?

  • CommonJS (简称 CJS)

    • 身世: Node.js 的“亲骨肉”。在早年间,JavaScript 只能在浏览器里写点简单脚本,没有模块的概念。Node.js 诞生后要在服务器端跑大型 JS 程序,于是自己发明了这套规范。

    • 特征: 使用 require() 引入模块,使用 module.exports 导出模块。

  • ES Module (简称 ESM)

    • 身世: JavaScript 官方(ECMAScript)的“正统继承人”。在 ES6 (2015年) 正式发布,旨在统一浏览器和服务器端的模块化标准。

    • 特征: 使用 import 引入模块,使用 export 导出模块。

2. 语法对比(最直的区别)

CommonJS (老派做法):

js
// module.exports (导出)
function add(a, b) { return a + b; }
module.exports = { add };

// require() (导入)
const math = require('./math.js');
console.log(math.add(1, 2));

ES Module (现代标准):

js
// export (导出)
export function add(a, b) { return a + b; }

// import (导入)
import { add } from './math.js';
console.log(add(1, 2));

3. 核心机制的区别(面试必考,开发必懂)

这是它们在底层运行机制上最本质的三个差异:

3.1 静态分析 vs 动态执行

  • ESM 是“静态”的(编译时加载): 在代码真正运行之前,JS 引擎会先进行一次静态分析,确定哪些模块被引入了。

    • 限制: import 语句必须写在文件的最顶层,绝不能写在 if 语句或函数里面。
    • 巨大优势: 催生了 Tree Shaking(摇树优化)。因为打包工具(如 Webpack、Vite)在打包前就能静态地知道你到底用了模块里的哪些功能,没用到的代码会被直接“摇”掉,大大缩小最终文件的体积。
  • CommonJS 是“动态”的(运行时加载): require 本质上就是一个普通的函数调用。

    • 灵活性: 代码执行到哪一行才去加载模块,你可以把 require 写在 if 语句里面,甚至动态拼接路径 require('./' + name + '.js')

    • 劣势: 无法做彻底的 Tree Shaking,因为打包工具不知道运行时到底会引入什么。

3.2 值的引用(绑定) vs 值的浅拷贝

  • ESM 导出的是“活的引用”(Live Bindings): 导入的值和导出的值是双向绑定的。如果导出模块内部修改了这个值,你导入的地方会实时同步更新(注意:你不能在导入方修改它,它是只读引用)。

  • CommonJS 导出的是“值的浅拷贝”: 当你 require 一个模块时,Node.js 会把那个时刻导出的值浅拷贝一份给你。如果在那个时刻之后,原模块内部改变了那个值,你这边是拿不到更新的。

3.3 异步 vs 同步加载

  • CommonJS 是同步加载: 它会阻塞代码执行,等模块读取完了再往下走。这在服务器端(Node.js)没问题,因为文件都在本地硬盘,读取极快;但在浏览器端,通过网络同步下载 JS 文件会导致页面卡死。

  • ESM 天然支持异步: 它是为浏览器(网络环境)设计的标准,支持通过网络异步请求模块资源,不会阻塞页面渲染。


4. 总结表

用一张表来快速回顾:

特性CommonJS (CJS)ES Module (ESM)
诞生背景Node.js 社区规范官方语言标准 (ES6)
语法require / module.exportsimport / export
加载时机运行时加载(动态)编译时静态分析(静态)
Tree Shaking不支持(很难支持)完美支持
输出机制值的(浅)拷贝动态只读引用(活的)
环境支持原生 Node.js现代浏览器 + Node.js (v12+)

💡 现代开发的最佳实践: 目前前端工程(Vue、React 等)早已全面拥抱 ES Module(尤其是基于 Vite 的新一代工具链)。而在 Node.js 环境中,虽然 CommonJS 依然存在,但 ESM 也已经成为默认推荐标准。

结论: 除非你在维护老旧的 Node.js 项目,否则在编写新代码时,请毫不犹豫地拥抱 ES Module (import/export)。 同的开发环境中做出合适的选择,同时也能更好地掌握 JavaScript 模块系统的发展和演变。

15.ES6 的 Proxy 可以实现什么功能?

ES6 的 Proxy 提供了一种拦截和自定义对象基本操作的能力,也被称为“元编程”。通过 Proxy,我们可以在对对象进行各种操作时(如读取、设置、删除属性,函数调用等)插入自定义逻辑,从而实现丰富的功能。下面详细介绍 Proxy 可以实现的一些主要功能:


1. 拦截和定制对象操作

Proxy 允许拦截对目标对象的各种操作,这些操作通过“陷阱”(trap)来处理。常见的拦截操作包括:

  • 属性读取(get)
    当访问对象的属性时,get 陷阱可以拦截该操作。例如,可以在属性读取时记录日志或返回动态计算的值:

    js
    const target = { a: 1 };
    const handler = {
      get(target, prop, receiver) {
        console.log(`正在读取属性: ${prop}`);
        return Reflect.get(target, prop, receiver);
      }
    };
    const proxy = new Proxy(target, handler);
    console.log(proxy.a);  // 控制台输出 "正在读取属性: a",然后输出 1
  • 属性赋值(set)
    在设置属性值时,set 陷阱可以进行数据校验或触发其他副作用:

    js
    const target = {};
    const handler = {
      set(target, prop, value, receiver) {
        if (typeof value !== 'number') {
          throw new Error(`属性 ${prop} 只能赋予数字值`);
        }
        return Reflect.set(target, prop, value, receiver);
      }
    };
    const proxy = new Proxy(target, handler);
    proxy.a = 42;  // 正常赋值
    // proxy.b = 'hello';  // 抛出错误
  • 属性删除(deleteProperty)
    拦截删除操作,可以用来控制或记录对象属性的删除:

    js
    const target = { a: 1 };
    const handler = {
      deleteProperty(target, prop) {
        console.log(`删除属性: ${prop}`);
        return Reflect.deleteProperty(target, prop);
      }
    };
    const proxy = new Proxy(target, handler);
    delete proxy.a;  // 控制台输出 "删除属性: a"
  • 其他操作
    Proxy 还可以拦截诸如 has(用于 in 操作符)、ownKeys(用于获取对象所有自有属性名)、getOwnPropertyDescriptordefinePropertypreventExtensionsisExtensible 等操作,允许开发者对对象的行为进行全面的定制。


2. 拦截函数调用和构造函数调用

如果目标是一个函数,Proxy 还能拦截函数调用(apply)和构造函数调用(construct),这对于实现一些函数包装、性能监控或自定义行为非常有用。

  • 函数调用拦截(apply)

    js
    function sum(a, b) {
      return a + b;
    }
    const handler = {
      apply(target, thisArg, argumentsList) {
        console.log(`调用函数 sum,参数: ${argumentsList}`);
        return Reflect.apply(target, thisArg, argumentsList);
      }
    };
    const proxy = new Proxy(sum, handler);
    console.log(proxy(2, 3));  // 输出日志并返回 5
  • 构造函数调用拦截(construct)

    js
    function Person(name) {
      this.name = name;
    }
    const handler = {
      construct(target, argumentsList, newTarget) {
        console.log(`正在使用 new 调用 Person,参数: ${argumentsList}`);
        return Reflect.construct(target, argumentsList, newTarget);
      }
    };
    const ProxyPerson = new Proxy(Person, handler);
    const person = new ProxyPerson('Alice');  // 输出日志
    console.log(person.name);  // 输出 "Alice"

3. 实现数据校验与数据绑定

由于可以在属性设置或读取时执行自定义逻辑,Proxy 常用于:

  • 数据校验:在设置属性值时进行数据类型或值域的校验,防止错误数据的写入。
  • 数据绑定与响应式编程:通过拦截数据的变化,可以实现依赖追踪,从而触发视图更新。这种模式在 MVVM 框架(如 Vue 3)中得到了应用。

4. 其他高级用途

  • 虚拟化对象:例如,当访问不存在的属性时,可以返回默认值或者动态生成的值。
  • 私有属性和数据隐藏:可以拦截对敏感属性的访问,实现一定程度上的封装和安全控制。
  • 调试与监控:记录对象的操作日志,帮助调试或性能分析。
  • 实现负索引数组:通过拦截数组的 get 操作,可以使数组支持负数索引,从而访问数组尾部的元素。

总结

ES6 的 Proxy 是一个非常强大的元编程工具,它允许开发者在对象的各种基本操作上插入自定义逻辑。借助 Proxy,可以实现如下功能:

  • 拦截和定制对象的读取、赋值、删除等操作
  • 拦截函数调用和构造函数调用
  • 数据校验和数据绑定,实现响应式编程
  • 虚拟对象、属性访问控制以及调试和监控
  • 扩展对象的默认行为,实现诸如负索引数组等高级功能

通过这些功能,Proxy 为 JavaScript 的元编程和自定义对象行为提供了极大的灵活性,使得在实际开发中可以针对特定需求设计出更为高效和灵活的解决方案。

15.ES6 新增了哪些字符串处理函数?

ES6 为 String 对象添加了一些新的方法,以便于处理字符串数据,以下列举一些常用的:

  1. includes():是否包含另一个字符串。
  2. trim():去除字符串两端的空格。
  3. repeat():将原字符串重复 n 次。
  4. replaceAll():将所有匹配的字符串替换为新字符串。
  5. split():根据指定的分隔符,将字符串分割成数组,并可指定返回的数组长度。
  6. slice():与 substring 类似,截取指定位置的字符串,具体区别看扩展。
  7. substring():与 slice 类似,都是用来截取字符串,具体区别看扩展。
  8. startsWith():判断是否以某个字符串开头;endsWith():是否以某个字符串结尾。

评论区

欢迎留言、补充或勘误。

xiaoba.blog