之前写过一个ES6的学习笔记,但比较简略,这里总结了一个比较全的
对于面向对象编程而言,更关注类的声明、属性、方法、静态方法、继承、多态、私有属性。
首先我们要先来说明在 JavaScript 世界里如何声明一个 “类”。在 ES6 之前大家都是这么做的:
let Animal = function (type) {
this.type = type
this.walk = function () {
console.log(`I am walking`)
}
}
let dog = new Animal('dog')
let monkey = new Animal('monkey')
在 ES6 中把类的声明专业化了,不在用 function 的方式了,请看:
class Animal {
constructor (type) {
this.type = type
}
walk () {
console.log(`I am walking`)
}
}
let dog = new Animal('dog')
let monkey = new Animal('monkey')
ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
导出(export)
ES6允许在一个模块中使用export来导出多个变量或函数。
导出变量
//test.js export var name = 'Rainbow'
心得:ES6不仅支持变量的导出,也支持常量的导出。
export const sqrt = Math.sqrt;//导出常量
ES6将一个文件视为一个模块,上面的模块通过 export 向外输出了一个变量。一个模块也可以同时往外面输出多个变量。
//test.js var name = 'Rainbow'; var age = '24'; export {name, age};
导出函数
// myModule.js export function myModule(someArg) { return someArg; }
导入(import)
定义好模块的输出以后就可以在另外一个模块通过import引用。
import {myModule} from 'myModule';// main.js import {name,age} from 'test';// test.js
心得:一条import 语句可以同时导入默认函数和其它变量。
import defaultMethod, { otherMethod } from 'xxx.js';
这是ES6中最令人激动的特性之一。=>
不只是关键字function的简写,它还带来了其它好处。箭头函数与包围它的代码共享同一个this
,能帮你很好的解决this的指向问题。有经验的JavaScript开发者都熟悉诸如var self = this;
或var that = this
这种引用外围this的模式。但借助=>
,就不需要这种模式了。
箭头函数的结构
箭头函数的箭头=>之前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头之后可以是一个表达式(作为函数的返回值),或者是用花括号括起的函数体(需要自行通过return来返回值,否则返回的是undefined)。
// 箭头函数的例子
()=>1 v=>v+1 (a,b)=>a+b
()=>{ alert("foo"); }
e=>{
if (e == 0)
{ return 0; }
return 1000/e;
}
卸载监听器时的陷阱
错误的做法
class PauseMenu extends React.Component{ componentWillMount(){ AppStateIOS.addEventListener('change', this.onAppPaused.bind(this)); } componentWillUnmount(){ AppStateIOS.removeEventListener('change', this.onAppPaused.bind(this)); } onAppPaused(event){ } } 复制代码
正确的做法
class PauseMenu extends React.Component{ constructor(props){ super(props); this._onAppPaused = this.onAppPaused.bind(this); } componentWillMount(){ AppStateIOS.addEventListener('change', this._onAppPaused); } componentWillUnmount(){ AppStateIOS.removeEventListener('change', this._onAppPaused); } onAppPaused(event){ } } 复制代码
除上述的做法外,我们还可以这样做:
class PauseMenu extends React.Component{ componentWillMount(){ AppStateIOS.addEventListener('change', this.onAppPaused); } componentWillUnmount(){ AppStateIOS.removeEventListener('change', this.onAppPaused); } onAppPaused = (event) => { //把函数直接作为一个arrow function的属性来定义,初始化的时候就绑定好了this指针 } } 复制代码
心得:不论是箭头函数还是bind,每次被执行都返回的是一个新的函数引用,因此如果你还需要函数的引用去做一些别的事情(譬如卸载监听器),那么你必须自己保存这个引用。
①.let声明的变量具有块级作用域,
{
let a = 1
}
console.log(a); //undefined a
变量是在代码块{}
中使用 let 定义的,它的作用域是这个代码块内部,外部无法访问。
②.let声明的全局变量不是全局对象的属性
var a = 1
console.log(window.a); //1
let a = 1
console.log(window.a); // undefined
③.用let重定义变量会抛出一个语法错误
var a = 1
var a = 2
console.log(a) //2
如果是 let ,则会报错
let a = 1
let a = 2
// VM131:1 Uncaught SyntaxError: Identifier 'a' has already been declared
// at :1:1
(2)Const
const
除了具有let
的块级作用域和不会变量提升外,还有就是它定义的是常量,在用const
定义变量后,我们就不能修改它了,对变量的修改会抛出异常。
const A= 123;
console.log(A);
A = 1;
console.log(A);
// Uncaught TypeError: Assignment to constant variable.
在es5之前遍历数组
for (var i = 0; i < array.length; i++) {
console.log(array[i]);
}
在es6之前遍历数组
for (let val of [1,2,3]) {
console.log(val);
}
// 1,2,3
数组是开发中经常用到的数据结构,它非常好用。在 JavaScript 的世界里有些对象被理解为数组,然而缺不能使用数组的原生 API,比如函数中的 arguments、DOM中的 NodeList等。当然,还有一些可遍历的对象,看上去都像数组却不能直接使用数组的 API,因为它们是伪数组(Array-Like)。要想对这些对象使用数组的 API 就要想办法把它们转化为数组,传统的做法是这样的:
let args = [].slice.call(arguments);
let imgs = [].slice.call(document.querySelectorAll('img'));
基本原理是使用 call 将数组的 api 应用在新的对象上,换句话说是利用改变函数的上下文来间接使用数组的 api。在 ES6 中提供了新的 api 来解决这个问题,就是 Array.from,代码如下:
let args = Array.from(arguments);
let imgs = Array.from(document.querySelectorAll('img'));
Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为7的空数组(注意:这是指一个有7个空位(empty)的数组,而不是由7个undefined组成的数组)。
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
Array(7); // [ , , , , , , ]
Array(1, 2, 3); // [1, 2, 3]
fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
let array = [1, 2, 3, 4]
array.fill(0, 1, 2)
// [1,0,3,4]
find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。
let array = [5, 12, 8, 130, 44];
let found = array.find(function(element) {
return element > 10;
});
console.log(found);
// 12
|
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。其实这个和 find() 是成对的,不同的是它返回的是索引而不是值。
let array = [5, 12, 8, 130, 44];
let found = array.findIndex(function(element) {
return element > 10;
});
console.log(found);
// 1
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,它将返回目标对象。
const target = { a: 1, b: 2 }
const source = { b: 4, c: 5 }
const returnedTarget = Object.assign(target, source)
console.log(target)
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget)
// expected output: Object { a: 1, b: 4, c: 5 }
从语法上可以看出源对象的个数是不限制的(零个或多个),如果是零个直接返回目的对象,如果是多个相同属性的会被后边的源对象的属相覆盖。
let s = Object.assign({ a: 1 })
// {a: 1}
静态方法是面向对象最常用的功能,在 ES5 中利用 function 实现的类是这样实现一个静态方法的。
let Animal = function (type) {
this.type = type
this.walk = function () {
console.log(`I am walking`)
}
}
Animal.eat = function (food) {
console.log(`I am eating`);
}
在 ES6 中使用 static 的标记是不是静态方法,代码如下:
class Animal {
constructor (type) {
this.type = type
}
walk () {
console.log(`I am walking`)
}
static eat () {
console.log(`I am eating`)
}
}
面向对象只所以可以应对复杂的项目实现,很大程度上要归功于继承。如果对继承概念不熟悉的同学,可以自行查询。在 ES5 中怎么实现继承呢?
// 定义父类
let Animal = function (type) {
this.type = type
}
// 定义方法
Animal.prototype.walk = function () {
console.log(`I am walking`)
}
// 定义静态方法
Animal.eat = function (food) {
console.log(`I am eating`)
}
// 定义子类
let Dog = function () {
// 初始化父类
Animal.call(this, 'dog')
this.run = function () {
console.log('I can run')
}
}
// 继承
Dog.prototype = Animal.prototype
从代码上看,是不是很繁琐?而且阅读性也较差。再看看 ES6 是怎么解决这些问题的:
class Animal {
constructor (type) {
this.type = type
}
walk () {
console.log(`I am walking`)
}
static eat () {
console.log(`I am eating`)
}
}
class Dog extends Animal {
constructor () {
super('dog')
}
run () {
console.log('I can run')
}
}
对于函数而言,经常会用到参数,关于参数的默认值通常都是写在函数体中,如在 ES5 的时候大家都会这么写:
1 2 3 4 5 6 7 8 |
|
当一个函数有很多参数涉及初始化的时候,这样写代码极其丑陋,所以在 ES6 中改变了对这种知识的写法:
1 2 3 4 |
|
在写函数的时候,部分情况我们不是很确定参数有多少个,比如求和运算,之前都是这么做的:
function sum () {
let num = 0
Array.prototype.forEach.call(arguments, function (item) {
num += item * 1
})
return num
}
console.log(sum(1, 2, 3))// 6
console.log(sum(1, 2, 3, 4))// 10
其实在上面说过,这个代码在 ES5 中可以这么写,在 ES6 就不能这么写了,因为 arguments 的问题。现在需要这样写:
let num = 0
nums.forEach(function (item) {
num += item * 1
})
return num
}
console.log(sum(1, 2, 3))// 6
console.log(sum(1, 2, 3, 4))// 10
当然,Rest Parameter 也可以和其他参数一起来用,比如:
function sum (base, ...nums) {
let num = base
nums.forEach(function (item) {
num += item * 1
})
return num
}
console.log(sum(30, 1, 2, 3))// 36
console.log(sum(30, 1, 2, 3, 4))// 40
在 ES6 之前对字符串的处理是相当的麻烦,看如下场景:
1. 字符串很长要换行
字符串很长包括几种情形一个是开发时输入的文本内容,一个是接口数据返回的文本内容。如果对换行符处理不当,就会带来异常。
2. 字符串中有变量或者表达式
如果字符串不是静态内容,往往是需要加载变量或者表达式,这个也是很常见的需求。之前的做法是字符串拼接:
var a = 20
var b = 10
var c = 'JavaScript'
var str = 'My age is ' + (a + b) + ' and I love ' + c
console.log(str)
从 ES6 开始可以这样定义字符串了。
`string text`
`string text line 1
string text line 2`
`string text ${expression} string text`
在这里你可以任意插入变量或者表达式,只要用 ${} 包起来就好。
在 ES6 中新增了变量赋值的方式:解构赋值。如果对这个概念不了解,我们可以快速展示一个小示例一睹风采:
let arr = ['Ilya', 'Kantor']
let firstName = arr[0]
let surname = arr[1]
想从数组中找出有意义的项要单独赋值给变量(一一的写),在 ES6 中就可以这样写了:
let [firstName, surname] = ['Ilya', 'Kantor']
console.log(firstName) // Ilya
console.log(surname) // Kantor
首先要说明 Promise 就是为了解决“回调地狱”问题的,它可以将异步操作的处理变得很优雅。如果我们使用 Promise 来解决上面那个问题该怎么做呢?
function loadScript (src) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(script)
script.onerror = (err) => reject(err)
document.head.append(script)
})
}
loadScript('1.js')
.then(loadScript('2.js'), (err) => {
console.log(err)
})
.then(loadScript('3.js'), (err) => {
console.log(err)
})
通过创建 Promise 对象开启一个异步操作的过程,一般用几步完成多次异步操作:
①.Promise.prototype.then()
var promise = new Promise(function (resolve, reject) {
resolve('传递给then的值')
})
promise.then(function (value) {
console.log(value)
}, function (error) {
console.error(error)
})
这段代码创建一个 Promise 对象,定义了处理 onFulfilled 和 onRejected 的函数(handler),然后返回这个 Promise 对象。
这个 Promise 对象会在变为 resolve 或者 reject 的时候分别调用相应注册的回调函数。
当 handler 返回一个正常值的时候,这个值会传递给 Promise 对象的 onFulfilled 方法。
定义的 handler 中产生异常的时候,这个值则会传递给 Promise 对象的 onRejected 方法
②.Promise.resolve()
一般情况下我们都会使用 new Promise()
来创建 Promise 对象,但是除此之外我们也可以使用其他方法。
Promise.resolve(42).then(function (value) {
console.log(value)
})
在这段代码中的 resolve(42) 会让这个 Promise 对象立即进入确定(即resolved)状态,并将 42 传递给后面 then 里所指定的 onFulfilled 函数。
③.Promise.reject()
Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。
new Promise(function (resolve, reject) {
reject(new Error('出错了'))
})
④.Promise.prototype.catch()
捕获异常是程序质量保障最基本的要求,可以使用 Promise 对象的 catch 方法来捕获异步操作过程中出现的任何异常。
function test () {
return new Promise((resolve, reject) => {
reject(new Error('es'))
})
}
test().catch((e) => {
console.log(e.message) // es
})
⑤.Promise.all()
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.all([p1, p2, p3]).then(function (results) {
console.log(results) // [1, 2, 3]
})
Promise.all 生成并返回一个新的 Promise 对象,所以它可以使用 Promise 实例的所有方法。参数传递promise数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回, 新创建的 Promise 则会使用这些 promise 的值。
如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。
由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Paomise.all 可以处理不同类型的 promose对
⑥.Promise.race()
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.race([p1, p2, p3]).then(function (value) {
console.log(value) // 1
})
Promise.race 生成并返回一个新的 Promise 对象。
参数 promise 数组中的任何一个 Promise 对象如果变为 resolve 或者 reject 的话, 该函数就会返回,并使用这个 Promise 对象的值进行 resolve 或者 reject。
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法,这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
Reflect.apply(Math.floor, undefined, [1.75])
// 1;
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index
// 4
Reflect.apply(''.charAt, 'ponies', [3])
// "i"
该方法与ES5中Function.prototype.apply()方法类似:调用一个方法并且显式地指定this变量和参数列表(arguments) ,参数列表可以是数组,或类似数组的对象。
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(…args).
var d = Reflect.construct(Date, [1776, 6, 4])
d instanceof Date // true
d.getFullYear() // 1776
静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。
const student = {}
Reflect.defineProperty(student, 'name', { value: 'Mike' }) // true
student.name // "Mike"
Reflect.deleteProperty 允许你删除一个对象上的属性。返回一个 Boolean 值表示该属性是否被成功删除。它几乎与非严格的 delete operator 相同。
var obj = { x: 1, y: 2 };
Reflect.deleteProperty(obj, "x"); // true
obj; // { y: 2 }
var arr = [1, 2, 3, 4, 5];
Reflect.deleteProperty(arr, "3"); // true
arr; // [1, 2, 3, , 5]
// 如果属性不存在,返回 true
Reflect.deleteProperty({}, "foo"); // true
// 如果属性不可配置,返回 false
Reflect.deleteProperty(Object.freeze({foo: 1}), "foo"); // false
Reflect.get() 方法的工作方式,就像从 object (target[propertyKey]) 中获取属性,但它是作为一个函数执行的。
// Object
var obj = { x: 1, y: 2 }
Reflect.get(obj, 'x') // 1
// Array
Reflect.get(['zero', 'one'], 1) // "one"
// Proxy with a get handler
var x = { p: 1 }
var obj = new Proxy(x, {
get (t, k, r) { return k + 'bar' }
})
Reflect.get(obj, 'foo') // "foobar"
静态方法 Reflect.getOwnPropertyDescriptor() 与 Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined。
Reflect.getOwnPropertyDescriptor({ x: 'hello' }, 'x')
// {value: "hello", writable: true, enumerable: true, configurable: true}
Reflect.getOwnPropertyDescriptor({ x: 'hello' }, 'y')
// undefined
Reflect.getOwnPropertyDescriptor([], 'length')
// {value: 0, writable: true, enumerable: false, configurable: false}
静态方法 Reflect.getPrototypeOf() 与 Object.getPrototypeOf() 方法是一样的。都是返回指定对象的原型(即,内部的 [[Prototype]] 属性的值)。
Reflect.has 用于检查一个对象是否拥有某个属性, 相当于in 操作符
Reflect.set 方法允许你在对象上设置属性。它的作用是给属性赋值并且就像 property accessor 语法一样,但是它是以函数的方式。
// Object
var obj = {};
Reflect.set(obj, "prop", "value"); // true
obj.prop; // "value"
// Array
var arr = ["duck", "duck", "duck"];
Reflect.set(arr, 2, "goose"); // true
arr[2]; // "goose"
// It can truncate an array.
Reflect.set(arr, "length", 1); // true
arr; // ["duck"];
// With just one argument, propertyKey and value are "undefined".
var obj = {};
Reflect.set(obj); // true
Reflect.getOwnPropertyDescriptor(obj, "undefined");
// { value: undefined, writable: true, enumerable: true, configurable: true }
Reflect.setPrototypeOf 方法改变指定对象的原型 (即,内部的 [[Prototype]] 属性值)
Reflect.setPrototypeOf({}, Object.prototype); // true
// It can change an object's [[Prototype]] to null.
Reflect.setPrototypeOf({}, null); // true
// Returns false if target is not extensible.
Reflect.setPrototypeOf(Object.freeze({}), null); // false
// Returns false if it cause a prototype chain cycle.
var target = {};
var proto = Object.create(target);
Reflect.setPrototypeOf(target, proto); // false
在 ES6 标准中新增的一个非常强大的功能是 Proxy,它可以自定义一些常用行为如查找、赋值、枚举、函数调用等。通过 Proxy 这个名称也可以看出来它包含了“代理”的含义,只要有“代理”的诉求都可以考虑使用 Proxy 来实现。
let p = new Proxy(target, handler)
从服务端获取的数据希望是只读,不允许在任何一个环节被修改。
// response.data 是 JSON 格式的数据,来自服务端的响应
// 在 ES5 中只能通过遍历把所有的属性设置为只读
for (let [key] of Object.entries(response.data)) {
Object.defineProperty(response.data, key, {
writable: false
})
}
如果我们使用 Proxy 就简单很多了:
let data = new Proxy(response.data, {
set(obj, key, value) {
return false
}
})
什么是 JavaScript Generators 呢?通俗的讲 Generators 是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。如果这句话不好理解,可以看下接下来的示例。
ES5之前做法
for (let i = 0; i < 5; i += 1) {
console.log(i)
}
// this will return immediately 0 -> 1 -> 2 -> 3 -> 4
用 Generator
function * generatorForLoop () {
for (let i = 0; i < 5; i += 1) {
yield console.log(i)
}
}
const genForLoop = generatorForLoop()
console.log(genForLoop.next()) // first console.log - 0
console.log(genForLoop.next()) // 1
console.log(genForLoop.next()) // 2
console.log(genForLoop.next()) // 3
对比下代码,常规的循环只能一次遍历完所有值,Generator 可以通过调用 next 方法拿到依次遍历的值,让遍历的执行变得“可控”。
function * gen () {
yield 1
yield 2
yield 3
}
let g = gen()
// "Generator { }"
这个是 Generator 的定义方法,有几个点值得注意:
yield 表达式
yield 表达式的返回值是 undefined,但是遍历器对象的 next 方法可以修改这个默认值
function * gen () {
let val
val = yield 1
console.log(`1:${val}`) // 1:undefined
val = yield 2
console.log(`2:${val}`) // 2:undefined
val = yield 3
console.log(`3:${val}`) // 3:undefined
}
var g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 2, done: false}
console.log(g.next()) // {value: 3, done: false}
console.log(g.next()) // {value: undefined, done: true}
yeild * 是委托给另一个遍历器对象或者可遍历对象
function * gen () {
let val
val = yield 1
console.log(`1:${val}`) // 1:undefined
val = yield 2
console.log(`2:${val}`) // 2:undefined
val = yield [3, 4, 5]
console.log(`3:${val}`) // 3:undefined
}
enerator 对象的 next 方法,遇到 yield 就暂停,并返回一个对象,这个对象包括两个属性:value 和 done
Array.prototype.includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
let array1 = [1, 2, 3]
console.log(array1.includes(2))
// expected output: true
var pets = ['cat', 'dog', 'bat']
console.log(pets.includes('cat'))
// expected output: true
console.log(pets.includes('at'))
// expected output: false
在 ES7 之前想判断数组中是否包含一个元素,基本可以这样写:
console.log(array1.find(function (item) { return item === 2 }))
|
我们一直都是这样用的:
|
在 ES7 可以这样写了:
console.log(2 ** 3)
async 和 await 是 Promise 的拓展,如果对 Promise 还不了解的话,请移步 Promise 章节进行学习。
async function firstAsync () {
return 27
}
firstAsync().then(console.log) // 27
也就是说上面的代码等同于:
async function firstAsync () {
return Promise.resolve(27)
}
async 的作用我们清楚了,await呢?话不多说:
async function firstAsync () {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Now it's done!"), 1000)
})
// wait until the promise returns us a value
let result = await promise
// "Now it's done!"
return result
}
firstAsync().then(console.log)
这段代码使用了 await,从这个字面的意思看就是“等待”,它等什么呢?很简单,它等 Promise 返回结果。上面代码的意思是 async 开启了一个 Promise 对象,这个函数内部嵌套了一个 Promise 操作,这个操作需要等 1 秒才返回 “Now it’s done!” 这个结果。也就是说 await 在拿到这个结果之前不会继续执行,一直等到结果才往后继续执行,也就是 async function 声明的 Promise 才会响应(console.log才执行)。
常规
async function firstAsync () {
let result = await 27
return result
}
firstAsync().then(console.log) // 27
对象
async function firstAsync () {
let result = await { a: 1 }
return result
}
firstAsync().then(console.log) // { a: 1 }
|
. await 不可以脱离 async 单独使用
function firstAsync() {
let promise = Promise.resolve(10);
let result = await promise; // Syntax error
}
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for…in,但是for…in还会遍历原型上的属性值)。
let grade = {
'lilei': 98,
'hanmei': 87
}
console.log(Object.values(grade)) // [98, 87]
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致。(区别在于 for-in 循环也枚举原型链中的属性)
let grade = {
'lilei': 98,
'hanmei': 87
}
for (let [key, value] of grade) {
console.log(key, value) // Uncaught TypeError: grade is not iterable
}
我们知道 Object 是不可直接遍历的,上述代码足以说明直接遍历触发了错误。如果使用 Object.entries() 则可以完成遍历任务。
let grade = {
'lilei': 98,
'hanmei': 87
}
for (let [k, v] of Object.entries(grade)) {
console.log(k, v)
// lilei 98
// hanmei 87
}
在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
这两个方法有什么用武之地呢?回忆下我们处理日期的时候经常要格式化,比如 01、02、10等。
1 2 3 4 5 6 7 |
|
之前输出1号到31号,需要手动判断是否小于10号,小于的在前面增加 0。现在可以这样做了:
for (let i = 1; i < 31; i++) {
console.log(i.toString().padStart(2, '0'))
}
String.prototype.padStart 和 String.prototype.padEnd 用法是相同的,只是一个在开头补白一个在结尾。
想理解 Object.getOwnPropertyDescriptors 这个方法之前,首先要弄懂什么是描述符(descriptor)?
const data = {
Portland: '78/50',
Dublin: '88/52',
Lima: '58/40'
}
还是上述那个对象,这里有 key 和 value,上边的代码把所有的 key、value 遍历出来,如果我们不想让 Lima 这个属性和值被枚举怎么办?
Object.defineProperty(data, 'Lima', {
enumerable: false
})
Object.entries(data).map(([city, temp]) => {
console.log(`City: ${city.padEnd(16)} Weather: ${temp}`)
// City: Portland Weather: 78/50
// City: Dublin Weather: 88/52
})
很成功,Lima 没有被遍历出来,那么 defineProperty 的第三个参数就是描述符(descriptor)。这个描述符包括几个属性:
我们知道 for…of 是同步运行的,有时候一些任务集合是异步的,那这种遍历怎么办呢?
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), item.then(console.log))
}
}
test()
// 1560090138232 Promise {}
// 1560090138234 Promise {}
// 1560090138235 Promise {}
// 100
// 2000
// 3000
await 的作用,它可以中断程序的执行直到这个 Promise 对象的状态发生改变,
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), await item.then(console.log))
}
}
test()
// 2000
// 1560091834772 undefined
// 100
// 1560091836774 undefined
// 3000
// 1560091836775 undefined
从返回值看确实是按照任务的先后顺序进行的,其中原理也有说明是利用了 await 中断程序的功能。不过,在 ES9 中也可以用 for…await…of 的语法来操作:
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for await (let item of arr) {
console.log(Date.now(), item)
}
}
test()
// 1560092345730 2000
// 1560092345730 100
// 1560092346336 3000
Promise.prototype.finally() 方法返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。
let connection;
db.open()
.then(conn => {
connection = conn;
return connection.select({ name: 'Jane' });
})
.then(result => {
// Process result
// Use `connection` to make more queries
})
.catch(error => {
// handle errors
})
.finally(() => {
connection.close();
});
前面在讲 function 的 Rest & Spread 方法,忘却的同学可以去复习下。在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const input = {
a: 1,
b: 2
}
const output = {
...input,
c: 3
}
console.log(output) // {a: 1, b: 2, c: 3}
JSON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
JSON.stringify('\u{D800}') === '"\\ud800"' // true
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const newArray = arr.flat(depth) 语法
const numbers = [1, 2, [3, 4, [5, 6]]]
console.log(numbers.flat())
// [1, 2, 3, 4, [5, 6]]
此时 flat 的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]] 这个数组会扁平会处理,不会再继续遍历内部的元素是否还有数组
const numbers = [1, 2, [3, 4, [5, 6]]]
console.log(numbers.flat(2))
// [1, 2, 3, 4, 5, 6]
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
// 返回新数组的元素
}[, thisArg])
参数 | 含义 | 必选 |
callback | 可以生产一个新数组中的元素的函数,可以传三个参数,currentValue,index,array | y |
thisArg | 遍历函数this的指向 | N |
const numbers = [1, 2, 3]
numbers.map(x => [x * 2]) // [[2], [4], [6]]
numbers.flatMap(x => [x * 2]) // [2, 4, 6]
这个小示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下 MDN 的示例:
let arr = ['今天天气不错', '', '早上好']
arr.map(s => s.split(''))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
arr.flatMap(s => s.split(''))
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
let str = ' foo '
console.log(str.length) // 8
str = str.trimStart()
console.log(str.length) // 5
trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。
let str = ' foo '
console.log(str.length) // 8
str = str.trimEnd()
console.log(str.length) // 6
matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器
在了解 matchAll 之前,我们回顾下 ES10 之前一共有多少种正则全部遍历的方法。
(1). RegExp.prototype.exec() with /g
function collectGroup1 (regExp, str) {
const matches = []
while (true) {
const match = regExp.exec(str)
if (match === null) break
// Add capture of group 1 to `matches`
matches.push(match[1])
}
return matches
}
collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`)
// [ 'foo', 'bar', 'baz' ]
(2). String.prototype.match() with /g
如果用 .match 方法结合 /g 的正则模式,将会把所有的匹配打包成一个数组返回,换句话说所有的捕获被忽略。 'abeabd'.match(/(a)b(?=e)/g) // ["ab"] 不过如果没有使用 /g 的正则模式,.match 的效果和 RegExp.prototype.exec() 是一致的。
(3). String.prototype.replace()
function collectGroup1 (regExp, str) {
const matches = []
function replacementFunc (all, first) {
matches.push(first)
}
str.replace(regExp, replacementFunc)
return matches
}
collectGroup1(/"([^"]*)"/ug, `"foo" and "bar" and "baz"`)
// ["foo", "bar", "baz"]
现在看下 .matchAll 方法,可以这样做:
function collectGroup1 (regExp, str) {
let results = []
for (const match of str.matchAll(regExp)) {
results.push(match[1])
}
return results
}
collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`)
// ["foo", "bar", "baz"]
方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。
Object.fromEntries([['foo', 1], ['bar', 2]])
// {foo: 1, bar: 2}
let obj = { abc: 1, def: 2, ghij: 3 }
let res = Object.fromEntries(
Object.entries(obj)
.filter(([ key, val ]) => key.length === 3)
.map(([ key, val ]) => [ key, val * 2 ])
)
console.log(res)
// res is { 'abc': 2, 'def': 4 }
我们知道,Symbol 的描述只被存储在内部的 [[Description]],没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:
const name = Symbol('My name is axuebin')
console.log(name.toString()) // Symbol(My name is axuebin)
console.log(name) // Symbol(My name is axuebin)
console.log(name === 'Symbol(My name is axuebin)') // false
console.log(name.toString() === 'Symbol(My name is axuebin)') // true
toString() 方法返回一个表示当前函数源代码的字符串
function hello (msg) {
console.log('hello')
}
console.log(hello.toString())
// function hello (msg) {
// console.log('hello')
// }
在 ES10 之前我们都是这样捕获异常的:
try {
// tryCode
} catch (err) {
// catchCode
}
在这里 err 是必须的参数,在 ES10 可以省略这个参数:
try {
console.log('Foobar')
} catch {
console.error('Bar')
}
在 ES10 增加了一个数据类型:BigInt,用于处理超过 2^53 的数字。
const aBigInt = 11n;
const aNumber = 156;
const aBigInt = BigInt(aNumber);
aBigInt === 156n // true
typeof aBigInt === bigint // true
参考链接
https://www.cnblogs.com/zhoulifeng/p/12259809.html
https://www.cnblogs.com/miaSlady/p/10955729.html