Skip to content

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 使用异步代码

    1. async 必须写在函数小括号前面
    2. await 必须要 async 在异步代码前
    3. 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())

如果 微任务和宏任务 在同一异步层级 先执行微任务 再宏任务

面试题