学习作用域、变量提升、闭包等语言特征,加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法,降低代码的冗余度。
了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
局部作用域分为函数作用域和块作用域。
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
1<script>2 // 声明 counter 函数3 function counter(x, y) {4 // 函数内部声明的变量5 const s = x + y6 console.log(s) // 187 }8 // 设用 counter 函数9 counter(10, 8)10 // 访问变量 s11 console.log(s)// 报错12</script>总结:
在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
xxxxxxxxxx281<script>2 {3 // age 只能在该代码块中被访问4 let age = 18;5 console.log(age); // 正常6 }7 8 // 超出了 age 的作用域9 console.log(age) // 报错10 11 let flag = true;12 if(flag) {13 // str 只能在该代码块中被访问14 let str = 'hello world!'15 console.log(str); // 正常16 }17 18 // 超出了 age 的作用域19 console.log(str); // 报错20 21 for(let t = 1; t <= 6; t++) {22 // t 只能在该代码块中被访问23 console.log(t); // 正常24 }25 26 // 超出了 t 的作用域27 console.log(t); // 报错28</script>JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
x1<script>2 // 必须要有值3 const version = '1.0.0';45 // 不能重新赋值6 // version = '1.0.1';78 // 常量值为对象类型9 const user = {10 name: '小明',11 age: 1812 }1314 // 不能重新赋值15 user = {};1617 // 属性和方法允许被修改18 user.name = '小小明';19 user.gender = '男';20</script>总结:
let 声明的变量会产生块作用域,var 不会产生块作用域const 声明的常量也会产生块作用域let 或 const注:开发中 let 和 const 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const 声明成常量。
<script> 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
xxxxxxxxxx91<script>2 // 此处是全局3 4 function sayHi() {5 // 此处为局部6 }78 // 此处为全局9</script>全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
xxxxxxxxxx201<script>2 // 全局变量 name3 const name = '小明'4 5 // 函数作用域中访问全局6 function sayHi() {7 // 此处为局部8 console.log('你好' + name)9 }1011 // 全局变量 flag 和 x12 const flag = true13 let x = 1014 15 // 块作用域中访问全局16 if(flag) {17 let y = 518 console.log(x + y) // x 是全局的19 }20</script>总结:
window 对象动态添加的属性默认也是全局的,不推荐!JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
在解释什么是作用域链前先来看一段代码:
xxxxxxxxxx131<script>2 // 全局作用域3 let a = 14 let b = 25 // 局部作用域6 function f() {7 let c8 // 局部作用域9 function g() {10 let d = 'yo'11 }12 }13</script>函数内部允许创建新的函数,f 函数内部创建的新函数 g,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
xxxxxxxxxx281<script>2 // 全局作用域3 let a = 14 let b = 256 // 局部作用域7 function f() {8 let c9 // let a = 10;10 console.log(a) // 1 或 1011 console.log(d) // 报错12 13 // 局部作用域14 function g() {15 let d = 'yo'16 // let b = 20;17 console.log(b) // 2 或 2018 }19 20 // 调用 g 函数21 g()22 }2324 console.log(c) // 报错25 console.log(d) // 报错26 27 f();28</script>总结:
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
xxxxxxxxxx391<body>2 <script>3 // 1. 闭包 : 内层函数 + 外层函数变量4 // function outer() {5 // const a = 16 // function f() {7 // console.log(a)8 // }9 // f()10 // }11 // outer()1213 // 2. 闭包的应用: 实现数据的私有。统计函数的调用次数14 // let count = 115 // function fn() {16 // count++17 // console.log(`函数被调用${count}次`)18 // }1920 // 3. 闭包的写法 统计函数的调用次数21 function outer() {22 let count = 123 function fn() {24 count++25 console.log(`函数被调用${count}次`)26 }27 return fn28 }29 const re = outer()30 // const re = function fn() {31 // count++32 // console.log(`函数被调用${count}次`)33 // }34 re()35 re()36 // const fn = function() { } 函数表达式37 // 4. 闭包存在的问题: 可能会造成内存泄漏38 </script>39</body>总结:
1.怎么理解闭包?
2.闭包的作用?
3.闭包可能引起的问题?
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问,
xxxxxxxxxx71<script>2 // 访问变量 str3 console.log(str + 'world!');45 // 声明变量 str6 var str = 'hello ';7</script>总结:
undefinedlet 声明的变量不存在变量提升,推荐使用 let注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let 可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料。
知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异。
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
xxxxxxxxxx141<script>2 // 调用函数3 foo()4 // 声明函数5 function foo() {6 console.log('声明之前即被调用...')7 }89 // 不存在提升现象10 bar() // 错误11 var bar = function () {12 console.log('函数表达式不存在提升现象...')13 }14</script>总结:
函数参数的使用细节,能够提升函数应用的灵活度。
xxxxxxxxxx101<script>2 // 设置参数默认值3 function sayHi(name="小明", age=18) {4 document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);5 }6 // 调用函数7 sayHi();8 sayHi('小红');9 sayHi('小刚', 21);10</script>总结:
undefinedarguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
xxxxxxxxxx141<script>2 // 求生函数,计算所有参数的和3 function sum() {4 // console.log(arguments)5 let s = 06 for(let i = 0; i < arguments.length; i++) {7 s += arguments[i]8 }9 console.log(s)10 }11 // 调用求和函数12 sum(5, 10)// 两个参数13 sum(1, 2, 4) // 两个参数14</script>总结:
arguments 是一个伪数组arguments 的作用是动态获取函数的实参xxxxxxxxxx81<script>2 function config(baseURL, other) {3 console.log(baseURL) // 得到 'http://baidu.com'4 console.log(other) // other 得到 ['get', 'json']5 }6 // 调用函数7 config('http://baidu.com', 'get', 'json');8</script>总结:
... 是语法符号,置于最末函数形参之前,用于获取多余的实参... 获取的剩余实参,是个真数组箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
xxxxxxxxxx311<body>2 <script>3 // const fn = function () {4 // console.log(123)5 // }6 // 1. 箭头函数 基本语法7 // const fn = () => {8 // console.log(123)9 // }10 // fn()11 // const fn = (x) => {12 // console.log(x)13 // }14 // fn(1)15 // 2. 只有一个形参的时候,可以省略小括号16 // const fn = x => {17 // console.log(x)18 // }19 // fn(1)20 // // 3. 只有一行代码的时候,我们可以省略大括号21 // const fn = x => console.log(x)22 // fn(1)23 // 4. 只有一行代码的时候,可以省略return24 // const fn = x => x + x25 // console.log(fn(1))26 // 5. 箭头函数可以直接返回一个对象27 // const fn = (uname) => ({ uname: uname })28 // console.log(fn('刘德华'))2930 </script>31</body>总结:
(){},并自动做为返回值被返回箭头函数中没有 arguments,只能使用 ... 动态获取实参
xxxxxxxxxx131<body>2 <script>3 // 1. 利用箭头函数来求和4 const getSum = (arr) => {5 let sum = 06 for (let i = 0; i < arr.length; i++) {7 sum += arr[i]8 }9 return sum10 }11 const result = getSum(2, 3, 4)12 console.log(result) // 913 </script>箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
xxxxxxxxxx451 <script>2 // 以前this的指向: 谁调用的这个函数,this 就指向谁3 // console.log(this) // window4 // // 普通函数5 // function fn() {6 // console.log(this) // window7 // }8 // window.fn()9 // // 对象方法里面的this10 // const obj = {11 // name: 'andy',12 // sayHi: function () {13 // console.log(this) // obj14 // }15 // }16 // obj.sayHi()1718 // 2. 箭头函数的this 是上一层作用域的this 指向19 // const fn = () => {20 // console.log(this) // window21 // }22 // fn()23 // 对象方法箭头函数 this24 // const obj = {25 // uname: 'pink老师',26 // sayHi: () => {27 // console.log(this) // this 指向谁? window28 // }29 // }30 // obj.sayHi()3132 const obj = {33 uname: 'pink老师',34 sayHi: function () {35 console.log(this) // obj36 let i = 1037 const count = () => {38 console.log(this) // obj 39 }40 count()41 }42 }43 obj.sayHi()4445 </script>知道解构的语法及分类,使用解构简洁语法快速为变量赋值。
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法,如下代码所示:
xxxxxxxxxx101<script>2 // 普通的数组3 let arr = [1, 2, 3]4 // 批量声明变量 a b c 5 // 同时将数组单元值 1 2 3 依次赋值给变量 a b c6 let [a, b, c] = arr7 console.log(a); // 18 console.log(b); // 29 console.log(c); // 310</script>总结:
= 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量undefined... 获取剩余单元值,但只能置于最末位undefined 时默认值才会生效注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法,如下代码所示:
xxxxxxxxxx131<script>2 // 普通对象3 const user = {4 name: '小明',5 age: 186 };7 // 批量声明变量 name age8 // 同时将数组单元值 小明 18 依次赋值给变量 name age9 const {name, age} = user1011 console.log(name) // 小明12 console.log(age) // 1813</script>总结:
= 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量undefinedundefined 时默认值才会生效注:支持多维解构赋值
xxxxxxxxxx511<body>2 <script>3 // 1. 这是后台传递过来的数据4 const msg = {5 "code": 200,6 "msg": "获取新闻列表成功",7 "data": [8 {9 "id": 1,10 "title": "5G商用自己,三大运用商收入下降",11 "count": 5812 },13 {14 "id": 2,15 "title": "国际媒体头条速览",16 "count": 5617 },18 {19 "id": 3,20 "title": "乌克兰和俄罗斯持续冲突",21 "count": 166922 },2324 ]25 }2627 // 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面28 // const { data } = msg29 // console.log(data)30 // 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数31 // const { data } = msg32 // msg 虽然很多属性,但是我们利用解构只要 data值33 function render({ data }) {34 // const { data } = arr35 // 我们只要 data 数据36 // 内部处理37 console.log(data)3839 }40 render(msg)4142 // 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData43 function render({ data: myData }) {44 // 要求将 获取过来的 data数据 更名为 myData45 // 内部处理46 console.log(myData)4748 }49 render(msg)5051 </script>forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
注意:
1.forEach 主要是遍历数组
2.参数当前数组元素是必须要写的, 索引号可选。
xxxxxxxxxx111<body>2 <script>3 // forEach 就是遍历 加强版的for循环 适合于遍历数组对象4 const arr = ['red', 'green', 'pink']5 const result = arr.forEach(function (item, index) {6 console.log(item) // 数组元素 red green pink7 console.log(index) // 索引号8 })9 // console.log(result)10 </script>11</body>filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
xxxxxxxxxx141<body>2 <script>3 const arr = [10, 20, 30]4 // const newArr = arr.filter(function (item, index) {5 // // console.log(item)6 // // console.log(index)7 // return item >= 208 // })9 // 返回的符合条件的新数组1011 const newArr = arr.filter(item => item >= 20)12 console.log(newArr)13 </script>14</body>