ES6 内置对象

311 阅读17分钟

1. 新增

Map对象

Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

Maps 和 Objects 的区别

  • 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
  • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

image.png

Map 的基本用法

Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

key 是对象

const myMap = new Map()
const keyObj = {}  // 内存地址

myMap.set(keyObj, "和键 keyObj 关联的值")

myMap.get(keyObj); // "和键 keyObj 关联的值"
myMap.get({}); // undefined, 因为 keyObj !== {}
// 数组
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

key 是函数

const myMap = new Map();
const keyFunc = function () { }; // 函数

myMap.set(keyFunc, "和键 keyFunc 关联的值");

myMap.get(keyFunc); // "和键 keyFunc 关联的值"
myMap.get(function () { }) // undefined, 因为 keyFunc !== function () {}

key 是 简单类型

如果对同一个键多次赋值,后面的值将覆盖前面的值。

const map = new Map();

map
.set(1, 'aaa')
.set(1, 'bbb');

map.get(1) // "bbb"

如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键。

比如0-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefinednull也是两个不同的键。

虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

let map = new Map();

map.set(-0, 123);
map.get(+0) // 123

map.set(true, 1);
map.set('true', 2);
map.get(true) // 1

map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3

map.set(NaN, 123);
map.get(NaN) // 123

注意点

注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

上面代码的setget方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined

// 数组
const map = new Map();

const k1 = ['a'];
const k2 = ['a'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) // 111
map.get(k2) // 222

上面代码中,变量k1k2的值是一样的,但是它们在 Map 结构中被视为两个键。

Map 实例的属性和操作方法

Map 结构的实例有以下属性和操作方法。

(1)size 属性

size属性返回 Map 结构的成员总数。

const map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2

(2)set(key, value)

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

const m = new Map();

m.set('edition', 6)        // 键是字符串
m.set(262, 'standard')     // 键是数值
m.set(undefined, 'nah')    // 键是 undefined

set方法返回的是当前的Map对象,因此可以采用链式写法。

let map = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

(3)get(key)

get方法读取key对应的键值,如果找不到key,返回undefined

const m = new Map();

const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数

m.get(hello)  // Hello ES6!

(4)has(key)

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

const m = new Map();

m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');

m.has('edition')     // true
m.has('years')       // false
m.has(262)           // true
m.has(undefined)     // true

(5)delete(key)

delete方法删除某个键,返回true。如果删除失败,返回false

const m = new Map();
m.set(undefined, 'nah');
m.has(undefined)     // true

m.delete(undefined)
m.has(undefined)       // false

(6)clear()

clear方法清除所有成员,没有返回值

let map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
map.clear()
map.size // 0

遍历方法

Map 结构原生提供三个遍历器生成函数和一个遍历方法。

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

💥 需要特别注意的是,Map 的遍历顺序就是插入顺序。

keys(),values(),entries()

const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

// 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 
for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"


// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

forEach

此外,Map 还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

forEach方法还可以接受第二个参数,用来绑定this

const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value);
  }
};

map.forEach(function(value, key, map) {
  this.report(key, value);
}, reporter);

// 上面代码中,forEach方法的回调函数的this,就指向reporter。

Map 的遍历和过滤

结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有mapfilter方法)。

const map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

const map1 = new Map(
  [...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}

const map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
    );
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

与其他数据结构的互相转换

(1)Map 转为数组

Map 转为数组最方便的方法,就是使用扩展运算符(...)。

const myMap = new Map()
  .set(true, 7)
  .set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

(2)数组 转为 Map

将数组传入 Map 构造函数,就可以转为 Map。

new Map([
  [true, 7],
  [{foo: 3}, ['abc']]
])
// Map {
//   true => 7,
//   Object {foo: 3} => ['abc']
// }

(3)Map 转为对象

如果所有 Map 的键都是字符串,它可以无损地转为对象。

如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map()
  .set('yes', true)
  .set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }

(4)对象转为 Map

对象转为 Map 可以通过Object.entries()

let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));

此外,也可以自己实现一个转换函数。

function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}

(5)Map 转为 JSON

Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON

function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON

function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}

let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'

(6)JSON 转为 Map

JSON 转为 Map,正常情况下,所有键名都是字符串。

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}

但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。

function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}

Set对象

基本用法

Set 对象允许存储任何类型的唯一值,无论是原始值或者是对象引用。

Set本身是一个构造函数,用来生成 Set 数据结构。

// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56

// 类似于
const set = new Set();
document
 .querySelectorAll('div')
 .forEach(div => set.add(div));
set.size // 56

向 Set 加入值的时候,不会发生类型转换,所以5"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。

let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}

// 上面代码向 Set 实例添加了两次NaN,但是只会加入一个。
// 这表明,在 Set 内部,两个NaN是相等的。

另外,两个对象总是不相等的。

let set = new Set();

set.add({});
set.size // 1

set.add({});
set.size // 2

// 由于两个空对象不相等,所以它们被视为两个值

Set 中的特殊值

Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  • +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
  • undefined 与 undefined 是恒等的,所以不重复;
  • NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。

代码

Set 实例的属性和方法

Set 结构的实例有以下属性。

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。

上面这些属性和方法的实例如下。

s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false

下面是一个对比,看看在判断是否包括一个键上面,Object结构和Set结构的写法不同。

// 对象的写法
const properties = {
  'width': 1,
  'height': 1
};

if (properties[someName]) {
  // do something
}

// Set的写法
const properties = new Set();

properties.add('width');
properties.add('height');

if (properties.has(someName)) {
  // do something
}

遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员。

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。

keys(),values(),entries()

keys方法、values方法、entries方法返回的都是遍历器对象。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
// entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等

可以省略values方法,直接用for...of循环遍历 Set

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
  console.log(x);
}
// red
// green
// blue

forEach()

Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。

Set 结构的键名就是键值(两者是同一个值)

let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

用途

去重

// 去除数组的重复成员
// 第一种
[...new Set(array)]

// 第二种
function dedupe(array) {
  return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]

// 去除字符串里面的重复字符
[...new Set('ababbc')].join('')
// "abc"

数组的map和filter

let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}

let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}

并集(Union)、交集(Intersect)和差集(Difference)

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

改变原来的 Set 结构

目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。

// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6

Proxy

💥 Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

基本用法

一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

let target = {
  name: 'Tom',
  age: 24
}
let handler = {
  get: function (target, key) {
    console.log('getting ' + key);
    return target[key]; // 不是target.key
  },
  set: function (target, key, value) {
    console.log('setting ' + key);
    target[key] = value;
  }
}
let proxy = new Proxy(target, handler)
proxy.name     // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25

// target 可以为空对象
let targetEpt = {}
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"
   
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,
// 因此目标对象与代理对象会互相影响
targetEpt
// {name: "Tom"}

// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {}
let proxyEmpty = new Proxy(targetEmpty, {})
proxyEmpty.name = "Tom"
targetEmpty // {name: "Tom"}

实例方法

get()

用于 target 对象上 propKey 的读取操作

let exam = {
  name: "Tom",
  age: 24
}
let proxy = new Proxy(exam, {
  get(target, propKey, receiver) {
    console.log('Getting ' + propKey);
    return target[propKey];
  }
})
proxy.name
// Getting name
// "Tom"

get() 方法可以继承

下面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。

let proxy = new Proxy({}, {
  get(target, propKey, receiver) {
    // 实现私有属性读取保护
    if (propKey[0] === '_') {
      throw new Erro(`Invalid attempt to get private     "${propKey}"`);
    }
    console.log('Getting ' + propKey);
    return target[propKey];
  }
});

let obj = Object.create(proxy);
obj.name
// Getting name

set()

用于拦截 target 对象上的 propKey 的赋值操作。如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。

let validator = {
  set: function (obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }
    // 对于满足条件的 age 属性以及其他属性,直接保存
    obj[prop] = value;
  }
};
let proxy = new Proxy({}, validator)
proxy.age = 100;
proxy.age           // 100
proxy.age = 'oppps' // 报错
proxy.age = 300     // 报错

第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。

const handler = {
  set: function (obj, prop, value, receiver) {
    obj[prop] = receiver;
  }
};
const proxy = new Proxy({}, handler);
proxy.name = 'Tom';
proxy.name === proxy // true
const exam = {}
Object.setPrototypeOf(exam, proxy)
exam.name = "Tom"
exam.name === exam // true

注意,严格模式下,set代理如果没有返回true,就会报错。

apply()

用于拦截函数的调用、call 和 reply 操作。

target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组。

function sub(a, b) {
  return a - b;
}
let handler = {
  apply(target, ctx, args) {
    console.log('handle apply');
    return Reflect.apply(...arguments) * 3;
  }
}
let proxy = new Proxy(sub, handler)
proxy(2, 1)
// handle apply
// 3

has()

用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。

此方法不判断一个属性是对象自身的属性,还是继承的属性。

let handler = {
  has(target, propKey) {
    console.log("handle has");
    return propKey in target;
  }
}
let exam = { name: "Tom" }
let proxy = new Proxy(exam, handler)
'name' in proxy
// handle has
// true

注意:此方法不拦截 for ... in 循环。

construct()

用于拦截 new 命令。返回值必须为对象。

let handler = {
  construct(target, args, newTarget) {
    console.log('handle construct')
    return Reflect.construct(target, args, newTarget)
  }
}
class Exam {
  constructor(name) {
    this.name = name
  }
}
let ExamProxy = new Proxy(Exam, handler)
let proxyObj = new ExamProxy('Tom')
console.log(proxyObj)
// handle construct
// exam {name: "Tom"}

deleteProperty()

用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除。

var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    delete target[key];
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

defineProperty()

用于拦截 Object.definePro若目标对象不可扩展,增加目标对象上不存在的属性会报错;若属性不可写或不可配置,则不能改变这些属性。

let handler = {
  defineProperty: function (target, propKey, propDesc) {
    console.log("handle defineProperty");
    return true;
  }
}
let target = {}
let proxy = new Proxy(target, handler)
proxy.name = "Tom"
// handle defineProperty
target
// {name: "Tom"}
// defineProperty 返回值为false,添加属性操作无效
let handler1 = {
  defineProperty: function (target, propKey, propDesc) {
    console.log("handle defineProperty");
    return false;
  }
}
let target1 = {}
let proxy1 = new Proxy(target1, handler1)
proxy1.name = "Jerry"
target1
// {}

getOwnPropertyDescriptor()

用于拦截 Object.getOwnPropertyD() 返回值为属性描述对象或者 undefined

let handler = {
    getOwnPropertyDescriptor: function(target, propKey){
        return Object.getOwnPropertyDescriptor(target, propKey);
    }
}
let target = {name: "Tom"}
let proxy = new Proxy(target, handler)
Object.getOwnPropertyDescriptor(proxy, 'name')
// {value: 'Tom', writable: true, enumerable: true, configurable: true}

getPrototypeOf()

主要用于拦截获取对象原型的操作。包括以下操作:

  • Object.prototype.proto
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • instanceof
let exam = {}
let proxy = new Proxy({}, {
  getPrototypeOf: function (target) {
    return exam;
  }
})
Object.getPrototypeOf(proxy) // {}


// 注意,返回值必须是对象或者 null ,否则报错。
// 另外,如果目标对象不可扩展(non-extensible),getPrototypeOf 方法必须返回目标对象的原型对象。
let proxy = new Proxy({}, {
  getPrototypeOf: function (target) {
    return true;
  }
})
Object.getPrototypeOf(proxy)
// TypeError: 'getPrototypeOf' on proxy: trap returned neither object // nor null

isExtensible()

用于拦截 Object.isExtensible 操作。

该方法只能返回布尔值,否则返回值会被自动转为布尔值。

let proxy = new Proxy({}, {
  isExtensible: function (target) {
    return true;
  }
})
Object.isExtensible(proxy) // true

// 注意:它的返回值必须与目标对象的isExtensible属性保持一致,否则会抛出错误。
let proxy = new Proxy({},{
    isExtensible:function(target){
        return false;
    }
})
Object.isExtensible(proxy)
// TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')

ownKeys()

用于拦截对象自身属性的读取操作。主要包括以下操作:

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in

方法返回的数组成员,只能是字符串或 Symbol 值,否则会报错。

若目标对象中含有不可配置的属性,则必须将这些属性在结果中返回,否则就会报错。

若目标对象不可扩展,则必须全部返回且只能返回目标对象包含的所有属性,不能包含不存在的属性,否则也会报错。

let proxy = new Proxy({
  name: "Tom",
  age: 24
}, {
  ownKeys(target) {
    return ['name'];
  }
});
Object.keys(proxy)
// [ 'name' ]f返回结果中,三类属性会被过滤:
//          - 目标对象上没有的属性
//          - 属性名为 Symbol 值的属性
//          - 不可遍历的属性
let target = {
  name: "Tom",
  [Symbol.for('age')]: 24,
};
// 添加不可遍历属性 'gender'
Object.defineProperty(target, 'gender', {
  enumerable: false,
  configurable: true,
  writable: true,
  value: 'male'
});
let handler = {
  ownKeys(target) {
    return ['name', 'parent', Symbol.for('age'), 'gender'];
  }
};
let proxy1 = new Proxy(target, handler);
Object.keys(proxy1)
// ['name']

preventExtensions()

拦截 Object.preventExtensions 操作。

该方法必须返回一个布尔值,否则会自动转为布尔值。

// 只有目标对象不可扩展时(即 Object.isExtensible(proxy) 为 false ),
// proxy.preventExtensions 才能返回 true ,否则会报错
var proxy = new Proxy({}, {
  preventExtensions: function (target) {
    return true;
  }
});
// 由于 proxy.preventExtensions 返回 true,此处也会返回 true,因此会报错
Object.preventExtensions(proxy) // TypeError: 'preventExtensions' on proxy: trap returned truish but // the proxy target is extensible
// 解决方案
var proxy = new Proxy({}, {
  preventExtensions: function (target) {
    // 返回前先调用 Object.preventExtensions
    Object.preventExtensions(target);
    return true;
  }
});
Object.preventExtensions(proxy)
// Proxy {}

setPrototypeOf

主要用来拦截 Object.setPrototypeOf 方法。

返回值必须为布尔值,否则会被自动转为布尔值。

若目标对象不可扩展,setPrototypeOf 方法不得改变目标对象的原型。

let proto = {}
let proxy = new Proxy(function () { }, {
  setPrototypeOf: function (target, proto) {
    console.log("setPrototypeOf");
    return true;
  }
}
);
Object.setPrototypeOf(proxy, proto);
// setPrototypeOf

Proxy.revocable()

用于返回一个可取消的 Proxy 实例。

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

let { proxy, revoke } = Proxy.revocable({}, {});
proxy.name = "Tom";
revoke();
proxy.name
// TypeError: Cannot perform 'get' on a proxy that has been revoked

// `Proxy.revocable()`方法返回一个对象,该对象的`proxy`属性是`Proxy`实例,`revoke`属性是一个函数,可以取消`Proxy`实例。
// 上面代码中,当执行`revoke`函数之后,再访问`Proxy`实例,就会抛出一个错误。

Reflect

💥 Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。

Reflect 对象对某些方法的返回结果进行了修改,使其更合理。

Reflect 对象使用函数的方式实现了 Object 的命令式操作。

静态方法

Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。下面是对它们的解释。

Reflect.get(target, name, receiver)

查找并返回 target 对象的 name 属性。

let exam = {
  name: "Tom",
  age: 24,
  get info() {
    return this.name + this.age;
  }
}
Reflect.get(exam, 'name'); // "Tom"

// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定receiver
let receiver = {
  name: "Jerry",
  age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20

// 当 name 为不存在于 target 对象的属性时,返回 undefined
Reflect.get(exam, 'birth'); // undefined

// 当 target 不是对象时,会报错
Reflect.get(1, 'name'); // TypeError

Reflect.set(target, name, value, receiver)

将 target 的 name 属性设置为 value。返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错。

  let exam = {
    name: "Tom",
    age: 24,
    set info(value) {
      return this.age = value;
    }
  }
  exam.age; // 24
  Reflect.set(exam, 'age', 25); // true
  exam.age; // 25

  // value 为空时会将 name 属性清除
  Reflect.set(exam, 'age',); // true
  exam.age; // undefined

  // 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 receiver , 所以修改的实际上是 receiver 的属性,
  let receiver = {
    age: 18
  }
  Reflect.set(exam, 'info', 1, receiver); // true
  receiver.age; // 1

  let receiver1 = {
    name: 'oppps'
  }
  Reflect.set(exam, 'info', 1, receiver1);
  receiver1.age; // 1

Reflect.has(obj, name)

是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。

如果 obj 不是对象则会报错 TypeError。

let exam = {
  name: "Tom",
  age: 24
}
Reflect.has(exam, 'name'); // true

Reflect.deleteProperty(obj, property)

是 delete obj[property] 的函数化,用于删除 obj 对象的 property 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

let exam = {
  name: "Tom",
  age: 24
}
Reflect.deleteProperty(exam, 'name'); // true
exam // {age: 24} 
// property 不存在时,也会返回 true
Reflect.deleteProperty(exam, 'name'); // true

Reflect.construct(obj, args)

等同于 new target(...args)

function exam(name) {
  this.name = name;
}
Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}

Reflect.getPrototypeOf(obj)

用于读取 obj 的 proto 属性。在 obj 不是对象时不会像 Object 一样把 obj 转为对象,而是会报错。

class Exam{} 
let obj = new Exam() 
Reflect.getPrototypeOf(obj) === Exam.prototype // true

Reflect.setPrototypeOf(obj, newProto)

用于设置目标对象的 prototype

let obj ={} 
Reflect.setPrototypeOf(obj, Array.prototype); // true

Reflect.apply(func, thisArg, args)

等同于 Function.prototype.apply.call(func, thisArg, args) 。

  • func 表示目标函数;
  • thisArg 表示目标函数绑定的 this 对象;
  • args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。 若目标函数无法调用,会抛出 TypeError 。
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5

Reflect.defineProperty(target, propertyKey, attributes)

用于为目标对象定义属性。如果 target 不是对象,会抛出错误。

let myDate = {}
Reflect.defineProperty(MyDate, 'now', {
  value: () => Date.now()
}); // true

const student = {};
Reflect.defineProperty(student, "name", { value: "Mike" }); // true
student.name; // "Mike"

Reflect.getOwnPropertyDescriptor(target, propertyKey)

用于得到 target 对象的 propertyKey 属性的描述对象。在 target 不是对象时,会抛出错误表示参数非法,不会将非对象转换为对象。

  let exam = {}
  Reflect.defineProperty(exam, 'name', {
    value: true,
    enumerable: false,
  })
  Reflect.getOwnPropertyDescriptor(exam, 'name')
  // { configurable: false, enumerable: false, value: true, writable: false}

  // propertyKey 属性在 target 对象中不存在时,返回 undefined
  Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined

Reflect.isExtensible(target)

用于判断 target 对象是否可扩展。返回值为 boolean 。如果 target 参数不是对象,会抛出错误。

let exam = {} 
Reflect.isExtensible(exam) // true

Reflect.preventExtensions(target)

用于让 target 对象变为不可扩展。如果 target 参数不是对象,会抛出错误。

let exam = {} 
Reflect.preventExtensions(exam) // true

Reflect.ownKeys(target)

用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。

let exam = {
  name: 1,
  [Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]
  </script>

组合使用

Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

let exam = {
  name: "Tom",
  age: 24
}
let handler = {
  get: function (target, key) {
    console.log("getting " + key);
    return Reflect.get(target, key);
  },
  set: function (target, key, value) {
    console.log("setting " + key + " to " + value)
    Reflect.set(target, key, value);
  }
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"

使用场景拓展

💥实现观察者模式

// 定义 Set 集合
const queuedObservers = new Set();
// 把观察者函数都放入 Set 集合中
const observe = fn => queuedObservers.add(fn);
// observable 返回原始对象的代理,拦截赋值操作
const observable = obj => new Proxy(obj, { set });
function set(target, key, value, receiver) {
  // 获取对象的赋值操作
  const result = Reflect.set(target, key, value, receiver);
  // 执行所有观察者
  queuedObservers.forEach(observer => observer());
  // 执行赋值操作
  return result;
}

2. 扩展

字符串

扩展的方法

子串的识别

ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。

  • includes() :返回布尔值,判断是否找到参数字符串。
  • startsWith() :返回布尔值,判断参数字符串是否在原字符串的头部。
  • endsWith() :返回布尔值,判断参数字符串是否在原字符串的尾部。

以上三个方法都可以接受两个参数,需要搜索的字符串,和可选的搜索起始位置索引。

let string = "apple,banana,orange"; 
string.includes("banana"); // true 
string.startsWith("apple"); // true 
string.endsWith("apple"); // false 
string.startsWith("banana",6) // true

注意点:

  • 这三个方法只返回布尔值,如果需要知道子串的位置,还是得用 indexOf 和 lastIndexOf 。
  • 这三个方法如果传入了正则表达式而不是字符串,会抛出错误。而 indexOf 和 lastIndexOf 这两个方法,它们会将正则表达式转换为字符串并搜索它。

字符串重复

repeat():返回新的字符串,表示将字符串重复指定次数返回。

  • 如果参数是小数,向下取整
  • 如果参数是 0 至 -1 之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次
  • 如果参数是 NaN,等同于 repeat 零次
  • 如果参数是负数或者 Infinity ,会报错:
  • 如果传入的参数是字符串,则会先将字符串转化为数字
const str = "Hello,"
str.repeat(2);  // "Hello,Hello,"

str.repeat(3.2)  // "Hello,Hello,Hello,"
str.repeat(-0.5)  // ""
str.repeat(NaN)  // ""
str.repeat(-1)  // RangeError: Invalid count value
str.repeat(Infinity)  // RangeError: Invalid count value
str.repeat("hh")  // ""
str.repeat("2")  // "Hello,Hello,"

字符串补全

  • padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。
  • padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。

console.log("h".padStart(5,"o"));  // "ooooh"
console.log("h".padEnd(5,"o"));    // "hoooo"
console.log("h".padStart(5));      // "    h"

// 如果指定的长度小于或者等于原字符串的长度,则返回原字符串:
console.log("hello".padStart(5,"A"));  // "hello"
// 如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!"));  // "hello,worl"
// 常用于补全位数:
console.log("123".padStart(10,"0"));  // "0000000123"

模板字符串

模板字符串相当于加强版的字符串,用反引号 ,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。

基本用法

普通字符串

let string = `Hello'\n'world`;
console.log(string); 
// "Hello'
// 'world"

多行字符串

let string1 =  `Hey,
can you stop angry now?`;
console.log(string1);
// Hey,
// can you stop angry now?

字符串插入变量和表达式。

变量名写在 中,{} 中,{} 中可以放入 JavaScript 表达式。

let name = "Mike";
let age = 27;
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info);
// My Name is Mike,I am 28 years old next year.

字符串中调用函数

function f(){
  return "have fun!";
}
let string2= `Game start,${f()}`;
console.log(string2);  // Game start,have fun!

注意要点

模板字符串中的换行和空格都是会被保留的

标签模板

标签模板,是一个函数的调用,其中调用的参数是模板字符串。

alert`Hello world!`;
// 等价于
alert('Hello world!');

当模板字符串中带有变量,会将模板字符串参数处理成多个参数。

function f(stringArr,...values){
 let result = "";
 for(let i=0;i<stringArr.length;i++){
  result += stringArr[i];
  if(values[i]){
   result += values[i];
        }
    }
 return result;
}
let name = 'Mike';
let age = 27;
f`My Name is ${name},I am ${age+1} years old next year.`;
// "My Name is Mike,I am 28 years old next year."
 
f`My Name is ${name},I am ${age+1} years old next year.`;
// 等价于
f(['My Name is',',I am ',' years old next year.'],'Mike',28);

应用

过滤 HTML 字符串,防止用户输入恶意内容。

function f(stringArr,...values){
 let result = "";
 for(let i=0;i<stringArr.length;i++){
  result += stringArr[i];
   if(values[i]){
     result += String(values[i]).replace(/&/g, "&amp;")
               .replace(/</g, "&lt;")
               .replace(/>/g, "&gt;");
    }
 }
 return result;
}
name = '<Amy&MIke>';
f`<p>Hi, ${name}.I would like send you some message.</p>`;
// <p>Hi, &lt;Amy&amp;MIke&gt;.I would like send you some message.</p>

国际化处理(转化多国语言)

i18n`Hello ${name}, you are visitor number ${visitorNumber}.`;  
// 你好**,你是第**位访问者

数值

数值的表示

二进制表示法新写法: 前缀 0b 或 0B 。

八进制表示法新写法: 前缀 0o 或 0O 。

console.log(0b11 === 3); // true
console.log(0B11 === 3); // true

console.log(0o11 === 9); // true
console.log(0O11 === 9); // true

Number.EPSILON

Number.EPSILON 属性表示 1 与大于 1 的最小浮点数之间的差。

它的值接近于 2.2204460492503130808472633361816E-16,或者 2-52。

测试数值是否在误差范围内:

0.1 + 0.2 === 0.3; // false
// 在误差范围内即视为相等
equal = (Math.abs(0.1 - 0.3 + 0.2) < Number.EPSILON); // true

最大/最小安全整数

安全整数

安全整数表示在 JavaScript 中能够精确表示的整数,安全整数的范围在 2 的 -53 次方到 2 的 53 次方之间(不包括两个端点),超过这个范围的整数无法精确表示。

最大安全整数

安全整数范围的上限,即 2 的 53 次方减 1 。

Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true
Number.MAX_SAFE_INTEGER === Number.MAX_SAFE_INTEGER + 1;     // false
Number.MAX_SAFE_INTEGER - 1 === Number.MAX_SAFE_INTEGER - 2; // false

最小安全整数

安全整数范围的下限,即 2 的 53 次方减 1 的负数。

Number.MIN_SAFE_INTEGER + 1 === Number.MIN_SAFE_INTEGER + 2; // false
Number.MIN_SAFE_INTEGER === Number.MIN_SAFE_INTEGER - 1;     // false
Number.MIN_SAFE_INTEGER - 1 === Number.MIN_SAFE_INTEGER - 2; // true

方法

Number 对象新方法

Number.isFinite()

用于检查一个数值是否为有限的( finite ),即不是 Infinity

console.log( Number.isFinite(1));   // true
console.log( Number.isFinite(0.1)); // true
 
// NaN 不是有限的
console.log( Number.isFinite(NaN)); // false
 
console.log( Number.isFinite(Infinity));  // false
console.log( Number.isFinite(-Infinity)); // false
 
// Number.isFinate 没有隐式的 Number() 类型转换,所有非数值都返回 false
console.log( Number.isFinite('foo')); // false
console.log( Number.isFinite('15'));  // false
console.log( Number.isFinite(true));  // false

// Number.isNaN()用于检查一个值是否为 NaN 。
console.log(Number.isNaN(NaN));      // true
console.log(Number.isNaN('true'/0)); // true
 
// 在全局的 isNaN() 中,以下皆返回 true,因为在判断前会将非数值向数值转换
// 而 Number.isNaN() 不存在隐式的 Number() 类型转换,非 NaN 全部返回 false
Number.isNaN("NaN");      // false
Number.isNaN(undefined);  // false
Number.isNaN({});         // false
Number.isNaN("true");     // false

从全局移植到 Number 对象的方法

逐步减少全局方法,用于全局变量的模块化。

方法的行为没有发生改变。

Number.parseInt()

用于将给定字符串转化为指定进制的整数。

// 不指定进制时默认为 10 进制
Number.parseInt('12.34'); // 12
Number.parseInt(12.34);   // 12
 
// 指定进制
Number.parseInt('0011',2); // 3
 
// 与全局的 parseInt() 函数是同一个函数
Number.parseInt === parseInt; // true
Number.parseFloat()
用于把一个字符串解析成浮点数。
Number.parseFloat('123.45')    // 123.45
Number.parseFloat('123.45abc') // 123.45
 
// 无法被解析成浮点数,则返回 NaN
Number.parseFloat('abc') // NaN
 
// 与全局的 parseFloat() 方法是同一个方法
Number.parseFloat === parseFloat // true

// Number.isInteger()用于判断给定的参数是否为整数。
Number.isInteger(value)
Number.isInteger(0); // true
// JavaScript 内部,整数和浮点数采用的是同样的储存方法,因此 1 与 1.0 被视为相同的值
Number.isInteger(1);   // true
Number.isInteger(1.0); // true
 
Number.isInteger(1.1);     // false
Number.isInteger(Math.PI); // false
 
// NaN 和正负 Infinity 不是整数
Number.isInteger(NaN);       // false
Number.isInteger(Infinity);  // false
Number.isInteger(-Infinity); // false
 
Number.isInteger("10");  // false
Number.isInteger(true);  // false
Number.isInteger(false); // false
Number.isInteger([1]);   // false
 
// 数值的精度超过 53 个二进制位时,由于第 54 位及后面的位被丢弃,会产生误判
Number.isInteger(1.0000000000000001) // true
 
// 一个数值的绝对值小于 Number.MIN_VALUE(5E-324),即小于 JavaScript 能够分辨
// 的最小值,会被自动转为 0,也会产生误判
Number.isInteger(5E-324); // false
Number.isInteger(5E-325); // true

// Number.isSafeInteger()用于判断数值是否在安全范围内。
Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1); // false
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1); // false

Math 对象的扩展

ES6 在 Math 对象上新增了 17 个数学相关的静态方法,这些方法只能在 Math 中调用。

  • Math.sign:判断数字的符号(正、负、0)
  • Math.trunc:用于返回数字的整数部分
  • Math.cbrt:用于计算一个数的立方根
  • Math.expm1():用于计算 e 的 x 次方减 1 的结果,即 Math.exp(x) - 1
  • Math.log1p(x):用于计算1 + x 的自然对数,即 Math.log(1 + x)
  • Math.log10(x):用于计算以 10 为底的 x 的对数
  • Math.log2():用于计算 2 为底的 x 的对数
  • Math.imul:两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数
  • Math.hypot:用于计算所有参数的平方和的平方根
  • Math.clz32:用于返回数字的32 位无符号整数形式的前导0的个数、
  • Math.fround:用于获取数字的32位单精度浮点数形式

双曲函数方法

  • Math.sinh(x): 用于计算双曲正弦。
  • Math.cosh(x): 用于计算双曲余弦。
  • Math.tanh(x): 用于计算双曲正切。
  • Math.asinh(x): 用于计算反双曲正弦。
  • Math.acosh(x): 用于计算反双曲余弦。
  • Math.atanh(x): 用于计算反双曲正切。

指数运算符

1 ** 2; // 1
// 右结合,从右至左计算
2 ** 2 ** 3; // 256
// **=
let exam = 2;
exam ** = 2; // 4

对象

简写

// ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值
const age = 12;
const name = "Amy";
const person = {age, name};
person   //{age: 12, name: "Amy"}
//等同于
const person = {age: age, name: name}

// 方法名也可以简写
const person = {
  sayHi(){
    console.log("Hi");
  }
}
person.sayHi();  //"Hi"
//等同于
const person = {
  sayHi:function(){
    console.log("Hi");
  }
}
person.sayHi();//"Hi"

// 如果是Generator 函数,则要在前面加一个星号:
const obj = {
  * myGenerator() {
    yield 'hello world';
  }
};
//等同于
const obj = {
  myGenerator: function* () {
    yield 'hello world';
  }
};

属性名表达式

// ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内。
const obj = {
 ["he"+"llo"](){
   return "Hi";
  }
}
obj.hello();  //"Hi"

// 注意点:属性的简洁表示法和属性名表达式不能同时使用,否则会报错
const hello = "Hello";
const obj = {
 [hello]
};
obj  //SyntaxError: Unexpected token }
 
const hello = "Hello";
const obj = {
 [hello+"2"]:"world"
};
obj  //{Hello2: "world"}

对象的拓展运算符

拓展运算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象。

// 基本用法
let person = {name: "Amy", age: 15};
let someone = { ...person };
someone;  //{name: "Amy", age: 15}

// 可用于合并两个对象 
let age = {age: 15};
let name = {name: "Amy"};
let person = {...age, ...name};
person;  //{age: 15, name: "Amy"}

// 自定义的属性和拓展运算符对象里面属性的相同的时候:
// 1.自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
let person = {name: "Amy", age: 15};
let someone = { ...person, name: "Mike", age: 17};
someone;  //{name: "Mike", age: 17}

// 2.自定义的属性在拓展运算度前面,则变成设置新对象默认属性值
let person = {name: "Amy", age: 15};
let someone = {name: "Mike", age: 17, ...person};
someone;  //{name: "Amy", age: 15}

// 拓展运算符后面是空对象,没有任何效果也不会报错
let a = {...{}, a: 1, b: 2};
a;  //{a: 1, b: 2}

// 拓展运算符后面是null或者undefined,没有效果也不会报错
let b = {...null, ...undefined, a: 1, b: 2};
b;  //{a: 1, b: 2}

对象的新方法

Object.assign(target, source_1, ···)

用于将源对象的所有可枚举属性复制到目标对象中。

assign 的属性拷贝是浅拷贝

let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);  
// 第一个参数是目标对象,后面的参数是源对象
target;  // {a: 1, b: 2, c: 3}

// 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
// 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
Object.assign(3);         // Number {3}
typeof Object.assign(3);  // "object"

// 因为 null 和 undefined 不能转化为对象,所以会报错:
Object.assign(null);       // TypeError: Cannot convert undefined or null to object
Object.assign(undefined);  // TypeError: Cannot convert undefined or null to object
// 当参数不止一个时,null 和 undefined 不放第一个,即不为目标对象时,会跳过 null 和 undefined ,不报错
Object.assign(1,undefined);  // Number {1}
Object.assign({a: 1},null);  // {a: 1}
 
Object.assign(undefined,{a: 1});  // TypeError: Cannot convert undefined or null to object

// assign 的属性拷贝是浅拷贝:
let sourceObj = { a: { b: 1}};
let targetObj = {c: 3};
Object.assign(targetObj, sourceObj);
targetObj.a.b = 2;
sourceObj.a.b;  // 2

// 同名属性替换
targetObj = { a: { b: 1, c:2}};
sourceObj = { a: { b: "hh"}};
Object.assign(targetObj, sourceObj);
targetObj;  // {a: {b: "hh"}}

// 数组的处理
Object.assign([2,3], [5]);  // [5,3]
// 会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。

Object.is(value1, value2)

用来比较两个值是否严格相等,与(===)基本类似。

// 基本用法
Object.is("q","q");      // true
Object.is(1,1);          // true
Object.is([1],[1]);      // false
Object.is({q:1},{q:1});  // false

// 与(===)的区别
// 1. +0不等于-0
Object.is(+0,-0);  //false
+0 === -0  //true
// 2. NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN  //false

数组

数组创建

Array.of()

将参数中所有值作为元素形成数组。

console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
 
// 参数值可为不同类型
console.log(Array.of(1, '2', true)); // [1, '2', true]
 
// 参数为空时返回空数组
console.log(Array.of()); // []

Array.from()

将类数组对象或可迭代对象转化为数组。

// 参数为数组,返回与原数组一样的数组
console.log(Array.from([1, 2])); // [1, 2]
 
// 参数含空位
console.log(Array.from([1, , 3])); // [1, undefined, 3]

Array.from(arrayLike[, mapFn[, thisArg]])

返回值为转换后的数组

arrayLike

想要转换的类数组对象或可迭代对象。

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

mapFn

可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。

console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]

thisArg

可选,用于指定 map 函数执行时的 this 对象。

let map = {
    do: function(n) {
        return n * 2;
    }
}
let arrayLike = [1, 2, 3];
console.log(Array.from(arrayLike, function (n){
    return this.do(n);
}, map)); // [2, 4, 6]

类数组对象

一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符。

let arr = Array.from({
  0: '1',
  1: '2',
  2: 3,
  length: 3
});
console.log(arr); // ['1', '2', 3]
 
// 没有 length 属性,则返回空数组
let array = Array.from({
  0: '1',
  1: '2',
  2: 3,
});
console.log(array); // []
 
// 元素属性名不为数值且无法转换为数值,返回长度为 length 元素值为 undefined 的数组  
let array1 = Array.from({
  a: 1,
  b: 2,
  length: 2
});
console.log(array1); // [undefined, undefined]

转换可迭代对象

转换 map

let map = new Map();
map.set('key0', 'value0');
map.set('key1', 'value1');
console.log(Array.from(map)); // [['key0', 'value0'],['key1','value1']]

转换 set

let arr = [1, 2, 3];
let set = new Set(arr);
console.log(Array.from(set)); // [1, 2, 3]

转换字符串

let str = 'abc';
console.log(Array.from(str)); // ["a", "b", "c"]

扩展的方法

查找

find()

查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。

let arr = Array.of(1, 2, 3, 4);
console.log(arr.find(item => item > 2)); // 3
 
// 数组空位处理为 undefined
console.log([, 1].find(n => true)); // undefined

findIndex()

查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引

let arr = Array.of(1, 2, 1, 3);
// 参数1:回调函数
// 参数2(可选):指定回调函数中的 this 值
console.log(arr.findIndex(item => item == 2)); // 1
 
// 数组空位处理为 undefined
console.log([, 1].findIndex(n => true)); //0

填充

fill()

将一定范围索引的数组元素内容填充为单个指定的值。

let arr = Array.of(1, 2, 3, 4);
// 参数1:用来填充的值
// 参数2:被填充的起始索引
// 参数3(可选):被填充的结束索引,默认为数组末尾
console.log(arr.fill(0,1,2)); // [1, 0, 3, 4]

copyWithin()

将一定范围索引的数组元素修改为此数组另一指定范围索引的元素。

// 参数1:被修改的起始索引
// 参数2:被用来覆盖的数据的起始索引
// 参数3(可选):被用来覆盖的数据的结束索引,默认为数组末尾
console.log([1, 2, 3, 4].copyWithin(0,2,4)); // [3, 4, 3, 4]
 
// 参数1为负数表示倒数
console.log([1, 2, 3, 4].copyWithin(-2, 0)); // [1, 2, 1, 2]
 
console.log([1, 2, ,4].copyWithin(0, 2, 4)); // [, 4, , 4]

遍历

entries()

遍历键值对。

for(let [key, value] of ['a', 'b'].entries()){
    console.log(key, value);
}
// 0 "a"
// 1 "b"
 
// 不使用 for... of 循环
let entries = ['a', 'b'].entries();
console.log(entries.next().value); // [0, "a"]
console.log(entries.next().value); // [1, "b"]
 
// 数组含空位
console.log([...[,'a'].entries()]); // [[0, undefined], [1, "a"]]

keys()

遍历键名。

for(let key of ['a', 'b'].keys()){
    console.log(key);
}
// 0
// 1
 
// 数组含空位
console.log([...[,'a'].keys()]); // [0, 1]

values()

遍历键值。

for(let value of ['a', 'b'].values()){
    console.log(value);
}
// "a"
// "b"
 
// 数组含空位
console.log([...[,'a'].values()]); // [undefined, "a"]

包含

includes()

数组是否包含指定值。

注意:与 Set 和 Map 的 has 方法区分;Set 的 has 方法用于查找值;Map 的 has 方法用于查找键名。

// 参数1:包含的指定值
[1, 2, 3].includes(1);    // true
 
// 参数2:可选,搜索的起始索引,默认为0
[1, 2, 3].includes(1, 2); // false
 
// NaN 的包含判断
[1, NaN, 3].includes(NaN); // true

嵌套数组转一维数组

flat()

console.log([1 ,[2, 3]].flat()); // [1, 2, 3]
 
// 指定转换的嵌套层数
console.log([1, [2, [3, [4, 5]]]].flat(2)); // [1, 2, 3, [4, 5]]
 
// 不管嵌套多少层
console.log([1, [2, [3, [4, 5]]]].flat(Infinity)); // [1, 2, 3, 4, 5]
 
// 自动跳过空位
console.log([1, [2, , 3]].flat());<p> // [1, 2, 3]

flatMap()

先对数组中每个元素进行了的处理,再对数组执行 flat() 方法。

// 参数1:遍历函数,该遍历函数可接受3个参数:当前元素、当前元素索引、原数组
// 参数2:指定遍历函数中 this 的指向
console.log([1, 2, 3].flatMap(n => [n * 2])); // [2, 4, 6]

数组缓冲区

数组缓冲区是内存中的一段地址。

定型数组的基础。

实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。

创建数组缓冲区

通过构造函数创建:

let buffer = new ArrayBuffer(10);
console.log(buffer.byteLength); // 10

分割已有数组缓冲区
let buffer = new ArrayBuffer(10);
let buffer1 = buffer.slice(1, 3);
console.log(buffer1.byteLength); // 2

视图

视图是用来操作内存的接口。

视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值数据类型来读取和写入数据。

DataView 类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型。

创建:

// 默认 DataView 可操作数组缓冲区全部内容
let buffer = new ArrayBuffer(10);
    dataView = new DataView(buffer); 
dataView.setInt8(0,1);
console.log(dataView.getInt8(0)); // 1
 
// 通过设定偏移量(参数2)与长度(参数3)指定 DataView 可操作的字节范围
let buffer1 = new ArrayBuffer(10);
    dataView1 = new DataView(buffer1, 0, 3);
dataView1.setInt8(5,1); // RangeError

扩展运算符

复制数组

let arr = [1, 2],
    arr1 = [...arr];
console.log(arr1); // [1, 2]
 
// 数组含空位
let arr2 = [1, , 3],
    arr3 = [...arr2];
console.log(arr3); // [1, undefined, 3]

合并数组

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