js
约 3752 字大约 13 分钟
2025-05-02
高阶js-作用域-函数-闭包
1. 作用域与作用域链
变量(函数名) 定义后在哪里用
定义: 标识符有效范围
标识符:变量 函数 形参 对象属性
作用域分类
全局: 在所以函数外面 js最顶层
局部: 在函数内部
块级(let const)
作用域链
逐级向上, 就近原则
2. ES6 函数新特性
1. 基础函数
函数定义
声明
表达式
函数参数
- 实参
- 形参
- 一般一一对应
函数参数列表
- 参数不定
- arguments(必须是声明式函数才有)---伪数组(有length)
- ...arr 展开运算符
函数参数默认值
函数调用方式
- 直接调用 fn()
- 对象里面方法 obj.fn()
- 回调 定义时不掉 后面调
- 自调 递归 必须有结束条件
2. ES6
ES6: 2025年 ECMAScipt发布
每年更新 版本加一
const fn = () => {
}
3. ES6 参数列表 【展开运算符】
真数组 参数列表
没有this arguments
const fn = (...arr) => {
}
let min = 2
let max = 3;//这里必须加分号不然他认为3[max, min] = [min, max],还有立即执行函数时也要
[max, min] = [min, max]
4. 闭包
在内部函数 使用外部函数变量 让外面变量不被垃圾回收
闭包的一般写法
外部函数包含内部函数,外部函数返回了内部函数
function fn(){ var num = 100; return function(){ num--; return num; } } fn()() // 两个函数多次执行 var fn2 = fn() fn2() // 多次执行里面函数
通过IIEF形成独立作用域
;(function(i){ i return '数据' })(100)
1 . 闭包-体验保管压岁钱
//将钱交给妈妈保存 function mom(){ var money = 500; // 提升到函数顶部 return function(num){ money -=num; return money } } //小源花钱 var spendMoney = mom(); // 存钱 console.log(spendMoney(100));// 花钱 console.log(spendMoney(50));
2. let 块级作用域
let {} 形成独立的作用域 相当于闭包
3. 函数柯里化
运用闭包 积简的 代码设计思路
将多个参数函数 分为几个函数 包起来
function sum(a, b, c) {
return a + b + c
}
const sum = a => b => c => a + b + c
4. 暂存死区
var 将当前变量提升到当前作用域最前面
let 不可在同一作用域下定义相同变量
let a = 1 console.log(a) // 1 function fn() { // 不知道是上面的a(上面违背就近原则) 还是下面的a(先定义后使用) 不可重复定义 console.log(a) // 报错 定义之后使用 let a = 2 conloe.log(a) // 2 }
面试题:什么是闭包
保护变量 不被全局污染
表现形式:外函数包内函数 外函数返回内函数 通过IIFE立即执行函数
解决问题 : ES5中 循环 + 事件监听 循环+ 定时器
缺陷: 保护变量 ,不被回收 ==》大量闭包 会内存泄漏 新数据加入不了
栈内存 (基本数据类型 ) 先进后出 堆内存(引用数据类型) 先进先出
- 多个dom事件绑定
const but = document.querySelectorAll('button')
for (var i = 0; i < but.length; i++) {
; (function (i) {
but[i].addEventListener('click', function () {
but[i].parentNode.remove()
})
})(i)
}
延时器输出
for (var i = 0; i < 5; i++) { ; (function (i) { setTimeout(function () { console.log(i) }, 1000) })(i) } for (var i = 0; i < 5; i++) { setTimeout(function (i) { console.log(i) }, 1000, i) }
ES5原型和原型链
1. 面向对象
面向过程: 更注重 事情发展的顺序,一步一步 流水线执行一个功能
面向对象:更注重结果 将不同事物封装 调用实现功能
类:描述具有相同属性的批量对象
对象:描述一个具象化事物
低耦合:功能功能之间尽量 封装成一个一个函数
高内聚: 同种功能尽量写在一个函数里面
1. 类表现形式
工厂 生产相同产品
只可以创建相同属性的对象
创建出来 对象 可以是不同对象
构造函数特征
函数名首字母大写 大驼峰
没有return
有属性和方法挂到this上
new 关键字调用 实例化
// Es5 //构造函数
function Phine(name, price, color) {
this.name = name
this.price = price
this.color = color
this.fn = function() {
}
}
const mi = new Phine('小米',)
方法过载【问题】内存溢出
没创建一个对象 里面的方法就会全部创建一次 方法不相等
解决方法 利用 原型对象
function Person(name, color, hight, wight, name) { this.name = name this.color = color this.hight = hight this.wight = wight } Person.prototype.eat = function () { console.log('吃饭') } Person.prototype.sleep = function () { console.log('睡觉') }
3. 原型空间
每一个构造函数都有一个原型空间
原型分为 显示原型 (prototype) 原型对象 里面有一个属性constructor 指向构造函数
每一个实例对象 都有 隐示原型(proto_) 对象原型

4. new 关键字的作用
实例化 创建对象
【面试题】
创建了一个空对象 let phone = {}
将空对象指向 this{}
在构造函数里加属性 自动return 出来 给实例化对象
this 指向 调用方的实例对象
5. 原型链
首先 在自己本身 找属性和方法
如果本身没有 再到原型空间 上查找
如果自己的 构造函数 的原型空间没有【原型空间里面有_proto__ 指向上一层原型空间】 则再向上面查找
直到找到Objec的原型空间 Object 里面的原型空间的_proto__ 指向null

6. this指向
- 全局this wimdown
函数【谁调用this指向谁】
- 一般函数 this wimdown
- 对象里面函数 this -->对象
- 箭头函数没有 this 上一级作用域的this
事件
- this --> 绑定的事件监听事件源 当前事件对象 【不和e.target一样】
构造函数
- this -> 调用者的实例对象
定时器 延时器
- windown 调用 this -> windown
8 改变this 指向
call【临时借用】
被借的对象.要借的函数.call(借给谁,参数1, 参数2)
**apply【**临时借用】
- 被借的对象.要借的函数.apply(借给谁,[参数1, 参数2])
bind 【永久借用】【返回函数】
- let fn = 被借的对象.要借的函数.bind(借给谁,参数1, 参数2)
- fn()
let stu = {
name: '00010',
study: function (address, book) {
return this.name + '在' + address + '学习' + book
}
}
let stu2 = {
name: '李四'
}
let stu3 = {
name: '王五'
}
let stu4 = {
name: '王麻子'
}
console.log(stu.study.call(stu2, '图书馆', '小红书'))
console.log(stu.study.apply(stu3, ['教室', '红楼梦']))
let fn = stu.study.bind(stu4, '图书馆', '小红书')
console.log(fn())

9 继承
子类可以使用父类的公共方法属性
人Preson
学生Student
1. 属性继承
// 在Student构造函数里面写
父类.call(this,参数1,参数2)
2. 原型方法继承
- 原型链继承
Student.prototype.__proyo__ = Preson.prototype
function Preson(name, age) {
this.name = name
this.age = age
this.SHi = function () {
console.log(this.name + '你好')
}
}
function Student(name, age) {
Preson.call(this, name, age)
this.son = function () {
console.log('唱歌')
}
Student.prototype.__proto__ = Preson.prototype
}
const stu = new Student('张三', 12)
console.log(stu)
stu.SHi()
- 原型链
子类.prototype.__proto__ = new 父类()
缺点: 子类的_proto__ 有父类的所以属性
组合式继承【constructor 指向会变】
将子类的prototype指向父类实例对象
// 需放在子类构造函数外面 子类.prototype = new 父类 // 需要修改 constructor的指向 子类.prototype.constructor = 子类
对象合并继承
Object.assign(子类.prototype,父类.prototype)
组合寄生继承
将父类的prototype合并到新的对象
再将新的对象给子类prototype
Student.proyotype = Object.create(父类.prototype) // 需要修改 constructor的指向 子类.prototype.constructor = 子类
10 eS6 继承
class Preson {
constructor(name, age) {
this.name = name
this.age = age
}
SHi() {
console.log(this.name + '你好')
}
}
class Student extends Preson {
constructor(nam, age, 参数3) {
super(nam, age) // 父类添加
this.参数3 = 参数3
}
son() {
console.log('唱歌')
}
}
const stu = new Student('张三', 12)
console.log(stu)
stu.SHi()

11 ES6语法糖
- 解构赋值
- 变量类型检测 type Object.proyotype.tostring() instanceof Array.isArray
- 深浅拷贝
- 模块化语法
- 异步代码 Promise
1 解构赋值
对象解构赋值
let user = { id: 1000, name: '张三', stu: { name: '张三' } } // 解构赋值 let {id, name: name11, stu:{name}} = user // name 改为name11
数组解构
//快速把数组里的值赋给一批变量 let arr = [1, 2, 3] let [a, , c] = arr // a === 1 c === 3 const arr1 = [1, 2, [3, 4]] const [a1, a2, [a3, a4]] = [1, 2, [3, 4]]
交换2个值
//交换2个值 let min = 2 let max = 3;//这里必须加分号不然他认为3[max, min] = [min, max],还有立即执行函数时也要 [max, min] = [min, max]
函数机构赋值
function sun(arr) { let [a, b, c] = arr return a + b + c } sum(100, 200, 300)
2. 展开运算符
讲一个数组或伪数组转换为一个列表
let arr = [1, 2, 3]
...arr // 1 2 3
合并多数组
let arr1 = [1, 2, 3] let arr2 = [4, 5, 6] let arr = [...arr1, ...arr2] // [1, 2, 3, 4, 5, 6]
伪数组 转真数组
[...伪数组] // 得到真数组
字符串转数组
let str = '123' // 方法 let arr = str.split('') let arr = [...str]
3. 变量类型检测
typrof
只可以区分基本数据类型 和引用数据类型
可以区分 number string boolean undefine function
不可以 null -> object NaN -> number
typeof 'aaa' //string typeof 123 // number
instanceof
就 [] {} 数组 的是 true
[] instanceof Object true
一般区分是否是引用数据类型 true 是 false 不是引用数据类型
一般用于 基本数据类型 通过new 才可以判断
可以区分数组
let s = {a: '123'} if(s instanceof Array)
变量 instanceof 构造函数名 // 是否时这个构造函数实例化出来的
万能检测方法 Object.proyotype.toString()
因为这个toString() 取 this来判断 不敢this指向 不然都是 Object
Object.prototype.toString.call(检测的变量).splice(8, -1) //Number Object.prototype.toString.apply(检测的变量)
let testType = (content) => { let type = Object.prototype.toString.call(content) return type.slice(8, -1) } console.log(testType(123)) console.log(testType('123')) console.log(testType(undefined)) console.log(testType(null)) console.log(testType(NaN)) console.log(testType(function () { })) console.log(testType(/^[1-9]$/)) console.log(testType({})) console.log(testType([])) Number String Undefined Null Number Function RegExp Object Array
Array.isArray判断数组
Array.isArray(obj) //是否是数组
12. 深浅拷贝
引用数据类型赋值 是赋值地址 在该其中一个值的时候 两个变量的值都会改
1. 浅拷贝
如果引用数据类型只有一层
let arr = [121, 52, 5]
利用for....in遍历每一个 放到新引用数据类型
let arr = [1, 2, 3, 4] let newArr = [] for (let key in arr) { newArr.push(arr[key]) } arr[0] = 999 console.log(arr) console.log(newArr) let obj = { nam: '张三', age: 2 } let newObj = [] for (let key in obj) { newObj[key] = obj[key] } obj.age = 999 console.log(obj) console.log(newObj)
对象 Object.assign()
let obj = { nam: '张三', age: 2 } let newObj = {} Object.assign(newObj, obj) obj.age = 999 console.log(obj) console.log(newObj)
展开运算法
let arr = [1, 2, 3] let neWArr = [...arr] let arr = {name:'张三'} let neWArr = {...arr}
2. 深拷贝
JSON转换
将数据放到栈里面,在放到堆里面
缺点
数据太大,会栈溢出
栈内存的性能要求高,不可存大批数据
JSON存有效数据 不可存 函数体 undefine转换会丢失 也会导致其他属性丢失
JSON.parse(JSON.stringify(arr))
递归 【完美】
一层一层循环 判断当前数据类型
如果不是引用数据 直接赋值
如果是引用数据类型 调用自己再循环
let obj = { name: '张三', age: 14, arr: [1, 2, 3], stu: { like: 12, id: '123', po: { name: 'wwww', age: 333 } }, fn: function () { }, // reg: /^[1-9]$/ } console.log(obj.reg) console.log('123'.constructor) // console.log(/^[1-9]$/ instanceof Object) let deepClone = (obj) => { if (obj === null || typeof obj !== 'object') return obj let newObj = Array.isArray(obj) ? [] : {} for (let key in obj) { if (obj[key] instanceof Object) { newObj[key] = deepClone(obj[key]) } else { newObj[key] = obj[key] } } return newObj } console.log(deepClone(obj))
lodash.js
const o = _.cloneDeep(obj)
13. Promise异步处理
js代码在执行过程在 会在后面执行 打破从上到下 从左到右的执行-----异步代码‘
- 定时器 延时器
- ajax
- 事件监听和执行函数
- Promise异步
1. 基础语法
- 3 个状态
// resolve 成功 fulfilled
// reject 失败 rejected // 不成功 不失败 就一直等待 pending
只会有一个结果
状态不可逆 等待---》成功 等待--> 失败
const p = new Promise((resolve, reject) => {
// 一段时间后
// 3 个状态
// resolve() 成功 状态改为成功
// reject() 失败 状态改为失败
// 不成功 不失败 就一直等待 pending
})
p.then(res => {
// 当状态为成功fulfilled 瞬间调用
}).catch(err=> {
// 当状态为失败rejected 瞬间调用
})
2. Promise解决问题
回调地狱
利用请求结果再次请求 包三层以上【代码可读性低】
let user1 = []
http.get(' http://8.137.157.16:9004/list', (data) => {
data.data.forEach(item => {
let id = {
id: item.id
}
http.post(`http://8.137.157.16:9004/info`, id, (res) => {
item.username = res.data.username
let id2 = {
id: item.id,
username: res.data.username
}
http.post(`http://8.137.157.16:9004/avatar`, id2, (res) => {
// item.username = res.data.username
user1.push(res.data)
getUser(user1)
})
})
})
})
3. Promise解决回调地狱
链式调用
p.then(res => {}).then(res=>{}).then(res=>{})
封装axios
/** * * http 模仿axios 封装ajax请求 * time 2025-3-4 * author x */ const http = (obj) => { const { url, method, data } = obj if (method === 'GET') { return http.get(url) } if (method === 'POST') { return http.post(url, data) } } /**@name 封装ajax的get请求 * * @url 完整路径请求地址 * @callbackFn 后端返回数据 * */ http.get = (url) => { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('get',url) xhr.send() xhr.onreadystatechange = () => { if (xhr.readyState === 4 && xhr.status === 200) { let res = JSON.parse(xhr.response) resolve(res) } } }) } /**@name 封装ajax的POST请求 * * @url 完整路径请求地址 * @formData JSON application/json * @callbackFn 后端返回数据 * */ http.post = (url, formData) => { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('post', url) let date = JSON.stringify(formData) // 设置请求头 xhr.setRequestHeader('Content-Type', 'application/json') xhr.send(date) xhr.onreadystatechange = () => { if (xhr.readyState === 4 && xhr.status === 200) { let res = JSON.parse(xhr.response) resolve(res) } } }) }, /**@name 封装ajax文件POST请求 * * @url 完整路径请求地址 * @formData file对象 * @callbackFn 后端返回数据 * */ http.upload = (url, formData) => { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('post', url) xhr.send(formData) xhr.onreadystatechange = () => { if (xhr.readyState === 4 && xhr.status === 200) { let res = JSON.parse(xhr.response) resolve(res) } } }) } window.$http = http
4. 异步转同步 async await
通过promise 使用异步代码
- async 必须写在函数小括号前面
- await 必须要 async 在异步代码前
- await 等待异步代码结束 返回成功结果
const getList = async () => { const res = await axios.get() }
try catch 异常捕获
解决 await 异常后续代码不执行 捕获异常
语法
try{
// 可能异常代码
}catch(err) {
}
5. promise内置静态方法
.finally 无论成功还是失败 都要执行
Promise.all()
将多个Promise 装到一直一起执行 ,结果一起返回
接口三 要接口一 和接口二 成功后执行
let p1 = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } }) let p2 = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } }) let p3 = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } }) let p4 = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } }) const p = Promise.all([p1, p2, p3, p4]) p.then(result => { console.log(result)//返回一个对象数组 }).catch(error => { console.log(error) })
没有Promise.all() 怎么解决
定义公共变量
接口1 + 1
接口2 +1
通过定时器 判断变量 ==2 马上调用接口三
Promise.race()
同时执行 Promise 返回一个结果
谁跑得快 就返回谁
14. 浏览器事件循环机制 event loop
同步异步的深入研究
1. Promise 是异步吗?
Promise 不是异步
.then() 方法是异步
2. 同步异步执行时机
先同步 后异步
3. 浏览器事件循环机制
代码执行顺序:
同步 :上到下 左到右
异步代码 :宏任务 微任务(Promise .then())
如果 微任务和宏任务 在同一异步层级 先执行微任务 再宏任务
面试题

