前端JavaScript进阶
JavaScript基础
作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问
1.局部作用域
局部作用域分为函数作用域和块作用域。
1.1. 函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
1. 函数内部声明的变量,在函数外部无法被访问
2. 函数的参数也是函数内部的局部变量
3. 不同函数内部声明的变量无法互相访问
4. 函数执行完毕后,函数内部的变量实际被清空了
1.2. 块作用域:
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
1. let 声明的变量会产生块作用域,var 不会产生块作用域
2. const 声明的常量也会产生块作用域
3. 不同代码块之间的变量无法互相访问
4. 推荐使用 let 或 const
2.全局作用域
<script>标签和 .js标签 的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。 全局作用域中声明的变量,任何其它作用域都可以被访问
1. 为 window 对象动态添加的属性默认也是全局的,不推荐!
2. 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
3. 尽可能少的声明全局变量,防止全局变量被污染
3.作用域链
作用域链本质上是底层的变量查找机制。
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
1. 嵌套关系的作用域串联起来形成了作用域链
2. 相同作用域链中按着从小到大的规则查找变量
3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域
4.JS垃圾回(GC)
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
不再用到的内存,没有及时释放,就叫做内存泄漏
JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
全局变量一般不会回收(关闭页面回收);一般情况下局部变量的值, 不用了, 会被自动回收掉
堆栈空间分配区别:
- 栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
垃圾回收算法:
1.引用计数法
IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法:
1. 跟踪记录被引用的次数
2. 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
3. 如果减少一个引用就减1 --
4. 如果引用次数是0 ,则释放内存
缺点:嵌套引用(循环引用),如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
2.标记清除法
1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
5.闭包
一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
闭包 = 内层函数 + 外层函数的变量
闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
常见的闭包的形式 外部可以访问使用 函数内部的变量
function outer() {let a = 100function fn() {console.log(a)}return fn
}常见的写法2
function outer() {let a = 100return function () {console.log(a)}
}
const fun = outer() //等价于const fun = function fn() { }
fun() // 调用函数
闭包应用:实现数据的私有
闭包的应用
普通形式 统计函数调用的次数
let i = 0
function fn() {i++console.log(`函数被调用了${i}次`)
}因为 i 是全局变量,容易被修改//修改
function count() {let i = 0function fn() {i++console.log(`函数被调用了${i}次`)}return fn
}
const fun = count()
6.变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
1. 变量在未声明即被访问时会报语法错误
2. 变量在var声明之前即被访问,变量的值为 undefined
3. let/const 声明的变量不存在变量提升
4. 变量提升出现在相同作用域当中
5. 实际开发中推荐先声明再访问变量
流程:
- 先把var 变量提升到当前作用域于最前面
- 只提升变量声明, 不提升变量赋值
- 然后依次执行代码
不建议使用var声明变量
函数进阶
1.函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
1. 函数提升能够使函数的声明调用更灵活
2. 函数表达式不存在提升的现象
3. 函数提升出现在相同作用域当中
var fun
1. 会把所有函数声明提升到当前作用域的最前面
2. 只提升函数声明,不提升函数调用
fn()
function fn() {console.log('函数提升')
}
fun()
var fun = function () {console.log('函数表达式')
}函数表达式 必须先声明和赋值, 后调用 否则 报错
2.函数参数
2.1. 动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
1. arguments 是一个伪数组,只存在于函数中
2. arguments 的作用是动态获取函数的实参
3. 可以通过for循环依次得到传递过来的实参
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)
2.2. 剩余参数(推荐)
剩余参数允许我们将一个不定数量的参数表示为一个数组
1. ... 是语法符号,置于最末函数形参之前,用于获取多余的实参
2. 借助 ... 获取的剩余实参,是个真数组
function getSum(a, b, ...arr) {console.log(arr) // 使用的时候不需要写 ...
}
getSum(2, 3)
getSum(1, 2, 3, 4, 5)
2.3.展开运算符
展开运算符(…),将一个数组进行展开
不会修改原数组
求数组最大值(最小值)、合并数组等
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)
3.箭头函数
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
使用场景:箭头函数更适用于那些本来需要匿名函数的地方
语法:
1. 箭头函数 基本语法
const fn = () => {console.log(123)
}
fn()
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:'刘德华'}
1. 箭头函数属于表达式函数,因此不存在函数提升
2. 箭头函数只有一个参数时可以省略圆括号 ()
3. 箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值 被返回
4. 加括号的函数体返回对象字面量表达式
箭头函数参数:
- 普通函数有arguments 动态参数
- 箭头函数没有 arguments 动态参数,但是有 剩余参数 ..args
箭头函数 this:
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值, 非常令人讨厌。 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
以前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()const obj = {uname: 'pink老师',sayHi: function () {console.log(this) // objlet i = 10const count = () => {console.log(this) // obj }count()}
}
obj.sayHi()
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此 DOM事件回调函数为了简便,还是不太推荐使用箭头函数
解构赋值
1.数组解构
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)
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
6.多维数组解构
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]
2.对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
基本语法:
1. 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
2. 对象属性的值将被赋值给与属性名相同的变量
3. 注意解构的变量名不要和外面的变量名冲突否则报错
4.对象中找不到与变量名一致的属性时变量值为 undefined
const { uname, age } = {age: 18, uname: 'pink' }
属性名和变量名必须一致1. 对象解构的变量名 可以重新改名 旧变量名: 新变量名
const { uname: username, age } = { uname: 'pink', age: 18 }2. 解构数组对象
const pig = [{uname: '佩奇',age: 6}
]
const [{ uname, age }] = pig3.多级对象解构
const pig = {name: '佩奇',family: {mother: '猪妈妈',father: '猪爸爸',sister: '乔治'},age: 6
}
const { name, family: { mother, father, sister } } = pigfunction render({ data }) {// const { data } = arr// 我们只要 data 数据// 内部处理console.log(data)
}
render(msg)
遍历数组 forEach 方法(重点)
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
主要使用场景: 遍历数组的每个元素
被遍历的数组.forEach(function (当前数组元素,当前数组元素的索引号)
const arr = ['red', 'green', 'pink']
const result = arr.forEach(function (item, index) {console.log(item) // 数组元素 red green pinkconsole.log(index) // 索引号
})
1. forEach 主要是遍历数组
2. 参数当前数组元素是必须要写的, 索引号可选。
筛选数组 filter 方法(重点)
filter() 筛选数组
返回值:返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
参数:currentValue 必须写, index 可选
因为返回新数组,所以不会影响原数组
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)
深入对象
1.创建对象三种方式
1. 利用对象字面量创建对象
const obj = {uname:'pig'
}2. 利用new Object 创建对象
const obj = new Object()
obj.uname = 'pink'
console.log(obj)
或者
const obj = new Object({ uname: 'pink' })
console.log(obj)3. 利用构造函数创建对象
2.构造函数
构造函数:是一种特殊的函数,主要用来初始化对象
使用场景:常规的{...} 语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一 遍,此时可以通过构造函数来快速创建多个类似的对象。
构造函数在技术上是常规函数
1. 它们的命名以大写字母开头。
2. 它们只能由"new" 操作符来执行。
// 创建一个猪 构造函数
function Pig(uname, age) {this.uname = unamethis.age = age
}
const p = new Pig('佩奇', 6)
console.log(p)
1. 使用new关键字调用函数的行为被称为实例化
2. 实例化构造函数时没有参数时可以省略()
3. 构造函数内部无需写return,返回值即为新创建的对象
4. 构造函数内部的return返回的值无效,所以不要写return
5. new Object() new Date()也是实例化构造函数
实例化执行过程
- 1. 创建新的空对象
- 2. 构造函数this指向新对象
- 3. 执行构造函数代码,修改this,添加新的属性
- 4. 返回新对象
3.实例成员&静态成员
实例成员: 通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
1. 实例对象的属性和方法即为实例成员
2. 为构造函数传入参数,动态创建结构相同但值不同的对象
3. 构造函数创建的实例对象彼此独立互不影响。
静态成员: 构造函数的属性和方法被称为静态成员
1. 构造函数的属性和方法被称为静态成员
2. 一般公共特征的属性或方法静态成员设置为静态成员
3. 静态成员方法中的this 指向构造函数本身
4. 静态成员只能由构造函数访问
内置构造函数
在JavaScript 中最主要的数据类型有6种:
- 基本数据类型: 字符串、数值、布尔、undefined、null
- 引用类型:对象
其实字符串、数值、布尔、等基本类型也都有专门的构造函数,这些我们称为包装类型。
JS中几乎所有的数据都可以基于构成函数创建。
引用类型 :Object,Array,RegExp,Date 等
包装类型 :String,Number,Boolean 等
1.Object
Object 是内置的构造函数,用于创建普通对象。
推荐使用字面量方式声明对象,而不是Object 构造函数
学习三个常用静态方法(静态方法就是只有构造函数Object可以调用的)
const o = { uname: 'pink', age: 18 }Object.keys静态方法获取对象中所有属性(键)
console.log(Object.keys(o)) //返回数组['uname', 'age']Object.values 静态方法获取对象中所有属性值
console.log(Object.values(o)) // ['pink', 18]Object. assign 静态方法常用于对象拷贝
const oo = {}
Object.assign(oo, o)
Object.assign(o, { gender: '女' })
console.log(o) //{ uname: 'pink', age: 18, gender: '女' }
2.Array
Array 是内置的构造函数,用于创建数组
创建数组建议使用字面量创建,不用Array构造函数创建
作用:reduce 返回函数累计处理的结果,经常用于求和等
arr.reduce(function(累计值, 当前元素){}, 起始值) //起始值为0可省略(纯数字,对象数组的不可省)const arr = [1,2,3]
const total= arr.reduce(function(prev,current){return prev +current
},10)
console.log(total)或者
const total= arr.reduce((prev,current)=>prev +current,10)
涨薪案例
const arr = [{name: '张三',salary: 10000
}, {name: '李四',salary: 10000
}, {name: '王五',salary: 20000
},
]
// 涨薪的钱数 10000 * 0.3
// const money = arr.reduce(function (prev, item) {
// return prev + item.salary * 0.3
// }, 0)
const money = arr.reduce((prev, item) => prev + item.salary * 0.3, 0)
console.log(money)
Array 对象方法
方法 | 描述 |
---|---|
[...] | 创建一个新数组。 |
concat() | 连接两个或更多的数组,并返回结果。 |
every() | 检测数值元素的每个元素是否都符合条件。 |
fill() | 使用一个固定值来填充数组。 |
filter() | 检测数值元素,并返回符合条件所有元素的数组。 |
find() | 返回符合传入测试(函数)条件的数组元素。 |
findIndex() | 返回符合传入测试(函数)条件的数组元素索引。 |
from() | 通过给定的对象中创建一个数组。 |
join() | 把数组的所有元素放入一个字符串。 |
keys() | 返回数组的可迭代对象,包含原始数组的键(key)。 |
map() | 通过指定函数处理数组的每个元素,并返回处理后的数组。 |
pop() | 删除数组的最后一个元素并返回删除的元素。 |
push() | 向数组的末尾添加一个或更多元素,并返回新的长度。 |
reduce() | 将数组元素计算为一个值(从左到右)。 |
reverse() | 反转数组的元素顺序。 |
shift() | 删除并返回数组的第一个元素。 |
slice() | 选取数组的一部分,并返回一个新数组。 |
some() | 检测数组元素中是否有元素符合指定条件。 |
sort() | 对数组的元素进行排序。 |
splice() | 从数组中添加或删除元素。 |
3.String
String 对象方法
方法 | 描述 |
---|---|
concat() | 连接两个或更多字符串,并返回新的字符串。 |
endsWith() | 判断当前字符串是否是以指定的子字符串结尾的(区分大小写)。 |
indexOf() | 返回某个指定的字符串值在字符串中首次出现的位置。 |
includes() | 查找字符串中是否包含指定的子字符串。 |
match() | 查找找到一个或多个正则表达式的匹配。 |
repeat() | 复制字符串指定次数,并将它们连接在一起返回。 |
replace() | 在字符串中查找匹配的子串,并替换与正则表达式匹配的子串。 |
search() | 查找与正则表达式相匹配的值。 |
slice() | 提取字符串的片断,并在新的字符串中返回被提取的部分。 |
split() | 把字符串分割为字符串数组。 |
startsWith() | 查看字符串是否以指定的子字符串开头。 |
substring() | 提取字符串中两个指定的索引号之间的字符。 |
toLowerCase() | 把字符串转换为小写。 |
toUpperCase() | 把字符串转换为大写。 |
trim() | 去除字符串两边的空白。 |
toLocaleLowerCase() | 根据本地主机的语言环境把字符串转换为小写。 |
toLocaleUpperCase() | 根据本地主机的语言环境把字符串转换为大写。 |
4.Number
Number 是内置的构造函数,用于创建数值
Number 对象方法
方法 | 描述 |
---|---|
isFinite | 检测指定参数是否为无穷大。 |
isInteger | 检测指定参数是否为整数。 |
isNaN | 检测指定参数是否为 NaN。 |
isSafeInteger | 检测指定参数是否为安全整数。 |
toExponential(x) | 把对象的值转换为指数计数法。 |
toFixed(x) | 把数字转换为字符串,结果的小数点后有指定位数的数字。 |
toLocaleString(locales, options) | 返回数字在特定语言环境下的表示字符串。 |
toPrecision(x) | 把数字格式化为指定的长度。 |
toString() | 把数字转换为字符串,使用指定的基数。 |
valueOf() | 返回一个 Number 对象的基本数字值。 |