1、声明
const:声明一个常量,let:声明一个变量;const/let 声明的常量/变量都只能作用于代码块(块级作用域或函数作用域)里;
if (true) {
let name = '布兰'
}
console.log(name) // undefined
const/let 不存在变量提升,所以在代码块里必须先声明然后才可以使用,这叫暂时性死区;
let name = 'bubuzou'
if (true) {
name = '布兰'
let name
}
console.log(name)
const/let 不允许在同一个作用域内,重复声明;
function setName(name) {
let name = '' // SyntaxError
}
const 声明时必须初始化,且后期不能被修改,但如果初始化的是一个对象,那么不能修改的是该对象的内存地址;
const person = {
name: '布兰'
}
person.name = 'bubuzou'
console.log(person.name) // 'bubuzou'
person = '' // TypeError
const/let 在全局作用域中声明的常量/变量不会挂到顶层对象(浏览器中是 window )的属性中;
var name = '布兰'
let age = 12
console.log(window.name) // '布兰'
console.log(window.age) // undefined
| var | let | const | |
|---|---|---|---|
| 变量提升 | √ | × | × |
| 全局变量 | √ | × | × |
| 重复声明 | √ | × | × |
| 重新赋值 | √ | √ | × |
| 暂时死区 | × | √ | √ |
| 块作用域 | × | √ | √ |
| 只声明不初始化 | √ | √ | × |
2、解构赋值
- 字符串解构:
const [a, b, c, d, e] = "hello"
-
对象解构
- 形式:
const { x, y } = { x: 1, y: 2 } - 默认:
const { x, y = 2 } = { x: 1 } - 改名:
const { x, y: z } = { x: 1, y: 2 }
- 形式:
-
数组解构
- 规则:数据结构具有
Iterator接口可采用数组形式的解构赋值 - 形式:
const [x, y] = [1, 2] - 默认:
const [x, y = 2] = [1]
- 规则:数据结构具有
-
函数参数解构
- 数组解构:
function Func([x = 0, y = 1]) {} - 对象解构:
function Func({ x = 0, y = 1 } = {}) {}
- 数组解构:
应用场景:
- 交换变量值:
let x = 1, y = 2;
[x, y] = [y, x]
console.log(x, y) // 2 1
- 返回函数多个值:
function Func() {
return [1, 2, 3];
}
const [x, y, z] = Func();
console.log(x); // 1
console.log(y); // 2
console.log(z); // 3
- 定义函数参数:
let person = {
name: '布兰',
age: 12
}
init(person)
// 普通用法
function init(person) {
let {name, age} = person
}
// 更简洁用法
function init({name, age}) {}
- 提取JSON数据:
let responseData = {
code: 1000,
data: {},
message: 'success'
}
let { code, data = {} } = responseData
- 定义函数参数默认值:
function initPerson({name = '布兰', age = 12} = {}) {
console.log(name, age)
}
initPerson() // '布兰' 12
initPerson({age: 20}) // '布兰' 20
- 遍历Map结构:
let map = new Map()
map.set('beijing', '北京')
map.set('xiamen', '厦门')
for (let [key, value] of map) {
console.log(key, value)
}
3、字符串扩展
- 可以使用
for...of正确遍历字符串:
let str = '😀🤣😜😍🤗🤔'
for (const emoji of str) {
console.log(emoji) // 😀🤣😜😍🤗🤔
}
for(let i = 0, l = str.length; i < l; i++) {
console.log(str[i]) // 不能正确输出表情
}
- 模板字符串使用两个反引号标识(``),可以用来定义多行字符串,或者使用它在字符串中插入变量:
- 在ES6中通过
${}就可以完成字符串的拼接,只需要将变量放在大括号之中。
let name = 'hero'
let tips = `Hello ${name},
welcome to my world.`
alert( tips )
String.raw()返回把字符串所有变量替换且对斜杠进行转义的结果:
String.raw`Hi\n${2+3}!` // "Hi\n5!"
-
字符串是否包含子串:
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!'
s.includes('o') // true
s.startsWith('Hello') // true
s.endsWith('!') // true
这三个方法都支持第二个参数,表示开始搜索的位置:
let s = 'Hello world!'
s.includes('Hello', 6) // false
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
上面代码表示,使用第二个参数 n 时,endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。
repeat(n)将当前字符串重复n次后,返回一个新字符串:
'x'.repeat(2) // 'xx'
'x'.repeat(1.9) // 'x'
'x'.repeat(NaN) // ''
'x'.repeat(undefined) // ''
'x'.repeat('2a') // ''
'x'.repeat(-0.6) // '',解释:0 ~ 1 之间的小数相当于 0
'x'.repeat(-2) // RangeError
'x'.repeat(Infinity) // RangeError
4、数值扩展
二进制(0b)和八进制(0o)表示法:
let num = 100
let b = num.toString(2) // 二进制的100:1100100
let o = num.toString(8) // 八进制的100:144
0b1100100 === 100 // true
0o144 === 100 // true
Number.isFinite() 判断一个数是否是有限的数,入参如果不是数值一律返回 false:
Number.isFinite(-2.9) // true
Number.isFinite(NaN) // false
Number.isFinite('') // false
Number.isFinite(false) // true
Number.isFinite(Infinity) // false
Number.isNaN() 判断一个数值是否为 NaN,如果入参不是 NaN 那结果都是 false:
Number.isNaN(NaN) // true
Number.isFinite('a'/0) // true
Number.isFinite('NaN') // false
数值转化:Number.parseInt() 和 Number.parseFloat(),非严格转化,从左到右解析字符串,遇到非数字就停止解析,并且把解析的数字返回:
parseInt('12a') // 12
parseInt('a12') // NaN
parseInt('') // NaN
parseInt('0xA') // 10,0x开头的将会被当成十六进制数
parseInt() 默认是用十进制去解析字符串的,其实他是支持传入第二个参数的,表示要以多少进制的 基数去解析第一个参数:
parseInt('1010', 2) // 10
parseInt('ff', 16) // 255
Number.isInteger() 判断一个数值是否为整数,入参为非数值则一定返回 false
5、对象扩展
- 对象属性简写:
let name = '布兰'
let person = {
name,
getName() {
return this.name
}
}
// 等同于
let person1 = {
name: '布兰',
getName: function() {
return this.name
}
}
- 属性名表达式:在用对象字面量定义对象的时候,允许通过属性名表达式来定义对象属性:
let name = 'name';
let person = {//在对象定义中,使用方括号 [] 表示属性名可以是一个表达式。在这里,name 是一个变量,其值为 'name' 字符串。
[name]: '布兰',
['get'+ name](){
return this.name
}
}
- 方法的
name属性,存在好几种情况,这里仅列出常见的几种:
情况一:普通对象方法的 name 属性直接返回方法名,函数声明亦是如此,函数表达式返回变量名:
let person = {
hi(){}
}
person.hi.name // 'hi'
情况二:构造函数的 name 为 anonymous:
(new Function).name // 'anonymous'
情况三:绑定函数的 name 将会在函数名前加上 bound:
js 代码解读复制代码function foo() {}
foo.bind({}).name // 'bound foo'
情况四:如果对象的方法使用了取值函数(getter)和存值函数(setter),则 name 属性不是在该方法上面,而是该方法的属性的描述对象的 get 和 set 属性上面:
let o = {
get foo(){},
set foo(x){}
}
o.foo.name // TypeError: Cannot read property 'name' of undefined
let descriptor = Object.getOwnPropertyDescriptor(o, "foo")
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
- 属性的可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。可以通过 Object.getOwnPropertyDescriptor() 来获取对象某个属性的描述:
let person = { name: '布兰', age: 12 }
Object.getOwnPropertyDescriptor(person, 'name')
// {
// configurable: true,
// enumerable: true,
// value: "布兰",
// writable: true,
// }
这里的 enumerable 就是对象某个属性的可枚举属性,如果某个属性的 enumerable 值为 false 则表示该属性不能被枚举,所以该属性会被如下 4 种操作忽略:
for...in:只遍历对象自身的和继承的可枚举的属性;Object.keys():返回对象自身的所有可枚举的属性的键名;JSON.stringify():只串行化对象自身的可枚举的属性;Object.assign(): 只拷贝对象自身的可枚举的属性。
let person = { name: '布兰' }
Object.defineProperty(person, 'age', {
configurable: true,
enumerable: false,
value: 12,
writable: true
})
person // { name: '布兰', age: 12 }
// 以下操作都将忽略 person 对象的 age 属性
for (let x in person) {
console.log(x) // 'name'
}
Object.keys(person) // ['name']
JSON.stringify(person) // '{"name": "布兰"}'
Object.assign({}, person) // { name: '布兰' }
Reflect.ownKeys(obj): 返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举:
// 基于上面的代码
Reflect.ownKeys(person) // ['name', 'age']
super关键字,指向对象的原型对象,只能用于对象的方法中,其他地方将报错:
let person = {
name: '布兰',
getName() {
return super.name
}
}
Object.setPrototypeOf(person, {name: 'hello'})
person.getName() // 'hello'
// 以下几种 super 的使用将报错
const obj1 = {
foo: super.foo
}
const obj2 = {
foo: () => super.foo
}
const obj3 = {
foo: function () {
return super.foo
}
}
Object.assign()用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),如果有同名属性,则后面的会直接替换前面的:
let target = { a: 1 }
let source1 = { a: 2, b: 3, d: {e: 1, f: 2} }
let source2 = { a: 3, c: 4, d: {g: 3} }
Object.assign(target, source1, source2)
target // { a: 3, b: 3, c: 4, d: {g: 3} }
Object.assign() 实现的是浅拷贝,如果源对象某个属性是对象,那么拷贝的是这个对象的引用:
let target = {a: {b: 1}}
let source = {a: {b: 2}}
Object.assign(target, source)
target.a.b = 3
source.a.b // 3
__proto__属性是用来读取和设置当前对象的原型,而由于其下划线更多的是表面其是一个内部属性,所以建议不在正式场合使用它,而是用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。Object.setPrototypeOf()用于设置对象原型,Object.getPrototypeOf()用于读取对象原型:
let person = {name: '布兰'}
Object.setPrototypeOf(person, {name: '动物'})
Object.getPrototypeOf(person) // {name: '动物'}
6、数组扩展
- 数组扩展运算符(...)将数组展开成用逗号分隔的参数序列,只能展开一层数组:
// 应用一:函数传参
Math.max(...[1, 2, 3]) // 3
// 应用二:数组合并
let merge = [...[1, 2], ...[3, 4], 5, 6] // 1, 2, 3, 4, 5, 6
// 应用三:浅克隆
let a = [1, 2, 3]
let clone = [...a]
a === clone // false
// 应用四:数组解构
const [x, ...y] = [1, 2, 3]
x // 1
y // [2, 3]
Array.from()可以将类数组对象(NodeList,arguments)和可迭代对象转成数组:
// 应用一:字符串转数组
Array.from('foo') // ['f', 'o', 'o']
// 应用二:数组合并去重
let merge = [...[1, 2], ...[2, 3]]
Array.from(new Set(merge)) // ['1', '2', '3']
// 应用三:arguments 转数组
function f() {
return Array.from(arguments)
}
f(1, 2, 3) // [1, 2, 3]
如果 Array.from() 带第二个参数 mapFn,将对生成的新数组执行一次 map 操作:
Array.from([1, 2, 3], (x) => x * x ) // [1, 4, 9]
Array.from({length: 3}, (v, i) => ++i) // [1, 2, 3]
Array.of()将一组参数转成数组:
Array.of(1, 2, 3) // [1, 2, 3]
// 类似于
function arrayOf(...params){
return [].slice.call(params)
}
arrayOf(1, 2, 3) // [1, 2, 3]
-
Array.copyWithin()在当前数组内部,将制定位置的成员复制到其他位置(会覆盖原来位置的成员),最后返回一个新数组。接收 3 个参数,参数为负数表示右边开始计算:target(必选):替换位置的索引;start(可选):从该位置开始读取数据,默认为 0;end(可选):从该位置结束读取数据(不包括该位置的数据),默认为原数组长度;
[1, 2, 3, 4, 5].copyWithin(-1) // [1, 2, 3, 4, 1]
[1, 2, 3, 4, 5].copyWithin(1) // [1, 1, 2, 3, 4]
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, -3, -1) // [3, 4, 3, 4, 5]
- 查找第一个出现的子成员:
find()和findIndex():
// 找出第一个偶数
[1, 6, 9].find((val, index, arr) => val % 2 === 0) // 6
// 找出第一个偶数的索引位置
[1, 6, 9].findIndex((val, index, arr) => val % 2 === 0) // 1
-
fill()使用给定的值来填充数组,有 3 个参数:value:填充值;start(可选),开始索引,默认为 0;end(可选):结束索引,默认为数组长度,不包括该索引位置的值;
// 初始化空数组
Array(3).fill(1) // [1, 1, 1]
[1, 2, 3, 4].fill('a', 2, 4) // [1, 2, 'a', 'a']
- 通过
keys()(键名)、entries()(键值)和values()(键值对) 获取数组迭代器对象,可以被for...of迭代,
let arr = ['a', 'b', 'c']
for (let x of arr.keys()) {
console.log(x) // 1, 2, 3
}
for (let v of arr.values()) {
console.log(v) // 'a' 'b' 'c'
}
for (let e of arr.entries()) {
console.log(e) // [0, 'a'] [0, 'b'] [0, 'c']
}
- 数组空位,是指数组没有值,比如:
[,,],而像这种[undefined]是不包含空位的。由于ES6之前的一些API对空位的处理规则很不一致,所以实际操作的时候应该尽量避免空位的出现,而为了改变这个现状,ES6的API会默认将空位处理成undefined:
[...[1, , 3].values()] // [1, undefined, 3]
[1, , 3].findIndex(x => x === undefined) // 1
7、正则扩展
RegExp构造函数,允许首参为正则表达式,第二个参数为修饰符,如果有第二个参数,则修饰符以第二个为准:
let reg = new RegExp(/xYz\d+/gi, i)
reg.flags // 'i'
u修饰符:含义为Unicode模式,用来正确处理大于\uFFFF的Unicode字符。也就是说,如果待匹配的字符串中可能包含有大于\uFFFF的字符,就必须加上u修饰符,才能正确处理。
// 加上 u 修饰符才能让 . 字符正确识别大于 \uFFFF 的字符
/^.$/.test('🤣') // false
/^.$/u.test('🤣') // true
// 大括号 Unicode 字符表示法必须加上 u 修饰符
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
// 有 u 修饰符,量词才能正确匹配大于 \uFFFF 的字符
/🤣{2}/.test('🤣🤣') // false
/🤣{2}/u.test('🤣🤣') // true
y修饰符,与g修饰符类似也是全局匹配;不同的是g是剩余字符中匹配即可,而y则是必须在剩余的第一个字符开始匹配才行,所以y修饰符也叫黏连修饰符:
// y修饰符
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
8、函数扩展
- 函数参数默认值。参数不能有同名的,函数体内不能用
let和const声明同参数名的变量:
function printInfo(name = '布兰', age = 12) {}
- 剩余(
rest) 参数(...变量名)的形式,用于获取函数的剩余参数,注意rest参数必须放在最后一个位置,可以很好的代替arguments对象:
function f(x, ...y) {
console.log(x) // 1
for (let val of y) {
coonsole.log(val) // 2 3
}
}
f(1, 2, 3)
- 箭头函数语法比函数表达式更简洁,并且没有自己的
this、arguments,不能用作构造函数和用作生成器。
几种箭头函数写法:
let f1 = () => {} // 没有参数
let f2 = (x) => {} // 1个参数
let f3 = x => {} // 1个参数可以省略圆括号
let f4 = (x, y) => {} // 2个参数以上必须加上圆括号
let f5 = (x = 1, y = 2) => {} // 支持参数默认值
let f6 = (x, ...y) => {} // 支持 rest 参数
let f7 = ({x = 1, y = 2} = {}) // 支持参数解构
箭头函数没有自己的 this:
function Person(){
this.age = 0
setInterval(() => {
this.age++
}, 1000)
}
var p = new Person() // 1 秒后 Person {age: 1}
通过 call/apply 调用箭头函数的时候将不会绑定第一个参数的作用域:
let adder = {
base: 1,
add: function(a) {
let f = v => v + this.base
return f(a)
},
addThruCall: function(a) {
let f = v => v + this.base
let b = {
base: 2
}
return f.call(b, a)
}
}
adder.add(1) // 输出 2
adder.addThruCall(1) // 仍然输出 2
箭头函数没有自己的 arguments 对象,不过可以使用 rest 参数代替:
let log = () => {
console.log(arguments) // ReferenceError
}
log(2, 3)
// 剩余参数代替写法
let restLog = (...arr) => {
console.log(arr) // [2, 3]
}
restLog(2, 3)
箭头函数不能用作构造器,和 new 一起用会抛出错误:
let Foo = () => {}
let foo = new Foo()
// TypeError: Foo is not a constructor
箭头函数返回对象字面量,需要用圆括号包起来:
let func2 = () => ({foo: 1})
9、Symbol
symbol 是一种基本数据类型,Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。
每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。
例子如下:
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');
console.log(typeof symbol1); // "symbol"
console.log(symbol3.toString()); // "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo')); // false
10、Set/WeakSet
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
所以我们可以通过Set实现数组去重
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]
WeakSet 结构与 Set 类似,但区别有以下两点:
WeakSet对象中只能存放对象引用, 不能存放值, 而Set对象都可以。WeakSet对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样,WeakSet对象是无法被枚举的, 没有办法拿到它包含的所有元素。
所以代码如下:
var ws = new WeakSet()
var obj = {}
var foo = {}
ws.add(window)
ws.add(obj)
ws.has(window) // true
ws.has(foo) // false, 对象 foo 并没有被添加进 ws 中
ws.delete(window) // 从集合中删除 window 对象
ws.has(window) // false, window 对象已经被删除了
ws.clear() // 清空整个 WeakSet 对象
11、Map/WeakMap
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
例子如下,我们甚至可以使用NaN来作为键值:
var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"
var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"
WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
跟Map的区别与Set跟WeakSet的区别相似,具体代码如下:
var wm1 = new WeakMap(),
wm2 = new WeakMap(),
wm3 = new WeakMap();
var o1 = {},
o2 = function(){},
o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // value可以是任意值,包括一个对象
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined,wm2中没有o2这个键
wm2.get(o3); // undefined,值就是undefined
wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是undefined)
wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // undefined,wm3已被清空
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
12、Proxy/Reflect
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy 的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
Proxy跟Reflect是非常完美的配合,例子如下:
const observe = (data, callback) => {
return new Proxy(data, {
get(target, key) {
return Reflect.get(target, key)
},
set(target, key, value, proxy) {
callback(key, value);
target[key] = value;
return Reflect.set(target, key, value, proxy)
}
})
}
const FooBar = { open: false };
const FooBarObserver = observe(FooBar, (property, value) => {
property === 'open' && value
? console.log('FooBar is open!!!')
: console.log('keep waiting');
});
console.log(FooBarObserver.open) // false
FooBarObserver.open = true // FooBar is open!!!
根据您的要求,我将继续补充和完善文档《第2周-ES实用指南(一)》的内容。以下是续写部分:
13、Class 类
但是在ES6之后,我们只需要写成以下形式,不必使用传统的方法写一个构造函数
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
const bran = new Person('布兰', 12);
bran.sayHello(); // Hello, my name is 布兰
14、Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件更合理和更强大。
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve('Success');
} else {
reject('Error');
}
});
promise.then(result => {
console.log(result); // Success
}).catch(error => {
console.error(error); // Error
});
15、模块化
ES6 引入了模块化的概念,使得代码组织更加清晰。
export function add(a, b) {
return a + b;
}
export const PI = 3.14;
// main.js
import { add, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14
16、迭代器和生成器
迭代器是一种接口,为各种不同的数据结构提供统一的访问机制。
const iterable = {
[Symbol.iterator]() {
let step = 0;
const iterator = {
next() {
step++;
if (step === 1) {
return { value: 'hello', done: false };
} else if (step === 2) {
return { value: 'world', done: false };
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
};
for (let value of iterable) {
console.log(value); // hello world
}
生成器函数使用 function* 定义,内部可以使用 yield 关键字来暂停和恢复执行。
function* generator() {
yield 1;
yield 2;
yield 3;
}
const gen = generator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3