JavaScript 进阶 - 第1天
本文是在学习作用域、变量提升、闭包等语言特征,为加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法,降低代码冗余度做的笔记。
理解作用域对程序执行的影响
能够分析程序执行的作用域范围
理解闭包本质,利用闭包创建隔离作用域
了解什么变量提升及函数提升
掌握箭头函数、解析剩余参数等简洁语法
作用域
了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
局部作用域 局部作用域分为函数作用域和块作用域。
函数作用域 在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
1 2 3 4 5 6 7 8 9 10 11 12 <script> function counter (x, y ) { const s = x + y console .log (s) } counter (10 , 8 ) console .log (s) </script>
总结:
函数内部声明的变量,在函数外部无法被访问
函数的参数也是函数内部的局部变量
不同函数内部声明的变量无法互相访问
函数执行完毕后,函数内部的变量实际被清空了
块作用域 在 JavaScript 中使用 {}
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script> { let age = 18 ; console .log (age); } console .log (age) let flag = true ; if (flag) { let str = 'hello world!' console .log (str); } console .log (str); for (let t = 1 ; t <= 6 ; t++) { console .log (t); } console .log (t); </script>
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script> const version = '1.0.0' ; const user = { name: '小明' , age: 18 } user = {}; user.name = '小小明' ; user.gender = '男' ; </script>
总结:
let
声明的变量会产生块作用域,var
不会产生块作用域
const
声明的常量也会产生块作用域
不同代码块之间的变量无法互相访问
推荐使用 let
或 const
注:开发中 let
和 const
经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const
声明成常量。
全局作用域 1 2 3 4 5 6 7 8 9 <script> function sayHi ( ) { } </script>
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script> const name = '小明' function sayHi ( ) { console .log ('你好' + name) } const flag = true let x = 10 if (flag) { let y = 5 console .log (x + y) } </script>
总结:
为 window
对象动态添加的属性默认也是全局的,不推荐!
函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
作用域链 在解释什么是作用域链前先来看一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 <script> let a = 1 let b = 2 function f ( ) { let c function g ( ) { let d = 'yo' } } </script>
函数内部允许创建新的函数,f
函数内部创建的新函数 g
,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script> let a = 1 let b = 2 function f ( ) { let c console .log (a) console .log (d) function g ( ) { let d = 'yo' console .log (b) } g () } console .log (c) console .log (d) f (); </script>
总结:
嵌套关系的作用域串联起来形成了作用域链
相同作用域链中按着从小到大的规则查找变量
子作用域能够访问父作用域,父级作用域无法访问子级作用域
闭包 闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <body> <script > function outer ( ) { let count = 1 function fn ( ) { count++ console .log (`函数被调用${count} 次` ) } return fn } const re = outer () re () re () </script > </body>
总结:
1.怎么理解闭包?
2.闭包的作用?
封闭数据,实现数据私有,外部也可以访问函数内部的变量
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
3.闭包可能引起的问题?
变量提升 变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问,
1 2 3 4 5 6 7 <script> console .log (str + 'world!' ); var str = 'hello ' ; </script>
总结:
变量在未声明即被访问时会报语法错误
变量在声明之前即被访问,变量的值为 undefined
let
声明的变量不存在变量提升,推荐使用 let
变量提升出现在相同作用域当中
实际开发中推荐先声明再访问变量
注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let
可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料 。
函数
知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异。
函数提升 函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script> foo () function foo ( ) { console .log ('声明之前即被调用...' ) } bar () var bar = function ( ) { console .log ('函数表达式不存在提升现象...' ) } </script>
总结:
函数提升能够使函数的声明调用更灵活
函数表达式不存在提升的现象
函数提升出现在相同作用域当中
函数参数 函数参数的使用细节,能够提升函数应用的灵活度。
默认值 1 2 3 4 5 6 7 8 9 10 <script> function sayHi (name="小明" , age=18 ) { document .write (`<p>大家好,我叫${name} ,我今年${age} 岁了。</p>` ); } sayHi (); sayHi ('小红' ); sayHi ('小刚' , 21 ); </script>
总结:
声明函数时为形参赋值即为参数的默认值
如果参数未自定义默认值时,参数的默认值为 undefined
调用函数时没有传入对应实参时,参数的默认值被当做实参传入
动态参数 arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script> function sum ( ) { let s = 0 for (let i = 0 ; i < arguments .length ; i++) { s += arguments [i] } console .log (s) } sum (5 , 10 ) sum (1 , 2 , 4 ) </script>
总结:
arguments
是一个伪数组
arguments
的作用是动态获取函数的实参
剩余参数 1 2 3 4 5 6 7 8 <script> function config (baseURL, ...other ) { console .log (baseURL) console .log (other) } config ('http://baidu.com' , 'get' , 'json' ); </script>
总结:
...
是语法符号,置于最末函数形参之前,用于获取多余的实参
借助 ...
获取的剩余实参,是个真数组
箭头函数 箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <body> <script > </script > </body>
总结:
箭头函数属于表达式函数,因此不存在函数提升
箭头函数只有一个参数时可以省略圆括号 ()
箭头函数函数体只有一行代码时可以省略花括号 {}
,并自动做为返回值被返回
箭头函数参数 箭头函数中没有 arguments
,只能使用 ...
动态获取实参
1 2 3 4 5 6 7 8 9 10 11 12 13 <body> <script > const getSum = (...arr ) => { let sum = 0 for (let i = 0 ; i < arr.length ; i++) { sum += arr[i] } return sum } const result = getSum (2 , 3 , 4 ) console .log (result) </script >
箭头函数 this 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <script> const obj = { uname : 'pink老师' , sayHi : function ( ) { console .log (this ) let i = 10 const count = ( ) => { console .log (this ) } count () } } obj.sayHi () </script>
解构赋值
知道解构的语法及分类,使用解构简洁语法快速为变量赋值。
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
数组解构 数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法,如下代码所示:
1 2 3 4 5 6 7 8 9 10 <script> let arr = [1 , 2 , 3 ] let [a, b, c] = arr console .log (a); console .log (b); console .log (c); </script>
总结:
赋值运算符 =
左侧的 []
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
变量的顺序对应数组单元值的位置依次进行赋值操作
变量的数量大于单元值数量时,多余的变量将被赋值为 undefined
变量的数量小于单元值数量时,可以通过 ...
获取剩余单元值,但只能置于最末位
允许初始化变量的默认值,且只有单元值为 undefined
时默认值才会生效
注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
对象解构 对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 <script> const user = { name : '小明' , age : 18 }; const {name, age} = user console .log (name) console .log (age) </script>
总结:
赋值运算符 =
左侧的 {}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
对象属性的值将被赋值给与属性名相同的变量
对象中找不到与变量名一致的属性时变量值为 undefined
允许初始化变量的默认值,属性不存在或单元值为 undefined
时默认值才会生效
注:支持多维解构赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <body> <script > const msg = { "code" : 200 , "msg" : "获取新闻列表成功" , "data" : [ { "id" : 1 , "title" : "5G商用自己,三大运用商收入下降" , "count" : 58 }, { "id" : 2 , "title" : "国际媒体头条速览" , "count" : 56 }, { "id" : 3 , "title" : "乌克兰和俄罗斯持续冲突" , "count" : 1669 }, ] } function render ({ data } ) { console .log (data) } render (msg) function render ({ data: myData } ) { console .log (myData) } render (msg) </script >
综合案例 forEach遍历数组 forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
注意:
1.forEach 主要是遍历数组
2.参数当前数组元素是必须要写的, 索引号可选。
1 2 3 4 5 6 7 8 9 10 11 <body> <script > const arr = ['red' , 'green' , 'pink' ] const result = arr.forEach (function (item, index ) { console .log (item) console .log (index) }) </script > </body>
filter筛选数组 filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <body> <script > const arr = [10 , 20 , 30 ] const newArr = arr.filter (item => item >= 20 ) console .log (newArr) </script > </body>