作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问
作用域分为:局部作用域、全局作用域
局部作用域
局部作用域分为函数作用域和块作用域
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问
块作用域
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
let 声明的变量会产生块作用域,var 不会产生块作用域
const 声明的常量也会产生块作用域
不同代码块之间的变量无法互相访问
推荐使用 let 或 const
全局作用域
<script>
标签 和 .js
文件 的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问
为 window 对象动态添加的属性默认也是全局的
函数中未使用任何关键字声明的变量为全局变量
尽可能少的声明全局变量,防止全局变量被污染
作用域链
作用域链本质上是底层的变量查找机制。
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
嵌套关系的作用域串联起来形成了作用域链
相同作用域链中按着从小到大的规则查找变量
子作用域能够访问父作用域,父级作用域无法访问子级作用域
垃圾回收机制
介绍
垃圾回收机制(Garbage Collection) 简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做内存泄漏
内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
全局变量一般不会回收(关闭页面回收)
一般情况下局部变量的值, 不用了, 会被自动回收
算法说明
堆栈空间分配区别:
栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面
引用计数
IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
- 如果减少一个引用就减1 –
- 如果引用次数是0 ,则释放内存
标记清除
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收
闭包
一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
闭包 = 内层函数 + 外层函数的变量
闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
变量提升
允许在变量声明之前即被访问(仅存在于var声明变量)
let/const 声明的变量不存在变量提升
函数进阶
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
函数表达式不存在提升的现象
函数参数
动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
使用:直接通过arguments.length
获取数组长度,然后使用arguments[i]
调用即可
剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
语法:
function fn(a1,a2,...other){
console.log(other)
}
… 是语法符号,置于最末函数形参之前,用于获取多余的实参
借助 … 获取的剩余实参,是个真数组
开发中,提倡多使用 剩余参数,而不是动态参数
展开运算符
...
能够将一个数组进行展开
典型运用场景: 求数组最大值(最小值)、合并数组等
示例:
const arr = [1,5,3,8,2]
// console.log(...arr) //1 5 3 8 2
箭头函数
引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
箭头函数更适用于那些本来需要匿名函数的地方
基本语法
//普通函数
const fn = function(){
console.log('普通函数')
}
fn()
//箭头函数
const fn = () => {
console.log('箭头函数')
}
fn()
语法2:只有一个参数可以省略小括号
//普通函数
const fn = function(x){
return x+x
}
console.log(fn(1))
//箭头函数
const fn = x => {
return x+x
}
console.log(fn(1))
语法3:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值
//普通函数
const fn = function(x,y){
return x+y
}
console.log(fn(1,2))
//箭头函数
const fn = (x,y) => x + y
console.log(fn(1,2))
语法4:加括号的函数体返回对象字面量表达式
const fn = uname => ({uname: uname})
console.log(fn('张三'))
箭头函数参数
普通函数有arguments 动态参数
箭头函数没有 arguments 动态参数,但是有 剩余参数 ...args
箭头函数 this
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值, 非常令人讨厌。
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
在开发中使用箭头函数前需要考虑函数中 this 的值,事件回调函数使用箭头函数时,this 为全局的 window,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数
解构赋值
数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
变量的顺序对应数组单元值的位置依次进行赋值操作
示例:
const [a,b,c] = [1,2,3]
//a = 1
//b = 2
//c = 3
交换2个变量:
let a = 1
let b = 3
[b,a]=[a,b]
//a = 3
//b = 1
数组开头的,前面有语句的 一定要注意前面得加分号
**变量多 单元值少的情况:**多余的变量将被赋值为undefined
**变量少 单元值多的情况:**多的值无意义
(可利用剩余参数解决变量少 单元值多的情况)
按需导入,可忽略某些值
const [a, , b, c]= [1,2,3,4]
//a = 1
//b = 3
//c = 4
支持多维数组
const [a, [b, c]] = [1, [2, 3]]
//a = 1
//b = 2
//c = 3
对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
基本语法:
-
赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
-
对象属性的值将被赋值给与属性名相同的变量
-
注意解构的变量名不要和外面的变量名冲突否则报错
-
对象中找不到与变量名一致的属性时变量值为 undefined
示例:
const user = {
name: '小明'
age: 18
}
const {name,age} = user
console.log(name) //小明
console.log(age) //18
给新的变量名赋值:
可以从一个对象中提取变量并同时修改新的变量名
示例:
const user = {
name: '小明'
age: 18
}
const {name: uname,age} = user
console.log(uname) //小明
console.log(age) //18
数组对象解构:
示例:
const user = [
{
name: '小明'
age: 18
}
]
const [{name,age}] = user
console.log(name,age)
多级对象解构:
示例:
const people = [
{
name: 'Lily'
family: {
mother: 'Lucy',
father: 'Jack',
sister: 'Julian'
},
age: 6
}
]
const [{name,family:{mother,father,sister}}] = people
console.log(name) //Lily
console.log(mother) //Lucy
console.log(father) //Jack
console.log(sister) //Julian
遍历数组:
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
被遍历的数组.foreach(function(当前数组元素,当前元素索引号){
//函数体
})
筛选数组:
筛选数组符合条件的元素,并返回筛选之后元素的新数组
语法:
被遍历的数组.filter(function(当前元素,当前元素索引号){
return 筛选条件
})
示例:
//筛选数组中大于30的元素
const score = [10,50,3,40,33]
const re = score.filter(function(item){
return item > 30
})
console.log(re) // [50,40,33]
深入对象
创建对象
-
利用对象字面量创建对象
const o = { name: 'zhangsan' }
-
利用
new Object
创建对象const o = new Object({name: 'zhangsan'})
-
利用构造函数创建对象
function User(name,age,gender){ this.name = name this.age = age this.gender = gender } const user = new User('zhangsan',18,'男')
成员
**实例成员:**通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
**静态成员:**构造函数的属性和方法被称为静态成员
内置构造函数
Object
Object 是内置的构造函数,用于创建普通对象。
const user = new Object({name: 'zhangsan', age: 18})
Object有三个常用静态方法
-
Object.keys(对象)
获得对象的所有键,并且返回一个数组
-
Object.values(对象)
获得对象的所有值,并且返回一个数组
-
Object.assign(接收对象,被拷贝的对象)
把后面的对象拷贝给前面的对象
属于是添加属性,不是覆盖
Array
Array 是内置的构造函数,用于创建数组
const arr = new Arr(3,5)
console.log(arr) //[3,5]
常见实例方法:
方法 | 作用 | 说明 |
---|---|---|
forEach | 遍历数组 | 不返回,不用于改变值,用于查找打印输出 |
filter | 过滤数组 | 筛选数组元素,并生成新数组 |
map | 迭代数组 | 返回新数组,新数组里面的元素是处理之后的值 |
reduce | 累计器 | 返回函数累计处理的结果,经常用于求和 |
reduce:
//基本语法
arr.reduce(function(){},起始值)
arr.reduce(function(累计值,当前元素,[,索引号][,源数组]){},起始值)
求和运算:
const arr = [1,5,9]
const count = arr.reduce((prev,item) => prev+item)
其他实例方法:
join
:数组元素拼接为字符串,返回字符串
find
:查找元素,返回符合测试条件的第一个数组元素值
every
:检测数组所有元素是否都符合指定条件,都通过返回true
some
:检测数组中的元素是否满足,有元素满足,返回true
concat
:合并两个数组,返回新数组
sort
:对原数组单元值排序
splice
:删除或替换原数组单元
reverse
:反转数组
findIndex
:查找元素的索引值
静态方法:
伪数组转换为真数组
Array.from()
String
实例方法:
split('分隔符')
:用来将字符串拆分成数组
substring(需要截取的第一个字符的索引[,结束的索引号])
:用于字符串截取
startWith(检测字符串[,检测位置索引号])
:检测是否以某个字符开头
includes(搜索的字符串[,检测位置索引号])
:判断一个字符串是否包含在另一个字符串中,根据情况返回true/false
toUpperCase
:用于将字母转换成大写
toLowercase
:用于将字母转换成小写
indexOf
:检测是否包含某字符
endWith
:检测是否以某字符结尾
replace
:用于替换字符串,支持正则匹配
match
:用于查找字符串,支持正则匹配
Number
内置构造函数,用于创建数值
常用方法:
toFixed()
设置保留小数位的长度
编程思想
面向过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
**优点:**性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。
**缺点:**没有面向对象易维护、易复用、易扩展
面向对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目
面向对象的特性:
- 封装性
- 继承性
- 多态性
**优点:**易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
**缺点:**性能比面向过程低
原型
构造函数通过原型分配的函数是所有对象所 共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
构造函数和原型对象中的this 都指向 实例化的对象
每个原型对象里面都有个constructor 属性(constructor 构造函数),该属性指向该原型对象的构造函数
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
对象都会有一个属性 __proto__
指向构造函数的 prototype 原型对象
继承
让子的prototype指向父,再让子的prototype的constructor指向自己就行
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
查找规则:
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__
指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
拷贝
无论是浅拷贝还是深拷贝都只针对引用类型
浅拷贝
拷贝的是地址
拷贝对象:Object.assign()
拷贝数组:Array.prototype.concat()
简单数据类型拷贝值,引用数据类型拷贝地址
引用类型单层对象拷贝没问题,多层就有问题
深拷贝
- 通过递归实现深拷贝
- js库
loadash
里面cloneDeep
内部实现了深拷贝 JSON.stringify()
实现
异常处理
throw抛异常
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
//抛出异常
throw new Error()
try-catch捕获异常
我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后
将预估可能发生错误的代码写在 try 代码段中
如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息
finally 不管是否有错误,都会执行
处理this
this指向
普通函数:谁调用函数,this指向谁
箭头函数:this会绑定外层this的值
改变this
-
call()
使用 call 方法调用函数,同时指定被调用函数中 this 的值
//thisArg,指定的this值 //arg函数参数 fn.call(thisArg,arg1,arg2,...)
-
apply()
使用 apply 方法调用函数,同时指定被调用函数中 this 的值
//thisArg,指定的this值 //argsArray:传递的值,必须包含在数组里面 //因此 apply 主要跟数组有关系,如使用 Math.max() 求数组的最大值 fn.apply(thisArg,[argArray])
-
bind()
bind() 方法不会调用函数。但是能改变函数内部this 指向
//返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数) fn.bind(thisArg,arg1,arg2,...)
性能优化
节流
连续触发事件但是在 n 秒中只执行一次函数
防抖
触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间