前言
我们学习了js初级之后,对js的基本语法已经有了基本的掌握,但是作为一个前端工程师,只掌握js初级的知识是远远不够的,因此我们必须得更加深入的学习js,所以我在这里详细地整理了一些js高级的知识。
一.作用域
作用域就是指变量能被访问的范围。
分类:1.全局作用域 2.局部作用域
1.全局作用域
<script> 标签和.js文件 的【最外层】就是所谓的全局作用域,在全局作用域中声明的变量,任何其它作用域都可以被访问。
在全局作用域声明的变量称为全局变量。
2.局部作用域
-
函数作用域
函数的内部就是函数作用域,在函数作用域声明的变量就是局部变量,在函数外面不能直接访问到函数作用域里面的变量。
注意:函数的形参也是局部变量,函数执行完毕后,函数内部的变量会被自动清空。
-
块级作用域
块级作用域:就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{ }都可以被看作是一个块级作用域。在块级作用域里面声明的变量也要看作局部变量,不能在外面被随便访问。
例如:
{
let a = 10
}
console.log(a)
这里控制台打印变量a是会报错的,因为局部变量不能在外部被直接访问。
3.作用域链
作用域链:嵌套关系的作用域串联起来形成了作用域链,作用域链本质上是底层的变量查找机制(就近原则、从内到外)
更通俗易懂的话来说就是里层的作用域里面能直接访问外层作用域的变量,外层作用域里面不能直接访问里层作用域的变量。
作用域链的查找规则:
- 会优先查找当前函数作用域中查找变量查找不到则会依次逐级查找父级作用域直到全局作用域
二.垃圾回收机制
在了解垃圾回收机制之前,我们先了解一个现象。
内存泄漏:不再用到的内存,没有及时释放掉,就叫做内存泄漏。
如果发生了内存泄漏的话,少量的内存泄漏对我们没什么影响,但是程序从来都是多次执行的,因此发生了内存泄漏的话,很容易造成泄漏堆积,而无论多大的内存,如果一直内存泄漏的话,内存迟早被耗光。
因此我们需要将不用的内存给及时释放掉,而释放这些内存就需要用到我们的垃圾回收机制。
1.引用计数法
- 跟踪记录每个值被引用的次数
- 如果这个值的被引用了一次,那么就记录次数1
- 多次引用会累加
- 如果减少一个引用就减1
- 如果引用次数是0 ,则释放内存 缺点:会出现循环引用的问题,因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露
2.标记清除法
- 标记
- 清除
当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。
三.闭包
闭包简单理解就是:
闭包 = 函数嵌套 + 里层函数访问外层函数的变量
function outer() {
const a = 1
function f() {
console.log(a)
}
f()
}
outer()
这就是一个闭包,调用outer()也能打印出a的值,但是在正常情况下,函数外部是访问不了函数内部的局部变量的,这里能访问到,因此能发现闭包的作用就是能在函数外部访问到函数内部的变量。
四.特殊参数
我们知道,每个函数的形参是在定义时就提前定义好的,因此数量也是固定的,所以如果当我们碰上一些不知道要传入多少个实参的问题的时候该怎么办呢?
这时候就可以用到这里讲的两个特殊参数了——arguments和剩余参数
1.arguments动态参数
当我们不知道需要传入的实参的个数的时候,就可以不设定形参,而在函数内部使用arguments,它是一个伪数组,里面包含了函数所有传入的实参
例如:假如我们需要定义一个能求出所有传入的实参的和的函数,这时候就可以用到arguments了
function getSum(){
let sum = 0
for(let i = 0 ; i < arguments.length ; i++) {
sum += arguments[i]
}
}
getSum(1)
getSum(1,2)
getSum(1,2,3)
这时候不管我们传入多少个实参,它都能实现所有的实参的和
但是需要注意的是,
arguments是一个伪数组,它不是一个真数组,因此那些数组的方法大多它都不能用
2.剩余参数
当我们传入的实参比函数定义的形参多的时候,我们该怎样获取多余的实参呢?
这个时候可以用到剩余参数了
语法:
function 函数名(a,b,...变量名) {
console.log(变量名)
}
注意:... 是剩余语法符号,放置于形参列表的最后面,用于获取多余的实参
因此如果当函数没有定义形参的时候,那么arguments动态参数就等于剩余参数,但是他们两个又是十分的不同,不同点在于动态参数是伪数组,而剩余参数是一个真数组
五.箭头函数
在js初级的时候,定义函数只有两种方法
1.用function关键字定义
function fn(){}
2.使用函数表达式定义
const fn = function(){}
但是我们发现这两种定义函数的方式还是很麻烦,因此有一种更简单的定义函数的方法,即使用箭头函数
箭头函数是根据函数表达式演变过来的,基本语法是省略函数表达式的function关键字,将()与{}之间使用=>连接起来,因此上面的函数表达式即可写成:
const fn = () => {}
但是如果只是这样的话,那么箭头函数相比函数表达式的方式也没有简单多少,因此箭头函数还有很多的省略语法
省略语法1:只有一个形参的时候,小括号可以省略
//函数表达式
const fn = function(a){}
//箭头函数
const fn = a => {}
省略语法2:当函数体只有一行代码时,大括号也可以省略
//函数表达式
const fn = function(){
console.log('hello world')
}
//箭头函数
const fn = () => console.log('hello world')
省略语法3:当函数体里面只有一行代码,是将结果return,return关键字可以省略
const fn = function(){
return 'hello world'
}
//箭头函数
const fn = () => 'hello world'
学会了上面三个省略语法,才能真正发现箭头函数的方便之处,将三个省略语法一起使用的时候,我们就能将复杂的函数通过箭头函数来简化
// 函数表达式
const fn = function(msg) {
return msg
}
// 箭头函数
const fn = msg => msg
但是使用箭头函数,有一个需要特别注意的地方,如果箭头函数需要返回对象,则必须给对象外面加上小括号
// 函数表达式
const fn = function() {
return {uname: '张三',age: 18}
}
// 箭头函数
const fn = () => ({uname: '张三',age: 18})
这是因为如果不加这个小括号,那么程序将分辨不出那个大括号到底是函数体的大括号,还是对象的大括号,而它会将那个大括号默认当成函数体的大括号来处理,如此的话,程序会报错
但是需要特别注意的是,箭头函数是和普通函数不同的,普通函数可以使用动态参数,而箭头函数是不能使用动态参数的,但是箭头函数仍然可以使用剩余参数,因此当箭头函数需要使用到arguments动态参数的时候,我们可以使用剩余参数代替。
还有一个需要注意的地方在于普通函数的this和箭头函数的this不同。普通函数的this是谁调用该函数。那么它的this就指向谁;而箭头函数自身是没有this的,它的this只能沿着作用域链向上级查找,找到一个离它作用域最近的作用域的this来作为它的this
六.解构
1.数组解构
语法:const [变量1,变量2,变量3] = 数组
数组解构的作用:将数组的单元值快速批量地赋给一系列的变量
例如:
const arr = [10,20,30]
const [a,b,c] = arr
通过数组解构,这样数组里的三个就依次被赋值给了a,b,c
因此,此时
a=10,b=20,c=30
2.对象解构
对象解构的基本语法和数组解构语法很相似,也是
const obj = {
uname:'张三',
age:'18'
}
const {uname,age} = obj
但是需要注意的是,对象解构的变量名必须和对象里面的属性名相同,如果不同的话则会解构出undefined
七.遍历数组-forEach方法
我们学js初级的时候,遍历数组只能使用for循环,但是今天这里介绍一种新的遍历数组的方法,那就是forEach方法
语法:
数组.forEach((item,index)=>{
})
这里的item是必写参数,item是指数组的元素,相当于for循环里面的的arr[i],这里的index是元素下标,是可选参数,可写可不写,它的回调函数的返回值是undefined
八.筛选数组-filter方法
我们之前学数组,想要取出里面的满足某个条件的元素的时候,方法是使用for循环遍历数组,然后里面写上条件判断,但是现在不需要这么繁琐了,我们可以直接使用filter方法来筛选数组里面满足条件的元素
语法:
数组.filter(function(item,index){
})
这里的item是必写参数,item是指数组的元素,相当于for循环里面的的arr[i],这里的index是元素下标,是可选参数,可写可不写,它的回调函数的返回值是undefined
它的回调函数返回值是满足条件的元素组成的数组