目录
一、作用域
含义:作用域就是规定变量可用被访问的范围
作用域分为局部作用域和全局作用域,局部作用域分函数作用域和块级作用域
1.1 函数作用域
- 变量只能在内部使用,外部不能使用
- 函数的参数也是内部的局部变量
- 不同函数内部变量不能相互访问
- 函数执行完后,内部变量实际被清空(垃圾回收机制)
1.2 块级作用域
- 在 { } 包含的就是块级作用域
- 代码块内部声明的变量外部将有可能被访问
- let 、const 声明的变量会产生块级作用域,var不会产生块级作用域
- 不同块级作用域之间的变量无法相互访问
1.3 全局作用域
含义:<script>标签和.js文件的最外层的就是全局作用域,在任何其他作用域都是可以访问的
- window 对象动态添加的属性默认是全局的,不推荐
- 函数中未使用任何关键字声明的变量为全局作用域,不推荐
- 尽可能少使用全局作用域,防止全局变量被污染
二、作用域链
作用域链的本质是底层的变量查找机制
在函数被执行时,会优先查找当前函数作用域中的变量
如果当前作用域查找不到会依次逐级查找父级作用域直到全局作用域
总结
- 嵌套关系的作用域串联形成了作用域链
- 相同的作用域链按从小到大的规则查找变量
- 子作用域能访问父作用域,父作用域无法访问子作用域
三、垃圾回收机制
JS 中内存的分配和回收都是自动完成的,内存不使用时会被垃圾回收机器自动回收
内存的生命周期
- 内存分配:声明变量、函数、对象时对自动分配内存
- 内存使用:使用了变量函数
- 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
注意
- 全局变量不会被回收(关闭页面回收)
- 局部变量的值,不用了会被自动回收
- 内存泄露:分配的内存因为特殊原因未被释放或者无法释放
垃圾回收机制-算法
栈:由操作系统自动分配和释放的,如参数值、局部变量,基本数据类型在栈里
堆:一般由程序员释放,若程序员不释放,又垃圾回收机制回收,复杂数据类型放在堆里
浏览器中垃圾回收机制有两种算法:引用计数法和标记清除法
3.1 引用计数法
IE 采用引用计数法,定义‘内存不再使用’,看的对象是否有指向它的引用,没有就会被回收
算法
- 跟踪记录被引用的次数
- 如果引用一次,记录次数加一;减少引用一个减一
- 如果引用次数是 0 ,则释放内存
注意:存在嵌套引用(循环引用)的问题:两个对象相互引用,会照成大量内存泄露问题
3.2 标记清除法(常用)
现在浏览器大多采用标记清除法,核心是“无法到达的对象”,
算法
- 从根部(全局对象)出发定时扫描内存中的对象
- 凡是从根部的查找不到的,就会被回收
四、闭包
概念:内层函数可以访问外层函数的变量形成了闭包
作用:私有化数据,外部可以访问函数内部的变量
应用:实现数据的私有化,如统计话术调用次数,函数调用一次,就++
存在内存泄露的问题:全局作用域可以调用闭包中外部函数,它的返回值是内部函数,进而访问内部函数,通过内部函数访问到变量,因此标记清除法无法清除闭包中的变量
解决闭包内存泄露问题:
1. 闭包使用完后释放,设全局作用域下调用闭包函数为null,如fun = null
function count() {
let i = 0
function fn() {
i++;
console.log("调用了"+i+"次");
}
return fn
}
const fun = count();
fun()
五、变量提升
含义:运行变量声明之前被访问(仅存在var声明变量),只提升声明不提升赋值!
console.log(c); //变量提升 undefined
var c = 10;
- 变量未声明被访问会报语法错误
- let/const 不存在变量提升
- 变量提升出现在相同作用域中
- 不建议使用var声明
六、函数进阶
6.1 函数提升
函数会把函数声明提升放在当前作用域最前面
- 只提升函数声明,不提升函数调用
- 函数提升优于变量提升
- 函数表达式必须先声明和赋值,后调用不然会报错
- 函数提升出现在相同的作用域
fun();// 函数提升,会报错,只提升声明,不提升调用
var fun = function(){
console.log("函数");
}
6.2 函数参数
函数参数有动态参数、剩余参数
动态参数:
arguments 只存在函数内部内置的伪数组变量,包含了所有的函数的参数
function getSum(){
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
console.log(sum);
}
getSum(2,1,4)
剩余参数
允许将一个不定数量的参数表示一个数组
- 特点是未与形参之前,用于获取多余的实参,getSum(a,b,...arr)
- 借助...获取的是剩余实参,是个真数组,可以使用pop、shift方法
- 实际开发中,提倡建议用剩余参数
- 使用场景:获取多余参数
- 与动态参数区别:动态参数是伪数组,剩余参数是真数组
function getSum(...arr){
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
console.log(sum);
}
getSum(2,1,4)
6.3 拓展:展开运算符...
含义:就是将数组进行展开,不会修改原数组
使用场景:求数组最大值最小值,合并数组
剩余参数中... 和展开运算符的区别...
- 剩余参数:只在函数中使用,得到真数组
- 展开运算符:在数组中使用,用于数组展开
let arr = [1,3,1,4,5]
let arr2 = [9,42]
let max = Math.max(...arr)
let min = Math.min(...arr)
let merger = [...arr,...arr2]
console.log(merger); //[1, 3, 1, 4, 5, 9, 42]
七、箭头函数
7.1 基本语法
目的:更简短的书写函数且不绑定this,比函数表达式更简洁
使用场景:适用于需要匿名函数的地方
只有一个形参可以省略小括号;let fn = x=>{x+x}
只有一行代码可以省略大括号,且可以省略return; let fn = x => x+x
箭头函数直接返回一个对象
7.2 箭头函数参数
普通函数里面有arguments动态参数,箭头函数没有动态参数,只有剩余参数..args
const getSum = (...arr) => {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
}
getSum(42,1)
7.3 箭头函数 this 指向
含义:箭头函数没有自己的 this,this指向自己作用域链的上一层 this
- 普通函数 this 指向调用者
- 箭头函数 this 指向自己作用域链的上一层 this
- 在dom 事件回调函数为了简便,不推荐使用箭头函数
const fn = () =>{
console.log(this); //上层作用域的this,在这里即window
}
const obj = {
name: 'lisa',
syaHi: ()=>{
console.log(this); //指向上层作用域obj的this,即window
},
sayHello: function(){
console.log(this); // 普通函数指向调用者,obj调用了sayHello,this指向obj
}
}
八、解构赋值
8.1 数组解构
含义:将数组单元值快速批量赋值给一系列变量
基本语法:典型应用交互2个变量
const [max,min,avg] = [100,0,50]
console.log(max);
// 应用交换两个变量
let a = 1;
let b = 2;
[a,b] = [b,a]
//变量多,单元值少
const [c,d,f] = [12,3];
console.log(f); // undefined
//变量少,单元值多
const [ e,...arr] = [1,2,3,4];
console.log(arr); // [2,3,4] 真数组
// 设置默认值
const [c = 0,d = 0,f = 0] = [12,3];
// 支持多维数组
const [a,b] = [1,[1,2]];
8.2 对象结构
- 要求属性名和变量名一致
- 变量名可以修改,旧变量:新变量 {name: n} = obj ,将name改为n
- 可结构数组对象
const obj = {
name: 'lisa',
age: 18
}
const {name, age, gender} = obj;
console.log(name); // lisa
console.log(gender); // underfined
// 修改变量名
let {name :n, age: a, gender:g} = obj;
console.log(a); // lisa
// 结构数组对象
const arr = [
{
uname: 'lisa',
uage: 18
}
]
let [{uname,uage}] = arr
九 、forEach 方法
含义:用于遍历数组中的每个元素
用法: arr.forEach((item,index)=>{ })
与map区别:map 有返回值,返回一个数组,forEach不会返回值
十、构造函数
构造函数:是一种特殊的函数,用来初始化对象
使用场景:通过构造函数来快速创建多个不同的对象
规范:
- 命名以大写字母开头的
- 只能由new操作符来执行
- 使用new 关键字调用函数的行为称为实例化
- 当实例化构造函数时没有参数可以省略()
- 构造函数无序写return,返回值即创建新的对象
- 构造函数内部的return返回的值无效,所以不要写return
10.1 new 实例化执行的过程
- 首先创建一个 空对象
- 构造函数中的 this 指向这个对象
- 执行构造函数代码,修改this,添加新的属性
- 返回新的对象
10.2 实例成员和静态成员
实例成员:实例对象上的属性和方法,结构相同但是值不同的对象,实例对象是彼此相互独立的
静态成员:构造函数的属性和方法称为静态成员,只能通过构造函数访问,静态方法中this指向构造函数
function Star(name, age) {
this.name = name;
this.age = age
}
// 实例成员
const s1 = new Star('lisa', 18);
const s2 = new Star('rose', 19);
console.log("s1", s1);
// 静态成员: 构造函数的属性和方法
Star.say = function () {
console.log("hi");
}
console.log("say", Star.say);
10.3 内置构造函数
内置构造函数有 object、array、string、number
引用数据类型:Object、Array、Date、RegExp
包装类型:String、Number、Boolean
(1)Object 内置构造函数
- Object.keys() 获取对象中的全部属性;返回数组形式的属性
- Object.values() 获取对象中的全部值;返回数组形式的值
- Object.Assign() 浅拷贝对象,常用于追加属性值
const people = {
name: 'lisa',
age: 18
}
console.log(Object.keys(people)); //['name', 'age']
console.log(Object.values(people)); //['name', 'age']
const p1 = Object.assign(people,{gender: 'girl'})
console.log(p1);
(2) Aaary 内置构造函数
- forEach:遍历数组,不返回数组,查找遍历数组
- filter:过滤数组,返回新的数组
- map:迭代数组,返回新数组,返回处理后的数组
- redece:累计器,常用于求和
let arr = [1,2,4]
// 无初始值
let count = arr.reduce((prev,cur)=> prev + cur,0)
console.log(count);
// 有初始值 200
let count2 = arr.reduce((prev,current) => prev + current, 200)
console.log(count2);
reduce 执行过程:
- 如果无初始值,则上一次值 = 数组的第一个数组元素的值
- 每一次,返回值给下一次循环的上一次的值
- 如果有初始值,则起始值 = 上一次值
数组常见方法(高阶函数)
- join:数组拼接字符串
- toString:字符串转数组
- find:查找元素
- every:检测数组所有元素是否符合指定条件,如果全部元素满足返回true,否则返回false
- some:检测数组中元素是否满足条件,如果有一个元素满足返回true,否则返回false
- concat:合并数组
- sort:原数组排序
- splice:删除或者替换数组元素
- reverse:翻转数组
- findIndex:查找元素的索引值
(3)String 内置方法
常见的实例方法
- length:获取字符串长度
- split:分隔字符串
- substring:字符串截取
- includes:判断字符串是否包含另一个字符
- toUpperCase:转大写
- toLowderCase:转小写
- indexOf:检测是否包含字符
- replace:替换字符
- match:查找字符串,支持正则匹配
- startWith:判断是不是以某字符开头
- endWith:判断是不是以某字符结尾
十一、面向对象
10.1 编程思想
(1)面向过程
含义:分析解决问题的步骤,然后用函数一个个实现,使用时依次调用
优点:性能高、适合硬件联系紧密的东西,如单片机
缺点:不易维护、不易复用、不易拓展
(2)面向对象
含义:把事务分成一个个对象,由对象之间的分工与合作;面向对象是以对象功能划分的,而不是步骤
优点:编程灵活;代码可复用;容易维护和开发;适合打下项目
缺点:性能比面向过程低一点
特征:封装性、继承性、多态性
实际开发中,面向过程更多!
10.2 构造函数
面向对象主要通过构造函数实现
构造函数封装存在问题:存在浪费内存问题
10.3 原型对象prototype
含义:每个构造函数都有一个 prototype 对象,这个可以挂载函数,这个就叫原型对象
作用:对象实例化不会多次创建原型上的函数,节约内存;实现方法共享
this指向:构造函数和原型对象中的 this 都指向实例化对象
function Star(name,age) {
this.name = name;
this.age = this.age;
}
// 原型对象
Star.prototype.SayHi = function(){
console.log("hi!");
}
10.4 constructor 属性
含义:每个原型对象都有一个constructor属性
作用:指向该原型对象的构造函数;Star.ptototype.constructor === Star
在原型上和实例对象上都有constructor属性
10.5 _proto_对象原型
__proto__里面有一个constructor,指向实例对象
实例对象有一个属性__proto__指向构造函数的prototype原型对象
function Star(name,age) {
this.name = name;
this.age = this.age;
}
// 原型对象
Star.prototype.SayHi = function(){
console.log("hi!");
}
let s1 = new Star('lisa',18)
console.log(s1.__proto__ === Star.prototype); //true
10.6 原型继承
const Person = {
eyes: 2,
head: 1
}
function Star(name,age) {
this.name = name;
this.age = this.age;
}
// 原型继承
Star.prototype = new Person()
10.7 原型链
含义:本质事原型上的查找规则,当访问当前实例上是否有这个属性,没有就依次逐级查找上一层原型,一直到object,如果没有则返回null;
可以使用instanceof 运算符检测构造函数的 prototype属性 是否在实例对象的原型链上
只要是对象就有__proto__,只要是__proto__就有constructor
十一、浅拷贝
含义:只针对引用数据类型,拷贝的是地址;修改和创建对象时被改变原对象;如果是简单数据类型拷贝值;引用数据类型拷贝的是地址
- 拷贝对象:Object.assign()/ {..obj}
- 拷贝数组:Array.prototype.concat() / [...arr]
赋值和浅拷贝区别:
赋值:只要是对象都会相互影响
浅拷贝:如果是一层对象不会影响,多层对象会互相影响
十二、深拷贝
深拷贝:拷贝的是对象,不是值
常见方法
- 递归
- lodash/ cloneDeep
- json.stringify()
递归:函数内部可以调用本身,这个函数就是递归函数
const Person = {
eyes: 2,
head: 1
}
const lisa = {}
function deelCopy(newObj,old){
for (const k in object) {
// 深拷贝,一定要先写数组,再写对象
if (old[k] instanceof Array) {
newObj[k] = []
deelCopy(newObj[k],old[k])
}else if(old[k] instanceof Array) {
newObj[k] = {}
deelCopy(newObj[k],old[k])
}else{
// 浅拷贝
newObj[k] = old[k]
}
}
}
deelCopy(lisa,Person)
const Person = {
eyes: 2,
head: 1
}
const lisa = JSON.parse(JSON.stringify(Person))
console.log(lisa);
十三、异常处理
13.1 throw 抛出异常
指的是代码执行过程中可能发送的错误,然后最大程度的避免错误的发生导致整个程序无法运行
- throw 抛出异常,程序会终止
- throw 后面跟的是错误信息提示
- Error 对象配合 throw 使用,能设置更详细的错误信息
function fn(x){
if(!x){
throw new Error("参数不能为空")
}
return 1
}
fn(); //Uncaught Error: 参数不能为空
13.2 try/ catch 捕获异常
捕获错误信息(浏览器提供)
function fn(){
try {
// 可能出现错误的代码
const p = document.querySelector('.div');
p.style.color = 'red'
}catch(err) {
// 拦截错误,不中断程序执行
console.log(err.message);
// 可加return 中断程序
}finally {
// 无论程序对不对都会执行的代码
}
}
fn();
十四、处理this
14.1 this指向
(1)普通函数this指向
- this指向函数调用者
- 没有明确的调用者指向window,严格模型下指向undefined
(2)箭头函数this指向
- 函数内不存在this,用上一级的this,向外层作用域查找
- this 指向最近一层作用域中的this指向
14.2 改变this
(1)bind
不是立即执行函数
语法:fn.call ( this, arg1,arg2,...)
(2)call
立即执行函数
语法:fn.call ( this, arg1,arg2,...)
const obj = {
name: 'rose'
}
function fn(x,y){
console.log(this);
}
fn.call(obj,1,2)
(3)apply
立即执行函数
语法:fn.call ( this, argArray)
使用场景
- bind:不调用函数,改变this执行,如改变定时器内部this指向
- call:立即调用函数改变this执行
- apply:常跟数组有关,借助数组对象实现最大值最小值
十五、性能处理
15.1 防抖 debounce
含义:在单位时间内触发事件,只执行最后一次
场景:搜索框的输入;手机号验证码输入检测
思路:核心是setTimerout定时器实现
- 声明一个定时器变量
- 判断是否有定时器变量,如果有先清空定时器
- 没有则开启定时器,存储到变量里
- 定时器里面调用执行函数
function debounce(fn,delay){
let timer;
return function(...args){
if (timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(this,args)
}, delay)
}
}
}
15.2 节流 throttle
含义:在单位时间内,频繁触发事件,只执行一次,隔多长时间内执行一次
场景:鼠标移动、页面缩放、滚动条滚动
思路:
- 声明一个定时器变量
- 判断是否有定时器,如果有就不开启新的定时器
- 如果没有则开启新的定时器,记得保存到变量里
function throttle(fn, delay) {
let timer;
return function (...args) {
if (!timer) {
fn.apply(this,args)
timer = setTimeout(()=>{
// 清空定时器
timer = null;
}, delay)
}
}
}