字符串(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.rawHi\n${2+3}!; // 'Hi\n5!' String.rawHi\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// ES60b111110111 === 503 // true0o767 === 503 // trueNumber('0b111') // 7Number('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`之间(不含两个端点),超过这个范围,无法精确表示这个值```javascriptMath.pow(2, 53) // 90071992547409929007199254740992 // 90071992547409929007199254740993 // 9007199254740992Math.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;}// ES6function 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````javascriptfunction test(a, b, c) {}console.log(test.length); // 3function test(a, b, c = 2) {}console.log(test.length); // 2function test(a = 0, b = 1, c = 2) {}console.log(test.length); // 0
// 参数指定默认值以后,后面参数没有指定默认值,不计入lengthfunction test(a, b = 1, c, d) { }console.log(test.length); // 1function test(a, b, c = 1, d) { }console.log(test.length); // 2function 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() {};// ES5bar.name // "baz"// ES6bar.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)```javascriptlet 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// ES5obj.foo = true;// ES6obj['a' + 'bc'] = 123;
// ES5var obj = {foo: true,abc: 123,hello: function() {}};// ES6let 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 //trueNaN === NaN // falseObject.is(+0, -0) // falseObject.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方法修改后,影响了objconsole.log(test.val); // 100console.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 // trueObject.setPrototypeOf('foo', {}) === 'foo' // trueObject.setPrototypeOf(true, {}) === true // true
