1.1 let 及 const
1.1.1 let 命令
用 var 声明变量有变量提升的情况。
1 console.log(a); 2 var a = 1;
如果没有第二行的声明,那么会看到“a is not defined”的错误。但有了第二行,打印结果就是“undefined”。
出现这种结果的原因就是 var 声明变量时的变量提升机制,等同于:
1 var a; 2 console.log(a); 3 a = 1;
针对上面的情况,ES6 引入 let。let 不仅没有变量提升的问题,同时还把作用于局限在代码块中,也就是声明作用域之外无法访问。
第一种是函数内部块级作用域。
1 function test(){ 2 let a; 3 } 4 5 test(); 6 console.log(a); // Uncaught ReferenceError: a is not defined
第二种是“{}”之间的区域。
{ let a; } console.log(a); // Uncaught ReferenceError: a is not defined
同时,let 禁止重复声明。
1.1.2 const 命令
声明常量,声明时必须进行初始化。
如果声明的是对象,仅可以修改对象的属性值。
1 const obj = { 2 name: "张三", 3 age: 20 4 }; 5 6 obj.name = "李四"; 7 8 console.log(obj); 9 10 obj = {}; // Uncaught TypeError: Assignment to constant variable.
通过 Object.freeze 函数冻结对象,可以确保对象属性不被修改,但仅限直接属性。
1 const obj = { 2 name: "张三", 3 age: 20 4 }; 5 6 Object.freeze(obj); // 冻结对象 7 8 obj.name = "李四"; 9 console.log(obj); // name 还是张三,并未改变
深度冻结对象示例:
1 const obj = { 2 name: "张三", 3 age: 20, 4 family: { 5 name: "张安", 6 age: 48 7 } 8 }; 9 10 // 深度冻结函数 11 function deepFreeze(obj){ 12 Object.freeze(obj); 13 14 for(let key in obj){ 15 // hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中(非继承属性)是否具有指定的属性, 16 // 如果 object 具有带指定名称的属性,则 hasOwnProperty 方法返回 true,否则返回 false。此方法不会检查对象原型链中的属性; 17 // 该属性必须是对象本身的一个成员。 18 // 如果要判断继承属性,通过原型链prototype判断,Object.prototype.hasOwnProperty(''toString'') 19 if(obj.hasOwnProperty(key) & typeof obj[key] === "object"){ 20 Object.freeze(obj[key]); 21 } 22 } 23 } 24 25 deepFreeze(obj); 26 obj.family.age = 50; // 并未修改成功,值还是 48 27 console.log(obj);
1.1.3 临时死区(temporal dead zone)
通过 let 与 const 声明的常量,不会存在变量提升的情况,会放在临时死区。
1 // 如果没有第二行,那么会打印 undefined 2 // 第二行存在的情况下,打印 Uncaught ReferenceError: Cannot access 'a' before initialization 3 console.log(typeof a); 4 let a;
1.1.4 循环中的 let 及 const
var 作用域。
1 let funArr = []; 2 for(var i = 0; i < 5;i++){ 3 funArr.push(function(){ 4 console.log(i); 5 }); 6 } 7 8 // 这里会打印出 5 个 5 9 funArr.forEach(item => { 10 item(); 11 });
在上面的程序中,i 是保存在全局作用域中。
要解决这样的问题,可以利用闭包创建独立作用域。
1 let funArr = []; 2 3 for(var i = 0; i < 5; i++){ 4 (function(i){ 5 funArr.push(function(){ 6 console.log(i); 7 }) 8 })(i) 9 }; 10 11 funArr.forEach(item => { 12 item(); 13 });
利用 let 及 const 提供的块级作用域可以简化并达到同样的效果。
1 let funArr = []; 2 3 for(let i = 0; i < 5; i++){ 4 funArr.push(function(){ 5 console.log(i); 6 }); 7 } 8 9 // 这里能正常打印出 0 - 5 10 funArr.forEach(item => { 11 item(); 12 });
由于 const 不能被重复赋值,因此这里的 let 不能换成 const。但 const 可以和 for-in 和 for-of 循环中使用。
1 let obj = { 2 name: '张三', 3 age: 20 4 }; 5 6 // 打印出 name, age 7 for(const i in obj){ 8 console.log(i); 9 } 10 11 let arr = ['张三', '李四', '狗蛋']; 12 // 依次打印出数组中的值 13 for(const value of arr){ 14 console.log(value); 15 }
1.2 解构赋值
1.2.1 数组的结构
1 // 运行后 a = 10, b = 20, c = 30 2 // 等号右边的值按照顺序依次赋给等号左边的值 3 let [a, b, c] = [10, 20, 30]; 4 5 // 左右不对应的情况,d = 10, e = 20 6 let [d, e] = [10, 20, 30]; 7 8 // 通过逗号隔开省略的元素,f = 30 9 let [,, f] = [10, 20, 30]; 10 11 // i 为 undefined 12 let [g, h, i] = [10, 20]; 13 14 15 // 通过“...”把特定的元素放在变量里,k 为 [20, 30] 16 let [j,...k] = [40, 50, 60]; 17 18 let [m, n] = [1, 2]; 19 // 通过解构赋值简化引入中间变量互换值 20 [n, m] = [m, n]; 21 console.log('m = ' + m + ', n = ' + n);
1.2.2 对象的解构
1 let obj = { 2 name: '张三', 3 age: 20 4 }; 5 6 // 结构中的名称必须和对象里的下标保持一致,不然值就是 undefined 7 let {name, age} = obj; 8 console.log('name = ' + name + ', age = ' + age);
上面的例子中,name 和 age 的顺序无关,只要名字相同就可以。
多层对象的处理方式如下:
1 let obj = { 2 name: '张三', 3 age: 20, 4 family:{ 5 father: '张一', 6 mother: '李二' 7 } 8 }; 9 10 // 如果想要打印 family,会报 Uncaught ReferenceError: family is not defined 11 let {name, age, family: {father, mother}} = obj; 12 onsole.log('name = ' + name + ', father = ' + father);
别名处理如下:
1 let obj = { 2 name: '张三', 3 age: 20 4 }; 5 6 // name 和 age 是 Uncaught ReferenceError: age is not defined 7 let {name : myName, age : myAge} = obj; 8 console.log('myName = ' + myName + ', myAge = ' + myAge);
1.2.3 解构的默认值及参数的结构
设了默认值,如果解构时没有值,那就取默认值。
1 let obj = { 2 name: '张三', 3 age: 20 4 }; 5 6 // height 设置了默认值,后面没有对应值的情况下,就取默认值 7 let {name, age, height = 175} = obj; 8 console.log('name = ' + name + ', age = ' + age + ', height = ' + height);
函数参数解构的例子。
1 let obj = { 2 name: '张三', 3 age: 20 4 }; 5 6 // 注意这里参数的写法,名称还是得相同,顺序无关 7 function fn({name, age} = {}){ 8 console.log('name = ' + name + ', age = ' + age) 9 } 10 11 fn(obj); 12 13 for(const i in obj){ 14 console.log(i); 15 }
1.3 字符串扩展
1.3.1 Unicode 支持
超过 uFFFF 会打印成两部分/前4位+后四位(首先将0x20BB7-10000计算超出的部分,将超出的部分使用20个2进制表示,将前十位与0xD800 相加转成16进制,后10位与0xDC00 相加),来自 https://blog.csdn.net/weixin_47295886/article/details/127076787。
加了个大括号后,对于超过 uFFFF 的,就不会打印出两个字符,而是一个字符。
console.log('\u{20BB7}');
1.3.2 新增字符串方法
ES5 中有一个 indexOf 方法判断一个字符串是否包含在另一个字符串中,找到返回索引位置,没有找到返回 -1.
ES6 新增了以下方法。
includes():返回布尔值,是否找到了字符串。
startWith():返回布尔值,被检测字符串是否在源字符串的头部。
endsWith():返回布尔值,被检测字符串是否在源字符串的结尾。
repeat():返回新的字符串将源字符串循环指定次数。
1 let res = 'a'.repeat(5); 2 // 打印出 aaaaa 3 console.log(res);
1.3.3 模板字符串
1 let persons = [{ 2 name: '张三', 3 age: 23 4 }, { 5 name: '李四', 6 age: 25 7 }, { 8 name: '王五', 9 age: 27 10 }]; 11 12 let str = ''; 13 for(let i = 0; i < persons.length; i++){ 14 // 模板字符串使用反引号来表示,嵌入的变量通过“${}”表示 15 str += `姓名是:${persons[i].name},年龄是:${persons[i].age}\n`; 16 }
“${}”支持三目运算符。
1.4 Symbol
ES5 中提供的 6 种数据类型:Undefined,NULL,布尔值(Boolean),字符串(String),数值(Number),对象(Object)。
ES6 提供了 Symbol 来表示唯一的值,每一个创建的 Symbol 都是唯一的。
1.7 异步编程
1.7.1 ES5 中的异步
JavaScript 引擎是基于事件循环的概念实现的。引擎会把任务放在一个任务队列里,通过事件循环机制执行任务队列里的任务。
异步任务不进入主线程。在 ES5 标准中是通过回调来解决执行顺序问题。
1.7.2 Promise 基本语法
实例化后可以得到 Promise 对象。Promise 对象有 3 种状态:pending、resolved(fulfilled)、rejected。
1 let p1 = new Promise(function(){ 2 3 }); 4 console.log(p1); // pending 5 6 let p2 = new Promise(function(resolve, reject){ 7 resolve('success...'); 8 }); 9 console.log(p2); // fulfilled 10 11 let p3 = new Promise(function(resolve, reject){ 12 reject('reject...'); 13 }); 14 p3.then(res => { 15 console.log(res); 16 }, err => { 17 console.log(err); 18 }); 19 console.log(p3); // rejected
1.7.3 Promise 处理异步问题
使用 Promise 对象提供的 then 方法,在每个 then 方法又返回一个 Promise 对象,这样就可以实现 then 的链式调用。
1 function asyncFn(){ 2 return new Promise((resolve, reject) => { 3 setTimeout(() => { 4 console.log('异步逻辑'); 5 resolve('success..'); 6 }, 1000); 7 }); 8 } 9 10 asyncFn().then( res => { 11 console.log(res); 12 return asyncFn(); 13 }).then(res => { 14 console.log(res); 15 });
ES7 标准中新增了 async 及 await 关键字,可以让 Promise 更简单易用。
1 let print = function(delay, msg){ 2 return new Promise(function(resolve, reject){ 3 setTimeout(function(){ 4 console.log(msg); 5 resolve(); 6 }, delay); 7 }); 8 } 9 10 async function asyncFunc(){ 11 await print(1000, 'Hello1'); 12 await print(4000, 'Hello4'); 13 await print(3000, 'Hello3'); 14 } 15 asyncFunc();
1.7.4 Promise 里的其他用法
可以使用 Promise.all 执行多个 Promise 对象。
1 let p = new Promise(function(resolve, reject){ 2 resolve('p1...'); 3 }); 4 5 let p2 = new Promise(function(resolve, reject){ 6 resolve('p2...'); 7 }); 8 9 let p3 = new Promise(function(resolve, reject){ 10 resolve('p3...'); 11 }); 12 13 Promise.all([p, p2, p3]).then(res => { 14 console.log(res); // 输出 ['p1...', 'p2...', 'p3...']
15 });
Promise.race 会返回最先执行完的结果。
1 let p1 = new Promise(function(resolve, reject){ 2 setTimeout(() => { 3 resolve('p1...'); 4 }, 3000); 5 }); 6 7 let p2 = new Promise(function(resolve, reject){ 8 setTimeout(() => { 9 resolve('p2...'); 10 }, 1000); 11 }); 12 13 let p3 = new Promise(function(resolve, reject){ 14 setTimeout(() => { 15 resolve('p3...'); 16 }, 2000); 17 }); 18 19 Promise.race([p1, p2, p3]).then(res => { 20 console.log(res); 21 }).catch(err => { 22 console.log(err); // 输出 p2... 23 });
1.8 模块化
在 ES5 中采取 AMD、CMD 来实现前段的模块化,比较典型的框架是 sea.js 和 require.js。
ES6 中提供了自己的模块化方式,在 script 标签中声明 type="module" 。
1.8.1 导入导出基本使用
1 // a.js 2 console.log('我是 a 模块'); 3 4 let obj = { 5 name: '张三', 6 age: 20 7 } 8 9 export let a = 10; 10 export default obj; // 默认导出只能有一个 11 12 13 //1.html 14 <script type="module"> 15 import A, {a} from './a.js'; //通过 export 导出的需要通过大括号解构变量;可以使用任何名称导入默认导出,
16 console.log(A, a);
17 </script>
1.8.2 导入导出变式写法
通过 as 来默认导出,也可以通过 as 起别名。
1 // a.js 2 console.log('我是 a 模块'); 3 4 let obj = { 5 name: '张三', 6 age: 20 7 } 8 9 export let a = 10; 10 let b = 20; 11 export {b as c}; // 导出时给 b 取别名为 c 12 export { obj as default}; // 默认导出 13 14 15 // 1.html 16 <script type="module"> 17 import A, {a, c as d} from './a.js'; // 导入时给 c 取别名为 d 18 console.log(A, a, d); 19 </script>
通过通配符导入所有导出。
待理解。
1.8.3 按需导入
延迟导入,当需要加载的时候再加载对应的模块来节省性能。
例子待