ES6学习

初次发布文章,有什么错误的地方请大佬们多多指教javascript

变量的解构赋值


数组的结构赋值---默认值

注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。因此,只有当一个数组成员严格等于undefined,默认值才会生效。java

let [x=1]=[undefined]  //x=1
let [y=1]=[null]    //y=null

上面代码中,若是一个数组成员是null,默认值就不会生效,由于null不严格等于undefinednode

若是默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。栗子:git

fn=()=> { return 'aaa' }
let [y=fn()]=[]   //y='aaa'
let [x=fn()]=[1]  //x=1

上面代码中,由于x能取到值,因此函数fn根本不会执行。上面的代码其实等价于下面的代码。github

let x;
if([1][0]===undefined){
    x=fn()
}else{
    x=[1][0]
}

默认值能够引用解构赋值的其余变量,但该变量必须已经声明。算法

let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError: y is not defined

上面最后一个表达式之因此会报错,是由于xy作默认值时,y尚未声明。编程

对象的解构赋值

对象的解构与数组有一个重要的不一样。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。栗子:数组

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

若是变量名与属性名不一致,必须写成下面这样。浏览器

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

与数组同样,解构也能够用于嵌套结构的对象。注意,下面栗子第二三loc是模式,不是变量,所以不会被赋值,第个才是赋值数据结构

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}
对象解构赋值----注意点

(1)若是要将一个已经声明的变量用于解构赋值,必须很是当心。

// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代码的写法会报错,由于 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题,即用()

// 正确的写法
let x;
({x} = {x: 1});

(2)因为数组本质是特殊的对象,所以能够对数组进行对象属性的解构。

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
字符串的解构赋值

字符串也能够解构赋值。这是由于此时,字符串被转换成了一个相似数组的对象

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

相似数组的对象都有一个length属性,所以还能够对这个属性解构赋值。

let {length : len} = 'hello';
len // 5
函数参数的解构赋值

undefined就会触发函数参数的默认值。

[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
用途

(1)交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

上面代码交换变量xy的值,这样的写法不只简洁,并且易读,语义很是清晰。


字符串的扩展

字符串遍历器

ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串能够被for...of循环遍历。

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"
模板字符串

模板字符串(template string)是加强版的字符串,用反引号(`)标识。它能够看成普通字符串使用,也能够用来定义多行字符串,或者在字符串中嵌入变量。模板字符串中嵌入变量,须要将变量名写在${}之中。

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`


// 多行字符串
`In JavaScript this is
 not legal.`

模板字符串之中还能调用函数。

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。


字符串的新增方法

实例方法:includes(), startsWith(), endsWith()

注:includes()也能够用于数组
传统上,JavaScript 只有indexOf方法,能够用来肯定一个字符串是否包含在另外一个字符串中。ES6 又提供了三种新方法。

  • includes( tager,index):返回布尔值,表示是否找到了参数字符串。
  • startsWith(tager,index):返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith(tager,n):返回布尔值,表示参数字符串是否在原字符串的尾部。

其中tager表示要查找的字符串,index表示开始搜索的位置(仅用于includes和startsWith),可省略(默认从0开始)。endsWith的行为与其余两个方法有所不一样。它针对前n个字符

let s = 'Hello world!';

s.startsWith('world') // true
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
实例方法:repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
实例方法:padStart(),padEnd()

ES2017 引入了字符串补全长度的功能。若是某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'

若是原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。

'xxx'.padStart(2, 'ab') // 'xxx'

若是用来补全的字符串与原字符串,二者的长度之和超过了最大长度,则会截去超出位数的补全字符串

'abc'.padStart(10, '0123456789')
// '0123456abc'

若是省略第二个参数,默认使用空格补全长度。

'x'.padStart(4) // '   x'
'x'.padEnd(4) // 'x   '
实例方法:trimStart(),trimEnd()

ES2019 对字符串实例新增了trimStart()trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串

const s = '  abc  ';

s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"

数值的扩展(number)

Number.isFinite(), Number.isNaN()

ES6 在Number对象上,新提供了Number.isFinite()Number.isNaN()两个方法。Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
//Number.isFinite()和isFinite()的区别是isFinite()会将值转成number类型再执行isFinite(),而Number.isFinite()只针对数值
isFinite(15); // true
isFinite('15'); // true          Number('15')---15
isFinite(true); // true          Number(true)---1
isFinite(false); // true         Number(false)---0
isFinite(null); // true          Number(null)-----0
isFinite(NaN); // false          Number(NaN)-----NaN
isFinite(undefined); // false    Number(NaN)-----NaN
isFinite('str'); // false        Number('str')-----NaN

注意,若是参数类型不是数值Number.isFinite一概返回false

Number.isNaN()用来检查一个值是否为NaN

Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true

若是参数类型不是NaNNumber.isNaN一概返回false

它们与传统的全局方法isFinite()isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效

Number.parseInt(), Number.parseFloat()

ES6 将全局方法parseInt()parseFloat(),移植到Number对象上面,行为彻底保持不变。

// ES5的写法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45

// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45

这样作的目的,是逐步减小全局性方法,使得语言逐步模块化。


函数的扩展

基本用法

参数变量是默认声明的,因此不能用letconst再次声明。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

使用参数默认值时,函数不能有同名参数。

// 不报错
function foo(x, x, y) {
  // ...
}

// 报错--有默认值
function foo(x, x, y = 1) {
  // ...
}

另外,一个容易忽略的地方是,参数默认值不是传值的,而是每次都从新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代码中,参数p的默认值是x + 1。这时,每次调用函数foo,都会从新计算x + 1,而不是默认p等于 100。

与解构赋值默认值结合使用
function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5
参数默认值的位置

一般状况下,定义了默认值的参数,应该是函数的尾参数。由于这样比较容易看出来,到底省略了哪些参数。若是非尾部的参数设置默认值,实际上这个参数是无法省略的。

function f(x = 1, y) {
  return [x, y];
}

f(, 1) // 报错
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
  return [x, y, z];
}

f(1, ,2) // 报错
f(1, undefined, null) // [1, 5, null]

注:若是传入undefined,将触发该参数等于默认值,null则没有这个效果,至关于赋值为null

做用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会造成一个单独的做用域(context)。等到初始化结束,这个做用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

上面代码中,函数f调用时,参数y = x造成一个单独的做用域。这个做用域里面,变量x自己没有定义,因此指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x
若是此时,全局变量x不存在(没有let x=1),就会报错。

下面这样写,也会报错。

var x = 1;

function foo(x = x) {
  // ...
}

foo() // ReferenceError: x is not defined

上面代码中,参数x = x造成一个单独做用域。实际执行的是let x = x,因为暂时性死区的缘由,这行代码会报错”x 未定义“。

下面是一个更复杂的例子。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1

上面代码中,函数foo的参数造成一个单独做用域。这个做用域里面,首先声明了变量x,而后声明了变量yy的默认值是一个匿名函数。这个匿名函数内部的变量x指向同一个做用域的第一个参数x,。函数foo内部又声明了一个内部变量x,该变量与第一个参数x因为不是同一个做用域,因此不是同一个变量,所以执行y后,内部变量x和外部全局变量x的值都没变。

若是将var x = 3var去除,函数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

箭头函数

ES6 容许使用“箭头”(=>)定义函数。

var f = () => 5;
// 等同于
var f = function () { return 5 };

因为大括号被解释为代码块,因此若是箭头函数直接返回一个对象,必须在对象外面加上括号,不然会报错。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

使用注意点
箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不能够看成构造函数,也就是说,不可使用new命令,不然会抛出一个错误。

(3)不可使用arguments对象,该对象在函数体内不存在。若是要用,能够用 rest 参数代替。

(4)不可使用yield命令,所以箭头函数不能用做 Generator 函数。

解释:什么不能用做构造函数?

new操做的主要步骤:

  1. 建立一个空对象
  2. 将对象的——proto——指向构造函数的prototype(箭头函数没有prototype)
  3. 绑定this(箭头函数没有单独的this,this来自上下文所以不能使用call或者apply将this指向新建立的对象
  4. 返回obj对象
尾调用优化

尾调用(Tail Call)是函数式编程的一个重要概念,自己很是简单,一句话就能说清楚,就是指某个函数的最后一步是调用另外一个函数。

咱们知道,函数调用会在内存造成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息,尾调用因为是函数的最后一步操做,因此不须要保留外层函数的调用帧。
尾调用必定是 return +方法名()

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

注意,只有再也不用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,不然就没法进行“尾调用优化”。

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

上面的函数不会进行尾调用优化,由于内层函数inner用到了外层函数addOne的内部变量one

注意,目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。

尾递归

函数调用自身,称为递归。若是尾调用自身,就称为尾递归。
递归很是耗费内存,由于须要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来讲,因为只存在一个调用帧,因此永远不会发生“栈溢出”错误。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代码是一个阶乘函数,计算n的阶乘,最多须要保存n个调用记录,复杂度 O(n) 。

若是改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120
catch 命令的参数省略

JavaScript 语言的try...catch结构,之前明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。

try {
  // ...
} catch (err) {
  // 处理错误
}

上面代码中,catch命令后面带有参数err

不少时候,catch代码块可能用不到这个参数。可是,为了保证语法正确,仍是必须写。ES2019 作出了改变,容许catch语句省略参数。

try {
  // ...
} catch {
  // ...
}

数组的扩展

扩展运算符

扩展运算符(spread)是三个点(...)。它比如 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

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

扩展运算符后面还能够放置表达式。

const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];
扩展运算符的应用

复制或者合并数组
这些方法都是浅拷贝,使用的时候须要注意。

//ES5
const a1 = [1, 2];
const a2 = a1.concat();
const a3 = a1.splice();
//ES6
const a1 = [1, 2];
const a2 = [...a1];

与解构赋值结合

// ES5
let list=[1,2,3,4,5,6]
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list   
//a=1 
//rest=[]

字符串/对象/数组
扩展运算符还能够将字符串转为真正的数组。

[...'hello']
// [ "h", "e", "l", "l", "o" ]
let obj={a: 1, b: 2} 
{...obj}  //{a: 1, b: 2}
let arr=[1,2,3,4]
[...arr]   //[1,2,3,4]

实现了 Iterator 接口的对象(Map、Set、Generator函数)
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),均可以用扩展运算符转为真正的数组。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

上面代码中,querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个相似数组的对象(相似数组对象必须有length属性)。

Array.from()

Array.from的语法
Array.from(arr).map(fn(),this)
Array.from(arr,()=>{})
第一个参数arr要转换的值(必须),第二个参数是map方法,若是第二个参数不是箭头函数用.的方式写,可接受第三个参数,修改map方法里面的this指向,若是第二个参数是箭头函数用,方式写第二个参数,箭头函数没有本身的this,因此不须要第三个参数

Array.from方法用于将两类对象转为真正的数组:相似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

下面是一个相似数组的对象,Array.from将它转为真正的数组。
常见的相似数组的对象是 DOM 操做返回的 NodeList 集合,以及函数内部的arguments对象

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组。

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

值得提醒的是,扩展运算符(...)也能够将某些数据结构转为数组。

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

// NodeList对象
[...document.querySelectorAll('div')]

扩展运算符背后调用的是遍历器接口(Symbol.iterator),若是一个对象没有部署这个接口,就没法转换。Array.from方法还支持相似数组的对象。所谓相似数组的对象,本质特征只有一点,即必须有length属性。所以,任何有length属性的对象,均可以经过Array.from方法转为数组,而此时扩展运算符就没法转换。

Array.from还能够接受第二个参数,做用相似于数组的map方法,用来对每一个元素进行处理,将处理后的值放入返回的数组。

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
Array.of()

Array.of方法用于将一组值,转换为数组。这个方法的主要目的,是弥补数组构造函数Array()的不足。由于参数个数的不一样,会致使Array()的行为有差别。Array()在只有一个参数的时候表示的是长度。

Array.of()  //[]
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array(3) // [, , ,]    
Array.of(3).length // 1
数组实例的 copyWithin()

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其余位置(会覆盖原有成员),而后返回当前数组。也就是说,使用这个方法,会修改当前数组

Array.prototype.copyWithin(target, start = 0, end = this.length)

它接受三个参数。

  • target(必需):从该位置开始替换数据。若是为负值,表示倒数
  • start(可选):从该位置开始读取数据(开始复制),默认为 0。若是为负值,表示从末尾开始计算。
  • end(可选):到该位置前中止读取数据(中止复制,不包括),默认等于数组长度。若是为负值,表示从末尾开始计算。

这三个参数都应该是数值,若是不是,会自动转为数值。

let arr=[1, 2, 3, 4, 5]
arr.copyWithin(0, 2,4)
//从下标为2的开始复制即从3开始复制,到下标为4的结束即到5结束(不包括5)
//[3, 4, 3, 4, 5]
数组实例的 find() 和 findIndex()

find()语法

语法一:target.find(fn,this)
语法二:target.find((val,index,arr)=>{}),fn为箭头函数

  • fn表示方法
  • this表示方法里用到的this指向(可省略)
  • val表示当前值
  • index表示下标(可省略)
  • arr表示原数组(可省略)

数组实例的find方法,用于找出第一个符合条件的数组成员。直到找出第一个返回值为true的成员,而后返回该成员。若是没有符合条件的成员,则返回undefined

[1, 4, -5, 10].find((n) => n < 0)
// -5

findIndex()

数组实例的findIndex方法的用法与find方法很是相似,返回第一个符合条件的数组成员的位置,若是全部成员都不符合条件,则返回-1

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
数组实例的 fill()

fill(target,start,end)
fill方法使用给定值,填充一个数组。
注意,若是填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
数组实例的 entries(),keys() 和 values()

能够用for...of循环进行遍历,惟一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"
数组实例的 includes()

语法:includes(target,start),第二个参数表示开始搜索的位置
includes()也能够用于字符串
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法相似。ES2016 引入了该方法。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
数组实例的 flat(),flatMap()

flat()语法

  • flat(num),num为拉平的的层数,默认为1,若是无论有多少层嵌套,都要转成一维数组,能够用Infinity关键字做为参数

数组的成员有时仍是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
若是原数组有空位,flat()方法会跳过空位。

[1, 2, , [4, 5]].flat()
// [1, 2, 4, 5]

flatMap()语法

  • flatMap(fn),fn为方法

flatMap()方法对原数组的每一个成员执行一个函数,而后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。flatMap()只能展开一层数组。

// 至关于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

对象的扩展

属性的简洁表示法

ES6 容许在大括号里面,直接写入变量和函数,做为对象的属性和方法。

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值

除了属性简写,方法也能够简写(对象的方法)。

const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  method: function() {
    return "Hello!";
  }
};

CommonJS 模块输出一组变量,就很是合适使用简洁写法。

function a(){}
function b(){}
module.exports = { a, b };

注意,简写的对象方法不能用做构造函数,会报错。

const obj = {
  f() {
    this.foo = 'bar';
  }
};

new obj.f() // 报错

只有function这种写法的能够做为构造函数,非function写法的属于简写,没有构造器的方法

属性名表达式

JavaScript 定义对象的属性,有两种方法。

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

上面代码的方法一是直接用标识符做为属性名,方法二是用表达式做为属性名,这时要将表达式放在方括号以内。
属性的可枚举性和遍历

属性的遍历

ES6 一共有 5 种方法能够遍历对象的属性。

(1)for...in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2)Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)全部可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的全部属性(不含 Symbol 属性,可是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的全部 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)全部键名,无论键名是 Symbol 或字符串,也不论是否可枚举。

super 关键字

咱们知道,this关键字老是指向函数所在的当前对象,ES6 又新增了另外一个相似的关键字super,指向当前对象的原型对象(不改变this的指向)。

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

上面代码中,对象obj.find()方法之中,经过super.foo引用了原型对象protofoo属性。

注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其余地方都会报错。

// 报错
const obj = {
  foo: super.foo
}

// 报错
const obj = {
  foo: () => super.foo
}

// 报错
const obj = {
  foo: function () {
    return super.foo
  }
}

上面三种super的用法都会报错,由于对于 JavaScript 引擎来讲,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,而后赋值给foo属性。目前,只有对象方法的简写法可让 JavaScript 引擎确认,定义的是对象的方法。

const proto = {
  x: 'hello',
  foo() {
    console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {
    super.foo();
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

上面代码中,super.foo指向原型对象protofoo方法,可是绑定的this却仍是当前对象obj,所以输出的就是world

对象的扩展运算符

解构赋值
注意,解构赋值的拷贝是浅拷贝,即若是一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined

上面代码中,对象o3复制了o2,可是只复制了o2自身的属性,没有复制它的原型对象o1的属性。

扩展运算符

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

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

因为数组是特殊的对象,因此对象的扩展运算符也能够用于数组。

let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}

对象的扩展运算符等同于使用Object.assign()方法。

let a=['a','b','c']
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
//{0: "a", 1: "b", 2: "c"}

上面的例子只是拷贝了对象实例的属性,若是想完整克隆一个对象,还拷贝对象原型的属性,能够采用下面的写法。

// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 写法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

上面代码中,写法一的__proto__属性在非浏览器的环境不必定部署,所以推荐使用写法二和写法三。

链判断运算符?.

编程实务中,若是读取对象内部的某个属性,每每须要判断一下该对象是否存在。或者使用三元运算符?:,判断一个对象是否存在。

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';
  
  //链式
  const firstName = message?.body?.user?.firstName || 'default';
//三目运算
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
//链式
const fooValue = myForm.querySelector('input[name=foo]')?.value

这样的层层判断很是麻烦,所以 ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。
上面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为nullundefined。若是是的,就再也不往下运算,而是返回undefined

链判断运算符有三种用法。

  • obj?.prop // 对象属性
  • obj?.[expr] // 同上
  • func?.(...args) // 函数或对象方法的调用

下面是这个运算符常见的使用形式,以及不使用该运算符时的等价形式。

a?.b
// 等同于
a == null ? undefined : a.b

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

上面代码中,特别注意后两种形式,若是a?.b()里面的a.b不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,若是a不是nullundefined,但也不是函数,那么a?.()会报错。

Null 判断运算符??

读取对象属性的时候,若是某个属性的值是nullundefined,有时候须要为它们指定默认值。常见作法是经过||运算符指定默认值。

const headerText = headerText || 'Hello, world!';

上面的代码都经过||运算符指定默认值,可是这样写是错的。开发者的原意是,只要属性的值为nullundefined,默认值就会生效,可是属性的值若是为空字符串或false0,默认值也会生效。

为了不这种状况,ES2020 引入了一个新的 Null 判断运算符??。它的行为相似||,可是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

const headerText = headerText ?? 'Hello, world!';

这个运算符的一个目的,就是跟链判断运算符?.配合使用,为nullundefined的值设置默认值。

const animationDuration = response.settings?.animationDuration ?? 300;

??有一个运算优先级问题,它与&&||的优先级孰高孰低。如今的规则是,若是多个逻辑运算符一块儿使用,必须用括号代表优先级,不然会报错。

(lhs && middle) ?? rhs;

对象的新增方法

Object.is()

ES5 比较两个值(字符串)是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺少一种运算,在全部环境中,只要两个值是同样的,它们就应该相等。

ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

不一样之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign() ---浅拷贝--合并对象

基本用法

Object.assign方法用于对象的合并,将源对象(source)的全部可枚举属性,复制到目标对象(target)。

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}

Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。

注意,若是目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

其余类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。可是,除了字符串会以数组形式,拷贝入目标对象,其余值都不会产生效果。

const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

数组的处理
Object.assign能够用来处理数组,可是会把数组视为对象。

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

上面代码中,Object.assign把数组视为属性名为 0、一、2 的对象,所以源数组的 0 号属性4覆盖了目标数组的 0 号属性1

Object.keys(),Object.values(),Object.entries()

Object.keys()

ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键名。

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

Object.values()
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键值。

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

Object.entries()
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键值对数组。

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操做,用于将一个键值对数组转为对象。

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

该方法的主要目的,是将键值对的数据结构还原为对象,所以特别适合将 Map 结构转为对象。

// 例一
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }

ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,做为遍历一个对象的补充手段,供for...of循环使用。

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
相关文章
相关标签/搜索