字符串(String)
字符串的扩展
字符的Unicode表示方法的扩展
- ES5只能用四位表示
\uxxxx
- 超出这个范围的字符,必须用两个双字节的形式表示:
\uD842\uDFB7
(”𠮷”)
- 超出这个范围的字符,必须用两个双字节的形式表示:
- ES6的Unicode表示方法的扩展:
\u{x} 到 \u{xxxxx}
- JS 规定有5个字符,不能在字符串里面直接使用,只能使用转义形式
- 反斜杠
'\'
:\u005C
或者'\\'
—— 转义形式 - 回车:
\u000D
或者\r
—— 转义形式 - 行分隔符:
'\u2028'
—— 转义形式 - 段分隔符:
'\u2029'
—— 转义形式 - 换行符:
'\u000A'
—— 转义形式
- 反斜杠
// ES6
"\u{20BB7}"
// "𠮷"
"\u{41}\u{42}\u{43}"
// "ABC"
let hell\u{6F} = 123;
console.log(hello); // 123
'\u{1F680}' === '\uD83D\uDE80'
// true
// JavaScript 共有 6 种方法可以表示一个字符
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
JSON 格式允许字符串里面直接使用
'\u2028'
(行分隔符)和'\u2029'
(段分隔符)- 这样一来,服务器输出的 JSON 被
JSON.parse
解析,就有可能直接报错 - 模板字符串现在就允许直接输入这两个字符。
- 另外,正则表达式依然不允许直接输入这两个字符,这是没有问题的,因为 JSON 本来就不允许直接包含正则表达式。
const json = '"\u2028"';
JSON.parse(json); // 可能报错
- 这样一来,服务器输出的 JSON 被
JSON 格式已经冻结(RFC 7159),没法修改了。
- 为了消除这个报错,ES2019 允许 JavaScript 字符串直接输入
'\u2028'
(行分隔符)和'\u2029'
(段分隔符)JSON.stringify()的改造
- 为了消除这个报错,ES2019 允许 JavaScript 字符串直接输入
根据标准,JSON 数据必须是 UTF-8 编码。但是,现在的
JSON.stringify()
方法有可能返回不符合 UTF-8 标准的字符串。- UTF-8 标准规定,
0xD800
到0xDFFF
之间的码点,不能单独使用,必须配对使用- 如可以
'\uDF06\uD834'
,不能'\uDF06'
- 如可以
- 为了确保返回的是合法的 UTF-8 字符,ES2019 改变了
JSON.stringify()
的行为。如果遇到0xD800
到0xDFFF
之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。JSON.stringify('\uD834') // "\ud834"
JSON.stringify('\uDF06\uD834') // "\udf06\ud834"
模板字符串
- UTF-8 标准规定,
ES5的字符串是用
''
(单引号)或者""
(双引号)包起来的——叫普通字符串ES6的字符串可以用
```(反引号)包起来。如果要引入变量
params,这样使用
${params}`——叫模板字符串String.fromCodePoint()
- ES6 提供了
String.fromCodePoint()
方法,可以识别大于0xFFFF
的字符 - 弥补了ES5中
String.fromCharCode()
方法的不足 - 在作用上,正好与其实例对象新增的
codePointAt()
方法相反。String.fromCharCode(0x20BB7) // "ஷ" 丢了一位,相当于String.fromCharCode(0x0BB7)
String.fromCodePoint(0x20BB7) // "𠮷"
- ES6 提供了
String.raw()
- 该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串
- 往往用于模板字符串的处理方法,不需要用括号调用
- 与模板字符串搭配时,使用方式比较独特,习惯就好
- 只需要把模板字符串放在
String.raw
后面就好了
- 用括号调用该方法的使用方式,
Stirng.raw({ raw: [...] }, ...)
``javascript // 与模板字符串的搭配 String.raw
Hi\n${2+3}!; // 'Hi\n5!' String.raw
Hi\n`; // ‘Hi\n’
// 用括号的方式调用 String.raw({ raw: [‘a’, ‘b’, ‘c’] }, 1 + 2, ‘good’) // ‘a3bgoodc’ String.raw({ raw: [‘a’, ‘b’, ‘c’] }, 1 + 2, ‘good’, ‘JS’) // ‘a3bgoodc’
```javascript
// raw方法的实现
String.myRaw = function (strings, ...values) {
let output = '';
if (!!strings === false || !!strings.raw === false) {
return new Error('第一个参数必须带有raw');
}
let rawLen = strings.raw.length;
let valLen = values.length;
let index;
for (index = 0; index < valLen; index++) {
if (!!strings.raw[index] === false) break;
if (index !== rawLen - 1) {
output += strings.raw[index] + values[index];
} else {
output += strings.raw[index];
}
}
for (; index < rawLen; index++) {
output += strings.raw[index];
}
return output;
}
实例对象新增的方法
codePointAt()
- 该方法的作用:能够正确处理 4 个字节储存的字符,返回一个字符的码点(十进制值)
- 如需转换为十六进制:
s.codePointAt(0).toString(16)
- 如需转换为十六进制:
- 对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符
- ES5中,
charAt()
方法无法读取整个字符,charCodeAt()
方法只能分别返回前两个字节和后两个字节的值。 ```javascript // ES5 var s = “𠮷”; s.length // 2 s.charAt(0) // ‘’ s.charAt(1) // ‘’ s.charCodeAt(0) // 55362 s.charCodeAt(1) // 57271
- 该方法的作用:能够正确处理 4 个字节储存的字符,返回一个字符的码点(十进制值)
// ES6 s.codePointAt(0) // 134071 s.codePointAt(1) // 57271
- `normalize()`
- 该方法作用:将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化
- 四个参数:
- NFC,默认参数,表示“标准等价合成”,返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
- NFD,表示“标准等价分解”,即在标准等价的前提下,返回合成字符分解的多个简单字符。
- NFKC,表示“兼容等价合成”,返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文)
- NFKD,表示“兼容等价分解”,即在兼容等价的前提下,返回合成字符分解的多个简单字符
- 许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode 提供了两种方法。
- 直接提供带重音符号的字符,比如`Ǒ`(\u01D1)
- 提供合成符号,即原字符与重音符号的合成,两个字符合成一个字符,比如`O`(\u004F)和`ˇ`(\u030C)合成`Ǒ`(\u004F\u030C)
- 这两种表示方法,在视觉和语义上都等价,但是 JS 不能识别,所以出现`normalize()`这个方法
```javascript
// 对于这个字符 Ǒ
'\u01D1'==='\u004F\u030C' // false
'\u01D1'.normalize() === '\u004F\u030C'.normalize() // true
'\u004F\u030C'.normalize('NFC').length // 1
'\u004F\u030C'.normalize('NFD').length // 2
'\u01D1'.normalize('NFD').length // 2
- 字符串中查找字符串
- ES5中,提供了
indexOf()
方法 - ES6中,新增了三个方法
**includes()**
:返回布尔值,表示是否找到了参数字符串**startsWith()**
:返回布尔值,表示参数字符串是否在原字符串的头部**endsWith()**
:返回布尔值,表示参数字符串是否在原字符串的尾部 ```javascript let s = ‘Hello world!’;
- ES5中,提供了
s.startsWith(‘Hello’) // true s.endsWith(‘!’) // true s.includes(‘o’) // true
// 三个方法都支持第二个参数,表示开始搜索的位置 s.startsWith(‘world’, 6) // true s.endsWith(‘Hello’, 5) // true s.includes(‘Hello’, 6) // false
- `repeat(n)`
- 该方法的作用:返回一个新字符串,表示将原字符串重复n次
- `n > 1`:n含小数,向下取整。如`2.9`是`2`,`2.3`是2
- `n <= -1`或`n = Infinity`:报错
- `-1 < n < 1`或者`n= NaN`:取`n`为`0`
- `n`为字符串时,会先转成数字
<a name="bEnCs"></a>
# 数值(Number)
<a name="vWBLT"></a>
## 数值的扩展
<a name="hxQar"></a>
### 二进制和八进制的表示
- ES5 开始,在严格模式之中,八进制就不再允许使用前缀`0`表示
- ES6 进一步明确
- 使用前缀`0o`或`0O`表示八进制
- 使用前缀`0b`或`0B`表示二进制
- 如果要将`0b`和`0o`前缀的字符串数值转为十进制,要使用`Number()`方法
```javascript
// ES6
0b111110111 === 503 // true
0o767 === 503 // true
Number('0b111') // 7
Number('0o10') // 8
Number内置对象上新增的方法
- ES5中,全局有两个方法
isFinite()
和isNaN()
isFinite(param)
用来检查一个数值是否为有限的(finite)isNaN(param)
用来检查一个值是否为NaN- 这两个方法,若
param
是非数值类型,转为数值再判断
ES6 在
Number
对象上,新增了两个方法Number.isFinite(param)
用来检查一个数值是否为有限的(finite)Number.isNaN()
用来检查一个值是否为NaN- 这个两个方法,当
param
是数值才判断true
或false
,其他都返回false
ES6 将全局方法
parseInt()
和parseFloat()
,移植到Number
对象上面,行为完全保持不变 ```javascript // ES5的写法 parseInt(‘12.34’) // 12 parseFloat(‘123.45#’) // 123.45
// ES6的写法 Number.parseInt(‘12.34’) // 12 Number.parseFloat(‘123.45#’) // 123.45
<a name="FrF8H"></a>
### 安全整数
- JS能够准确表示的整数范围在`-2^53`到`2^53`之间(不含两个端点),超过这个范围,无法精确表示这个值
```javascript
Math.pow(2, 53) // 9007199254740992
9007199254740992 // 9007199254740992
9007199254740993 // 9007199254740992
Math.pow(2, 53) === Math.pow(2, 53) + 1
// true
- ES6引入了
Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示这个范围的上下限 ```javascript Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true Number.MAX_SAFE_INTEGER === 9007199254740991 // true
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true Number.MIN_SAFE_INTEGER === -9007199254740991 // true
- `Number.isSafeInteger(num)`则是用来判断一个整数是否落在这个范围之内
- 返回布尔值
<a name="QJ6VF"></a>
### Math对象上新增的方法
- ES6 在 Math 对象上新增了 17 个与数学相关的方法
- 所有这些方法都是静态方法,只能在 Math 对象上调用
- 下面列举一些方法
- `Math.trunc()`:用于去除一个数的小数部分,返回整数部分
- `Math.sign()`:用来判断一个数到底是正数、负数、还是零
- 参数为正数,返回`+1`
- 参数为负数,返回`-1`
- 参数为 0,返回`0`
- 参数为-0,返回`-0`
- 其他值,返回`NaN`
- `Math.cbrt()`:用于计算一个数的立方根
- `Math.hypot()`:返回所有参数的平方和的平方根
- 新增指数,对数方法
- `Math.expm1(x)`:返回`ex - 1`
- `Math.log1p(x)`:返回`loge(1 + x)`
- `Math.log10(x)`:返回`log10(1 + x)`
- `Math.log2(x)`:返回`log2(1 + x)`
- 双曲函数的方法
- `Math.sinh(x)` :返回`x`的双曲正弦
- `Math.cosh(x)` :返回`x`的双曲余弦
- `Math.tanh(x)` :返回`x`的双曲正切
- `Math.asinh(x)` :返回`x`的反双曲正弦
- `Math.acosh(x)` :返回`x`的反双曲余弦
- `Math.atanh(x)`: 返回`x`的反双曲正切
<a name="wUoDH"></a>
# 函数(Function)
<a name="whxJP"></a>
## 函数的参数默认值
- ES5 不能为函数的参数设置默认参数,得变通一下
- ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面
- 参数变量是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
```javascript
// ES5
// 但万一参数是布尔值,就会有问题
function add (a, b) {
a = a || 0;
b = b || 0;
return a + b;
}
// ES6
function add(a = 0, b = 0) {
return a + b;
}
- 函数参数与解构赋值结合 ```javascript // 写法一 function m1({x = 0, y = 0} = {}) { return [x, y]; }
// 写法二 function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; }
// 函数没有参数的情况 m1() // [0, 0] m2() // [0, 0]
// x 和 y 都有值的情况 m1({x: 3, y: 8}) // [3, 8] m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 无值的情况 m1({x: 3}) // [3, 0] m2({x: 3}) // [3, undefined]
// x 和 y 都无值的情况 m1({}) // [0, 0]; m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0] m2({z: 3}) // [undefined, undefined]
- 函数的`length`属性
- `length`表示:返回没有指定默认值的参数个数
- 注意:某个参数指定默认值以后,后面参数没有指定默认值,不计入`length`
```javascript
function test(a, b, c) {}
console.log(test.length); // 3
function test(a, b, c = 2) {}
console.log(test.length); // 2
function test(a = 0, b = 1, c = 2) {}
console.log(test.length); // 0
// 参数指定默认值以后,后面参数没有指定默认值,不计入length
function test(a, b = 1, c, d) { }
console.log(test.length); // 1
function test(a, b, c = 1, d) { }
console.log(test.length); // 2
function test(a, b, c, d = 1) { }
console.log(test.length); // 3
剩余(rest)运算符/扩展(展开)运算符
- 剩余(rest)运算符/扩展(展开)运算符:
...
- 剩余(rest)运算符:用于获取函数的多余参数,这样就不需要使用
arguments
对象了- 运算后,返回一个数组
- ES6引入的
- 扩展(展开)运算符:用于取出参数对象的所有可遍历属性,拷贝到当前对象之中
...{}
或...[]
- ES9引入的
```javascript // 注意:rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错 // 下面就报错了 function f(a, …b, c) { // … }// rest运算符的用法
function add(a, ...rest) {
let res = a;
for (let i in rest) {
res += rest[i];
}
return res;
}
add(1, 2, 3, 4, 5, 6, 7);
- 剩余(rest)运算符:用于获取函数的多余参数,这样就不需要使用
// 函数的length属性,不包括 rest 参数 function f(a, b = 1, …rest) {} console.log(f.length); // 1
<a name="eeoKA"></a>
## 函数的name属性
- 函数的`name`属性:返回该函数的函数名
- 这个属性早就被浏览器广泛支持,但是直到 ES6,才将其写入了标准
```javascript
// 如果将一个匿名函数赋值给一个变量
var f = function () {};
// ES5 的name属性会返回空字符串
f.name // ""
// ES6 的name会返回实际的函数名
f.name // "f"
// 如果将一个具名函数赋值给一个变量
// ES5 和 ES6 的name属性都返回这个具名函数原本的名字
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
// bind返回的函数,name属性值会加上bound前缀
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
箭头函数
- ES6 允许使用“箭头”
=>
定义函数 ```javascript // ES6 const add = (a, b) => { return a + b; } // 箭头函数的返回值,可以这样简化 const add = (a, b) => a + b; // 省去写return
// 等同于ES5 var add = function(a, b) { return a + b; }
```javascript
// 箭头函数如果参数只有一个,还可以去掉小括号
const square = a => a * a;
箭头函数的this
- 函数体内的this对象,继承的是外层代码块的this
- 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向 ```javascript // 与setTimeout结合 // ES6 function test() { setTimeout(() => { console.log(this.id); }) } var id = 1; test.call({ id: 2 });
// ES5 function test() { setTimeout(function () { console.log(this.id); }) } var id = 1; test.call({ id: 2 });
<a name="MspHb"></a>
### 使用箭头函数需注意
- 函数没有prototype原型
- 不可以作为构造函数。new了会报错
- 没有`arguments`
- 不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数
<a name="ZKvcI"></a>
# 数组(Array)
<a name="FjGHr"></a>
## 数组的扩展
<a name="ae5ER"></a>
### Array内置对象上新增的方法
- `Array.from()`:用于将两类对象转为真正的数组
- 类似数组的对象(array-like object)
- DOM 操作返回的 NodeList 集合
- 函数内部的`arguments`对象
- 可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
```javascript
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// 或者
var arr1 = Array.prototype.slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
// Array.prototype.slice的模拟内部实现
Array.prototype.slice = function(start, end) {
let result = [],
start = start || 0,
end = end || this.length;
for(let i = start;i < end;i++){
result.push(this[i]);
}
return result;
}
对象(Object)
对象的扩展
属性和方法更简洁的书写
- 属性 ```javascript // 案例一:数据属性 const foo = ‘bar’; const a = { foo }; a // { foo: “bar” }
// 等同于 const a = { foo: foo };
```javascript
// 案例二:访问器属性
let obj = {
_visit: 0,
get visit() {
return ++this._visit;
},
set visit(val) {
this._visit = val;
}
}
/**
* visit这个属性的特性
* {
* cocnfigurate: true,
* enumerable: true,
* get: ...,
* set: ...,
* }
*/
- 方法 ```javascript // 案例三 const o = { method() { return “Hello!”; } };
// 等同于 const o = { method: function() { return “Hello!”; } };
<a name="tFItV"></a>
### 属性名表达式
- JS 定义对象的属性,有两种方法
- ES5 中只能使用标识符定义属性
- ES6 允许用表达式作为对象的属性名,即把表达式放在方括号内
```javascript
// ES5
obj.foo = true;
// ES6
obj['a' + 'bc'] = 123;
// ES5
var obj = {
foo: true,
abc: 123,
hello: function() {}
};
// ES6
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123,
['h' + 'ello']() {
return 'hi';
},
};
属性名的遍历
- 五种遍历方式
for...in
:循环遍历对象自身的 和 继承的可枚举属性(不含 Symbol 属性)Object.keys(obj)
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名Object.getOwnPropertyNames(obj)
:返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名Object.getOwnPropertySymbols(obj)
:返回一个数组,包含对象自身的所有 Symbol 属性的键名Reflect.ownKeys(obj)
:返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
- 这 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则
- 首先遍历所有数值键,按照数值升序排列
- 其次遍历所有字符串键,按照加入时间升序排列
- 最后遍历所有 Symbol 键,按照加入时间升序排列 ```javascript let obj = {
b: 0, 10: 0, 2: 0, a: 0 }
Reflect.ownKeys(obj); // [‘2’, ‘10’, ‘b’, ‘a’, Symbol()]
<a name="ILjcL"></a>
### Object内置对象上新增的方法
- `Object.is(a, b)`:
- 作用:用来比较两个值是否严格相等。与严格比较运算符`===`的行为只有两处不同
- `Object.is()`的 `+0`不等于`-0`
- `Object.is()`的 `NaN`等于自身
```javascript
// ES5自定义Object.is方法
Object.defineProperty(Object, 'is', {
value: function(x, y) {
if (x === y) {
// 针对+0 不等于 -0的情况
return x !== 0 || 1 / x === 1 / y;
}
// 针对NaN的情况
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
});
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign(target, source1, source2...)
- 作用:将源对象
source
的所有可枚举属性,复制到目标对象target
,然后返回target
- 注意
- 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
- 该方法是浅拷贝
Object.assign()
无法正确拷贝set
和get
方法 ```javascript // 案例一 const target = { a: 1, b: 1 };
- 作用:将源对象
const source1 = { b: 2, c: 2 }; const source2 = { c: 3 };
Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
```javascript
// 案例二:只传了target
// Object.assign()会直接返回该参数
const obj = {a: 1};
Object.assign(obj) === obj // true
// 如果该参数不是对象,则会先转成对象,然后返回
typeof Object.assign(2) // "object"
// 案例三
// source是undefined、null、数值、布尔值,会被忽略
let obj = {a: 1};
Object.assign(obj, undefined); // {a: 1}
Object.assign(obj, null); // {a: 1}
Object.assign(obj, 10); // {a: 1}
Object.assign(obj, true); // {a: 1}
// source是字符串会以数组形式,拷贝入目标对象
const str = 'abc';
const obj = Object.assign({}, str);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
// 相当于
const arr = ['a', 'b', 'c'];
const obj = Object.assign({}, arr);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
// 案例四:Object.assign会把数组视为属性名为0,1,2这些的对象,所以发生同名替换
const target = ['a', 'b', 'c'];
const source = ['d', 'f']
const obj = Object.assign(target, source);
console.log(obj); // ['d', 'f', 'c']
// 案例五:Object.assign()无法正确拷贝set和get方法
// 在ES8中,新增了一个方法,更加方便地解决这个问题
let obj = {
_val: 0,
get val() {
return this._val
},
set val(val) {
this._val = val
}
};
let test = Object.assign(obj);
test.val = 100; // 调用set方法修改后,影响了obj
console.log(test.val); // 100
console.log(obj.val); // 100
对象的原型
__proto__
:用来读取或设置当前对象的原型对象- 由于浏览器广泛支持,才被加入了 ES6
- 只有浏览器必须部署这个属性,其他运行环境不一定需要部署
Object.setPrototypeOf(object, prototype)
:用来设置一个对象的原型对象,返回参数对象本身Object.getPrototypeOf(object)
:用于读取一个对象的原型对象 ```javascript // 案例一 let proto = {}; let obj = { x: 10 }; Object.setPrototypeOf(obj, proto);
proto.y = 20; proto.z = 40;
obj.x // 10 obj.y // 20 obj.z // 40
```javascript
// 如果第一个参数不是对象,会自动转为对象。
// 但是由于返回的还是第一个参数,所以这个操作不会产生任何效果
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true