ECMAScript 6.0(简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言;
ECMAScript是JavaScript的规范,而JavaScript是ECMAScript的实现;
ES6是一个历史名词,泛指 5.1 版本后的JavaScript 标准,而ECMAScript 2015则是正式名称。
历史版本 1997年:ECMAScript 1.0 1998年:ECMAScript 2.0 1999年:ECMAScript 3.0 2006年:ECMAScript 4.0 未通过 2009年:ECMAScript 5.0 2015年:ECMAScript 6.0 至今,版本号改用年号的形式。
ES6新增了let,用来声明变量,用法类似于var,但是let有块级作用域,且在相同作用域中,只能重复声明同一个变量,因此不能在函数内部重新声明参数。
在for循环中,设置循环变量的部分是一个父作用域,循环体内部是一个子作用域。
let不会发生变量提升,在使用let声明变量之前使用该变量会抛出错误。
let声明的变量会绑定当前的作用域,不受外部影响,所以使用let声明变量之前,该变量都是不能用的,这就称为 暂时性死区 (temporal dead zone,简称 TDZ)。
因为有了暂时性死区,所以typeof也不再是百分百安全的操作了,因为如果typeof在变量声明前的死区运行,就会报错 ReferenceError,如果一个变量根本没有声明,使用typeof反而不会报错。
比较隐蔽的死区:
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
//因为参数y还没有声明,属于“死区”。如果y的默认值是x,就不会报错,因为此时x已经声明了
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
// 不报错
var x = x;
// 报错
let x = x; // ReferenceError: x is not defined
//在x的声明语句执行完成之前就去取x的值,所以才会报错
为什么需要块级作用域:
因为内层变量可能会覆盖外层变量;用来计数的循环变量会泄露为全局变量。
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
// IIFE 写法 (function () { var tmp = ...; ... }()); // 块级作用域写法 { let tmp = ...; ... }
块级作用域与函数声明:
ES6引入了块级作用域,明确允许可以在块级作用域中声明函数,但是在块级作用域外不能引用域内的函数,这点与let类似。
ES6的块级作用域必须有大括号,这样JS引擎才会认为存在块级作用域:
// 第一种写法,报错 if (true) let x = 1; // 第二种写法,不报错 if (true) { let x = 1; } //函数声明也是如此!
const 声明一个只读的常量。一旦声明,常量的值就不能改变,并必须立即初始化;只声明不赋值,就会报错,其作用域与let相同;声明的常量也不会提升,同样存在暂时性死区,且不可重复声明。
如果声明的是对象类型,那么该对象中的数据是可以改变的,因为它只保证该对象的地址不能变。
可以使用Object.freeze()
方法冻结对象,这样就不能修改对象中的数据了(只能冻结一级属性与方法),如下是一个实现彻底冻结的函数:
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };
ES6声明变量的六种方法:
ES5:var和function、ES6:let和const,还有import和class。
解构(Destructuring)赋值就是按照一定的模式从数组或对象中提取值,然后对变量进行赋值,这是ES6新增的。
如果解构不成功,变量的值就等于undefined;等号左边的模式可以只匹配一部分右边的数组,这叫做不完全解构。
只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值,ES6内部会使用===
来判断一个位置是否有值,只有数组成员为undefined时,默认值才会生效,为null不会生效;如果默认值是一个表达式,那么只有在用到时才会求值;默认值可以引用解构赋值的其它变量,但必须是已声明的。
<script>
//同时将数组中多个值分别赋给a,b,c
let arr = [1, 2, 3];
let [a, b, c] = arr;
//交换变量值
let x = 1, y = 2;
[y, x] = [x, y];
console.log(x, y); //2,1
//将第三个值赋给d
let [, , d] = arr;
console.log(d); //3
//从嵌套数组中将1赋给e,3赋给f,5赋给g
let arrs = [1, [2, 3, 4], 5, 6];
let [e, [, f,], g] = arrs;
console.log(e, f, g); //1,3,5
//设置默认值,右边赋值对象为undefined,默认值生效,否则照常赋值
let [h = 1] = []; //let [h=1]=[100] //h=100
console.log(h); //1
script>
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
对象的解构赋值可以取到继承的属性。
<script>
let obj = {
name: 'twj',
age: 600,
data: {
list: ['aaa', 'bbb', 'ccc']
}
}
//对象解构不需要按照顺序来,是先按照属性名找有没有,找到再赋值给相应的变量
let { age, name } = obj;
console.log(name, age); // twj 600
let { data: { list }, name: n, age: a, err = '没有错误' } = obj;
console.log(list, n, a, err); // [aaa,bbb,ccc] twj 600 没有错误
function getData() {
let obj = {
name: 'twj',
age: 600,
data: {
list: ['aaa', 'bbb', 'ccc']
}
}
test(obj)
}
function test({ name, data: { list } }) { //函数参数也能使用解构赋值
console.log(name, list); //twj [aaa,bbb,ccc]
}
getData();
script>
<script>
let myname = 'twj';
let [x, y, z] = myname;
console.log(x, y, z); // t w j
let { length } = myname;
console.log(length); //3
script>
如果要将一个已经声明的变量用于解构赋值,必须非常小心:
// 错误的写法 let x; {x} = {x: 1}; // SyntaxError: syntax error // 正确的写法 let x; ({x} = {x: 1}); //因为JS引擎会将{x}理解成一个代码块,只有不将大括号放在行首才能避免这个问题。
解构赋值允许等号左边的模式之中,不放置任何变量名:
({} = [true, false]); ({} = 'abc'); ({} = []);
由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构:
let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; first // 1 last // 3
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。由于
undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
因为对于编译器来说,一个式子是模式还是表达式,得解析到(或解析不到)等号才能知道,所以只要有导致解构的歧义,就得使用圆括号,但是并不是所有情况都能使用圆括号的。
不能使用圆括号的情况:
//1、变量声明语句 let [(a)] = [1]; let {x: (c)} = {}; //2、函数参数(也属于变量声明) function f([(z)]) { return z; } // 报错 function f([z,(x)]) { return x; } // 报错 //3、赋值语句的模式 ({ p: a }) = { p: 42 }; // 报错 ([a]) = [5]; // 报错
可以使用圆括号的情况:
//只有赋值语句的非模式部分,可以使用圆括号。 [(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
在反引号中的${}
就是用来书写变量或表达式的位置。
<head>
<meta charset="UTF-8">
<style>
.active {
color: rgb(188, 7, 22);
}
style>
head>
<body>
<h1>演示使用模板字符串h1>
<ul>
ul>
<script>
let name = 'twj'
//拼接字符串,换行并插入单个值(原始方法)
let oli = "\
" + name + "\
";
//使用模板字符串
let oli2 = `
${name}
`;
console.log(oli2);
function test() {
return "函数返回结果"
}
//插入多个值
let arr = ['twj', 'wbd', 'xjp'];
let newList = arr.map(function (item, index) {
//模板字符串的中可以插入变量、表达式、函数等
return `${index === 0 ? 'active' : ''} ">
${item}
${test()}
`
})
console.log(newList);
//将li添加到ul里面
let oul = document.querySelector("ul");
oul.innerHTML = newList.join("");
script>
body>
include函数: 判断字符串中是否存在指定字符。
let text = "Hello world!";
console.log(text.includes("o")); //true ,是否包含该字符
console.log(text.startsWith("Hello")); //true ,是否以该字符开头
console.log(text.endsWith("!")); //true ,是否以该字符结尾
//这三个方法都支持第二个参数,表示开始搜索的位置。
console.log(text.includes("Hello", 6)); //false
console.log(text.startsWith("world", 6)); //true
console.log(text.endsWith("Hello", 5)); //true
//上面代码表示,使用第二个参数n时,endsWith针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
repeat函数: 将原字符串重复n次,并返回重复后的新字符串。
console.log(text.repeat(2)); //Hello world!Hello world!
console.log(text.repeat(0)); //空字符串
console.log(text.repeat(2.7)); //Hello world!Hello world!
console.log(text.repeat("3")); //Hello world!Hello world!Hello world!
//1. 各进制的写法
let num1 = 100;
let num2 = 0x100;
let num3 = 0b100;
let num4 = 0o100;
console.log(num1, num2, num3, num4); //100 256 4 64
//2. Number.isFinite & Number.isNaN
let num5 = Number.isFinite(100)
let num6 = Number.isFinite(100 / 0)
let num7 = Number.isFinite(Infinity)
let num8 = Number.isFinite("100")
console.log(num5, num6, num7, num8) //true false false false
//Number.isFinite对于非数值一律返回false,Number.isNaN只有对于NaN才返回true
let num9 = Number.isNaN(100)
let num10 = Number.isNaN(NaN)
let num11 = Number.isNaN("twj")
let num12 = Number.isNaN("100")
console.log(num9, num10, num11, num12) //false true false false
//3. Number.isInteger:用来判断一个数组是否为整数
let num13 = Number.isInteger(100)
let num14 = Number.isInteger(100.0)
let num15 = Number.isInteger("kerwin")
let num16 = Number.isInteger("100")
console.log(num13, num14, num15, num16) //true true false false
//4. Number.EPILON:极小常量
//它表示1与大于1的最小浮点数之间的差。2.220446049250313e-16
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON
}
console.log(isEqua1(0.1 + 0.2, 0.3)) //true
console.log(0.1 + 0.2 === 0.3) //fa1se
//5. Math.trunc:将小数部分抹掉,返回一个整数
console.log(Math.trunc(1.2)) //1
console.log(Math.trunc(1.8)) //1
console.log(Math.trunc(-1.8)) //-1
console.log(Math.trunc(-1.2)) //-1
//6. Math.sign:用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
Math.sign(-100) // -1
Math.sign(100) // +1
Math.sign(o1) // +0
Math.sign(-0) // -0
Math.sign("kerwin") // NaN
// ...展开
let arr = [1, 2, 3]
//let arr2 = arr.concat()
// let arr2 = [...arr] //实现浅拷贝
// arr2.pop() //删除arr2中的元素,arr并不会受影响
// console.log(arr, arr2)
let arr2 = [4, 5, 6]
console.log(...arr, ...arr2) // 1 2 3 4 5 6
let myarr = [1, 2, 3, 4, 5, 6, 7, 8]
let [a, b, ...c] = myarr
console.log(a, b, c) //1 2 [3,4,5,6,7,8]
//Array.from():用于将一个类似数组的对象转换成数组
function test() {
//console.log(arguments) //[1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(Array.from(arguments)) //[1,2,3,4]
}
test(1, 2, 3, 4)
let olis = document.querySelectorAll("li")
console.log(Array.from(olis)); //[li,li,li,li]
//Array.of():将一组值转化为数组,也就是新建数组
let arr3 = Array(3)
let arr4 = Array.of(3)
console.log(arr3, arr4) //[empty×3] [3]
//find() findIndex():查找数组元素/下标
let arr5 = [11, 12, 13, 14, 15]
//找到大于13的元素
let res = arr5.find(function (item) {
return item > 13
})
console.log(res) //14
//找到大于13的那个元素的下标
let index = arr5.findIndex(function (item) {
return item > 13
})
console.log(index) //3
//findLast() findLastIndex():ES2022新增的
//这两个方法与前面两个方法类似,不过是从后面开始查找
let index1 = arr5.findLastIndex(function (item) {
return item > 13
})
console.log(index1) //4
//fill():填充数组元素的值
let arr6 = new Array(3).fill("twj")
console.log(arr6) //['twj', 'twj', 'twj']
let arr7 = [11, 22, 33]
console.log(arr7.fill('twj', 1, 2)) //[11, 'twj', 33]
//flat() flatMap():扁平化处理,将嵌套数组拉伸成一维数组
let arr8 = [1, 2, 3, [4, 5, 6]]
let arr9 = arr8.flat()
console.log(arr9) //[1, 2, 3, 4, 5, 6]
//复杂的多维数组可通过flatMap来处理
let arr10 = [
{
name: "A",
list: ["安庆", "安阳", "鞍山"]
},
{
name: "B",
list: ["北京", "保定", "包头"]
}
]
let res1 = arr10.flatMap(function (item) {
return item.list
})
console.log(res1) //['安庆', '安阳', '鞍山', '北京', '保定', '包头']
//1. 对象简写
let name = "moduleA"
let obj = {
name, //name:name
test1() { //方法可以简写为这样
},
[name + "test2"]() { //方法名也可以为表达式形式
}
}
//2. 对象属性使用表达式
let name1 = "a"
let obj1 = {
[name1 + "bc"]: "twj"
}
console.log(obj1) //{abc:'twj'}
//3. 扩展运算符... ES2018支持
let obj2 = {
name: "twj"
}
let obj3 = {
...obj2
}
let obj4 = {
age: 100
}
obj2.name = "whh"
console.log(obj2) //{name: 'whh'}
//console.log(...obj2, ...obj4) //报错
//4. Object.assign:合并多个对象
let obj5 = {
name: "twj"
}
let obj6 = {
age: 100
}
//影响的是obj5,可以用个空对象来接收返回的新对象
console.log(Object.assign(obj5, obj6)) //{name: 'twj', age: 100}
//5. Object.is:判断两个对象是否相等
//因为 === 无法判断两个NaN是否相等,以及+0和-0是否相等
console.log(Object.is(5, 5)) //true
console.log(Object.is({}, {})) //false
console.log(Object.is(NaN, NaN)) //true
console.log(Object.is(+0, -0)) //false
console.log(+0 === -0) //true
//1. 箭头函数,写法简洁
let test = () => "666"
console.log(test()) //666
let arr = ["aaa", "bbb", "ccc"]
let newarr = arr.map(item => `${item}`)
console.log(newarr) //['aaa ', 'bbb ', 'ccc ']
//2. 只有return可以省略,如果返回对象则需要加圆括号(因为大括号会被解释为代码块)
let test2 = () => ({
name: "twj",
age: 100
})
console.log(test2()) //{name: 'twj', age: 100}
//3. 如果只有一个参数,可以省略()
let newarr2 = arr.map(item => `${item}`)
console.log(newarr2) //['aaa ', 'bbb ', 'ccc ']
//4. 无法访问arguments,无法new
let test3 = () => {
console.log(arguments) //无法访问的哦!
}
//test3(1, 2, 3, 4) //ReferenceError: arguments is not defined
//new test3() //TypeError: test3 is not a constructor
箭头函数没有this:
<body>
模糊搜索: <input type="text" id="mysearch">
<script>
let osearch = document.querySelector("#mysearch")
//原始方式this指向window,需要用个临时变量来改变指向
osearch.oninput = function () {
let _this = this
setTimeout(function () {
console.log(_this.value)
console.log(`发送${_this.value}到后端,获取列表数据`)
}, 1000)
}
//而箭头函数就没这个问题,谁调用就指向谁,箭头函数没有this,this指向父作用域
osearch.oninput = function () {
setTimeout(() => {
console.log(this.value)
console.log(`发送${this.value}到后端,获取列表数据`)
}, 1000)
}
script>
body>
ES6 引入了一种新的原始数据类型
Symbol
,表示独一无二的值。它属于 JavaScript 语言的原生数据类型之一,其他数据类型是:undefined
、null
、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。
let s1 = Symbol() //生成了Symbol类型的数据
let s2 = Symbol()
console.log(s1) //Symbol()
console.log(s1 === s2) //false
//1. 不能进行运算
//console.log(s1 + "abc") //报错
//2. 显式调用toString()
console.log(s1.toString() + "abc") //Symbol()abc 没啥用!
//2. 隐式转换toString()
if (s1) {
console.log("执行")
}
// let obj = {
// name: "twj",
// age: 100
// }
// let name = Symbol()
// obj[name] = "hhh"
// console.log(obj[name]) //hhh
//避免后期添加新属性时,属性名的冲突
let keys = {
name: Symbol(),
age: Symbol(),
location: Symbol(),
test: Symbol()
}
let obj = {
[keys.name]: "twj",
[keys.age]: 100,
[keys.location]: "beijing",
[keys.test]() {
console.log("test")
}
}
obj.name = "aaaaaa"
console.log(obj) //{name: 'aaaaaa', Symbol(): 'twj', Symbol(): 100, Symbol(): 'beijing', Symbol(): ƒ}
obj[keys.test]() //test
遍历Symbol:
//Symbol可接受一个参数用作Symbol的描述,方便我们区分
let keys = {
name: Symbol("name"),
age: Symbol("age"),
location: Symbol("location"),
test: Symbol("test")
}
let obj = {
[keys.name]: "twj",
[keys.age]: 100,
[keys.location]: "beijing",
[keys.test]() {
console.log("test")
}
}
obj.name = "aaaaaa"
console.log(obj) //{name: 'aaaaaa', Symbol(name): 'twj', Symbol(age): 100, Symbol(location): 'beijing', Symbol(test): ƒ}
//for..in 只能遍历出普通的属性
for (let i in obj) {
console.log(i) //name
}
//遍历得到Symbol的属性
console.log(Object.getOwnPropertySymbols(obj)) //[Symbol(name), Symbol(age), Symbol(location), Symbol(test)]
console.log(Reflect.ownKeys(obj)) //['name', Symbol(name), Symbol(age), Symbol(location), Symbol(test)]
//遍历得到所有属性
Reflect.ownKeys(obj).forEach(item => {
console.log(item, obj[item])
})
//还能使用Symbol.for()重新使用同一个Symbol的值,略...
Symbol作为常量:
//作为常量
const VIDEO = Symbol();
const AUDIO = Symbol();
const IMAGE = Symbol();
function play(type) {
switch (type) {
case VIDEO:
console.log("视频播放")
break;
case AUDIO:
console.log("音频播放")
break;
case IMAGE:
console.log("图片播放")
break;
}
}
//这样就能统一代码的一致性,只能使用这三个常量
play(VIDEO)
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of循环。
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的
next
方法,可以将指针指向数据结构的第一个成员。- 第二次调用指针对象的
next
方法,指针就指向数据结构的第二个成员。- 不断调用指针对象的
next
方法,直到它指向数据结构的结束位置。
let arr = ["twj", "wdf", "jjb"]
for (const a of arr) {
console.log(a)
}
//arr数组原型中封装了Symbol(Symbol.iterator)属性
let iter = arr[Symbol.iterator]() //得到遍历器对象
console.log(iter)
console.log(iter.next()) //{value: 'twj', done: false}
console.log(iter.next()) //{value: 'wdf', done: false}
console.log(iter.next()) //{value: 'jjb', done: false}
console.log(iter.next()) //{value: 'undefined', done: none}
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是"可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。
Array、Map、Set、String、TypedArray、arguments 对象、NodeList 对象。
常规遍历:
let arr = ["twj", "wdf", "jjb"]
for (const a of arr) {
console.log(a)
}
console.log(arr)
//arr数组原型中封装了Symbol.iterator属性,也称遍历器接口,用于返回一个遍历器
let iter = arr[Symbol.iterator]() //得到遍历器对象
console.log(iter)
console.log(iter.next()) //{value: 'twj', done: false}
console.log(iter.next()) //{value: 'wdf', done: false}
console.log(iter.next()) //{value: 'jjb', done: false}
console.log(iter.next()) //{value: 'undefined', done: none}
//字符串也能使用for..of进行遍历,因为它也封装了遍历器接口
let str = "twjnbnb"
for (const s of str) {
console.log(s)
}
//arguments也封装了遍历器接口
function test() {
for (let arg of arguments) {
console.log(arg)
}
}
test(1, 2, 3, 4)
手动封装Interator接口:
<body>
<ul>
<li>111li>
<li>222li>
<li>333li>
ul>
<script>
//对象是非线性的,所以没有封装遍历器接口,但是可以借助Array类原型的遍历器接口!
let obj = {
0: "twj",
1: "wdf",
2: "jjb",
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for (let i of obj) {
console.log(i)
}
//手动埋一个遍历器接口
let obj2 = {
code: 200,
name: "obj2",
list: ["jjb", "twj", "wdf"],
[Symbol.iterator]() {
let index = 0
return {
next: () => {
return {
value: this.list[index++],
done: index === (this.list.length + 1) ? true : false
}
}
}
}
}
//for..of成功!
for (let i of obj2) {
console.log(i)
}
//没毛病!
let iter = obj2[Symbol.iterator]()
console.log(iter)
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
//展开运算符会默认调用对象中的遍历器,配合解构赋值就能自动转换成数组!
console.log([...obj2]) //['jjb', 'twj', 'wdf']
let oli = document.querySelectorAll("li");
console.log([...oli]) //[li, li, li]
console.log(Array.from(oli)) //[li, li, li]
script>
body>
Set 类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。
基本用法:
let s1 = new Set([1, 2, 3, 4, 2, 1])
console.log(s1) //{1,2,3,4}
console.log([...s1]) //[1,2,3,4]
console.log(Array.from(s1)) //[1,2,3,4]
let s2 = new Set()
s2.add(11)
s2.add(22)
s2.add(22)
s2.add(33)
console.log(s2) //{11,22,33}
Set实例的属性:
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。Set 实例的方法分为两大类:操作方法和遍历方法,如下为操作方法:
Set.prototype.add(value)
:添加某个值,返回 Set 结构本身。Set.prototype.delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.has(value)
:返回一个布尔值,表示该值是否为Set
的成员。Set.prototype.clear()
:清除所有成员,没有返回值。如下为遍历方法:
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员
使用示例:
//size:返回Set实例的成员总数
console.log(s1.size) //4
//add():添加成员
s1.add(5).add(6)
//has():判断是否存在指定成员
console.log(s1.has(5)) //true
//delete():删除指定成员
s1.delete(5)
console.log(s1.has(5)) //false
//clear():清除set中所有成员
s1.clear()
//keys():返回键名的遍历器
for (let i of s2.keys()) {
console.log(i)
}
//values():返回键值的遍历器
for (let i of s2.values()) {
console.log(i)
}
//entries():返回键值对的遍历器
for (let i of s2.entries()) {
console.log(i)
}
//使用解构赋值
let arr = ["aa", "bb", "cc"]
for (let [index, item] of arr.entries()) {
console.log(index, item)
}
//forEach():使用回调函数遍历每个成员
s2.forEach((item, index) => {
console.log(item, index)
})
对复杂数组去重:
let arr2 = [1, 2, 2, "twj", "twj", [1, 2], [3, 4], [1, 2], { name: "twj" }
, { age: 100 }, { name: "twj" }, undefined, undefined, NaN, NaN]
function uni(arr2) {
let res = new Set()
return arr2.filter((item) => {
//判断有没有重复的,没有添加到set中并返回true
let id = JSON.stringify(item)
if (res.has(id)) {
return false
} else {
res.add(id)
return true
}
})
}
console.log(uni(arr2))
Map类似于数组,也是键值对的集合,但是"键"的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
基本用法:
let m1 = new Map([
["name", "twj"],
["age", 100],
[{ a: 1 }, "北京"]
])
console.log(m1) //{'name' => 'twj', 'age' => 100, {Object} => '北京'}
let o = { a: 1 }
let m2 = new Map()
m2.set("name", "twj")
m2.set("age", 100)
m2.set(o, "北京")
console.log(m2) //{'name' => 'twj', 'age' => 100, {Object} => '北京'}
//使用for..of
for (let i of m2) {
console.log(i)
}
//使用扩展运算符+解构赋值
console.log([...m2])
size
: 返回Map结构的成员总数。
Map.prototype.set(key , value)
: 添加key对应得value,返回Map结构本身。
Map.prototype.get (key)
: 获取key对应的value
Map.prototype.delete(key)
: 删除某个键(键名+键值)
Map.prototype.has(key)
: 某个键是否在当前Map对象之中。Map的遍历方法与Set一样。
使用示例:
//map实例的属性和方法
console.log(m2.get(o)) //北京
console.log(m2.has("age")) //true
m2.delete("age")
console.log(m2.has("age")) //false
console.log(m2.size) //2
//m2.clear() //清空map
//console.log(m2)
//map的遍历
for (let i of m2.keys()) {
console.log(i)
}
for (let i of m2.values()) {
console.log(i)
}
for (let [index, item] of m2.entries()) {
console.log(index, item)
}
//与entries()作用一样
for (let [index, item] of m2) {
console.log(index, item)
}
m1.forEach((item, index) => {
console.log(item, index)
})
Proxy如其名,它的作用是在对象和和对象的属性值之间设置一个代理,获取该对象的值或者设置该对象的值,以及实例化等等多种操作,都会被拦截住,经过这一层我们可以统一处理,我们可以认为它就是“代理器”
拦截对象属性:
<div id="box">div>
<script>
//常规方式实现拦截对象属性
let obj = {}
//obj为要拦截的对象,data为拦截的属性,一旦访问了data属性,就会调用get方法,
//一旦修改了data属性,就会调用set方法,拦截后我们就可以做一些响应式的操作,比如更新DOM节点的内容....
Object.defineProperty(obj, "data", {
get() {
console.log("get")
},
set(value) {
console.log("set", value)
//设置DOM
box.innerHTML = value
}
})
//defineProperty的缺点:每一次只能拦截一个属性,也只能拦截对象,所以就引出了Proxy
script>
使用Proxy拦截对象属性:
<div id="box"></div>
<script>
//Proxy不仅能拦截对象还能拦截数组、Set、Map等,其实就是给原对象加一个代理,这个代理可以比作是一个中介,
//通过这个中介来帮我们拦截对象,拦截到对象属性后我们就可以进行相应的操作了
//Proxy代理拦截几乎所有数据类型,并且能拦截多个属性
let obj = {}
let proxy = new Proxy(obj, {
get(target, key) {
console.log("get", target[key])
return target[key]
},
//target为原对象,key为属性名,value为属性值
set(target, key, value) {
console.log("set", target, key, value)
if (key === "data") {
box.innerHTML = value
}
target[key] = value
},
has() { //has()只能控制判断代理中是否存在某个属性,原对象它并不会做处理
return false
}
})
</script>
代理set/map需注意:
//如果是代理的set和map,就要注意其方法的this指向,如果不改变this的指向,就无法操作其方法
let s = new Set()
let proxy = new Proxy(s, {
get(target, key) {
//判断如果是方法,就要修正this指向
let value = target[key]
if (value instanceof Function) {
return value.bind(target) //将this指向target,target也就是s
}
return value
},
set() {
console.log("set")
}
})
Proxy本质上属于元编程而非破坏性数据劫持,在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。
Reflect(反射)可以用于获取目标对象的行为,它与Object类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与Proxy是对应的,作用如下:
代替Object的某些方法:
let obj = {}
Reflect.defineProperty(obj, "name", {
value: "twj",
writable: false, //设置属性值不可改
enumerable: false //设置name属性不可删
})
console.log(obj)
修改某些Object方法返回结果:
//好处就是在处理异常代码时不用再try..catch了,因为它返回的是布尔值,所以程序也不会因为异常中断了
//,而Object会直接抛出异常中断程序
let obj = {}
Reflect.defineProperty(obj, "name", {
value: "twj",
writable: false, //设置属性值不可改
enumerable: false //设置name属性不可删
})
let res = Reflect.defineProperty(obj, "name", {
value: "whh" //再次修改了属性值,就会返回false
})
console.log(res) //false
命令式变为函数行为:
let obj = {
name: "twj"
}
//查询对象中是否存在某个属性:老写法
console.log("name" in obj) //true
//新写法
console.log(Reflect.has(obj, "name")) //true
//删除属性:老写法
//delete obj.name
//新写法
Reflect.deleteProperty(obj, "name")
console.log(Reflect.has(obj, "name")) //false
//修改和获取对象属性值:新写法
Reflect.set(obj, "age", 100) //在obj上添加一个属性age,赋值为100
console.log(Reflect.get(obj, "age")) //获取obj上的age属性值
配合Proxy实现更好的拦截:
let s = new Set()
let proxy = new Proxy(s, {
get(target, key) {
//判断如果是方法,就要修正this指向
let value = Reflect.get(target, key)
if (value instanceof Function) {
return value.bind(target) //将this指向target,target也就是s
}
return value
},
set(target, key, value) {
//走原对象的默认行为即可
Reflect.set(...arguments)
}
})
//拦截数组,动态监测长度,非常方便!
let arr = [1, 2, 3]
let proxy2 = new Proxy(arr, {
get(target, key) {
console.log("get", key)
return Reflect.get(...arguments)
},
set(target, key, value) {
console.log("set", key, value)
return Reflect.set(...arguments)
}
})
proxy2.push(4) //会触发两次get,两次set
promise是异步编程的一种解决方案,比传统的解决方案回调函数,更合理和更强大。ES6将其写进了语言标准。统一了用法,原生提供了Promise对象。
promise指定回调函数方式更灵活易懂。解决了异步回调地狱的问题。
当一个回调函数嵌套一个回调函数的时候就会出现一个嵌套结构;
当嵌套的多了就会出现回调地狱的情况;比如我们发送三个ajax请求:
- 第一个正常发送;
- 第二个请求需要第一个请求的结果中的某一个值作为参数;
- 第三个请求需要第二个请求的结果中的某一个值作为参数。
function ajax(url, successcb, failcb) {
setTimeout(() => successcb("111"))
}
//回调地狱!
ajax("/aaa", function (data) {
console.log(data)
ajax("/bbb", function (data) {
console.log(data)
ajax("/ccc", function (data) {
console.log(data)
}, function () {
})
}, function () {
})
}, function () {
})
let pro = new Promise(function (resolve, reject) {
//执行器函数
setTimeout(() => {
//resolve(1000)
reject("天天迟到,没有奖金!")
}, 1000)
})
// pro.then(() => {
// console.log("奖金")
// }, () => {
// console.log("啥也没有")
// })
//catch()用于注册一个在 promise 被拒绝时调用的函数。 它会立即返回一个等价的 Promise 对象,
//这可以允许你 链式 调用其他 promise 的方法
pro.then((res) => {
console.log("奖金", res)
}).catch((err) => {
console.log("啥也没有", err)
})
Promise对象通过自身的状态,来控制异步操作,Promise实例具有三种状态:
异步操作未完成(pending) 异步操作成功(fulfilled) 异步操作失败(rejected) |这三种状态的变化途径只有两种:从未完成到成功、从未完成到失败。
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是Promise这个名字的由来,它的英语意思是"承诺",一旦承诺成效,就不得再改变了。这也意味着,Promise实例的状态变化只可能发生一次。
因此,Promise的最终结果只有两种:
异步操作成功,Promise实例传回一个值(value),状态变为fulfilled。 异步操作失败,Promise实例抛出一个错误(error),状态变为rejected。
手动封装一个Ajax:
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open("get", url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText)
} else {
reject(xhr.responseText)
}
}
}
})
}
ajax("1.json").then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
.then链式调用:
let pro = new Promise(function (resolve, reject) {
//执行器函数
setTimeout(() => {
resolve(1000)
//reject("天天迟到,没有奖金!")
}, 1000)
})
pro.then((res) => {
console.log("奖金1", res)
//如果return 非promise类型,pending->fulfilled
//如果return promise类型,那就根据这个新的promise对象的结果,
//来决定pending->fulfilled 还是 pending->rejected
return res
}).then((res) => {
console.log("奖金2", res)
}).catch((err) => {
console.log("啥也没有", res)
})
解决回调地狱:
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open("get", url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText)
} else {
reject(xhr.responseText)
}
}
}
})
}
ajax("1.json").then(res => {
console.log(res)
//一旦请求成功,就进入下一个then,否则就进入catch
return ajax("2.json", res)
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
all和race函数:
let pro1 = new Promise(function (resolve, reject) {
//执行器函数
setTimeout(() => {
resolve(1000)
}, 1000)
})
let pro2 = new Promise(function (resolve, reject) {
//执行器函数
setTimeout(() => {
resolve(2000)
}, 2000)
})
let pro3 = new Promise(function (resolve, reject) {
//执行器函数
setTimeout(() => {
resolve(3000)
}, 3000)
})
/*Promise.all:
参数为多个promise对象,只有这三个promise状态都变成fulfilled,才会进入then
,并且将返回值封装成一个数组传入;这样就可以实现loading加载效果只有在多个任务执行完才消失。
*/
Promise.all([pro1, pro2, pro3]).then(res => {
console.log(res) //[1000, 2000, 3000]
}).catch(err => {
console.log(err)
})
/*Promise.race:
参数为多个promise对象,这三个promise谁最先变更状态,谁就进入then,并把数据传入;
这样可以实现请求多个服务器资源,谁最快就请求那个服务器,还有如果请求超时了,可以在指定时间内进入catch,不占用资源
*/
Promise.race([pro1, pro2, pro3]).then(res => {
console.log(res) //1000
}).catch(err => {
console.log(err)
})
Generator函数是ES6提供的一种异步编程解决方案,它是一个状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
基本用法:
//可以让函数暂停执行,yield(产出)相当于一个暂停的标记
//,遇到这个标记就暂停不往下执行了,除非通过next()才能往下执行
function* gen() {
console.log(11)
yield //产出
console.log(22)
yield
console.log(33)
}
let g = gen()
g.next()
g.next()
g.next()
/
function* gen2() {
console.log(11)
yield "aaa" //可以产出一个结果
console.log(22)
yield "bbb"
console.log(33)
}
let g2 = gen2()
let res1 = g2.next()
console.log(res1)
let res2 = g2.next()
console.log(res2)
let res3 = g2.next()
console.log(res3)
//可以直接使用for..of遍历g
基本原理:
//yield可以产出一个值,不过在第一次调用next()的时候,在yield这里会暂停,所以“传入-111”不会输出,
//并且也不会将产出的结果返回给res1,第二次执行next(),就可以将产出值返回给函数外的res1,然后输出第一条语句,
//接着再输入函数内部的第一条语句,以此类推...
function* gen() {
let res1 = yield "aaa"
console.log("gen内部", res1)
let res2 = yield "bbb"
console.log("gen内部", res2)
}
let g = gen()
let res1 = g.next("传入-111")
console.log(res1)
let res2 = g.next("传入-222")
console.log(res2)
let res3 = g.next("传入-333")
console.log(res3)
结合异步操作:
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open("get", url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText)
} else {
reject(xhr.responseText)
}
}
}
})
}
function* gen() {
let res = yield ajax("1.json")
console.log("第一个请求的结果", res)
let res2 = yield ajax("2.json")
console.log("第二个请求的结果", res2)
}
//自动版本,递归实现
function AutoRun(gen) {
let g = gen();
function next(data) {
let res = g.next(data);
if (res.done) return
res.value.then(function (data) {
next(data);
});
}
next();
}
AutoRun(gen)
//手动版本
//let g = gen()
//console.log(g.next()) //promise
// g.next().value.then(data => {
// g.next(data).value.then(res => {
// g.next(res)
// })
// })
//async await 属于内置的自动执行器!
基本用法:
//传统构造函数写法
// function Person(name, age) {
// this.name = name
// this.age = age
// }
// Person.prototype.say = function () {
// console.log(this.name, this.age)
// }
// let obj = new Person("twj", 100)
// console.log(obj)
//ES6新写法
let s = Symbol('say')
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
hhh() {
console.log("hhh")
}
[s]() {
console.log(this.name, this.age)
}
}
let obj = new Person("twj", 100)
console.log(obj.__proto__ === Person.prototype) //true
obj[s]() //twj 100
get/set:
<body>
<ul id="list">
ul>
<script>
class Person {
constructor(name, age, location, id) {
this.name = name
this.age = age
this.location = location
this.ele = document.querySelector(`#${id}`)
}
get location() {
console.log("get")
//return this.location //死循环
}
set location(data) {
console.log("set", data)
//this.location = data //栈溢出
}
get html() {
return this.ele.innerHTML
}
set html(data) {
this.ele.innerHTML = data.map(item => `${item}`).join("")
}
}
let obj = new Person("twj", 100, "长沙", "list")
obj.html = ["111", "222", "333"]
script>
body>
静态属性和方法:
class Person {
static myname = "person类的名字"
static mymethod = function () {
console.log("mymethod")
}
constructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log(this.name, this.age)
}
}
//等价于static写法,不用new,直接通过类名.来访问
// Person.myname = "person类的名字"
// Person.mymethod = function () {
// console.log("mymethod")
// }
let obj = new Person("twj", 100)
console.log(Person.myname)
Person.mymethod()
基本用法:
class Person {
static myname = "person类的名字"
static mymethod = function () {
console.log("mymethod")
}
constructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log(this.name, this.age)
}
}
//可以继承属性和方法(静态的也可以),可以重写父类方法等,使用super调用父类构造器,这些特点都与Java类似
class Student extends Person {
constructor(name, age, score) {
super(name, age)
this.score = score
}
getScore() {
console.log(this.score)
}
say() {
super.say()
console.log(this.score)
}
}
let obj = new Student("twj", 100, 149)
渲染DOM元素:
<body>
<div class="box1">
<h2></h2>
<ul></ul>
</div>
<div class="box2">
<h2></h2>
<img src="" alt="" style="width: 100px;" />
<ul></ul>
</div>
<script>
var data1 = {
title: "体育",
list: ["体育-1", "体育-2", "体育-3"]
}
var data2 = {
title: "综艺",
url: "https://fanyi-cdn.cdn.bcebos.com/static/translation/img/header/logo_e835568.png",
list: ["综艺 - 1", "综艺 - 2", "综艺3"]
}
class CreatBox {
constructor(select, data) {
this.ele = document.querySelector(select)
this.title = data.title
this.list = data.list
this.render()
}
render() {
let oh1 = this.ele.querySelector("h2")
let oul = this.ele.querySelector("ul")
oh1.innerHTML = this.title
oul.innerHTML = this.list.map(item =>
`${item}`).join("")
}
}
new CreatBox(".box1", data1)
//因为只多了个图片,所以直接继承CreatBox即可
class CreatImgBox extends CreatBox {
constructor(select, data) {
super(select, data)
this.imgUrl = data.url
this.render() //这里需要重新调用一下render(),与this指向有关
}
render() {
super.render()
let oimg = this.ele.querySelector("img")
oimg.src = this.imgUrl
}
}
new CreatImgBox(".box2", data2)
</script>
</body>
ES Module是ES6中提出的一个新的模块加载方式,简称ES6模块。ES Module的最大好处就是取代commonJS和AMD两种模块加载方式,实现服务端和浏览器端模块加载的大一统。当然,ES Module在资源加载速度和静态分析方面也是好处大大的。
JavaScript现在有两种模块。一种是ES6模块,简称ESM;另一种是CommonJS模块,简称CJS。
CommonJS模块是Node.js专用的,与ES6模块不兼容。语法上面,两者最明显的差异是,CommonJS模块使用require()
和module.exports
,ES6模块使用import
和export
。
ES6模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
模块化的作用:
- 异步加载:异步加载JS文件,结合了async和defer的好处;
- 私密不漏:需要哪些组件就导出哪些,不需要导出所有,这样私密性就好一点;
- 重名不怕:多个JS文件中函数或变量重名,通过模块化可以解决重名冲突的问题;
- 依赖不乱:比如说有1.js、2.js两个文件,2.js中需要用到1.js中的函数,那么1.js就需要比2.js先加载完,不然就会找不到这个函数,使用模块化的方式就能很好的解决这个问题,也就是在2.js中导入1.js中导出的函数即可。
这种方式应该是大家日常开发中使用频率最高的的一种接口导出方式,尤其是在封装公共方法或者抽取业务模块的时候。并不支持 export 后面直接使用变量名称。
export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
// 正确使用方式
export const moduleName = "es module";
export const moduleType = "js";
export const moduleSize = 200;
// 错误使用方式
export moduleName;
const moduleType = "js";
export moduleType;
这种方式就是在模块进行大量的逻辑的铺排,然后在模块的底部将需要对外开放的接口一次性导出即可,最大的好处就是可读性高,可以在模块的底部快速的找到对外导出的接口,然后按需导入想要使用的接口。
const moduleName = "es module";
const moduleType = "js";
const moduleSize = 200;
export {
moduleName,
moduleType,
moduleSize,
}
这种方式其实就是前面第2个语法的语法糖,相当于进行了
export { * as default }
,当然,也能单个单个的导出。
const moduleInfo = {
name: 'es module',
type: 'js',
size: 200,
}
export default moduleInfo;
针对export中的前两种接口导出方式,import有与之对应的接口引入方式。这种接口引入的方式的语法是:
import { [name] } from '[moduleName]'
,其中name对应export的接口名称,moduleName对应模块名称或者模块路径。
通过export { [name] }
导出的接口,不支持通过import [name]
的方式导入。
// 正确使用方式
import { moduleName, moduleType, moduleSize } from './modules/index';
// 错误使用方式
import module from './modules/index';
针对
export default [name]
导出的接口也有一套与之匹配的接口导入方式。比较常规的导入方式是通过import [name]
的方式引入,这也是我们日常开发中使用最高频的导入方式。
// 正确使用方式
import module from './modules/index';
// 错误使用方式
import { module } from './modules/index';
//export default其实是export { * as default },我们其实也可以通过 import 普通接口的方式将其导入:
import { default as module } from './modules/index';
说实话,在不考虑代码tree shake的前提条件下,其实是蛮省事情的:
import * as module from './modules/index';
不仅可以单独使用export和import,还可以将它们组合起来一起使用,组合起来更加便捷。
//导入index模块到当前模块,然后导出,这样其它导入当前模块的,就能同时用index模块和当前模块的组件了,*后面还能跟as取别名
export * from './modules/index'
//其实就是下面这段代码的变种:
import * as index from './modules/index';
export { ...index }
导出: 可以导出任意类型数据
// module.js
module.exports = {
name:'banana',
age:18,
eat:function(){
console.log('I like eating bananas')
}
}
module.exports.userName = 'admin'
导入:
// app.js
const obj = require('./module.js')
console.log(obj) // { name: 'banana', age: 18, eat: [Function: eat], userName: 'admin' }
// 如果只想导入某个属性,可以使用解构赋值
const { name } = require('./module')
console.log(name) // 'banana'
上一篇文章 | 下一篇文章 |
---|---|
无 | 敬请期待... |