ECMAScript 6
作用域
局部作用域
1. 函数作用域
- 函数内部声明的变量只能在函数内部被访问,外部无法直接访问
① 函数的参数也是函数内部的局部变量
② 不同函数内部声明的变量无法互相访问
③ 函数执行完毕后,函数内部的变量实际被清空了
2. 块作用域
- JS 中使用
{}
包裹的代码称为代码块,代码块内部声明的变量外部将 有可能 无法被访问
① let、const 声明的变量 会 产生块作用域,var 声明的变量 不会 产生块作用域
② 不同代码块之间的变量无法互相访问
③ 推荐使用 let 或 const
全局作用域
<script> 标签
和.js 文件
的【最外层】就是全局作用域,在此声明的变量在函数内部也可以被访问。全局作用域中声明的变量,任何其它作用域都可以被访问
① 为 window 对象动态添加的属性默认也是全局的【不推荐】
② 函数中未使用任何关键字声明的变量为全局变量【不推荐】
③ 尽可能少的声明全局变量,防止全局变量被污染
作用域链
1. 本质
- 底层的 变量查找机制
① 函数被执行时,优先查找当前 函数作用域中查找变量
② 若当前作用域查找不到,则会依次 逐级查找父级作用域 直到全局作用域
2. 总结
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能访问父作用域,父作用域无法访问子作用域
JS 垃圾回收机制
1. 垃圾回收机制(Garbage Collectio,GC)
- JS 中 内存 的分配和回收都是 自动完成 的,内存在不使用的时候会被 垃圾回收器 自动回收
2. 内存的生命周期
- 内存分配:声明变量、函数、对象时,系统会自动分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由 垃圾回收器 自动回收不再使用的内存
3. 说明
- 全局变量一般不会回收【关闭页面回收】
- 一般情况下,局部变量的值不用了,会被自动回收掉
4. 内存泄漏
程序中分配的内存由于某种原因,程序 未释放 或 无法释放
5. 算法说明
- 堆栈空间分配区别
- 栈(OS):由 OS自动分配释放 函数的参数值、局部变量等【基本数据类型 放到栈中】
- 堆(OS):一般由程序员分配释放,若程序员不释放,由 GC 回收【复杂数据类型 放到堆中】
- 两种常见的浏览器垃圾回收算法
引用计数法、标记清除法 - 引用计数法【IE 采用】【定义“内存不再使用”,是看某对象是否有指向它的引用,若无则回收对象】
- 算法
① 跟踪记录被引用的次数
② 若被引用了一次,则记录次数为1,多次引用则 ++
③ 若减少一个引用,则 –
④ 若引用次数为0,则释放内存 - 缺点:嵌套引用(循环引用)
若两对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄漏function fn() { let o1 = {} let o2 = {} o1.a = o2 o2.a = o1 return '引用计数无法回收' } fn()
- 算法
- 标记清除法
- 算法【现今常用】【将“不再使用的对象”,定义为“无法达到的对象”】
① 从 根部(即 JS 中的全局对象)出发定时扫描内存中的对象
② 凡是能从 根部到达 的对象,都是 还需使用 的
③ 那些 无法 由根部出发触及到的 对象被标记 为不再使用,稍后进行 回收
- 算法【现今常用】【将“不再使用的对象”,定义为“无法达到的对象”】
闭包
-
使用闭包函数创建隔离作用域避免全局变量污染
-
概念:闭包 = 内存函数 + 外层函数的变量
一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域 -
作用:封闭数据、提供操作,外部也可以访问函数内部的变量
-
缺陷:可能造成 内存泄漏
-
应用:实现数据的私有
-
格式:
function outer() { let a = 100 function fn() { console.log(a) } return fn } /* 等价于 function outer() { let a = 100 return function() { console.log(a) } } */ // outer() === fn === function fn() {} const fun = outer() fun()
-
案例 - 统计函数调用次数【可能造成 内存泄漏】
// // 问题:i是全局变量,易被修改 // let i = 0 // function fn() { // i++ // console.log(`函数被调用了${i}次`) // } function count() { let i = 0 function fn() { i++ console.log(`函数被调用了${i}次`) } return fn } const fun = count() // 全局,不会被回收,除非页面被关掉
-
示例
变量提升
- 变量提升仅存在于 var 声明的变量
- 示例
// 1. 把所有 var 声明的变量提升到当前作用域的最前面 // 2. 只提升声明,不提升赋值 console.log(num + ' 件') // undefined 件 var num = 10 // 等价于以下代码 // var num // console.log(num + ' 件') // num = 10
- 注意
① 变量在未声明即被访问时会报语法错误;变量在 var 声明之前即被访问,变量的值为 undefined
② let / const 声明的变量不存在变量提升
③ 变量提升出现在相同作用域当中
④ 实际开发中推荐先声明再访问变量【推荐使用 let / const】
函数进阶
函数提升
- 函数在声明之前即可被调用
- 示例
// 1. 把所有 函数 声明提升到当前作用域的最前面 // 2. 只提升声明,不提升赋值 fun() var fun = function () { console.log('函数表达式不存在提升现象') } // 3. 函数表达式 必须先声明和赋值,后调用,否则报错 // 等价于以下代码 // var fun // fun() // fun = function () { // console.log('函数表达式不存在提升现象') // }
函数参数
1. 动态参数
arguments
是函数内部内置的伪数组变量,包含了调用函数时传入的所有实参- 总结
①arguments
是一个 伪数组,只存在于函数中【出了函数作用域无法打印】
②arguments
的作用是动态获取函数的实参
③ 可通过 for 循环依次得到传递过来的实参 - 语法
function getSum() { // console.log(arguments) let sum = 0 for (let i = 0; i < arguments.length; i++) { sum += arguments[i] } console.log(sum) } getSum(2, 3, 4)
2. 剩余参数【推荐使用】
- 剩余参数允许将一个不定数量的参数表示为一个数组
...
是语法符号,置于最末函数形参之前,用于获取 多余 的实参
借助...
获取的剩余实参,是个 真数组【有 pop()、push() 等方法】- 语法
function getSum(a, b, ...arr) { // 至少两个形参 // console.log(arr) // 使用的时候不用加 ... let sum = a + b for (let i = 0; i < arr.length; i++) { sum += arr[i] } console.log(sum) } getSum(2, 3, 4)
3. 展开运算符
- 展开运算符
...
将一个数组进行展开 - 说明
① 不会修改原数组
② 典型运用场景:求数组最大值 / 最小值、合并数组等 - 语法
const arr1 = [1, 2, 3] // console.log(...arr1) // 1 2 3 // console.log(Math.max(1, 2, 3)) // 3 // 1. 求数组最大值 // 实际:...arr1 === 1, 2, 3 console.log(Math.max(...arr1)) // 3 // 2. 合并数组 const arr2 = [3, 4, 5] const arr = [...arr1, ...arr2] console.log(arr) // [1, 2, 3, 3, 4, 5]
4. 剩余参数 vs 展开运算符
- 剩余参数:函数参数中使用,得到真数组【[1, 2, 3]】
- 展开运算符:数组中使用,数组展开【1 2 3】
箭头函数
- 目的:比函数表达式更简短的函数写法,并且不绑定 this
- 使用场景:更适用于那些本来需要匿名函数的地方
1. 基本语法
- 语法
① 箭头函数只有一个参数时,可省略小括号()
【无参时,不可省略小括号】
② 箭头函数函数体只有一行代码时,可省略花括号{}
,写到一行上;且自动做为返回值被返回,无需写 return
③ 加括号 的函数体返回对象字面量表达式
注:箭头函数属于表达式函数,故 不存在函数提升 - 语法
function fn() { console.log(123) } const fun1 = function () { // 函数表达式 console.log(456) } const fun2 = () => { // 匿名 箭头函数 console.log(789) } fun2() // 789 const fun3 = (x) => { console.log(x) } fun3(1) // 1 const fun4 = x => { // 只有一个形参时,可以省略小括号 console.log(x) } fun4(2) // 2 const fun5 = x => console.log(x) // 只有一行代码时,可以省略大括号 fun5(3) // 3 const fun6 = x => x + x // 只有一行 return 代码时,可以省略 return console.log(fun6(4)) // 8 const fun7 = (uname) => ({ uname: uname }) // 箭头函数可以直接返回一个对象【本来应是花括号包花括号,但因冲突,故变成小括号包花括号】 console.log(fun7('刘德华')) // Object
2. 箭头函数参数
- 普通函数有
arguments
动态参数 - 箭头函数无
arguments
动态参数,但有剩余参数...args
const getSum = (...arrs) => { let sum = 0 for (let i = 0; i < arrs.length; i++) { sum += arrs[i] } return sum } const res = getSum(2, 3) console.log(res) // 5
3. 箭头函数 this
- 箭头函数出现之前,每一个新函数根据它是 被如何调用的 来定义这个函数的 this 值,很麻烦
箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层沿用 this - 开发中,使用箭头函数前需要考虑函数中 this 的值,事件回调函数使用箭头函数时,this 为全局的 window。故 DOM 事件回调函数为了简便,不太推荐使用箭头函数
<body> <button id="btn">button</button> <script> const btn = document.querySelector("#btn") btn.addEventListener('click', () => { console.log(this) // Window }) btn.addEventListener('click', function () { console.log(this); // btn }) </script> </body>
- 示例
<body> <button id="btn">button</button> <script> console.log(this) // Window function fn1() { console.log(this) // Window } fn1() // this 指向 函数的调用者,实际写法是 window.fn() const obj1 = { name: 'andy', sayHi: function () { console.log(this); // obj1 } } obj1.sayHi() const fn2 = () => { console.log(this); // Window } fn2() // 箭头函数无 this,故向作用域链上一层找,即 script 里的 this,即 Window let btn = document.querySelector("#btn") btn.addEventListener('click', () => { console.log(this) // Window }) const obj2 = { name: 'bob', sayHi: () => { console.log(this) // Window } } obj2.sayHi() // 箭头函数无 this,故向作用域链上一层找,即 obj2 里的 this,即 Window.obj2() const obj3 = { name: 'cindy', sayHi: () => { let i = 10 const count = () => { console.log(this) // Window } count() } } obj3.sayHi() const obj4 = { name: 'david', sayHi: function () { let i = 10 const count = () => { console.log(this) // obj4 } count() } } obj4.sayHi() </script> </body>
解构赋值
- 解构赋值是一种快速为变量赋值的简洁语法,本质上仍是为变量赋值
数组解构
1. 数组解构
- 将数组的单元值快速、批量赋值给一系列变量的简洁语法
- 基本语法
① 赋值运算符 = 左侧的[]
用于批量声明变量,右侧 数组的单元值 将被赋值给左侧的变量
② 变量的顺序对应数组单元值的位置依次进行赋值操作 - 语法【变量数量 === 单元值数量】
语法【变量数量 > 单元值数量】const arr = [100, 60, 80] const [max, min, avg] = arr // 数组解构 // 等价于 // const max = arr[0] // const min = arr[1] // const avg = arr[2] console.log(max, min, avg); // 100 60 80
语法【变量数量 < 单元值数量】const [a, b, c, d] = [1, 2, 3] console.log(a, b, c, d); // 1 2 3 undefined
语法【防止 undefined 传递,可设置默认值(只有单元值为 undefined 时默认值才会生效)】const [e, f] = [4, 5, 6] console.log(e, f); // 4 5 const [e, f, ...g] = [4, 5, 6, 7] console.log(e, f); // 4 5 console.log(g) // [6, 7],真数组
语法【按需导入,忽略某些返回值】const [e = 0, f = 0] = [4] console.log(e, f); // 4 0
语法【支持多维数组的结构】const [a, b, , d] = [1, 2, 3, 4] console.log(a, b, d) // 1 2 4
const arr = [1, 2, [3, 4]] console.log(arr[0], arr[1], arr[2][0], arr[2][1]) // 1 2 3 4 const [a, b, [c, d]] = [1, 2, [3, 4]] console.log(a, b, c, d) // 1 2 3 4
- 案例 - 交换两个变量
let a = 1 let b = 2; // 这里必须有分号 [b, a] = [a, b] // 或 [a, b] = [b, a] console.log(a, b) // 2 1
2. js 前必须加分号的情况
- 立即执行函数
(function t() {})(); // 法一 ;(function t() {})() // 法二
- 数组开头的,特别是前面有语句的
;[b, a] = [a, b] // 数组开头的,特别是前面有语句的,一定注意加分号
const str = 'pink'; // 此处必须要加分号 [1, 2, 3].map(function (item) { console.log(item) // 1 2 3 })
对象解构
- 将对象属性和方法快速、批量赋值给一系列变量的简洁语法
- 基本语法
① 赋值运算符 = 左侧的{}
用于批量声明变量,右侧 对象的属性值 将被赋值给左侧的变量
② 对象属性的值将被赋值给与属性名 相同 的变量
③ 注意解构的变量名不要和外面的变量名冲突,否则报错
④ 对象中找不到与变量名一致的属性时,变量值为undefined
- 语法【属性名和变量名必须一致】
语法【给新的变量名赋值【旧变量名: 新变量名】】【可从一个对象中提取变量并同时修改新的变量名】const { uname, age } = { uname: 'andy', age: 18 } // 属性名和变量名必须一致 // 等价于 const uname = obj.uname console.log(uname, age); // andy 18
语法【数组对象解构】const uname = 'andy' const { uname: username, age } = { uname: 'bob', age: 18 } // 对象解构的变量名可以重新改名 console.log(username, age) // bob 18
语法【多级对象解构】const pig = [ { uname: '佩奇', age: 18 } ] const [{ uname, age }] = pig console.log(uname, age) // 佩奇 18
const pig = { name: '佩奇', family: { mother: '猪妈妈', father: '猪爸爸', brother: '乔治' }, age: 6 } const { name, family: { mother, father, brother } } = pig console.log(name, mother, father, brother); // 佩奇 猪妈妈 猪爸爸 乔治
const pig = [ { name: '佩奇', family: { mother: '猪妈妈', father: '猪爸爸', brother: '乔治' }, age: 6 } ] const [{ name, family: { mother, father, brother } }] = pig console.log(name, mother, father, brother); // 佩奇 猪妈妈 猪爸爸 乔治
数组方法
遍历数组方法
1. forEach()
- 用于调用 数组 的每个元素,并将元素传递给回调函数
① 主要遍历数组【适合于遍历 数组对象】,不遍历对象
② 参数当前数组元素必写,索引号可选
③ 只遍历,不返回数组【map 会返回数组】 - 语法
被遍历的数组.forEach(function(当前数组元素, [当前元素索引号]){ })
- 示例
const arr = ['red', 'orange', 'green'] const result = arr.forEach(function (item, index) { console.log(item) // red orange green console.log(index) // 0 1 2 }) console.log(result) // undefined
2. map()
- 按顺序,映射 遍历处理数组里每个值,并返回 新数组
- 语法
被遍历的数组.map(function(val, index){ return 新数组 })
- 示例
let arr1 = ['小明', '小红', '小刘', '小李', '小赵'] let newArr1 = arr1.map(val => { return '张' + val }) console.log(newArr1) // ['张小明', '张小红', '张小刘', '张小李', '张小赵'] let arr2 = [ { name: "小明", age: 19 }, { name: "小红", age: 10 }, { name: "小青", age: 39 }, { name: "小爱", age: 15 }, ] let newArr2 = arr2.map(obj => { return { name: obj.name, age: obj.age, iq: Math.floor(Math.random() * (100 - 50 + 1) + 50) // 值为 50~100 的随机整数 } }) console.log(arr2) console.log(newArr2) let newArr3 = arr2.map(obj => { return { name: obj.name } }) console.log(newArr3)
筛选数组方法
filter()
- 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
① 会返回筛选后元素的新数组。若无符合条件的元素,则返回空数组
② 参数当前数组元素必写,索引号可选
③ 因为返回新数组,故不会影响原数组 - 语法
被遍历的数组.filter(function(当前数组元素, [当前元素索引号]){ return 筛选条件 })
- 示例
const score = [10, 50, 3, 40, 33] const res = score.filter(function (item) { return item > 30 }) console.log(res) // [50, 40, 33]
综合案例
- 示例
const msg = { "code": 200, "msg": "获取新闻列表成功", "data": [ { "id": 1, "title": "5G商用自己,三大运用商收入下降", "count": 58 }, { "id": 2, "title": "国际媒体头条速览", "count": 56 }, { "id": 3, "title": "乌克兰和俄罗斯持续冲突", "count": 1669 } ] } // 需求1:只选出 data 数据 // const { data } = msg // console.log(data) // Array(3) // 需求2:把 data 选出当作参数,传递给函数 // function render({ data }) { // console.log(data); // Array(3) // } // render(msg) // 需求3:将 render() 中的数据名改为 myData function render({ data: myData }) { console.log(myData); // Array(3) } render(msg)
- 案例 - 筛选后渲染页面
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .filter a:active, .filter a:focus { background: #05943c; color: #fff; } </style> </head> <body> <div class="filter"> <a data-index="1" href="javascript:;">0-100元</a> <a data-index="2" href="javascript:;">100-300元</a> <a data-index="3" href="javascript:;">300元以上</a> <a href="javascript:;">全部区间</a> </div> <div class="list"> <!-- <div class="item"> <img src="" alt=""> <p class="name"></p> <p class="price"></p> </div> --> </div> <script> const goodsList = [ { id: '4001172', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机1', price: '100.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001173', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机2', price: '150.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001173', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机3', price: '291.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001173', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机4', price: '292.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001173', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机5', price: '293.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001173', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机6', price: '299.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001173', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机7', price: '300.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001173', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机8', price: '400.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, ] function render(arr) { let str = '' arr.forEach(item => { const { name, picture, price } = item str += ` <div div class="item" > <img src=${picture} alt=""> <p class="name">${name}</p> <p class="price">${price}</p> </div> ` }) document.querySelector('.list').innerHTML = str } render(goodsList) // 页面一打开就进行渲染 document.querySelector('.filter').addEventListener('click', e => { const { tagName, dataset } = e.target if (tagName === 'A') { let arr = goodsList if (dataset.index === '1') { arr = goodsList.filter(item => item.price > 0 && item.price <= 100) } else if (dataset.index === '2') { arr = goodsList.filter(item => item.price >= 100 && item.price <= 300) } else if (dataset.index === '3') { arr = goodsList.filter(item => item.price >= 300) } render(arr) } }) </script> </body>