let定义的变量,是没有声明提升的。
1// 尝试在定义之前,访问
2console.log(a); // undefined
3console.log(b); // 报错
4// 定义变量a和b
5var a = 10;
6let b = 11;
7
1.3 不可以重复定义
let定义的变量,不允许重复。var过的变量也不可以let。
1var a = 10;
2var a = 11;
3// 连续通过var定义多个同名变量,是被允许的。
4let a = 11;
5
1.4 不会挂载到window (undefined)
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var的全局变量是会被挂载到window身上作为属性存在的。
let的变量,就算是全局也不会挂载到window。
1var a = 10;
2let b = 11;
33console.log(window.a); // 10
4console.log(window.b); // undefined
5
1.5 for循环中
let定义的循环变量,在for循环中,循环时是多少,就是多少。
1// 定义一个数组
2var arr = [];
3// 通过let定义
4for (let i = 0; i < 10; i++) {
5 arr.push(function() {
6 console.log(i);
7 });
8}
9// 让不同的数组成员执行 结果是输出?
10arr[0](); // 0
11arr[1](); // 1
12arr[2](); // 2
13arr[3](); // 3
14arr[7](); // 7
15
二:const 定义常量
const是ES6中新增的关键字。用于定义“常量”。常量指的是一直不变的量。
2.1 不可被=修改
11const a = 10;
2a = 11; // 尝试修改a常量
3
常量保存对象
1// 使用常量保存应用类型的数据
2const obj = {};
3obj.a = 10;
4console.log(obj);
5
结论:const只关心变量是否被=修改。至于变量保存的内容是否变化,const不关心。
2.2 其它特点
const与let一块出来的。所以也遵循let的其它特点: 没有变量声明的提升、不会挂载到window、不可以重复定义。
注:千万不要将循环变量用const来定义。
******三:******数组的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
以前,为变量赋值,只能直接指定值。
1let a = 1;
2let b = 2;
3let c = 3;
4
5
ES6 允许写成下面这样。
1// 封装数组
2let arr = [1, 2, 3];
3
4// 解构数组
5let [a, b, c] = arr;
6
7// 等价于
8// let a = arr[0];
9// let b = arr[1];
10// let c = arr[2];
11
12
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
1let [foo, [[bar], baz]] = [1, [[2], 3]];
2foo // 1
3bar // 2
4baz // 3
5
6let [ , , third] = ["foo", "bar", "baz"];
7third // "baz"
8
9let [x, , y] = [1, 2, 3];
10x // 1
11y // 3
12
13let [head, ...tail] = [1, 2, 3, 4];
14head // 1
15tail // [2, 3, 4]
16
17let [x, y, ...z] = ['a'];
18x // "a"
19y // undefined
20z // []
21
如果解构不成功,变量的值就等于undefined。
1let [foo] = [];
2let [bar, foo] = [1];
3
4
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
1let [x, y] = [1, 2, 3];
2x // 1
3y // 2
4
5let [a, [b], d] = [1, [2, 3], 4];
6a // 1
7b // 2
8d // 4
9
如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。
1// 报错
2let [foo] = 1;
3let [foo] = false;
4let [foo] = NaN;
5let [foo] = undefined;
6let [foo] = null;
7let [foo] = {};
8
9
******四:******对象的解构赋值
解构语法: let {key, key1, key2……} = obj;
1// 如下是一个工厂函数,它将创建对象的代码 封装在函数内部
2function createObjectFactory(username, age, sex) {
3 var obj = {
4 username: username,
5 age: age,
6 sex: sex
7 }
8 return obj;
9}
10// 其实是将name、age、sex三个形参封装起来。
11let obj = createObjectFactory("小白", 13, "女");
12
解构:
1// 以前的解构代码
2var username = obj.username;
3var age = obj.age;
4var sex = obj.sex;
5console.log(username, age, sex);
6
7// ES6中 针对不同的数据结构,提供了不同的解构方式
8var {username, age, sex} = obj;
9console.log(username, age, sex);
10
五:字符串5.1 多行字符串
1// 现在
2let str2 = `<div>大家好,我是一个多行字符串</div>
3<ul>
4 <li>1</li>
5 <li>1</li>
6 <li>1</li>
7 <li>1</li>
8 <li>1</li>
9 <li>1</li>
10 <li>1</li>
11 <li>1</li>
12</ul>
13`;
14
5.2插值语法
ES6中,在多行字符串中,提供了插值语法: ${}
开辟了一个JS执行环境
内部可以调用任何的JS语法
1// 通过ajax请求一个对象回来
2let obj = {
3 username: "老王",
4 bank: {
5 title: "最好的我们",
6 content: "隔壁老王"
7 }
8}
9// 现在
10let str2 = `<div>个人简介</div>
11 <div><span>${obj.username}</span></div>
12 <div><span>${obj.bank.title}</span></div>
13 <div><span>${obj.bank.content}</span></div>
14`;
15console.log(str2);
16
5.3 startsWith
str.startsWith(str1, pos); 该方法用于判定一个字符串是否以另外一个字符串开头(或者从指定位置开头)
str1: 开头字符串
pos: 指定的位置 下标slice的第一个参数的值
结果:布尔值 如果str的pos下标位置是str1.则返回真。否则返回假。
1// 定义一个字符串
2let str = "今天天气不错";
3// 定义另外一个字符串
4let str1 = "今天";
5// 判定str1是不是str的开头字符串
6let result = str.startsWith(str1);
7console.log(result); //true
8
5.4 endsWith
str.endsWith(str1, pos); 该方法用于判定一个字符串是否以另外一个字符串结尾(或者从指定位置结尾)
str1: 结尾字符串
pos: 指定的位置 slice方法的第二个参数
结果:布尔值 如果str的pos下标位置是str1.则返回真。否则返回假。
1// 定义一个字符串
2let str = "今天天气不错";
3// 定义另外一个字符串
4let str1 = "天气";
5
6// 判定 str 是否是以 str1 作为结尾
7let result = str.endsWith(str1, 4);
8
9console.log(result); //true
10
11
5.5 includes
str.includes(str1, pos); 该方法用于判定一个字符串是否包含另外一个字符串(或者从指定位置之后是否包含)
str1: 被包含的字符串
pos: 截取位置 从该位置开始往后截取
return: 布尔值 如果从该位置开始往后截取的字符串中包含str1字符串,则为真,否则为假。
1// 定义一个字符串
2let str = "今天天气不错";
3// 定义另外一个字符串
4let str1 = "天气";
5
6// 判定 str1 是否在str中
7let result = str.includes(str1, 2);
8console.log(result); //true
9
5.6 repeat
str.repeat(num); 该方法用于将str复制num次。
num:被复制的次数
最终的长度:str.length * num;
返回值:被复制之后的新字符串
1// 定义一个字符串
2let str = "你好";
3let str1 = str.repeat(100);
4console.log(str1, str1.length);
5
六:对象的新增方法6.1 Object.is
该方法用于判定两者是否全等。
1 0 === -0 // true
2 Object.is(0, -0); // false
3
4
5 1NaN === NaN // false
6Object.is(NaN, NaN); // true
7
6.2 Object.assign
Object.assign(target); 该方法用于给某一个对象增加其它对象的属性和方法。
该方法接收任意个参数,第一个是被赋予者。剩余的都是赋予者。
target: 被增加属性和方法的对象
剩余的所有参数都是提供属性和方法的对象。
注:这是浅复制、浅拷贝。
1// 定义一个对象
2let obj = {
3};
4
5// 定义一些其它对象
6let obj1 = {
7 color: "red",
8 sayHello() {
9 console.log(123);
10 }
11}
12// 定义其它对象
13let obj2 = {
14 name: "张三",
15 dog: {
16 name: "旺财"
17 }
18}
19// 将obj1和obj2所具备的属性和方法交给obj对象
20Object.assign(obj, obj1, obj2);
21// 输出obj
22console.log(obj);
23
七:Es6新增数组方法7.1 Array.of
该方法用于定义一个新的数组。
Array.of接收任意个参数,每一个参数都被当做数组的成员去处理。
7.2 Array.from
该方法用于将一个类数组对象,转化为数组。
类数组对象:数字下标、length属性
7.3 find
arr.find(handler) 该方法用于模糊查询数组中的成员
handler: 函数 该函数必须返回一个布尔值
该函数中有3个参数:value、index、arr
返回值:返回的是符合handler布尔值表达式的成员
7.4 findIndex
arr.findIndex(handler) 该方法用于模糊查询数组中的成员的索引
handler: 函数 该函数必须返回一个布尔值
该函数中有3个参数:value、index、arr
返回值:返回的是符合handler布尔值表达式的成员的索引 如果没有找到,则返回 -1
7.5 数组的内部复制
arr.copyWithin(pos, start, end); 该方法用于数组的内部复制 将数组内的start开始(包含)到end结束(不包含)复制,从pos位置开始粘贴(替换)。
pos: 粘贴(替换)的起始位置
start: 复制的开始位置(包含)
end: 复制的结束位置(不包含)
具体用法参考我总结的另一篇文章
八:Symbol
ES6之前,一共有6种数据类型:string、number、boolean、undefined、null、object
ES6中,新增了一种symbol类型。它表示一种“独一无二的符号”类型。属于值类型。
8.1 定义symbol
Symbol函数每执行一次,定义一个symbol数据值。
参数只有说明的作用,没有任何其它作用。
Symbol函数不是一个构造函数,不要使用new、不要使用new、不要使用new。
1let s = Symbol("你好");
2let s1 = Symbol("你好");
3console.log(s);
4console.log(s1);
5console.log(s === s1);
6
8.2 作用
通常是用来解决对象的属性名无法重复的问题。
1// 定义对象
2let obj = {
3 [s]: "你好",
4 [s1]: "你好1"
5}
6console.log(obj);
7
九:代理 proxy
ES6中,新增了一个构造函数,用于生成代理对象。
代理:访问的是对象A,其实访问的是另外一个对象B。
A是B的代理对象。
1let proxy = (function() {
2 // 源对象
3 let star = {
4 name: "胡歌",
5 age: 38,
6 sex: "男",
7 hobby: ["读书", "跑步", "摄影", "旅游"]
8 };
9 // 代理对象
10 return new Proxy(star, {
11 // get方法在通过proxy对象进行属性的读取或者访问时,会执行
12 get: function(star, prop, proxy) {
13 console.log("想要读取属性", arguments);
14 if (prop === "age") {
15 return star[prop] - 10;
16 }
17 return star[prop];
18 },
19 // set方法在通过proxy对象进行属性的设置时,会执行
20 set: function() {
21 console.log("想要设置属性", arguments);
22 }
23 });
24})();
25
十::Number的新增方法10.1 isNaN
在ES6之前,window身上有一个isNaN方法。 判定一个值是否是NaN
在ES6中,Number身上也增加了一个isNaN方法 判定一个数字是否是NaN
1// 定义一些值
2let num = 1/0;
3let num1 = 0/0;
4let num2 = "0";
5let num3 = NaN;
6let num4 = "1asdfdsaf23";
7
8// window.isNaN
9console.log(window.isNaN(num)); // false
10console.log(window.isNaN(num1)); // true
11console.log(window.isNaN(num2)); // false
12console.log(window.isNaN(num3)); // true
13console.log(window.isNaN(num4)); // true
14
15
16// Number.isNaN
17console.log(Number.isNaN(num)); // false
18console.log(Number.isNaN(num1)); // true
19console.log(Number.isNaN(num2)); // false
20console.log(Number.isNaN(num3)); // true
21console.log(Number.isNaN(num4)); // false
22
10.2 isFinite
window.isFinite 用于判定一个值是否是有限的
Number.isFinite 用于判定一个数字是否是有限的 如果参数不是数字 直接false
1// 定义一些值
2let num = 1/0;
3let num1 = 0/0;
4let num2 = "0";
5let num3 = NaN;
6let num4 = "1asdfdsaf23";
7
8// window.isFinite
9console.log(window.isFinite(num)); // false
10console.log(window.isFinite(num1)); // false
11console.log(window.isFinite(num2)); // true
12console.log(window.isFinite(num3)); // false
13console.log(window.isFinite(num4)); // false
14
15
16// Number.isFinite
17console.log(Number.isFinite(num)); // false
18console.log(Number.isFinite(num1)); // false
19console.log(Number.isFinite(num2)); // false
20console.log(Number.isFinite(num3)); // false
21console.log(Number.isFinite(num4)); // false
22
10.3 isInteger
Number.isInteger 该方法用于判定一个数字是否是整数
1// 定义一些值
2let num = 1/0;
3let num1 = 0.0;
4let num2 = "0";
5let num3 = NaN;
6
7// Number.isInteger
8console.log(Number.isInteger(num)); // false
9console.log(Number.isInteger(num1)); // true
10console.log(Number.isInteger(num2)); // false
11console.log(Number.isInteger(num3)); // false
12
十一:箭头函数11.1 定义
ES6中新增了一种函数,叫做箭头函数。
定义语法: var fun = () => {}
11.2 箭头函数中的this
箭头函数中的this,在定义的时候,就会确定下来。(不是写完代码就能确定的)
1// 定义箭头函数
2var fun = () => {
3 console.log(this);
4}
5fun(); // window
6fun.call(document.body); // window
7document.onclick = fun; // widnow
8
如果想要改变箭头函数中的this的指向,就将箭头函数的定义代码放在一个普通函数内。在调用普通函数的时候,改变该函数的this。也就改变了箭头函数的this.
1// 尝试修改箭头函数中的this
2function createArrowFun() {
3 // 返回一个箭头函数
4 return () => {
5 console.log(this);
6 }
7}
8var arrowFun1 = createArrowFun();
9arrowFun1(); // window
10
11var arrowFun2 = createArrowFun.call(document.body);
12arrowFun2(); // document.body
13
11.3 参数默认值
ES6中,给所有的函数,添加了参数默认值功能。
语法: function fun(a = x, b = x, c = x) {}
1// 参数默认值 不仅仅箭头函数拥有 普通函数也拥有
2function fun(a = 1, b = 2) {
3 return a + b;
4}
5
6console.log(fun()); // 3
7
11.4无法作为构造函数
因为ES6中,已经增加了class关键字,所以不需要再使用构造函数去模拟了。而且箭头函数中的this它不变。
1// 定义一个箭头函数
2let arrowFun = () => {
3
4}
5new arrowFun();
6
11.5省略写法
ES6将代码的省略做到了极致。
规则:
1 如果形参中只有一个参数,则可以省略圆括号
2 如果函数体中只有一条代码而且还是返回值,则可以省略{}和return
1let fun = a => a + 2;
2console.log(fun(1)); //3
3
11.6arguments
箭头函数中,取消了arguments 改为使用拓展语法代替 ...arg
1// 箭头函数中,取消了arguments 改为使用拓展语法代替
2let arrowFun = () => {
3 console.log(arguments);
4}
5// 调用
6arrowFun();
7
十二:...语法(拓展语法)
ES6中,因为新增了箭头函数,而箭头函数有一个特点就是不可以再使用arguments。所以,想要获取所有的参数,就需要别的方式。...语法就是补充。
12.1 第一种 函数中使用
1// 定义函数
2function fun(a, b, c, ...arg) { // 通过...语法获取剩余参数 该语法只能够写在最后面
3 console.log(arg);
4}
5
6// 调用函数
7fun(1, 2, 3);
8fun(1, 2, 3, 4, 5);
9fun(1, 2, 3, 4, 5, 6, 7);
10
12.2 第二种 解构时使用
1// 定义数组
2let arr = [1, 2, 3, 4, 5, 6, 7];
3// 解构 只要前3项,剩余的要放入另外一个数组
4let [a, b, c, ...d] = arr;
5console.log(a, b, c, d);
6
12.3 第三种 传递参数时使用
1// 定义数组
2let arr = [1, 2, 3, 4];
3// 输出数组
4console.log(arr);
5console.log(...arr);
6
十三:数组的迭代器方法
ES6中,新增了迭代器接口。
13.1 keys方法
该方法用于获取数组的所有的keys 也就是下标、索引
demo1: 获取迭代器对象
1// 定义数组
2let arr = ["a", "b", "c", "d", "e"];
3// 调用keys方法 返回迭代器对象
4let iterator = arr.keys();
5console.log(iterator);
6
demo2: 迭代器对象调用一次next
1// 每一次迭代器对象调用next方法 都会返回一次结果
2let result1 = iterator.next();
3console.log(result1);
4
本次的输出结果是一个对象,对象中有value属性,就是我们这一次得到的结果。对象中有done属性,表示迭代是否完成。false表示未完成,就可以继续。如果true,表示已经完成。
demo3: 代码多次调用next
1let result1 = iterator.next();
2console.log(result1);
3console.log(iterator.next());
4console.log(iterator.next());
5console.log(iterator.next());
6console.log(iterator.next());
7console.log(iterator.next());
8console.log(iterator.next());
9console.log(iterator.next());
10
13.2 values方法
该方法与keys方法的使用方式一致,区别:
keys方法的迭代器对象调用了next之后返回的对象中的value是下标
values方法的迭代器对象调用了next之后返回的对象中的value是成员
1let arr = ["a", "b", "c", "d", "e"];
2// 调用values方法 返回迭代器对象
3let iterator = arr.values();
4// 每一次迭代器对象调用next方法 都会返回一次结果
5let result1 = iterator.next();
6console.log(result1);
7console.log(iterator.next());
8console.log(iterator.next());
9console.log(iterator.next());
10console.log(iterator.next());
11console.log(iterator.next());
12console.log(iterator.next());
13console.log(iterator.next());
14
******13.3 ******entries方法
该方法与keys方法、values方法的使用方式一致,区别:
keys方法的迭代器对象调用了next之后返回的对象中的value是下标
values方法的迭代器对象调用了next之后返回的对象中的value是成员
entries方法的迭代器对象调用了next之后返回的对象中的value是长度为2的数组
第一个成员是数组的下标
第二个成员是数组的成员
1// 定义数组
2let arr = ["a", "b", "c", "d", "e"];
3// 调用entries方法 返回迭代器对象
4let iterator = arr.entries();
5// 每一次迭代器对象调用next方法 都会返回一次结果
6let result1 = iterator.next();
7console.log(result1);
8console.log(iterator.next());
9console.log(iterator.next());
10console.log(iterator.next());
11console.log(iterator.next());
12console.log(iterator.next());
13console.log(iterator.next());
14console.log(iterator.next());
15
什么叫做迭代器?
定义:给定一种方式,能够顺序的遍历结构内部的数据,同时又不暴露内部结构的方式。
ES6中就定义了一个迭代器接口,并且已经给部分数据结构实现了该迭代器。比如数组。
使用方式:
通常是在数据结构的对象的原型上,定义该迭代器接口的获取方式。在调用了该原型方法之后,会返回迭代器对象。
迭代器对象的使用方式是统一的: iterator.next()
得到的结果的结构也是统一的: {value: xxx, done: boolean}
13.4 for……of循环
该循环是用于循环迭代器、或者实现了迭代器接口的数据结构的。
举例:
调用了数组的keys、values、entries方法之后ASP 变量,得到一个迭代器对象:iterator
for (let i of iterator) {
i: 返回的对象value属性和done属性中的value的值
}
数组是数据结构,数组实现了迭代器接口:所以我们可以通过for of直接迭代数组
let arr = [1, 2, 3, 4, 5, 6];
for (let i of arr) {
i: 成员
}
对象也是数据结构,但是没有实现迭代器接口:所以我们不可以通过for of直接迭代对象
let obj = {};
for (var i of obj) {}
报错:obj is not iterable
13.5 迭代迭代器接口对象
1// 定义数组
2let arr = ["a", "b", "c", "d", "e"];
3// 调用entries方法 返回迭代器对象
4let iterator = arr.entries();
5// 每一次都手工调用iterator的next 太过繁琐
6// 所以 ES6提供了for of循环
7
8for (let i of iterator) {
9 console.log(i);
10}
11
迭代实现了迭代器接口的数据结构
1// 直接迭代arr
2for (let i of arr) {
3 console.log(i);
4}
5
迭代一个没有实现迭代器接口的数据结构
1// 定义对象
2let obj = {
3 a: 1,
4 b: 2,
5 c: 3
6}
7// 尝试for of迭代obj
8for (let i of obj) {
9}
10
十四:新的数据结构 Set WeakSet14.1 Set
Set是一个新的数据结构,可以认为是一个内容不可重复的数组。
注:初始化的时候,一定要new Set
1// 定义新的数据结构
2let s = new Set([1, 2, 3, 4, 5, 6, 6, 6, 6, 6]);
3console.log(s);
4
方法:
add:参数是任意类型的值
作用:将Set容器内,增加一个新的成员。
注:一定不可以添加已有的。如果添加的是已有的成员,添加失败。
delete:参数是任意类型的值
作用:将Set容器内,移除一个指定的成员。 如果该成员存在,则移除。
forEach:参数是函数 ES5中的迭代器方法
作用:根据Set成员的个数,执行函数多次。每一次的函数的参数是Set的某一个成员
函数的三个参数:Set的value、Set的key、Set自身
has:参数是任意的值
作用:判定参数是否已经存在于Set内部
clear: 没有参数。
作用:清空Set内部的所有内容。
14.2 WeakSet
WeakSet是一个与Set相关的数据结构。
与Set的区别:
1 每一个成员只能是引用类型
2 WeakSet不影响垃圾回收机制
WeakSet所使用的成员,不会导致标记数值的变化。
1let ws = new WeakSet([{}, {}]);
2
非引用类型成员会报错:
1let ws = new WeakSet([1, 2, 3]);
2
十五:Map
Map是一个超对象。与对象有关。
普通对象的key只能是字符串。
Map对象的key可以是任意类型的值。可以是字符串、是数字、布尔值、undefined、null、symbol、引用类型值。
1let map = new Map();
2
set方法:
接收两个参数
第一个参数是超对象的key。值是任意的。
第二个参数是超对象的value。值也是任意的。
get方法:
接收一个参数
第一个参数是超对象的key。值是任意的
返回值:
该参数key对应的value。
其余方法:
与Set一致。
15.1WeakMap
这是一个弱Map。
与WeakSet一致,WeakSet成员只可以是引用类型。
WeakMap的key,只可以是引用类型。
1// 初始化
2let wm = new WeakMap();
3wm.set("a", "1");
4
报错
与WeakSet一致,WeakSet不会影响到垃圾回收机制的判定。
WeakMap也不会。
15.2垃圾回收机制
垃圾:内存中没有用的内容,叫做垃圾。比如定义了一个变量,却从来没有使用。
引用计数:
当开辟一个内存地址时,会根据应用它的具体成员的多少来计算数值。当数值不为0时,依旧被使用。当数值为0时,会被清除掉。
标记清除:
进场和出场:整体内存有一个范围,当开辟一个地址时,如果有变量引用,则视为“进场”。如果变量、元素不再引用,则视为“出场”。
引用计数存在一个问题:如果一个元素身上有事件函数,则当把该元素移除时,元素引用函数,函数引用元素。互相引用,导致数字都不为0.无法被移除。所以委托模式,为了解决这个问题,就不给可能被移除的元素自身添加事件。
15.3setInterval与setTimeout
这两个函数,都是用来开启异步代码。
setInterval的参数函数会按照一定的时间间隔执行。
setTimeout的参数函数到时间之后只执行一次。
返回值:
都是数字。
返回值表示的含义是当前的定时器、延时器在浏览器中的编号。
该数字只有在你想要关闭对应的定时器、延时器时才有用。
注:编号是有顺序可循的,所以可以自己猜要关闭哪个。不推荐。推荐的方式还是使用变量来保存这个编号。
(编辑:威海站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|