作用域&解构&箭头函数
JavaScript作用域&解构&箭头函数
以下为学习过程中的极简提炼笔记,以供重温巩固学习
准备工作
- 已完成JavaScript前面9节学习
学习目的
- 学习作用域、变量提升、闭包等语言特征,加深对 JavaScript 的理解
- 掌握变量赋值、函数声明的简洁语法,降低代码的冗余度
- 理解作用域对程序执行的影响
- 能够分析程序执行的作用域范围
- 理解闭包本质,利用闭包创建隔离作用域
- 了解什么变量提升及函数提升
- 掌握箭头函数、解析剩余参数等简洁语法
- 学习ES6新特性让代码书写更加简洁便利
作用域
目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问
作用域分为:
局部作用域
- 函数作用域
- 块作用域
全局作用域
局部作用域-函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问
局部作用域语法案例:
<script>
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
const s = x + y
console.log(s) // 18
}
// 调用 counter 函数
counter(10, 8)
// 访问变量 s
console.log(s)// 此处报错 函数外部不能使用局部作用域变量
</script>
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
局部作用域-块作用域
在 JavaScript 中使用 {}
包裹的代码称为代码块 代码块内部声明的变量外部将【有可能】无法被访问
块作用域语法案例:
<script>
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常
}
// 超出了 age 的作用域
console.log(age) // 报错
let flag = true;
if(flag) {
// str 只能在该代码块中被访问
let str = 'hello world!'
console.log(str); // 正常
}
// 超出了 age 的作用域
console.log(str); // 报错
for(let t = 1; t <= 6; t++) {
// t 只能在该代码块中被访问
console.log(t); // 正常
}
// 超出了 t 的作用域
console.log(t); // 报错
</script>
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值
<script>
// 必须要有值
const version = '1.0.0';
// 不能重新赋值
// version = '1.0.1';
// 常量值为对象类型
const user = {
name: '小明',
age: 18
}
// 不能重新赋值
user = {};
// 属性和方法允许被修改
user.name = '小小明';
user.gender = '男';
</script>
总结:
let
声明的变量会产生块作用域,var
不会产生块作用域const
声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let
或const
注:开发中 let
和 const
经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const
声明成常量
语法格式案例
<body>
<script>
// for (var i = 1; i <= 3; i++) {
// // 块作用域
// console.log(i)
// }
// console.log(i)
// for (let i = 1; i <= 3; i++) {
// // 块作用域
// console.log(i)
// }
// for (let i = 1; i <= 3; i++) {
// // 块作用域
// console.log(i)
// }
if (true) {
let i = 10
}
console.log(i)
</script>
</body>
全局作用域
<script>
标签 和 .js 文件 的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问
全局作用域中声明的变量,任何其它作用域都可以被访问
全局作用域语法案例:
<script>
// 全局变量 name
const name = '小明'
// 函数作用域中访问全局
function sayHi() {
// 此处为局部
console.log('你好' + name)
}
// 全局变量 flag 和 x
const flag = true
let x = 10
// 块作用域中访问全局
if(flag) {
let y = 5
console.log(x + y) // x 是全局的
}
</script>
总结:
- 为
window
对象动态添加的属性默认也是全局的,不推荐! - 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误
作用域链
在解释什么是作用域链前先来看一段代码:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// 局部作用域
function g() {
let d = 'yo'
}
}
</script>
函数内部允许创建新的函数,f
函数内部创建的新函数 g
,会产生新的函数作用域,由此可知作用域产生了嵌套的关系
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来
作用域链的解释:
- 作用域链本质上是底层的变量查找机制
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
如下代码所示:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// let a = 10;
console.log(a) // 1 或 10
console.log(d) // 报错
// 局部作用域
function g() {
let d = 'yo'
// let b = 20;
console.log(b) // 2 或 20
}
// 调用 g 函数
g()
}
console.log(c) // 报错
console.log(d) // 报错
f();
</script>
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
垃圾回收机制
- 垃圾回收机制(Garbage Collection) 简称 GC
- JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收;正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
- 但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况;不再用到的内存,没有及时释放,就叫做内存泄漏
- JavaScript环境中分配的内存,一般有如下的内存生命周期
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
- 说明:
- 全局变量一般不会回收(关闭页面回收);
- 一般情况下局部变量的值, 不用了, 会被自动回收掉
- 垃圾回收机制-算法说明
堆栈空间分配区别:
- 栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
下面介绍两种常见的浏览器垃圾回收算法: 引用计数法 和 标记清除法
IE采用的引用计数算法, 定义“内存不再使用”, 就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
- 如果减少一个引用就减1 --4. 如果引用次数是0 ,则释放内存
存在一个致命的问题:嵌套引用(循环引用) 如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露
现代的浏览器已经不再使用引用计数算法了 现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的
核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
标记清除法核心思路是:从根部扫描对象,能查找到的就是使用的,查找不到的就要回收
语法格式案例
<body>
<script>
for (let i = 1; i <= 3; i++) {
}
let num = 10
function fn() {
const str = 'andy'
// str = 'lily'
console.log(str)
}
fn()
fn()
fn()
</script>
</body>
闭包
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量 从代码形式上看闭包是一个做为返回值的函数
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域 理解:闭包 = 内层函数 + 外层函数的变量 作用:封闭数据,提供操作,外部也可以访问函数内部的变量 应用:实现数据的私有,允许将函数与其所操作的某些数据(环境)关联起来 可能引起的问题:内存泄漏
如下代码所示:
<body>
<script>
// 1. 闭包 : 内层函数 + 外层函数变量
// function outer() {
// const a = 1
// function f() {
// console.log(a)
// }
// f()
// }
// outer()
// 2. 闭包的应用: 实现数据的私有。统计函数的调用次数
// let count = 1
// function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
// 3. 闭包的写法 统计函数的调用次数
function outer() {
let count = 1
function fn() {
count++
console.log(`函数被调用${count}次`)
}
return fn
}
const re = outer()
// const re = function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
re()
re()
// const fn = function() { } 函数表达式
// 4. 闭包存在的问题: 可能会造成内存泄漏
</script>
</body>
语法格式案例
<body>
<script>
// 简单的写法
// function outer() {
// let a = 10
// function fn() {
// console.log(a)
// }
// fn()
// }
// outer()
// 常见的闭包的形式 外部可以访问使用 函数内部的变量
// function outer() {
// let a = 100
// function fn() {
// console.log(a)
// }
// return fn
// }
// outer() === fn === function fn() {}
// const fun = function fn() { }
// // 常见的写法2
// function outer() {
// let a = 100
// return function () {
// console.log(a)
// }
// }
// const fun = outer()
// fun() // 调用函数
// 外面要使用这个 10
// 闭包的应用
// 普通形式 统计函数调用的次数
// let i = 0
// function fn() {
// i++
// console.log(`函数被调用了${i}次`)
// }
// 因为 i 是全局变量,容易被修改
// 闭包形式 统计函数调用的次数
function count() {
let i = 0
function fn() {
i++
console.log(`函数被调用了${i}次`)
}
return fn
}
const fun = count()
</script>
</body>
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
<script>
// 访问变量 str
console.log(str + 'world!');
// 声明变量 str
var str = 'hello ';
</script>
总结:
- 变量在未声明即被访问时会报语法错误
- 变量在声明之前即被访问,变量的值为
undefined
let
声明的变量不存在变量提升,推荐使用let
- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
说明:
JS初学者经常花很多时间才能习惯变量提升,还经常出现一些意想不到的bug,正因为如此,ES6 引入了块级作用域, 用let 或者 const声明变量,让代码写法更加规范和人性化
注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let
可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料。
语法格式案例
<body>
<script>
// 1. 把所有var声明的变量提升到 当前作用域的最前面
// 2. 只提升声明, 不提升赋值
// var num
// console.log(num + '件')
// num = 10
// console.log(num)
function fn() {
console.log(num)
var num = 10
}
fn()
</script>
</body>
- 用哪个关键字声明变量会有变量提升? - var
- 变量提升是什么流程?
- 先把var 变量提升到当前作用域于最前面
- 只提升变量声明, 不提升变量赋值
- 然后依次执行代码 我们不建议使用var声明变量
函数进阶
知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用
<script>
// 调用函数
foo()
// 声明函数
function foo() {
console.log('声明之前即被调用...')
}
// 不存在提升现象
bar() // 错误
var bar = function () {
console.log('函数表达式不存在提升现象...')
}
</script>
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
语法格式案例
<body>
<script>
// 1. 会把所有函数声明提升到当前作用域的最前面
// 2. 只提升函数声明,不提升函数调用
fn()
function fn() {
console.log('函数提升')
}
// fun()
// var fun = function () {
// console.log('函数表达式')
// }
// 函数表达式 必须先声明和赋值, 后调用 否则 报错
</script>
</body>
函数参数
函数参数的使用细节,能够提升函数应用的灵活度。
函数参数包括:
- 动态参数
- 剩余参数
函数参数-默认值
<script>
// 设置参数默认值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);
}
// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
</script>
总结:
- 声明函数时为形参赋值即为参数的默认值
- 如果参数未自定义默认值时,参数的默认值为
undefined
- 调用函数时没有传入对应实参时,参数的默认值被当做实参传入
函数参数-动态参数
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参 当不确定传递多少个实参参与函数运算时,就使用
<script>
// 求生函数,计算所有参数的和
function sum() {
// console.log(arguments)
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
的作用是动态获取函数的实参,当不确定传递多少个实参的时候就使用- 可以通过 for循环 依次得到传递过来的实参
语法格式案例
<body>
<script>
function getSum() {
// arguments 动态参数 只存在于 函数里面
// 是伪数组 里面存储的是传递过来的实参
// console.log(arguments) [2,3,4]
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log(sum)
}
getSum(2, 3, 4)
getSum(1, 2, 3, 4, 2, 2, 3, 4)
</script>
</body>
函数参数-剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
剩余参数主要的使用场景是:用于获取多余的实参
<script>
function config(baseURL, ...other) {
console.log(baseURL) // 得到 'http://baidu.com'
console.log(other) // other 得到 ['get', 'json']
}
// 调用函数
config('http://baidu.com', 'get', 'json');
</script>
总结:
...
是语法符号,置于最末函数形参之前- 剩余参数主要的使用场景:用于获取多余的实参
- 借助
...
获取的剩余实参,是个真数组 - 开发中,还是提倡多使用 剩余参数
语法格式案例
<body>
<script>
function getSum(a, b, ...arr) {
console.log(arr) // 使用的时候不需要写三个点 ...
}
getSum(2, 3)
getSum(1, 2, 3, 4, 5)
</script>
</body>
展开运算符
展开运算符(…),将一个数组进行展开,可以利用求数组最大值以及合并数组等操作
<script>
const arr1 = [1, 2, 3]
// 展开运算符 可以展开数组
// console.log(...arr)
// console.log(Math.max(1, 2, 3))
// ...arr1 === 1,2,3
// 1 求数组最大值
console.log(Math.max(...arr1)) // 3
console.log(Math.min(...arr1)) // 1
// 2. 合并数组
const arr2 = [3, 4, 5]
const arr = [...arr1, ...arr2]
console.log(arr)
</script>
总结:
- 不会修改原数组
- 典型运用场景: 求数组最大值(最小值)、合并数组等
区别:
- 剩余参数:在函数内部作为参数使用,得到真数组
- 展开运算符:数组中使用,数组展开
箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁 使用场景:箭头函数更适用于那些本来需要匿名函数的地方
<script>
// 0. 普通函数
// const fn = function () {
// console.log(123)
// }
// 1. 箭头函数 基本语法
// const fn = () => {
// console.log(123)
// }
// fn()
// 1.1 代入参数
// const fn = (x) => {
// console.log(x)
// }
// fn(1)
// 2. 只有一个形参的时候,可以省略小括号
// const fn = x => {
// console.log(x)
// }
// fn(1)
// // 3. 只有一行代码的时候,我们可以省略大括号
// const fn = x => console.log(x)
// fn(1)
// 4. 只有一行代码的时候,可以省略return
// const fn = x => x + x
// console.log(fn(1))
// 5. 箭头函数可以直接返回一个对象
// const fn = (uname) => ({ uname: uname })
// console.log(fn('刘德华'))
// 打印结果是:{uname: 刘德华 }
</script>
总结:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
()
- 箭头函数函数体只有一行代码时,可以写到一行,并省略花括号
{}
,无需写 return 直接返回值,自动做为返回值被返回 - 加括号的函数体返回对象字面量表达式
箭头函数-参数
箭头函数中没有 arguments
,只能使用 ...
动态获取实参
普通函数有 arguments
动态参数,箭头函数没有 arguments
动态参数,但是有 剩余参数 ...args
<script>
// 1. 利用箭头函数来求和
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) // 9
</script>
箭头函数-this
- 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
- 事件回调函数使用箭头函数时,this 为全局的 window
- 在开发中【使用箭头函数前需要考虑函数中 this 的值】,因此,DOM事件回调函数为了简便,不推荐使用箭头函数
<script>
// 以前this的指向: 谁调用的这个函数,this 就指向谁
// console.log(this) // window
// // 普通函数
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的this 是上一层作用域的this 指向
// const fn = () => {
// console.log(this) // window
// }
// fn()
// 对象方法箭头函数 this
// const obj = {
// uname: 'pink老师',
// sayHi: () => {
// console.log(this) // this 指向谁? window
// }
// }
// obj.sayHi()
const obj = {
uname: 'pink老师',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this) // obj
}
count()
}
}
obj.sayHi()
</script>
数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
如下代码所示:
<script>
// 普通的数组
let arr = [1, 2, 3]
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
// js 前面必须加分号情况
// 1. 立即执行函数要加
// 2. 使用数组的时候
// (function t() {})();
// 或者
// ;(function t() {})()
// let a = 1
// let b = 2; 注意分号
// [b, a] = [a, b]
// 多维数组
const [a, [b, c]] = ['苹果', ['小米','华为']]
console.log(a)
console.log(b)
console.log(c)
</script>
总结:
- 赋值运算符
=
左侧的[]
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 - 变量的顺序对应数组单元值的位置依次进行赋值操作
- 变量的数量大于单元值数量时,多余的变量将被赋值为
undefined
- 变量的数量小于单元值数量时,可以通过
...
获取剩余单元值,但只能置于最末位,此时剩余参数返回的还是一个数组 - 允许初始化变量的默认值,且只有单元值为
undefined
时默认值才会生效
注:支持多维解构赋值
语法格式案例
<body>
<script>
// const arr = [100, 60, 80]
// 数组解构 赋值
// // const [max, min, avg] = arr
const [max, min, avg] = [100, 60, 80]
// // const max = arr[0]
// // const min = arr[1]
// // const avg = arr[2]
console.log(max) // 100
console.log(avg) // 80
// 交换2个变量的值
let a = 1
let b = 2;
[b, a] = [a, b]
console.log(a, b)
// 案例 2
// 必须加分号的情况
// 1. 立即执行函数要加
// (function () { })();
// (function () { })();
// 2. 使用数组的时候
// const arr = [1, 2, 3]
const str = 'pink';
[1, 2, 3].map(function (item) {
console.log(item)
})
let a = 1
let b = 2
;[b, a] = [a, b]
console.log(a, b)
// 案例3
// const pc = ['海尔', '联想', '小米', '方正'];
// [hr, lx, mi, fz] = pc
// console.log(hr, lx, mi, fz);
// function getValue() {
// return [100, 60]
// }
// [max, min] = getValue()
// console.log(max, min);
// const pc = ['海尔', '联想', '小米', '方正']
// const [hr, lx, mi, fz] = ['海尔', '联想', '小米', '方正']
// console.log(hr)
// console.log(lx)
// console.log(mi)
// console.log(fz)
// // 请将最大值和最小值函数返回值解构 max 和min 两个变量
// function getValue() {
// return [100, 60]
// }
// const [max, min] = getValue()
// console.log(max)
// console.log(min)
// 1. 变量多, 单元值少 , undefined
// const [a, b, c, d] = [1, 2, 3]
// console.log(a) // 1
// console.log(b) // 2
// console.log(c) // 3
// console.log(d) // undefined
// 2. 变量少, 单元值多
// const [a, b] = [1, 2, 3]
// console.log(a) // 1
// console.log(b) // 2
// 3. 剩余参数 变量少, 单元值多
// const [a, b, ...c] = [1, 2, 3, 4]
// console.log(a) // 1
// console.log(b) // 2
// console.log(c) // [3, 4] 真数组
// 4. 防止 undefined 传递
// const [a = 0, b = 0] = [1, 2]
// const [a = 0, b = 0] = []
// console.log(a) // 1
// console.log(b) // 2
// 5. 按需导入赋值
// const [a, b, , d] = [1, 2, 3, 4]
// console.log(a) // 1
// console.log(b) // 2
// console.log(d) // 4
// const arr = [1, 2, [3, 4]]
// console.log(arr[0]) // 1
// console.log(arr[1]) // 2
// console.log(arr[2]) // [3,4]
// console.log(arr[2][0]) // 3
// 多维数组解构
// const arr = [1, 2, [3, 4]]
// const [a, b, c] = [1, 2, [3, 4]]
// console.log(a) // 1
// console.log(b) // 2
// console.log(c) // [3,4]
const [a, b, [c, d]] = [1, 2, [3, 4]]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
console.log(d) // 4
</script>
</body>
对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法,如下代码所示:
<script>
// 普通对象
const user = {
name: '小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量 name age
// 即 user对象中,对应的name属性值、age属性值,将赋予左边变量
const {name, age} = user
console.log(name) // 小明
console.log(age) // 18
// 1. 对象解构的变量名 可以重新改名 旧变量名: 新变量名
const { uname: username, age } = { uname: 'pink老师', age: 18 }
console.log(username)
console.log(age)
// 2. 解构数组对象
const pig = [
{
uname: '佩奇',
age: 6
}
]
const [{ uname, age }] = pig
console.log(uname)
console.log(age)
</script>
总结:
- 赋值运算符
=
左侧的{}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 - 对象属性的值将被赋值给与属性名相同的变量
- 对象中找不到与变量名一致的属性时变量值为
undefined
- 允许初始化变量的默认值,属性不存在或单元值为
undefined
时默认值才会生效 - 可以从一个对象中提取变量并同时修改新的变量名
注:支持多维解构赋值
语法格式案例
<body>
<script>
// 案例 1
// 1. 这是后台传递过来的数据
const msg = {
"code": 200,
"msg": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G商用自己,三大运用商收入下降",
"count": 58
},
{
"id": 2,
"title": "国际媒体头条速览",
"count": 56
},
{
"id": 3,
"title": "乌克兰和俄罗斯持续冲突",
"count": 1669
},
]
}
// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
// const { data } = msg
// console.log(data)
// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</script>
</body>
<body>
<script>
// 案例 2
// 对象解构
// const obj = {
// uname: 'pink老师',
// age: 18
// }
// obj.uname
// obj.age
// const uname = 'red老师'
// 解构的语法
// const { uname, age } = {age: 18, uname: 'pink老师' }
// // 等价于 const uname = obj.uname
// // 要求属性名和变量名必须一直才可以
// console.log(uname)
// console.log(age)
// 1. 对象解构的变量名 可以重新改名 旧变量名: 新变量名
// const { uname: username, age } = { uname: 'pink老师', age: 18 }
// console.log(username)
// console.log(age)
// 2. 解构数组对象
const pig = [
{
uname: '佩奇',
age: 6
}
]
const [{ uname, age }] = pig
console.log(uname)
console.log(age)
</script>
</body>
语法格式案例
<body>
<script>
// const pig = {
// name: '佩奇',
// family: {
// mother: '猪妈妈',
// father: '猪爸爸',
// sister: '乔治'
// },
// age: 6
// }
// // 多级对象解构
// const { name, family: { mother, father, sister } } = pig
// console.log(name)
// console.log(mother)
// console.log(father)
// console.log(sister)
const person = [
{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治'
},
age: 6
}
]
const [{ name, family: { mother, father, sister } }] = person
console.log(name)
console.log(mother)
console.log(father)
console.log(sister)
</script>
</body>
forEach遍历数组
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
注意:
1.forEach 主要是遍历数组,是主要使用场景
2.参数当前数组元素是必须要写的, 索引号可选
<body>
<script>
被遍历的数组.forEach(function (当前数组元素,当前元素索引号){
函数体
})
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['red', 'green', 'pink']
const result = arr.forEach(function (item, index) {
console.log(item) // 数组元素 red green pink
console.log(index) // 索引号
})
// console.log(result)
</script>
</body>
filter筛选数组
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
<body>
<script>
被遍历的数组.filter(function (currentValue,index){
return 筛选条件
})
const arr = [10, 20, 30]
// const newArr = arr.filter(function (item, index) {
// // console.log(item)
// // console.log(index)
// return item >= 20
// })
// 返回的符合条件的新数组
const newArr = arr.filter(item => item >= 20)
console.log(newArr)
</script>
</body>
- 返回值:返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
- 参数:currentValue 必须写, index 可选
- 因为返回新数组,所以不会影响原数组
语法格式案例
<body>
<script>
// 筛选数组中大于30的元素
const score = [10,50,20,40,33,30]
const re = score.filter(function (item){
return item > 30
})
console.log(re) //结果[50,40,33]
</script>
</body>
综合案例
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商品渲染</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.list {
width: 990px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
padding-top: 100px;
}
.item {
width: 240px;
margin-left: 10px;
padding: 20px 30px;
transition: all .5s;
margin-bottom: 20px;
}
.item:nth-child(4n) {
margin-left: 0;
}
.item:hover {
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
transform: translate3d(0, -4px, 0);
cursor: pointer;
}
.item img {
width: 100%;
}
.item .name {
font-size: 18px;
margin-bottom: 10px;
color: #666;
}
.item .price {
font-size: 22px;
color: firebrick;
}
.item .price::before {
content: "¥";
font-size: 14px;
}
</style>
</head>
<body>
<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: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
price: '289.00',
picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
},
{
id: '4001594',
name: '日式黑陶功夫茶组双侧把茶具礼盒装',
price: '288.00',
picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',
},
{
id: '4001009',
name: '竹制干泡茶盘正方形沥水茶台品茶盘',
price: '109.00',
picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
},
{
id: '4001874',
name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
price: '488.00',
picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
},
{
id: '4001649',
name: '大师监制龙泉青瓷茶叶罐',
price: '139.00',
picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
},
{
id: '3997185',
name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
price: '108.00',
picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',
},
{
id: '3997403',
name: '手工吹制更厚实白酒杯壶套装6壶6杯',
price: '99.00',
picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',
},
{
id: '3998274',
name: '德国百年工艺高端水晶玻璃红酒杯2支装',
price: '139.00',
picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',
},
]
// 1. 声明一个字符串变量
let str = ''
// 2. 遍历数据
goodsList.forEach(item => {
// console.log(item) // 可以得到每一个数组元素 对象 {id: '4001172'}
// const {id} = item 对象解构
const { name, price, picture } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
// 3.生成的 字符串 添加给 list
document.querySelector('.list').innerHTML = str
</script>
</body>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商品渲染</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.list {
width: 990px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
}
.item {
width: 240px;
margin-left: 10px;
padding: 20px 30px;
transition: all .5s;
margin-bottom: 20px;
}
.item:nth-child(4n) {
margin-left: 0;
}
.item:hover {
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
transform: translate3d(0, -4px, 0);
cursor: pointer;
}
.item img {
width: 100%;
}
.item .name {
font-size: 18px;
margin-bottom: 10px;
color: #666;
}
.item .price {
font-size: 22px;
color: firebrick;
}
.item .price::before {
content: "¥";
font-size: 14px;
}
.filter {
display: flex;
width: 990px;
margin: 0 auto;
padding: 50px 30px;
}
.filter a {
padding: 10px 20px;
background: #f5f5f5;
color: #666;
text-decoration: none;
margin-right: 20px;
}
.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>
// 2. 初始化数据
const goodsList = [
{
id: '4001172',
name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
price: '289.00',
picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
},
{
id: '4001594',
name: '日式黑陶功夫茶组双侧把茶具礼盒装',
price: '288.00',
picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',
},
{
id: '4001009',
name: '竹制干泡茶盘正方形沥水茶台品茶盘',
price: '109.00',
picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
},
{
id: '4001874',
name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
price: '488.00',
picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
},
{
id: '4001649',
name: '大师监制龙泉青瓷茶叶罐',
price: '139.00',
picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
},
{
id: '3997185',
name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
price: '108.00',
picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',
},
{
id: '3997403',
name: '手工吹制更厚实白酒杯壶套装6壶6杯',
price: '100.00',
picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',
},
{
id: '3998274',
name: '德国百年工艺高端水晶玻璃红酒杯2支装',
price: '139.00',
picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',
},
]
// 1. 渲染函数 封装
function render(arr) {
// 声明空字符串
let str = ''
// 遍历数组
arr.forEach(item => {
// 解构
const { name, picture, price } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
// 追加给list
document.querySelector('.list').innerHTML = str
}
render(goodsList) // 页面一打开就需要渲染
// 2. 过滤筛选
document.querySelector('.filter').addEventListener('click', e => {
// e.target.dataset.index e.target.tagName
const { tagName, dataset } = e.target
// 判断
if (tagName === 'A') {
// console.log(11)
// arr 返回的新数组
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>
备注:innerHTML
HTML DOM innerHTML 属性
innerHTML 属性在 HTML DOM 中非常重要,它允许开发者获取或设置 HTML 元素的内容。这个属性的值是一个字符串,包含了元素的所有子节点的 HTML 标记。通过使用 innerHTML,开发者可以轻松地修改页面的结构和内容。
代码示例:
以下是一个简单的例子,展示了如何使用 innerHTML 来改变页面中元素的内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>示例</title>
<script>
function changeContent() {
document.getElementById('myDiv').innerHTML = '新的内容';
}
</script>
</head>
<body>
<div id="myDiv">原始内容 </div>
<button onclick="changeContent()">点击我</button>
</body>
</html>
在这个例子中,当用户点击按钮时,<div>
元素的内容会被更新为 "新的内容"。
使用注意事项
使用 innerHTML 时需要注意的是,如果设置的内容包含了不安全的代码,比如来自用户的输入,那么可能会导致跨站脚本攻击(XSS)。因此,在使用 innerHTML 插入内容之前,应该对内容进行适当的清理和转义。
此外,innerHTML 与 textContent 和 innerText 属性不同。textContent 返回元素及其后代的文本内容,而不包括任何 HTML 标签; innerText 返回的是元素及其子元素的“渲染”文本内容,它会考虑 CSS 样式(例如隐藏的元素不会包含在返回的文本中)。
浏览器兼容性
innerHTML 属性得到了所有主流浏览器的支持,包括 Chrome、Firefox、Safari、Edge 和 Internet Explorer。这意味着开发者可以放心地在不同的浏览器环境中使用这个属性来动态地操作页面内容。