ES6(菜鸟教程)
本文依据菜鸟教程的ES6教程,作出部分内容的注释及精简,还在更新中。
快速学习的话,重点查看“对象”“数组”“函数”这三个模块。
详见https://www.runoob.com/w3cnote/es6-tutorial.html
let与const
let命令
let是在代码块内有效,var在全局范围内有效
{
let a = 0;
var b = 1;
}
a // ReferenceError: a is not defined
b // 1
let只能声明一次,var可以声明多次
let a = 1;
let a = 2;
var b = 3;
var b = 4;
a // Identifier 'a' has already been declared
b // 4
for 循环计数器很适合用 let(经典例子)
for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
})
}
// 输出十个 10
for (let j = 0; j < 10; j++) {
setTimeout(function(){
console.log(j);
})
}
// 输出 0123456789
PS: 变量 i 是用 var 声明的,在全局范围内有效,所以全局中只有一个变量 i, 每次循环时,setTimeout 定时器里面的 i 指的是全局变量 i ,而循环里的十个 setTimeout 是在循环结束后才执行,所以此时的 i 都是 10。
变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 12345。(若每次循环的变量 j 都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环的值)。
console.log(1);
setTimeout(function () {
console.log(2);
}, 0);
console.log(3);
//输出:132
JS运行机制:
- 所有同步任务都在主线程上执行,形成一个执行栈(Call Stack)
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
setTimeout运行机制:
setTimeout 和 setInterval的运行机制,其实就是将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断。
这意味着,setTimeout指定的代码,必须等到本次执行的所有同步代码都执行完,才会执行。
let 不存在变量提升,var 会变量提升
console.log(a); //ReferenceError: a is not defined
let a = "apple";
console.log(b); //undefined
var b = "banana";
PS: 变量 b 用 var 声明存在变量提升,所以当脚本开始运行的时候,b 已经存在了,但是还没有赋值,所以会输出 undefined。
变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a 不存在,所以会报错。
const命令
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
基本用法:
const PI = "3.1415926";
PI // 3.1415926
const MY_AGE; // SyntaxError: Missing initializer in const declaration
暂时性死区:
var PI = "a";
if(true){
console.log(PI); // Cannot access 'PI' before initialization
const PI = "3.1415926";
}
PS: ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明变量 PI 之前使用它会报错。
注意:
const 如何做到变量在声明初始化之后不允许改变的?其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动。此时,你可能已经想到,简单类型和复合类型保存值的方式是不同的。是的,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。
解构赋值
概述
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
解构模型
在解构中,有下面两部分参与:
- 解构的源,解构赋值表达式的右边部分
- 解构的目标,解构赋值表达式的左边部分
数组模型的解构(Array)
基本
let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3
可嵌套
let [a, [[b], c]] = [1, [[2], 3]];
// a = 1
// b = 2
// c = 3
可忽略
let [a, , b] = [1, 2, 3];
// a = 1
// b = 3
不完全解构
let [a = 1, b] = []; // a = 1, b = undefined
剩余运算符
let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]
字符串等
在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据。
let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'
解构默认值
let [a = 2] = [undefined]; // a = 2
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 3, b = a] = []; // a = 3, b = 3
let [a = 3, b = a] = [1]; // a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
- a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3
- a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a =1
- a 与 b 正常解构赋值,匹配结果:a = 1,b = 2
对象模型的解构(Object)
基本
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'
//baz is not defined
可嵌套可忽略
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj;
// x = 'hello'
// y = 'world'
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { }] } = obj;
// x = 'hello'
不完全解构
let obj = {p: [{y: 'world'}] };
let {p: [{ y }, x ] } = obj;
// x = undefined
// y = 'world'
剩余运算符
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10
// b = 20
// rest = {c: 30, d: 40}
解构默认值
let {a = 10, b = 5} = {a: 3};
// a = 3; b = 5;
let {a: aa = 10, b: bb = 5} = {a: 3};
// aa = 3; bb = 5;
Symbol
概述
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
ES6 数据类型除了 Number 、 String 、 Boolean 、 Object、 null 和 undefined ,还新增了 Symbol 。
基本用法
Symbol函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
let sy = Symbol("KK");
console.log(sy); // Symbol(KK)
typeof(sy); // "symbol"
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk");
sy === sy1; // false
使用场景
作为属性名
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
let sy = Symbol("key1");
// 写法1
let syObject = {};
syObject[sy] = "kk";
console.log(syObject); // {Symbol(key1): "kk"}
// 写法2
let syObject = {
[sy]: "kk"
};
console.log(syObject); // {Symbol(key1): "kk"}
// 写法3
let syObject = {};
Object.defineProperty(syObject, sy, {value: "kk"});
console.log(syObject); // {Symbol(key1): "kk"}
Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
let syObject = {};
syObject[sy] = "kk";
syObject[sy]; // "kk"
syObject.sy; // undefined
注意点
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);
for (let i in syObject) {
console.log(i);
} // 无输出
Object.keys(syObject); // []
Object.getOwnPropertySymbols(syObject); // [Symbol(key1)]
Reflect.ownKeys(syObject); // [Symbol(key1)]
Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false(why?)
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SZVhzlRg-1679643900967)(C:\Users\刘\AppData\Roaming\Typora\typora-user-images\image-20230317152126537.png)]
Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"
Map与Set
Map 对象
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
Maps 和 Objects 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
Map中的Key
key是字符串
var myMap = new Map();
var keyString = "a string";
myMap.set(keyString, "和键'a string'关联的值");
myMap.get(keyString); // "和键'a string'关联的值"
myMap.get("a string"); // "和键'a string'关联的值"
// 因为 keyString === 'a string'
key是对象
var myMap = new Map();
var keyObj = {};
myMap.set(keyObj, "和键 keyObj 关联的值");
myMap.get(keyObj); // "和键 keyObj 关联的值"
myMap.get({}); // undefined, 因为 keyObj !== {}
key是函数
var myMap = new Map();
var keyFunc = function () {}, // 函数
myMap.set(keyFunc, "和键 keyFunc 关联的值");
myMap.get(keyFunc); // "和键 keyFunc 关联的值"
myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}
key是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"
PS:虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。
ES6对象
对象字面量
属性的简洁表示法
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");
}
//函数的本质其实是个表达式,则函数就相当于“键值对”的value。此式中,sayHi相当于key,function()相当于value。
}
person.sayHi();//"Hi"
可以看出,JavaScript具有“键值对”的特性 => JSON/向量/对象/结构体
属性名表达式
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"}
注意点
自定义的属性和拓展运算符对象里面属性的相同的时候:自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
let person = {name: "Amy", age: 15};
let someone = { ...person, name: "Mike", age: 17};
someone; //{name: "Mike", age: 17}
自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。
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}
ES6数组
数组创建
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]
可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。
console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
可选,用于指定 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]
//this=>map
类数组对象
一个类数组对象必须含有 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]
ES6函数
函数参数的扩展
默认参数
基本用法
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",18); // Amy,18
fn("Amy",""); // Amy,
fn("Amy"); // Amy,17
注意点:使用函数默认参数时,不允许有同名参数。
// 不报错
function fn(name,name){
console.log(name);
}
// 报错
//SyntaxError: Duplicate parameter name not allowed in this context
function fn(name,name,age=17){
console.log(name+","+age);
}
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",null); // Amy,null
函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值。
function f(x,y=x){
console.log(x,y);
}
f(1); // 1 1
function f(x=y){
console.log(x);
}
f(); // ReferenceError: y is not defined
不定参数
不定参数用来表示不确定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
基本用法
function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4
箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
基本用法:
var f = v => v;//可以看做是这样一个优先级:var f = (v=>v)
//等价于
var f = function(a){
return a;
}
f(1); //1
当箭头函数没有参数或者有多个参数,要用 () 括起来。
var f = (a,b) => a+b;
f(6,2); //8
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f = (a,b) => {
let result = a+b;
return result;
}
f(6,2); // 8
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来。
// 报错
var f = (id,name) => {id: id, name: name};
f(6,2); // SyntaxError: Unexpected token :
// 不报错
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}
注意点:没有 this、super、arguments 和 new.target 绑定。
var func = () => {
// 箭头函数里面没有 this 对象,
// 此时的 this 是外层的 this 对象,即 Window
console.log(this)
}
func(55) // Window
var func = () => {
console.log(arguments)
}
func(55); // ReferenceError: arguments is not defined
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
function fn(){
setTimeout(()=>{
// 定义时,this 绑定的是 fn 中的 this 对象
console.log(this.a);
},0)
}
var a = 20;
// fn 的 this 对象为 {a: 18}
fn.call({a: 18}); // 18
不可以作为构造函数,也就是不能使用 new 命令,否则会报错。