字符串的扩展
正则的扩展
数值的扩展
函数的扩展
数组的扩展
对象的扩展
4. 字符串的扩展
charCodeAt() || codePointAt() 字符串 转 字符
1 | let s = "𠮷"; |
对于这种4个字节的字符(𠮷),JavaScript 不能正确处理,字符串长度会误判为2,而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值
ES6 提供了codePointAt方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点
1 | let s = '𠮷a'; |
String.fromCharCode() || String.fromCodePoint() 字符 转 字符串
1 | String.fromCodePoint(0x20BB7) |
注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上
includes(), startsWith(), endsWith() 字符串: 包含 || 以什么开始 || 以什么结束
1 includes():返回布尔值,表示是否找到了参数字符串
2 startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
3 endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
1 | let s = 'Hello world!'; |
repeat方法返回一个新字符串,表示将原字符串重复n次1
2
3'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
padStart(),padEnd() 向前填充 || 向后填充 字符串 第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串1
2
3
4
5'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
template string 模板字符串 ${a}
5. 正则的扩展
RegExp 构造函数 (RegExp对象 前面是正则的匹配原理, 后面的是修饰符)1
2
3
4var regex = new RegExp('xyz', 'i');
var regex = new RegExp(/xyz/i);
// 等价于
var regex = /xyz/i;
常用的字符串的正则 : match()、replace()、search()和split()
String.prototype.match 调用 RegExp.prototype[Symbol.match]
u修饰符 || y修饰符 || g修饰符(全局) || s修饰符(使得.可以匹配任意单个字符, 这被称为dotAll模式)
RegExp.prototype.flags : ES6 为正则表达式新增了flags属性,会返回正则表达式的修饰符1
2
3/foo.bar/.test('foo\nbar') // false
/foo[^]bar/.test('foo\nbar') // true
/foo.bar/s.test('foo\nbar') // true
新的字符匹配 \p 和 u修饰符 一起用 , 去匹配字符 和 数字
正则表达式使用圆括号进行组匹配
使用exec方法,就可以将这三组匹配结果提取出来1
2
3
4
5
6
7
8
9
10
11
12
13
14const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
// 具名组匹配 (有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值 ?<组名> )
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31
String.prototype.matchAll : 可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。所以可以用for…of循环取出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const string = 'test1test2test3';
// g 修饰符加不加都可以
const regex = /t(e)(st(\d?))/g;
for (const match of string.matchAll(regex)) {
console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
// 转为数组方法一
[...string.matchAll(regex)]
// 转为数组方法二
Array.from(string.matchAll(regex));
6. 数值的扩展
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示
如果要将0b和0o前缀的字符串数值转为十进制,要使用Number方法1
2
3
4
50b111110111 === 503 // true
0o767 === 503 // true
Number('0b111') // 7
Number('0o10') // 8
Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity 如果参数类型不是数值,Number.isFinite一律返回false
Number.isNaN()用来检查一个值是否为NaN
而这两个新方法只对数值有效 , 不是数值都返回false
ES6 将全局方法 parseInt() 和 parseFloat() 移植到Number对象上面,行为完全保持不变。 取整数部分 || 取浮点数部分
Number.isInteger()用来判断一个数值是否为整数 如果参数不是数值,Number.isInteger返回false
如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数
(Number.isInteger(3.0000000000000002) // true 小数的精度达到了小数点后16个十进制位,转成二进制位超过了53个二进制位,导致最后的那个2被丢弃了, 就当成是带浮点的整数了)
Number.EPSILON实际上是 JavaScript 能够表示的最小精度(可以接受的最小误差范围)。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。
Number.EPSILON.toFixed(20) // “0.00000000000000022204”
引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的
1 | // 为浮点数运算,部署了一个误差检查函数 |
Number.isSafeInteger() : 整数范围在-2^53到2^53之间(不含两个端点),这是 JavaScript 能够精确表示数据的极限
(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) => (-9007199254740992 , 9007199254740992)
用 Number.isSafeInteger() 来检测
9007199254740993不是一个安全整数,但是Number.isSafeInteger会返回结果,显示计算结果是安全的。这是因为,这个数超出了精度范围,导致在计算机内部,以9007199254740992的形式储存1
29007199254740993 === 9007199254740992
// true
Math.trunc() 方法用于去除一个数的小数部分,返回整数部分. 对于非数值,Math.trunc内部使用Number方法将其先转为数值
1 | Math.trunc('123.456') // 123 |
Math.sign() 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
参数为正数,返回+1;
参数为负数,返回-1;
参数为 0,返回0;
参数为-0,返回-0;
其他值,返回NaN
Math.cbrt() 方法用于计算一个数的立方根
还有一些正弦函数等, 指数函数等,用到了再查
7. 函数的扩展
1 函数参数的默认值
2 rest 参数
3 严格模式
4 name 属性
5 箭头函数
6 双冒号运算符
7 尾调用优化
8 函数参数的尾逗号
函数参数的默认值 : function log (x, y = ‘world’) { console.log(y) } 惰性求值 => 没有传值的时候, 才用默认值
上面代码中,参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错
参数的解构赋值 : 封装 fetch1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
// 报错
// 函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method才会取到默认值GET
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method);
}
fetch('http://example.com') // "GET"
一般 设置默认值 的参数 放在最后
函数的 length : 指定了默认值以后,函数的length属性,将返回 没有指定默认值 的 参数个数。1
2
3
4
5
6
7
8
9
10(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
// 这是因为 length 属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length属性。如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
(function(...args) {}).length // 0
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
函数参数作用域 :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
// 上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。
// 如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}
foo() // 2
x // 1
应用 : 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误1
2
3
4
5
6
7
8
9
10function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错
函数的name 属性 返回函数名 或者 anonymous(匿名的); bind返回的函数,name属性值会加上bound前缀
1 | const bar = function baz() {}; |
arrow function 箭头函数 () => {} 箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
foo::bar;
尾调用优化 : 就是指某个函数的最后一步是调用另一个函数, 即只保留内层函数的调用帧 调用帧只有一项,这将大大节省内存
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。
总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归
func.arguments:返回调用时函数的参数。
func.caller:返回调用当前函数的那个函数。
尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。
8. 数组的扩展
扩展运算符
Array.from()
Array.of()
copyWithin()
find() 和 findIndex()
fill()
entries(),keys() 和 values()
includes()
flat(),flatMap()
数组的空位
扩展运算符
[…document.querySelectorAll(‘div’)]
1 | // a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化 |
扩展运算符 : (…rest)只能放在参数的最后一位,否则会报错。
Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符1
2
3
4
5
6
7const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]
Array.from() : 对象 转 数组 (类似数组的对象 || 可遍历(iterable)的对象)
只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组
扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换
理解 : Array.from() 就是调用 Symbol.iterator 把类数组 或者 对象转为数组, 就是一个循环迭代遍历的过程
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组
1 | // 封装对象转数组函数 |
Array.of方法用于将一组值,转换为数组
Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组
1 | Array.of(3, 11, 8) // [3,11,8] |
数组实例的 find() 和 findIndex() (一个找值 一个找下标)
组实例的find方法,用于找出第一个符合条件的数组成员
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
1 | [1, 5, 10, 15].find(function(value, index, arr) { |
indexOf方法无法识别数组的NaN成员,但是f ind || findIndex 方法可以借助Object.is方法做到
1 | [NaN].indexOf(NaN) |
fill方法使用给定值,填充一个数组
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
1 | ['a', 'b', 'c'].fill(7, 1, 2) |
数组实例的 keys() 和 values() entries() 用于遍历数组 (底层都是 Symbol.iterator() 可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历)
1 | for (let index of ['a', 'b'].keys()) { |
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似
1 | [1, 2, 3].includes(2) // true |
includes 相比 indexOf 的优点 :
indexOf 方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判
另外,Map 和 Set 数据结构有一个has方法,需要注意与includes区分
Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)
Map 结构的has方法,是用来查找键名的,比如Map.prototype.has(key)
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
1 | [1, 2, [3, 4]].flat() |
由于数组空位的处理规则非常不统一,所以建议避免出现空位
forEach(), every() 和some() ,filter(), reduce() 都会跳过空位
ES6 则是明确将空位转为 undefined
entries()、keys()、values()、find()和findIndex()会将空位处理成undefined
9. 对象的扩展
属性的简洁表示法
属性名表达式
方法的 name 属性
Object.is()
Object.assign()
属性的可枚举性和遍历
Object.getOwnPropertyDescriptors()
proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
super 关键字
Object.keys(),Object.values(),Object.entries()
对象的扩展运算符
表达式还可以用于定义方法名 :
1 | let obj = { |
函数的name属性,返回函数名。对象方法也是函数,因此也有name属性
1 | const person = { |
Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0
Object.is就是部署这个算法的新方法(“Same-value equality”(同值相等))。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致
1 | Object.is('foo', 'foo') |
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)1
2
3
4
5
6
7const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
1 | this.setState({ |
其他类型的值(即数值、字符串和布尔值)不在首参数,不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果
1 | const v1 = 'abc'; |
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用1
2
3
4
5const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换
1 | // Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1 |
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象
1 | let obj = { foo: 123 }; |
目前,有四个操作会忽略enumerable为false的属性 :
for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。 (总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for…in循环,而用Object.keys()代替)
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
ES6 一共有 5 种方法可以遍历对象的属性 :
(1)for…in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj) 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj) 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
(4)Object.getOwnPropertySymbols(obj) 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则 :
首先遍历所有 数值键,按照数值升序排列。
其次遍历所有 字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列
1 | Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) |
上面代码中,Reflect.ownKeys方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2和10,其次是字符串属性b和a,最后是 Symbol 属性
Object.getPrototypeOf()(读操作)
Object.setPrototypeOf()(写操作)
Object.create()(生成操作)
1 | let proto = {}; |
上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性
关键字super,指向当前对象的原型对象 (继承父类)
1 | const proto = { |
上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world
Object.keys() 可枚举的属性(enumerable) 不含继承的
Object.values() 可枚举的属性 不含 Symbol 值的属性
Object.entries() 可枚举的属性 不含继承的 不含 Symbol 值的属性
1 | let {keys, values, entries} = Object; |
Object.entries() 的基本用途是遍历对象的属性 另一个用处是,将对象转为真正的Map结构
1 | let obj = { one: 1, two: 2 }; |
对象的结构赋值
1 | let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; |