JavaScript大总结es6++(next)部分(持续完善ing)

428 阅读25分钟

javaScript的正在朝完整化发展,结合服务端node的诞生以及第三方各种插件的快速崛起,我们可以用来干什么?

javaScript足够灵活,且很多方法的参数以及参数个数非常灵活。

请充分思考这两句话的涵义!

javaScript大总结es5部分请移步juejin.cn/editor/draf…

1. 基础扩展

  • 1.1 块级作用域(let、const)
var 可以重复声明,无法限制修改,函数级作用域
let 不能重复声明,变量可以修改,块级作用域,谷歌浏览器为了调试方便console窗口可以重复声明
const 不能重复声明,常量不能修改,块级作用域,但是内部可以修改

const a={}
a.b=a
console.log(a)
以上赋值可以,且这是一个典型的闭环

若想 冻结修改内部 无效 可以这么做
let a=Object.freeze({})
a.b=a
console.log(a)
  • 1.2 函数 参数增强(参数默认值和剩余参数)
参数的扩展、数组展开
function show(a,b,...[c,d,e]){}
等价于
function show(a,b,c,d,e){}

默认参数
为定义好的可选参数赋值即为默认参数
传值覆盖,空值默认
function show(a,b={}){}

原始函数
function(){}
箭头函数
()=>{}
箭头函数有几个使用注意点。
(1)箭头函数没有自己的this对象,箭头函数里的this指的是运行环境上层作用域中的this,且箭头函数里的this不可被call,apply,bind修改
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。


ES2019 对函数实例的toString()方法做出了修改。
toString()
方法返回函数代码本身,以前会省略注释和空格。

catch 命令的参数省略
JavaScript 语言的try...catch结构,以前明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。
try {
  // option
} catch (err) {
  // 处理错误
}
上面代码中,catch命令后面带有参数err。
很多时候,catch代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。ES2019 做出了改变,允许catch语句省略参数。
try {
  // option
} catch {
  // 处理错误
}
  • 1.3解构赋值(数组解构、对象解构、参数解构)
解构赋值(数组解构、对象解构、参数解构)应该注意
1.左右两边结构必须一致
2.右边必须是合法的值
3.声明和赋值不可分离
let [a,b,c]=[1,2,3];
let {a,b,c}={a:1,b:2,c:3};
let [json,arr,num,str]=[{a:1,b:2},[1,2],1,'xcbdh'];
4.允许赋默认值
let {a,b,c=3}={a:1,b:2}
5.可以结构一切可以结构的东西,自行脑补

注意
let x;
({x} = {x: 1});
  • 1.4模板字符串
使用${}放变量
let {a,b,c=3}={a:1,b:2}
console.log(`a值为${a},b值为${b},c值为(${a}+${b})为${a+b}`) // a值为1,b值为2,c值为(1+2)为3
  • 1.4class与extends (创建对象、面向对象、继承)
1.class关键字、构造器和类分离\
2.class内部添加方法

class User{
	constructor(name,pwd){
		this.name=name;
		this.pwd=pwd;
	}
	showName(){
		alert(this.name);
	}
	showPwd(){
		alert(this.pwd);
	}
}
//继承:
class VipUser extends User{
	constructor(name,pwd,level){
		super(name,pwd);
		this.level=level;
	}
	showLevel(){
		alert(this.level);
	}
}


面向对象应用-React 编程式组件
class Item extends React.Component{
	constructor(...args){
		super(...args);
	}
	
	render(){
		return <li>{this.props.str}</li>
	}
}

class List extends React.Component{
	constructor(...args){
		super(...args);
	}
	
	render(){
		return <ul>
			{this.props.arr.map(a=><Item str={a}></Item>)}
		</ul>;
	}
}

window.onload=function(){
	let oDiv=document.getElementById('div1');
	
	ReactDOM.render(
		<List arr={['aaa','bbb','ccc']}></List>,
		oDiv
	)
}
  • 1.5 Promise
异步:操作之间无关,可同时进行多个操作
同步:同时只能做一个操作

new Promise((resolve, reject)=>{
    // 一段耗时的异步操作
    resolve('成功') // 数据处理完成
    // reject('失败') // 数据处理出错
  }
).then((res) => { // 成功
   console.log(res)
}).catch((err) => {// 失败
  console.log(err)
  // throw(err)
}).finally(()=>{
  console.log("finally")
})
resolve作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
promise有三个状态:
1、pending[待定]初始状态
2、fulfilled[实现]操作成功
3、rejected[被否决]操作失败
当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
promise状态一经改变,不会再变。
Promise对象的状态改变,只有两种可能:
从pending变为fulfilled
从pending变为rejected。
这两种情况只要发生,状态就凝固了,不会再变了。


Promise.all() 批量执行
Promise.all([p1, p2, p3])用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise
它接收一个数组作为参数
数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态改变
当所有的子Promise都完成,该Promise完成,返回值是全部值得数组
有任何一个失败,该Promise失败,返回值是第一个失败的子Promise结果

Promise.race() 类似于Promise.all() ,区别在于它有任意一个完成就算完成

Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

  • 1.6 for of、for in遍历
性能:for循环 >forof > forEach > forin

-  forEach只能用于数组。
-  forin 语句以原始插入顺序迭代对象的可枚举属性。
    对于这个原始插入顺序,是按照继承和原型链的顺序的。以数组举例:自身 -> Array.prototype -> Object.prototype
-  forof 语句遍历可迭代要迭代的众多类型数据(字符串,任何object,生成器等),功能却丰富的多。
-  如果你不想修改语句块中的变量 , 也可以使用const代替let
-  forof迭代出的数据是基于可迭代对象的默认迭代器*的。这一点和…扩展运算符分割元素异曲同工。

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}


forof遍历Map
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);
    
for (let entry of iterable) {
  console.log(entry);
}
// ["a", 1]
// ["b", 2]
// ["c", 3]

for (let [key, value] of iterable) {
  console.log(value);
}
// 1
// 2
// 3


function* fibonacci() { // a generator function
  let [prev, curr] = [0, 1];
  while (true) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}
for (let n of fibonacci()) {
  console.log(n);
  // truncate the sequence at 1000
  if (n >= 1000) {
    break;
  }
}


for...of循环用于遍历同步的 Iterator 接口。
新引入的for await...o`循环,则是用于遍历异步的 Iterator 接口。
async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
  }
}
// a
// b

1.7 Proxy

Proxy 用于修改某些操作的默认行为, 等同于在语言层面做出修改, 所以属于一种“ 元编程”( meta programming), 即对编程语言进行编程。
roxy 可以理解成, 在目标对象之前架设一层“ 拦截”, 外界对该对象的访问, 都必须先通过这层拦截, 因此提供了一种机制, 可以对外界的访问进行过滤和改写。 Proxy 这个词的原意是代理, 用在这里表示由它来“ 代理” 某些操作, 可以译为“ 代理器”。

Proxy 支持的拦截操作一览, 一共 13 种:
get(target, propKey, receiver): 拦截对象属性的读取, 比如proxy.foo和proxy['foo']set(target, propKey, value, receiver): 拦截对象属性的设置, 比如proxy.foo = v或proxy['foo'] = v, 返回一个布尔值。
has(target, propKey): 拦截propKey in proxy的操作, 返回一个布尔值。
deleteProperty(target, propKey): 拦截delete proxy[propKey] 的操作, 返回一个布尔值。
ownKeys(target): 拦截Object.getOwnPropertyNames(proxy)、 Object.getOwnPropertySymbols(proxy)、 Object.keys(proxy)、 for...in循环, 返回一个数组。 该方法返回目标对象所有自身的属性的属性名, 而Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey): 拦截Object.getOwnPropertyDescriptor(proxy, propKey), 返回属性的描述对象。
defineProperty(target, propKey, propDesc) : 拦截Object.defineProperty(proxy, propKey, propDesc)、 Object.defineProperties(proxy, propDescs) , 返回一个布尔值。
preventExtensions(target) : 拦截Object.preventExtensions(proxy) , 返回一个布尔值。
getPrototypeOf(target) : 拦截Object.getPrototypeOf(proxy) , 返回一个对象。
isExtensible(target) : 拦截Object.isExtensible(proxy) , 返回一个布尔值。
setPrototypeOf(target, proto) : 拦截Object.setPrototypeOf(proxy, proto) , 返回一个布尔值。 如果目标对象是函数, 那么还有两种额外操作可以拦截。
apply(target, object, args) : 拦截 Proxy 实例作为函数调用的操作, 比如proxy(...args) 、 proxy.call(object, ...args) 、 proxy.apply(...) 。
construct(target, args) : 拦截 Proxy 实例作为构造函数调用的操作, 比如new proxy(...args) 。

一个技巧是将 Proxy 对象, 设置到object.proxy属性, 从而可以在object对象上调用。
var object = { proxy: new Proxy(target, handler) };

var handler = {
  get: function (target, name) {
    console.log("get")
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },
  set: function (target, thisBinding, args) {
    console.log("set")
    return 20;
  },
  apply: function (target, thisBinding, args) {
    console.log("apply")
    return args[0];
  },
  construct: function (target, args) {
    console.log("construct")
    return { value: args[1] };
  }
};

var fproxy = new Proxy({}, handler);
var fproxy2 = new Proxy(function (x, y) {
  return x + y;
}, handler);
let obj = Object.create(proxy);
fproxy2(1, 2)

虽然 Proxy 可以代理针对目标对象的访问, 但它不是目标对象的透明代理, 即不做任何拦截的情况下, 也无法保证与目标对象的行为一致。 主要原因就是在 Proxy 代理的情况下, 目标对象内部的this关键字会指向 Proxy 代理。

有些原生对象的内部属性, 只有通过正确的this才能拿到, 所以 Proxy 也无法代理这些原生对象的属性。这时, this绑定原始对象, 就可以解决这个问题。

const target = new Date('2015-01-01');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  }
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1


ES5 提供了 Object.defineProperty 方法, 该方法可以在一个对象上定义一个新属性, 或者修改一个对象的现有属性, 并返回这个对象。

Object.defineProperty(obj, prop, descriptor)
参数:
   obj: 要在其上定义属性的对象。
   prop: 要定义或修改的属性的名称。
   descriptor: 将被定义或修改的属性的描述符。
缺点: Object.defineProperty的第一个缺陷, 无法监听数组变化

区别:
   Proxy可以直接监听对象而非属性
   Proxy直接可以劫持整个对象, 并返回一个新对象, 不管是操作便利程度还是底层功能上都远强于Object.defineProperty。
   Proxy可以直接监听数组的变化
   Proxy有多达13种拦截方法, 不限于apply、 ownKeys、 deleteProperty、 has等等是Object.defineProperty不具备的。
  • 1.8 Reflect
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 APIReflect对象的设计目的有这样几个。

(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
老写法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}
新写法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true4Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {
  set: function(target, name, value, receiver) {
    var success = Reflect.set(target, name, value, receiver);
    if (success) {
      console.log('property ' + name + ' on ' + target + ' set to ' + value);
    }
    return success;
  }
});
上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。

下面是另一个例子。
var loggedObj = new Proxy(obj, {
  get(target, name) {
    console.log('get', target, name);
    return Reflect.get(target, name);
  },
  deleteProperty(target, name) {
    console.log('delete' + name);
    return Reflect.deleteProperty(target, name);
  },
  has(target, name) {
    console.log('has' + name);
    return Reflect.has(target, name);
  }
});
上面代码中,每一个Proxy对象的拦截操作(get、delete、has),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

有了Reflect对象以后,很多操作会更易读。
老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1

静态方法
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对象的方法是一一对应的。

  • 1.9 生成器(Generator)
生成器就是通过构造函数Generator创建出来的对象,生成器既是一个迭代器,同时又是一个可迭代的对象。

function*() {
   yield
}

next 方法的参数
yield表达式本身没有返回值,或者说总是返回undefined。
next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

const fs = require('fs');
const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};
const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};


ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。

上面代码的函数gen可以写成async函数,就是下面这样。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
(2)更好的语义。
asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promiseasync函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

async函数用法
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

下面是一个例子。

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log。

另一种情况是,await命令后面是一个thenable对象(即定义了then方法的对象),那么await会将其等同于 Promise 对象。

class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(
      () => resolve(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const sleepTime = await new Sleep(1000);
  console.log(sleepTime);
})();
// 1000
上面代码中,await命令后面是一个Sleep对象的实例。这个实例不是 Promise 对象,但是因为定义了then方法,await会将其视为Promise处理。


异步 Generator 函数 
语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。
异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。
异步 Generator 函数也可以通过next方法的参数,接收外部传入的数据
async function* gen() {
  yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }


yield* 语句
yield*语句也可以跟一个异步遍历器。
async function* gen1() {
  yield 'a';
  yield 'b';
  return 2;
}

async function* gen2() {
  // result 最终会等于 2
  const result = yield* gen1();
}
  • 1.10 新增Map
Map数据结构,它类似于对象,也是键值对的集合,但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以作为键名。也就是说,Object结构提供了“字符串-值”的对应,Map结构提供了“值-值”的对应。是一种更完善的Hash结构实现。如果你需要‘键值对’的数据结构,MapObject更适合。  

实例的属性和操作方法
size属性 size属性返回Map结构的成员总数。

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

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

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

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

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


遍历方法
Map结构原生提供是三个遍历器生成函数和一个遍历方法。遍历顺序是插入顺序。
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历Map的所有成员

例子

const map = new Map([
     [1,'a'],
     [2,'b'],
     [3,'c'],
]);
map.set("foo",1).set("baz",2);
const map1 = new Map([...map].filter(([k,v])=> k < 3));
console.log(map1);
const map2 = new Map([...map].map( ([k,v]) =>[ k * 2,v]));
  • 1.10 新增WeakMap
Weakmap结构与Map结构类似,也是用于生成键值的集合。
WeakMap可以使用Set方法添加成员。
WeakMapWeakSet相似,没有遍历方法和清空方法,WeakMap只有四个方法可以使用:get(),set(),has(),delete()。

WeakMapMap有两点区别。
第一:和WeakSet一样,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
第二:WeakSet的键名所指的对象,不计入垃圾回收机制。
WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。

键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。
  • 1.11 新增Set
Set 类似于数组,但是成员的值都是唯一的,没有重复的值。
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

[...new Set(array)] 上面的方法也可以用于,去除字符串里面的重复字符。

Set 实例的属性和方法
size
返回Set实例的成员总数。

add(value)
添加某个值,返回 Set 结构本身。

delete(value)
删除某个值,返回一个布尔值,表示删除是否成功。

has(value)
返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。

遍历操作
keys()
返回键名的遍历器

values()
返回键值的遍历器

entries()
返回键值对的遍历器

forEach()
使用回调函数遍历每个成员

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

扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。

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

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

  • 1.11 新增WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。

首先,WeakSet 的成员只能是对象,而不能是其他类型的值。

其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收,如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。

由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。

WeakSet 结构有以下三个方法。

add(value)
向 WeakSet 实例添加一个新成员。

delete(value)
清除 WeakSet 实例的指定成员。

has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。

WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

下面是 WeakSet 的另一个例子。

const foos = new WeakSet()
class Foo {
  constructor() {
    foos.add(this)
  }
  method () {
    if (!foos.has(this)) {
      throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
    }
  }
}
上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用 WeakSet 的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏。
  • 1.11 新增模块导出导入
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 RubyrequirePythonimport,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJSAMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJSAMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJSAMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

export 命令
模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。


import 命令
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
注意:
import命令具有提升效果,会提升到整个模块的头部,首先执行。
如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

exportimport 的复合写法
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
export { foo as myFoo } from 'my_module';
整体输出
export * from 'my_module';
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

严格模式主要有以下限制:
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀 0 表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
evalarguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface)
上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。
其中,尤其需要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this
  • 1.12 ArrayBuffer 类型化数组
javascript数组(Array)长什么样子,相信大家都清楚,那么我说说差别应该就可以了解这究竟是个什么了!
数组里面可以放数字、字符串、布尔值以及对象和数组等,ArrayBuffer01组成的二进制数据
数组放在堆中,ArrayBuffer则把数据放在栈中(所以取数据时后者快)
ArrayBuffer初始化后固定大小,数组则可以自由增减。


ArrayBuffer作为内存区域,可以存放多种类型的数据。不同数据有不同的存储方式,这就叫做“视图”。目前,JavaScript提供以下类型的视图:
Int8Array8位有符号整数,长度1个字节。
Uint8Array8位无符号整数,长度1个字节。
Int16Array16位有符号整数,长度2个字节。
Uint16Array16位无符号整数,长度2个字节。
Int32Array32位有符号整数,长度4个字节。
Uint32Array32位无符号整数,长度4个字节。
Float32Array32位浮点数,长度4个字节。
Float64Array64位浮点数,长度8个字节。


ArrayBuffer与字符串的互相转换
ArrayBuffer转为字符串,或者字符串转为ArrayBuffer,有一个前提,即字符串的编码方法是确定的。假定字符串采用UTF-16编码(JavaScript的内部编码方式),可以自己编写转换函数。

ArrayBuffer转为字符串,参数为ArrayBuffer对象
function ab2str(buf) {
   return String.fromCharCode.apply(null, new Uint16Array(buf));
}
 
字符串转为ArrayBuffer对象,参数为字符串
function str2ab(str) {
    var buf = new ArrayBuffer(str.length*2); // 每个字符占用2个字节
    var bufView = new Uint16Array(buf);
    for (var i=0, strLen=str.length; i<strLen; i++) {
         bufView[i] = str.charCodeAt(i);
    }
    return buf;
}


DataView视图
如果一段数据包括多种类型(比如服务器传来的HTTP数据),这时除了建立ArrayBuffer对象的复合视图以外,还可以通过DataView视图进行操作。

DataView视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer对象的各种类型化视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而DataView的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。

DataView本身也是构造函数,接受一个ArrayBuffer对象作为参数,生成视图。

DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]);
下面是一个实例。

var buffer = new ArrayBuffer(24);
var dv = new DataView(buffer);

DataView视图提供以下方法读取内存:
getInt8:读取1个字节,返回一个8位整数。
getUint8:读取1个字节,返回一个无符号的8位整数。
getInt16:读取2个字节,返回一个16位整数。
getUint16:读取2个字节,返回一个无符号的16位整数。
getInt32:读取4个字节,返回一个32位整数。
getUint32:读取4个字节,返回一个无符号的32位整数。
getFloat32:读取4个字节,返回一个32位浮点数。
getFloat64:读取8个字节,返回一个64位浮点数。
这一系列get方法的参数都是一个字节序号,表示从哪个字节开始读取。


DataView视图提供以下方法写入内存:
setInt8:写入1个字节的8位整数。
setUint8:写入1个字节的8位无符号整数。
setInt16:写入2个字节的16位整数。
setUint16:写入2个字节的16位无符号整数。
setInt32:写入4个字节的32位整数。
setUint32:写入4个字节的32位无符号整数。
setFloat32:写入4个字节的32位浮点数。
setFloat64:写入8个字节的64位浮点数。
这一系列set方法,接受两个参数,第一个参数是字节序号,表示从哪个字节开始写入,第二个参数为写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,false或者undefined表示使用大端字节序写入,true表示使用小端字节序写入。
  • 1.13装饰器 Decorator
Decorator 提案经过了大幅修改,目前还没有定案。
装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。许多面向对象的语言都有这项功能,目前有一个提案将其引入了 ECMAScript。
装饰器是一种函数,写成@ + 函数名。它可以放在类和类方法的定义前面。

装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。

Mixin
在装饰器的基础上,可以实现Mixin模式。所谓Mixin模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。

function mixins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list);
  };
}

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // "foo"

2. 数据 方法 实例 扩展

  • 2.1字符串
新方法

includes(), startsWith(), endsWith()
传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。


includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
这三个方法都支持第二个参数,表示开始搜索的位置。

repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次,参数如果是小数,会被取整。如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
padStart()和padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
例如:'123456'.padStart(10,'0')//"0000123456" 字符串取10位,开头补0

trimStart(),trimEnd()
行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

matchAll()
matchAll()方法返回一个正则表达式在当前字符串的所有匹配。

replaceAll()
历史上,字符串的实例方法replace()只能替换第一个匹配。如果要替换所有的匹配,不得不使用正则表达式的g修饰符。
正则表达式毕竟不是那么方便和直观,ES2021 引入了replaceAll()方法,可以一次性替换所有匹配。
用法与replace()相同,返回一个新字符串,不会改变原字符串。
String.prototype.replaceAll(searchValue, replacement)
上面代码中,searchValue是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有g修饰符)。
如果searchValue是一个不带有g修饰符的正则表达式,replaceAll()会报错。这一点跟replace()不同。
replaceAll()的第二个参数replacement是一个字符串,也可以是一个函数,表示替换的文本
$&:匹配的子字符串。
$` :匹配结果前面的文本。
$':匹配结果后面的文本。
$n:匹配成功的第n组内容,n是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。
$$:指代美元符号$。

'1112221112222'.replaceAll("222",($1)=>{
   return $1+'a'+$1
}  
// "111222a222111222a2222"
这个替换函数可以接受多个参数。第一个参数是捕捉到的匹配内容,第二个参数捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。

const str = '123abc456';
const regex = /(\d+)([a-z]+)(\d+)/g;

function replacer(match, p1, p2, p3, offset, string) {
  return [p1, p2, p3].join(' - ');
}
str.replaceAll(regex, replacer)
// 123 - abc - 456
上面例子中,正则表达式有三个组匹配,所以replacer()函数的第一个参数match是捕捉到的匹配内容(即字符串123abc456),后面三个参数p1、p2、p3则依次为三个组匹配。

String.fromCodePoint() 
用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于0xFFFF的字符。
注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。

String.raw() 
该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。

作为函数,String.raw()的代码实现基本如下。

String.raw = function (strings, ...values) {
  let output = '';
  let index;
  for (index = 0; index < values.length; index++) {
    output += strings.raw[index] + values[index];
  }
  output += strings.raw[index]
  return output;
}

codePointAt() 
JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符。codePointAt()方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点


normalize()
许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode 提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。
ES6 提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。
'\u01D1'.normalize() === '\u004F\u030C'.normalize() // true
normalize方法可以接受一个参数来指定normalize的方式,参数的四个可选值如下。
NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。
NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。)
NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。
'\u004F\u030C'.normalize('NFC').length // 1
'\u004F\u030C'.normalize('NFD').length // 2
上面代码表示,NFC参数返回字符的合成形式,NFD参数返回字符的分解形式。

不过,normalize方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过 Unicode 编号区间判断。
  • 2.2 Object扩展
Object.is(value1, value2);
传入两个要比较的值,判断是否相同,全等的话返回true,不全等返回false。
Object.is()它用来比较两个值是否严格相等,与严格比较运算符( === )的行为基本一致,是在三等号判断的基础上新增了两个不同之处。
三等号既要判断值的类型是否相等,还要判断引用地址是否相等。所以Object.is()也是,在判断对象和数组这些引用类型的数据是不相等的。

Object.assign()
Object.assign()方法用于对象的合并,可以将多个源对象( source )的所有可枚举属性,按顺序合并到目标对象( target )。
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会`覆盖`前面的属性!!
 
Object.is()
用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
  
Object.keys()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历( enumerable )属性的键名数组。

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

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

Object.fromEntries() § ⇧
Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。
该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。

Object.getOwnPropertyDescriptors()
getOwnPropertyDescriptor()方法会返回某个对象所有自身属性(非继承属性)的描述对象(descriptor)。

__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法。

__proto__属性
__proto__属性(前后各两个下划线),用来读取或设置当前对象的原型对象(prototype)。
Object.setPrototypeOf(),Object.getPrototypeOf()
JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法。

__proto__属性
__proto__属性(前后各两个下划线),用来读取或设置当前对象的原型对象(prototype)。目前,所有浏览器Object.setPrototypeOf()
写操作

Object.getPrototypeOf()
读操作

Object.create()
生成操作

实现上,__proto__调用的是Object.prototype.__proto__,具体实现如下。

  • 2.3 Array扩展
Array.from()
将伪数组或可遍历对象转换为真正的数组
  
find((item, index) => item.endWith("。"))
用于找出第一个符合条件的数组成员,如果没有找到返回undefined

findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1

findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [{
     id: 1,
     name: '张三'
 }, { 
     id: 2,
     name: '李四'
 },{ 
     id: 2,
     name: '王五'
 }]; 
 let target = ary.find((item, index) => item.id == 2);
//找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个
  
  
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9); 
console.log(index); // 2

includes()
[1, 2, 3].includes(2) // true 
[1, 2, 3].includes(4) // false

另外, MapSet 数据结构有一个has方法,需要注意与includes区分。
Map 结构的has方法,是用来查找键名的,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。
Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。
  • 2.3 RegExp扩展
RegExp扩展构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。
而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

ES2020增加了String.prototype.matchAll()方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。

RegExp实例属性
.flags 
返回正则表达式的修饰符。

.source 属性
返回正则表达式的正文


正则可以编组和引用

编组
ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
具名组匹配 在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名。

具名组匹配等于为每一组匹配加上了 ID,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码。

如果具名组没有匹配,那么对应的groups对象属性会是undefined。
引用
如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法。

示例:
匹配任意闭合HTML标签的正则表达式:
<(?<HtmlTag>[\w]+)[^>]*?>((?<Nested><\k<HtmlTag>[^>]*>)|<\/\k<HtmlTag>>(?<-Nested>)|.*?)*<\/\k<HtmlTag>>

如果只想匹配div标签,可以使用下面的正则表达式:
<(?<HtmlTag>div)[^>]*?>((?<Nested><\k<HtmlTag>[^>]*>)|</\k<HtmlTag>>(?<-Nested>)|.*?)*</\k<HtmlTag>>
你可以把div修改成任意你想要匹配的HTML标签

如果想同时匹配多个HTML标签,可以使用下面的正则表达式:
<(?<HtmlTag>(div|span|h1))[^>]*?>((?<Nested><\k<HtmlTag>[^>]*>)|</\k<HtmlTag>>(?<-Nested>)|.*?)*</\k<HtmlTag>>

如果想匹配包含ID的标签,可以使用下面的正则表达式:
<(?<HtmlTag>[\w]+)[^>]*\s[iI][dD]=(?<Quote>["']?)footer(?(Quote)\k<Quote>)[^>]*?(/>|>((?<Nested><\k<HtmlTag>[^>]*>)|</\k<HtmlTag>>(?<-Nested>)|.*?)*</\k<HtmlTag>>)
这个正则匹配任意id为footer的HTML标签

'2015-01-02'.replace(re, (
   matched, // 整个匹配结果 2015-01-02
   capture1, // 第一个组匹配 2015
   capture2, // 第二个组匹配 01
   capture3, // 第三个组匹配 02
   position, // 匹配开始的位置 0
   S, // 原字符串 2015-01-02
   groups // 具名组构成的一个对象 {year, month, day}
 ) => {
 let {day, month, year} = groups;
 return `${day}/${month}/${year}`;
});

3. 运算符

  • 3.1表达式逻辑运算符
链判断运算符?
let data={}
data.obj?.obj


链判断运算符?.有三种写法。
obj?.prop // 对象属性是否存在
obj?.[expr] // 同上
func?.(...args) // 函数或对象方法是否存在


??运算符
它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
??本质上是逻辑运算,它与其他两个逻辑运算符&&和||有一个优先级问题,它们之间的优先级到底孰高孰低。优先级的不同,往往会导致逻辑运算的结果不同。
现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。


逻辑赋值运算符
ES2021 引入了三个新的逻辑赋值运算符(logical assignment operators),将逻辑运算符与赋值运算符进行结合。

或赋值运算符
x ||= y
// 等同于
x || (x = y)

与赋值运算符
x &&= y
// 等同于
x && (x = y)

Null 赋值运算符
x ??= y
等同于
x ?? (x = y)

这三个运算符||=、&&=、??=相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。

它们的一个用途是,为变量或属性设置默认值。
老的写法
user.id = user.id || 1;
新的写法
user.id ||= 1;
  • 3.2幂运算符(**)
`x ** y` 等价于 `Math.pow(x, y)`。但是在之前的 V8 引擎实现时有一个 bug,导致两者并不总是相等,比如:`Math.pow(99,99)` 的结果是 `3.697296376497263e+197`,但是 `99**99` 的结果是 `3.697296376497268e+197`。

Math.pow() 特殊值处理
返回 `x` 的 `y` 次方的依赖于实现的近似值
-`y` 是 `NaN`, 返回结果是 `NaN`.
-`y` 是 `+0`, 返回结果是 `1`, 即使 `x` 是 `NaN`.
-`y` 是 `−0`, 返回结果是 `1`, 即使 `x` 是 `NaN`.
-   若 `x` 是 `NaN` 且 `y` 是非零 , 返回结果是 `NaN`.
-   若 `abs(x)>1` 且 `y` 是 `+∞`, 返回结果是 `+∞`.
-   若 `abs(x)>1` 且 `y` 是 `−∞`, 返回结果是 `+0`.
-   若 `abs(x)==1` 且 `y` 是 `+∞`, 返回结果是 `NaN`.
-   若 `abs(x)==1` 且 `y` 是 `−∞`, 返回结果是 `NaN`.
-   若 `abs(x)<1` 且 `y` 是 `+∞`, 返回结果是 `+0`.
-   若 `abs(x)<1` 且 `y` 是 `−∞`, 返回结果是 `+∞`.
-   若 `x` 是 `+∞` 且 `y>0`, 返回结果是 `+∞`.
-   若 `x` 是 `+∞` 且 `y<0`, 返回结果是 `+0`.
-   若 `x` 是 `−∞` 且 `y>0` 且 `y` 是奇数 , 返回结果是 `−∞`.
-   若 `x` 是 `−∞` 且 `y>0` 且 `y` 不是奇数 , 返回结果是 `+∞`.
-   若 `x` 是 `−∞` 且 `y<0` 且 `y` 是奇数 , 返回结果是 `−0`.
-   若 `x` 是 `−∞` 且 `y<0` 且 `y` 不是奇数 , 返回结果是 `+0`.
-   若 `x` 是 `+0` 且 `y>0`, 返回结果是 `+0`.
-   若 `x` 是 `+0` 且 `y<0`, 返回结果是 `+∞`.
-   若 `x` 是 `−0` 且 `y>0` 且 `y` 是奇数, 返回结果是 `−0`.
-   若 `x` 是 `−0` 且 `y>0` 且 `y` 不是奇数, 返回结果是 `+0`.
-   若 `x` 是 `−0` 且 `y<0` 且 `y` 是奇数, 返回结果是 `−∞`.
-   若 `x` 是 `−0` 且 `y<0` 且 `y` 不是奇数, 返回结果是 `+∞`.
-   若 `x<0` 且 `x` 是有限的, 且 `y` 是有限的, 且 `y` 不是整数 , 返回结果是 `NaN`.