一、作用域
1、局部作用域
(1)函数作用域
(2)块作用域
let和const会产生块作用域 ,而var不会产生块作用域
2、全局作用域
script标签和js文件的【最外层】变量
3、作用域链
本质:底层的变量查找机制
4、JS垃圾回收机制(GC)
(1)内存的生命周期
- 内存分配
- 内存使用
- 内存回收
(2)算法说明
- 引用计数法:嵌套循环容易使内存泄漏
- 标记清除法:从全局出发,找不到就回收
5、闭包
闭包=内存函数+外层函数的变量
作用:外部访问局部变量、局部函数
function outer(){ let i=1 return function(){ console.log(i) } } const fun = outer() fun()
应用:实现数据的私有,外部无法直接修改变量
注:容易引起内存泄漏
6、变量提升
JS的缺陷(使用var的时候):代码执行前会将所有的var声明的变量提升到当前作用域的最前面,只提升变量声明,不提升变量声明——不建议
二、函数进阶
1、函数提升
2、函数参数
(1)动态参数(不推荐)
数组arguments:不管拿多少变量,都能获取到。
方法:不用形参,函数内部直接使用arguments
缺点:伪数组
(2)剩余参数(推荐)
arr获取的是多余的实参,是真数组,可以使用数组方法
function getSum(a,b,...arr)
...arr是展开数组
3、箭头参数
(1)特点
替代匿名函数
属于表达式函数,不存在函数提升
(2)基本语法
//基本写法 const fn = () =>{ } //只有一个形参的时候可以省略小括号 const fn= x => { } //只有一个形参的时候可以省略大括号 const fn= x => console.log(x) //只有一行代码可以省略return const fn= x => return x+x const fn= x => x+x //箭头函数可以直接返回一个对象 const fn= (unname) => ({uname:uname})
(3)箭头函数参数
普通函数有arguments,箭头函数没有
(4)箭头函数this
this指的是对象
箭头函数的this指向上一层作用域的this
三、解构赋值
1、数组解构
将数组单元值快速批量赋值给变量的简洁语法
const arr = [100,60,80] const [max,min,avg ] =arr 等同于 //const max=arr[0] //const min=arr[1] //const avg=arr[2]
典型应用:交换两个变量的值
let a=1 let b=2 ;[b,a]=[a,b] //这里必须加分号
补充:必须加;的情况
- 立即执行函数
(function(){})();
- 数组的时候:数组开头的
const str='pink'; [1,2,3].map(function(item)){}
2、对象解构
将对象属性和方法快速批量赋值给变量的简洁语法
(1)基本
const[{uname,age}]=obj
(2)多级对象
复合对象需要在前面加一个名字
const pig={ name:'佩奇' family:{ mother:'猪妈妈' father:'猪爸爸' sister:'乔治' } age:6 } const [{name,family:{mother,father,sister}}]=pig
四、数组forEach遍历和filter筛选
1、forEach语法
(1)语法
const arr=['red','green','blue'] arr.forEach(function(item,index){ console.log(item) console.log(index) })
(2)作用
主要遍历数组——加强版for循环
和map(返回数组)的区别,不返回值
2、filter方法
(1)语法
const arr=[10,20,30] arr.filter(funtion(item,index){ return item>=20 })
(2)特点
用于筛选数组
函数返回生成一个新数组
五、深入对象
1、创建对象三种方式
(1)通过字面量
const o={ name:'佩奇' }
(2)利用new Object创建对象
const o=new Object({name:'佩奇'}) console.log(o)
(3)利用构造函数创建对象
2、构造函数
(1)是什么
特殊函数,用于初始化对象
(2)语法
function pig(name){ this.name=name } const peppa=new Pig('佩奇') //实例化
3、实例成员&静态成员
(1)实例成员
构造函数创建的对象叫实例对象,实例对象的属性和方法为实例成员
(2)静态成员
构造函数的属性和方法被称为静态成员
- 静态成员只能构造函数来访问
- 静态方法中的this指向构造函数
4、内置构造函数
(1)Object
- Object.keys(o)
- Object.values(o)
- Object.assign(新对象,原对象)
const o={name:'佩奇',age:6} Object.keys(o) //返回数组['uname','age'] Object.values(o) //返回数组['佩奇','6']
(2)Array
- forEach
- filter
- map
- join
- reduce 返回累计处理结果,经常用于求和
arr.reduce(function(上一次值,当前值){},初始值)
如果需要累加的是对象数组,初始值必须写0
- find
找出符合的对象
const arr=[ {name:'小米',price:1999}, {name:'华为',price:3999} ] const mi=arr.find(function(item)){ return item.name==='华为' } //返回{name:'小米',price:1999}
- every
- some
- from
把伪数组转换成真数组,可使用pop、push等
- ...
(3)String
- length
- split('分隔符') 分割字符串
- substring(需要截取的第一个字符的索引[,结束的索引号]) 窃取字符串
- startwith(检测字符串[,检测位置索引号]) 从索引值搜索是否以目标字符串开头
- endwith(检测字符串[,检测位置索引号]) 从索引值搜索是否以目标字符串结束
- includes(搜索的字符串[,检测位置索引号]) 判断是否包含
- ...
(4)Number
- toFixed(保留位数) 让数字指定保留的位数,默认整数
六、深入面向对象
1、编程思想
- 面向过程:面向函数
- 面向对象
- 封装性
- 继承性
- 多态性
2、构造函数
优点好用,缺点浪费内存——e.g一个对象相同方法但是不等
this指向实例化对象
3、原型对象prototype
能够利用原型对象实现方法共享——原型对象的函数和构造函数都指向实例化对象(this)
公共属性写在构造函数里
公共方法写在原型对象里
function Star(uname,age){ this.uname=uname this.age=age } Star.prototype.sing=function(){}
4、 constructor属性
指向原型对象的构造函数
prototype和__proto__均有
5、对象原型_proto_
对象都有一个属性_proto_指向构造函数的prototype原型对象
- JS非标准属性
- [[prototype]]和__proto__意义相同
function Star(){} const ldh =new Star() console.log(ldh.__proto__ === Star.prototype) //true
6、原型继承
const person={ eyes:2, head:1 } function Woman(){ } Woman.prototype=person Woman.prototype.constructor=Woman
缺点:继承同一个person,新加方法会有问题
=>用构造函数的写法
7、原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起
什么是原型链?——一种查找规则:为对象成员查找机制提供一个方向
8、instanceof
作用:判断对象属不属于原型链上
const ldh=new person() console.log(ldh instanceof person) //true console.log(ldh instanceof Object) //true
七、 模态框封装(案例)
1、modalBox 模态框属性
2、(打开模态框)设置open挂载点,页面显示模态框是在open里面
3、(关闭模态框)在open中添加关闭的绑定事件close
注:BUG-会打开多个——先判断,有就移除,没有就添加
box&&box.remove() //逻辑中断,有box则后面不执行,无则执行box.remove()
八、深浅拷贝
1、深浅拷贝(只针对引用类型)
(1)浅拷贝——简单数据类型拷贝值,引用数据类型拷贝地址
- 拷贝对象:Object.assign(新对象,旧对象) | 展开对象{...obj}
- 缺点:复杂对象拷贝会出现问题
- 拷贝数组 :Array.prototype.concat() | 展开数组[...arr]
(2)深拷贝——拷贝的是对象不是地址
- 方法1:通过递归(=自己调自己)实现深拷贝
//递归函数 function getTime(){ document.querySelector('div').innerHTML= new Date().toLocalString() setTimeout(getTime,1000) } getTime()
//深拷贝的方法——做到新对象不会影像旧对象 //1、运用函数递归的方法 //2、如果是普通的,则直接赋值;遇到数组,需要再次调用递归函数。遇到对象,同样再次调用递归函数。 //3、先Array后对象 function deepCopy(newObj,oldObj){ for (let k in oldObj){ if (oldObj[k] instanceof Array){ newObj[k]=[] deepCopy(newObj[k],oldObj[k]) } else if(oldObj[k] instanceof Object){ newObj[k]={} deepCopy(newObj[k],oldObj[k]) } else{ newObj[k]=oldObj[k] } } }
- 方法2:利用lodash
//1、引用lodash.min.js <script src="./lodash.min.js"></script> //2、用_cloneDeep(拷贝对象)方法进行深拷贝 <script> const obj={ uname:'pink', age:18, hobby:['乒乓球','足球'], family:{ baby:'小pink' } } const o =_.cloneDeep(obj) </script>
- 方法3:利用JSON
//利用JSON.stringify()把JSON转换为字符串 //JSON.parse可以将字符串转换为对象 //因为通过对象转换成字符串再转为对象,和原来的对象已经不一样了,修改新对象不会被影响 const o=JSON.parse(JSON.stringify(obj))
九、 异常处理
1、throw 抛异常
//会自动抛出异常并中断程序 //经常配合Error使用 function counter(x,y){ if(!x || !y){ throw new Error("没有参数传递过来") } return x+y } counter()
2、try/catch/finally 捕获异常
//可能发生错误的代码,要写到try //拦截错误,提示浏览器提供的错误信息,但不中断程序的执行 function fn(){ try{ const p=document.querSelector('.p') p.style.color='red' } catch(err){ console.log(err.message) throw new Error("你看看,选择器错误了吧") } finally { //finally 不管你程序对不对,一定会执行的代码 alert("执行") } }
3、debugger
打断点——可以直接跳转到改行代码调试
十、处理this
1、this指向
(1)普通函数的指向:谁调用指向谁
//1、指向window (1) console.log(this) //指向window (2) function fn (){ console.log(this) } //指向window (3) setInterval(function(){ console.log(this) },1000) //指向window (4) <button>点击</button> document.querySelector('button').addEventLisener('click',function(){ console.log(this) }) //指向window //2、指向对象 const obj={ sayHI:function(){ console.log(this) } } obj.sayHI() //指向obj
(2)箭头函数的指向
与普通函数不同,箭头函数this不受调用方式的影响
箭头函数不存在this,沿用的是上一级的
- 不适用:构造函数,原型函数,dom事件函数等等
//箭头函数中断this为函数声明函数的this一致 const user={ name:'小明' walk:()=>{ console.log(this) } } user.walk() //指向window
!!!注:原型对象最好不 要用箭头函数,否则this不指向实例对象
2、改变this
(1)call() - 了解
调用函数,同事只当被调用函数的this的值
fn.call(thisArg,arg1,arg2,...)
const obj={ uname:'pink' } function fn (x,y){ console.log(this)//指向window console.log(x+y) } fn.call() //单纯调用fn,指向window fn.call(obj,1,2) //调用fn,改变this,指向obj
(2)apply() -理解
fn.call(thisArg,[argsArray]) 第二个参数必须是数组
const obj={ uname:'pink' } function fn (x,y){ console.log(this)//指向window console.log(x+y) } fn.call(obj,[1,2])//指向obj,打印3
使用场景:求数组的最大值
1、用数组遍历
2、扩展运算符
Math.max(...[arr])
3、用apply方法
Math.max/min.apply(Math/null,数组名)
//传统写法 const max = Math.max(1,2,3) console.log(max) //数组求最大值 const arr=[1,2,3] const max = Math.max.apply(Math,arr) const min = Math.min.apply(null,arr)
(3)bind()
call()、apply() 会调用函数,但bind()不会调用函数
会改变函数内部this指向
返回值是一个函数,这个函数的this是更改的obj
const obj={ age:18 } function fn(){ console.log(this) } const fun=fn.bind(obj) fun() //打印obj对象
应用:有一个按钮,点击立马禁用,2s后开启
<button>按钮</button> const btn=document.querySelector('.button') btn.addEventListener('click',function(){ this.disabled=true window.setTimeout(function(){ // this.disabled=false //无法打开按钮 //btn.disabled=false //可以打开按钮 },2000) //或者 window.setTimeout(function(){ // this.disabled=false //无法打开按钮 //btn.disabled=false //可以打开按钮 }.bind(btn),2000) //或者 window.setTimeout(function(){ // this.disabled=false //无法打开按钮 //btn.disabled=false //可以打开按钮 }.bind(this),2000) //上一层的this指向btn //或者 window.setTimeout(=>{ this.disabled=false //可以打开按钮,箭头函数没有this,沿用上一层的this },2000) //上一层的this指向btn })
(4)改变this的三种方法及区别(面试常考)
十一、性能优化
1、防抖 debounce
(1)特点
- 单位时间内,频繁触发事件,只执行最后一次
- 王者回城,只要被打断,就需要重新来
(2)语法:_.debounce(fun,时间)
(3)使用场景
- 搜索框搜索输入,只需要用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
(4)案例
要求:鼠标在盒子上移动,里面的数字就会变化+1
const box=document.querySelector('.box') let i=1 function mousemove(){ box.innerHTML=i++ } box.addEventListener('mousemove',mousemove) //数字变化很快
(改造:不是移动1像素就+1)
方法1、lodash提供的防抖函数 _.debounce(func,[wait=0],[option=])<script src="./lodash.min.js"></script> const box=document.querySelector('.box') let i=1 function mousemove(){ box.innerHTML=i++ } box.addEventListener('mousemove',_.debounce(mousemove,500))
方法2、手写一个防抖函数来处理(面试常考)
//核心:利用setTimeout进行实现 //1、声明定时器变量 //2、每次鼠标移动(事件触发)就判断是否有定时器,如果有就先清除定时器 //3、没有就开启定时器,存入到定时器变量里面 //4、定时器里面写函数调用 const box=document.querySelector('.box') let i=1 function mousemove(){ box.innerHTML=i++ } function(fn,t){ let timer //这里用return是因为要反复调用该函数 return function(){ if(timer) clearTimeout(timer) timer=setTimeout(function{ fn() },t) } } box.addEventListener('mousemove',_.debounce(mousemove,500))
2、节流
(1)特点
- 单位时间内,频繁触发事件,只执行一次
- 王者荣耀技能冷却,期间无法继续释放技能
(2)语法:_.throttle(fun,时间)
(3)使用场景
高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等
(4)案例
要求:鼠标再盒子上移动,不管移动多少次,每隔500ms才+1
方法1:使用lodash库实现节流,500ms之后采取+1
<script src="./lodash.min.js"></script> const box=document.querySelector('.box') let i=1 function mousemove(){ box.innerHTML=i++ } box.addEventListener('mousemove',_.throttle(mousemove,500))
方法2:手写节流函数
//核心:利用setTimeout进行实现 //1、声明定时器变量 //2、每次鼠标移动(事件触发)就判断是否有定时器,如果有就先清除定时器 //3、没有就开启定时器,存入到定时器变量里面 //4、定时器里面写函数调用 const box=document.querySelector('.box') let i=1 function mousemove(){ box.innerHTML=i++ } function(fn,t){ let timer //这里用return是因为要反复调用该函数 return function(){ if(!timer) { timer = setTimeout(function(){ fn() timer=null //在setTimeout函数中不能用clearTimeout(timer),因为定时器在运行 },t) } } box.addEventListener('mousemove',_.debounce(mousemove,500))