es6+疑难解析
实际上 JavaScript 是 ECMAScript 的扩展语言
ECMAScript 只提供了最基本的语法
浏览器端 服务器端
es6发布的特性可归纳为以下4类:
- 解决原有语法上的一些问题或者不足(如:let、const)
- 对原有语法进行增强(如:解构赋值)
- 全新的对象、全新的方法、全新的功能(如:promise)
- 全新的数据类型和数据结构(如:symbol、set、get)
到目前为止,es6+中共有8
种数据类型,分别是:
String
、Number
、Boolean
、null
、undefined
、Symbol
、BigInt
、Object
下面我们分别来对es6中的新特性来做下介绍。
1. let、const
首先来看var与let的区别
- var会变量提升,let不会
- let会形成块级作用域,仅在块级作用域内有效
- let会形成暂时性死区,在作用域内,声明前不可使用
- 同一个作用域内,let不允许重复声明
const和let作用类似,但是const在let基础上多了只读属性(变量声明过后不允许再被修改),另外const不允许修改是指不允许修改内存地址,而不是指不允许修改常量中的属性。
来看下面代码,我们可以发现,i的输出每次都为foo,而不是for循环中i的值,
for (let i = 0; i < 3; i++) {
let i = "foo";
console.log(i); // foo
}
来对for循环做一下拆解,结合上面let的的功能,我们就明白了。
let i = 0;
if (i < 3) {
let i = "foo";
console.log(i);
}
i++;
if (i < 3) {
let i = "foo";
console.log(i);
}
i++;
if (i < 3) {
let i = "foo";
console.log(i);
}
i++;
我们可以看到,let形成了块级作用域,所以每次的i都为'foo',只有当我们没有在块作用域中声明i时,i的值才会取外部for循环中的i
总结:建议主用const
,辅助let
,不用var
2. 赋值解构
2.1 数组赋值解构
来看下面这个例子,当我们需要获取路径时,es5中我们需要这么做
var path = '/foo/bar/baz'
var tmp = path.split('/')
var rootDir = tmp[1]
而在es6中使用赋值解构,我们只需要按如下写法:
const [, rootDir] = path.split('/')
console.log(rootDir)
2.2 对象赋值解构
我们来对对象做赋值解构,并为之设置别名和默认值:
const obj = { name: "zxh", age: 18 };
const { name: objName = 'jack' } = obj;
console.log(objName);
应用:简化console.log()方法
const { log } = console
log(1)
3. 模版字符串
3.1 换行
在es5中,如果我们需要对字符串换行,那么我们需要在每行末尾使用\n
,那么在es6中,就简单多了,来看下面例子:
const str = `
this is first
this is second
`
3.2 转义
如果字符串中含有** ` **,那么我们需要使用\
来转义
const str = `this is a \`string\``
3.3 带标签的模版字符串
const name = "tom";
const gender = true;
function myTagFunc(strings, name, gender) {
console.log(strings); // ['hey,', 'is a', '.']
console.log(name); // tom
console.log(gender); // true
return 1;
}
const result = myTagFunc`hey, ${name} is a ${gender}.`;
console.log(result); // 1
4. 字符串扩展
startsWith
、endsWith
、includes
5. 函数扩展
5.1 函数参数默认值
如果函数某个参数有默认值,那么有默认值的参数必须写在尾部。如果非尾部的参数设置默认值,那么该参数的实参是没法省略的。
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]
如果我们给函数传入undefined,将触发该参数等于默认值,null则没有这个效果。
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
// -----------------------------------
function foo(x = 5, y = 6) {
console.log(x, y);
}
foo(undefined, null)
// 5 null
5.2 剩余参数
剩余参数只能出现在尾部,并且只能使用一次
function getname(name, ...args) {}
5.3 函数length属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
length属性的含义是,该函数预期传入的参数个数
。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest 参数也不会计入length属性
。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
5.4 箭头函数
我们通过筛选数组中的基数这个例子来看普通函数和箭头函数区别
// 普通函数
const arr = [1, 2, 3, 4, 5, 6, 7];
arr.filter(function (item) {
return item % 2;
});
// 箭头函数
arr.filter(i => i % 2);
必包:
// 这也算一个必包,setTimeout是在sayHiAsync函数外执行的,但是拿到了sayHiAsync函数的this
const person = {
name: 'tom',
sayHiAsync() {
const _this = this
setTimeout(function () {
console.log(_this.name)
}, 1000)
}
}
6. 对象
6.1 计算()动态属性名
const obj = {
[Math.random]: 123
}
6.2 Object.is()
由于==
会做隐式转换,而===
又无法判断NaN,因此我们可以使用Object.is()
console.log(0==false) // true
console.log(0===false) // false
console.log(+0===-0) // true
console.log(NaN===NaN) // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
7. proxy
7.1 Object.defineProperty与Proxy区别
-
defineProperty只能监视属性的读写,而Proxy监听的更多对象操作。
具体可参考下图表格:
image.png - 支持数组对象监视
ES5通过重写数组方法来实现劫持,Proxy如下:
const list = []
const listProxy = new Proxy(list, {
set(target, property, value) {
console.log('set', property, value) // 0 100
target[property] = value
return true
}
})
listProxy.push(100)
- 非侵入式监听,不需要对原对象做任何操作
8. Reflect
8.1 Reflect成员方法就是Proxy处理对象的默认实现
const obj = {
foo: "123",
bar: "456",
};
const proxy = new Proxy(obj, {
// 如果我们没有定义get方法,那么Proxy会默认一个get方法如下,返回Reflect
get(target, property) {
console.log(" watch logic~");
return Reflect.get(target, property);
},
});
console.log(proxy.foo);
8.2 Reflect最大作用是提供了一套完整的对象调用方法
它的内部封装了一系列对对象的底层操作,便于我们更方便的操作对象
const obj = {
name: "zce",
age: 18,
};
// es5
console.log("name" in obj);
console.log(delete obj["age"]);
console.log(Object.keys(obj));
// Reflect
console.log(Reflect.has(obj, "name"));
console.log(Reflect.deleteProperty(obj, "age"));
console.log(Reflect.ownKeys(obj));
Reflect共有13个api,具体查阅文档
8. Promise
9. 类
9.1 静态方法
es5中通过给函数挂载方法来实现静态方法(函数也是一个对象,可以像对象一样直接挂载),在中es6,我们通过static关键词来定义静态方法
注意:静态方法的this不指向实例对象
class Person {
constructor(name) {
this.name = name;
}
say() {
console.log(`hi, my name is ${this.name}`);
}
static create(name) {
return new Person(name);
}
}
const tom = Person.create("tom");
tom.say();
2. 继承
class Student extends Person {
constructor(name, number) {
super(name, number);
this.number = number;
}
hello() {
super.say();
console.log(`my school number is ${this.number}`);
}
}
const s = new Student("jack", " 100");
s.hello();
10. Set Map
10.1 Set
我们通过Array.from()
或者展开运算符
可以将Set转化为数组
const arr = [1, 2, 3, 4, 5]
const result = Array.from(new Set(arr))
// or
const result = [...new Set(arr)]
10.2 Map
与对象类似,是一个键值对集合,目的是为了解决对象只能用字符串作键值
在es5中,普通对象如果键不是字符串,那么键就等于输入键的toString()结果作为键,es6开始对象支持用字符串或Symbol来作为键值。而在Map中,任意类型数据都可作为键
// 普通对象
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
// 如果键不是字符串,那么键就等于输入键的toString()结果作为键
console.log(Object.keys(obj)) // ['true', '123', '[object Object]']
// Map 任意类型数据都可作为键
const m = new Map();
const tom = { name: "tom" };
m.set(tom, 90);
console.log(m);
console.log(m.get(tom));
11. Symbol
es6开始对象支持用字符串或Symbol来作为键值,symbol最主要的作用就是为对象添加独一无二的属性名
Symbol()===Symbol // false
// Symbol 描述符
Symbol('foo')
Symbol('bar')
11.1 利用Symbol来实现私有成员
// a.js ======================================
const name = Symbol();
const person = {
[name]: "zce",
say() {},
};
// b.js =======================================
// Symbol是唯一的,外面的Symbol与定义时的不一样
// person[Symbol()]
person.say();
11.2 如何获取Symbol
- 通过Symbol.for()传入标识符获取
const s1 = Symbol.for('foo') // 传入标识符
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
Symbol('foo') === Symbol('foo') // false
注意,传入的标识符应该是字符串,如果不是字符串,会默认转为字符串,所以以下代码是相等的
Symbol.for(true) === Symbol.for('true')
11.3 扩展方法
const obj0 = {};
console.log(obj0.toString()); // [object Object]
const obj1 = {
[Symbol.toStringTag]: "Xobject",
};
console.log(obj1.toString()); // [object Xobject]
11.4 获取Symbol键值
普通的for in
、Object.keys()
、JSON.stringify()
是获取不到Symbol的键值的,我们需要通过Object.getOwnPropertySymbols
获取,所以Symbol特别适合设置对象私有成员
const obj = {
[Symbol()]: "symbol value",
foo: "normal value",
};
for (var key in obj) {
console.log(key); // foo
}
console.log(Object.keys(obj)); // 'foo' ]
console.log(JSON.stringify(obj)); // {"foo":"normal value"}
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol() ]
12. for of
for of可以通过break跳出循环,在代码中建议使用for of。但是我们可以发现,for of不支持对象,因为对象没有实现Iterable
接口,我们在Iterable
会详细讲解。
实现Iterable
接口就是for..of
的前提,也就是说,只要该种数据结构实现了(可迭代接口)Iterable
,那么就可以使用for of
const arr = [1, 2, 3, 4, 5];
for (const item of arr) {
console.log(item); // 1,2,3,4,5
if (item > 100) {
break;
}
}
const m = new Map();
m.set("foo", "123");
m.set("bar", " 345");
for (const [key, value] of m) {
console.log(key, value);
}
13. Iterable(迭代器)
13.1 每一个可迭代数据原型上都有一个Symbol.iterator方法
const set = new Set(["foo", "bar", "baz"]);
const iterator = set[Symbol.iterator]();
console.log(iterator.next()); // { value: 'foo', done: false }
console.log(iterator.next()); // { value: 'bar', done: false }
console.log(iterator.next()); // { value: 'baz ', done: false }
console.log(iterator.next()); // { value: undefined, done: true }
console.log(iterator.next()); // { value: undefined, done: true }
13.2 给对象添加迭代器
给对象添加迭代器以后,就可以使用for of
循环了
const obj = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function () {
let index = 0
const _self = this
return {
next: function () {
const result = {
value: _self.store[index],
done: index >= _self.store.length
}
index++
return result
}
}
}
}
13.3 迭代器应用
遍历结构
const todos = {
life: ['吃皈', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外語'],
work: ['喝茶'],
// 普通模式,通过each模式
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
callback(item)
}
},
// 迭代器模式
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
// 使用each
todos.each(function (item) {
console.log(item)
})
// 使用迭代器
for(const item of todos) {
console.log(item)
}
14. Generator(生成器)
解决异步嵌套
14.1 应用
- 使用Generator实现迭代器
// 以上一节例子为例
[Symbol.iterator]: function* () {
const all = [...this.life, ...this.learn, ...this.work]
// let index = 0
// return {
// next: function () {
// return {
// value: all[index],
// done: index++ >= all.length
// }
// }
// }
for (const item of all) {
yield item
}
}
- 发号器
function* createIdMaker() {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
15. es2016
15.1 includes
indexof不能查找数组中的NaN(掘金遍历数组的文章)
15.2 指数运算
计算2的10次方
// es5:
Math.pow(2, 10)
// es7:
2 ** 10
16. es2017
16.1 对象扩展
Object.values() // 对象值的数组
Objece.entries // 对象键值对数组
- 通过Objece.entries 可以使对象用for of 循环
const obj = {
foo: 'foo',
bar: 'bar'
}
for (const [item, index] of Object.entries(obj)) {
console.log(index, item)
}
- 将对象转为Map形式对象
new Map(Object.entries(obj))
- Object.getOwnPropertyDescriptors
以下问题,当拷贝一个对象时,对象中的getter属性被当作了普通属性,所以修改p2中firstName的值,fullName没有变化,这时我们可以通过Object.getOwnPropertyDescriptors解决
const p1 = {
firstName: 'Lei',
lastName: 'Wang',
get fullName() {
return this.firstName + ' ' + this.lastName
}
}
console.log(p1.fullName) // Lei Wang
const p2 = Object.assign({}, p1)
p2.firstName = 'Zhao'
console.log(p2.fullName) // Lei wang
const descriptors = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.defineProperties({}, descriptors)
p2.firstName = 'Zhao'
console.log(p2.fullName) // zhao wang