面试题--JS

1. 函数传参是值传递, 还是引用传递?

  • 函数调用时, 是将实参变量的数据拷贝一份赋值给形参变量
  • 只是实参变量数据可能是基本类型值 ==> 值传递
  • 也可能是引用类型的值(也就是地址值) ==> 引用传递/值传递
  • 注意下面的代码, 准确的说不是将a内存的地址赋值给b, 而是将a中保存的地址值赋值给b
    var a = {}
    var b = a
    

2. 说说你对作用域与作用域链的理解

  • 作用域

    • 一个变量可以合法使用的范围/区域
    • 作用域起到了隔离变量, 避免了变量重名冲突的问题(也就是允许了不同作用域中可以有同名的变量)
    • 分类:
      • 全局作用域
      • 函数作用域
      • 块作用域 => ES6的let或const变量
  • 作用域链

    • 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
    • 本质: 包含由内向外的多个变量对象的数组 ==> 这个可以不用说
    • 当查找一个变量, 在整个作用域链中都找不到时: 会报引用错误(RefrenceError), 错误信息(message)为这个变量没有定义

3. 说说变量提升与函数提升

  • 变量提升
    • 变量声明提升: 变量的声明部分被提升到了作用域的最前面执行 ==> 预解析
    • 结果/现象: 在变量声明语句前, 就可以访问这个变量, 只是值为undefined
  • 函数提升
    • 函数声明提升: 函数声明语句被提升到了作用域的最前面执行 ==> 预解析
    • 结果/现象: 在函数声明语句前, 就可以调用这个函数了
  • let/const变量没有变量提升

4. 区分执行函数定义与执行函数

  • 执行函数定义: 创建函数对象, 如果指定了函数名, 同时会定义变量并指向这个函数对象
  • 执行函数: 执行函数内部的语句
  • 必须先执行函数定义, 再执行函数 ===> 注意: 函数定义有可能会提升到最上面执行

5. 说说你对闭包的理解

  • 是什么?

    • 通过chrome的debugger调试工具得知: 闭包本质是内部函数中的一个容器(非js对象), 这个容器中包含了引用的变量
  • 如何产生?

    • 嵌套的内部函数引用了外部函数的变量, 当调用外部时就会产生闭包
    • 闭包不是在调用内部函数时产生, 而是在创建内部函数对象时产生
  • 作用?

    • 延长局部变量的生命周期
    • 让函数外部能间接操作内部的局部变量
  • 区别产生闭包与使用闭包及释放闭包?

    • 产生闭包: 内部函数对象创建时产生, 包含那个被引用的变量的容器(不是js对象)
    • 使用闭包: 执行内部函数
    • 释放闭包: 让内部函数对象成为垃圾对象, 断开指向它的所有引用
  • 应用?

    • 举删除删除列表中的的某个商品的例子(带确定框)
    • IIFE
    • 模块编译之后的运行代码
  • 写一个简单的闭包程序

    function fn1() {
      var a = 2;
      var b = 3;
      function fn2() {
        a++;
        console.log(a);
      }
     return fn2;
    }
    var f = fn1();
    f();
    f();
    f = null;
    

6. 如何判断函数中的this?

  • 常规情况下, 函数中的this取决于执行函数的方式
    • fn(): 直接调用 ==> this是? window
    • new fn(): new调用 ==> this是? 新建的对象
    • obj.fn(): 通过对象调用 ==> this是? obj
    • fn.call/apply(obj): 通过函数对象的call/apply来调用 ==> this是? obj
    • bind(obj)返回的函数 ==> this是? obj
  • 特殊情况:
    • 箭头函数 > this是? 外部作用域的this》 沿着作用域链去外部找this
    • 回调函数 它不是我们调用的
      • 定时器/ajax/promise/数组遍历相关方法回调 ==> this是? window
      • DOM事件监听回调 ==> 发生事件的DOM元素
      • vue控制的回调函数(生命周期/methods/watch/computed) ==> this是? 组件的实例
      • React控制的生命周期回调, 事件监听回调 ==> this是? 组件对象 / undefined

7. 如何改变(指定)函数中的this?

  • 情况一: 将任意对象指定为当前函数中的this
    • 函数立即调用: call() / apply()
    • 函数后面某个时候调用: bind()
  • 情况二: 将外部的this指定为当前函数中的this
    • ES6之前: 将外部this保存为其它名称变量, 当前函数中不使用this, 而使用这个变量
    • ES6之后: 箭头函数

8. 说说原型与原型链

  • 原型

    • 每个函数都有一个显式原型属性: prototype
    • 每个实例都有一个隐式原型属性: __proto__
    • 实例的__proto__与对应函数的prototype都指向原型对象
    • 函数的默认的原型对象是Object的实例, 也就是一个{}
    • 原型对象上有一个constructor属性指向对应的构造函数

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZtCL10Zn-1676984567660)(./images/显示原型与隐式原型.png)]

  • 原型链

    • 从对象的__proto__开始, 连接的所有对象。也可称为“隐式原型链”

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9bvteIF-1676984567665)(./images/image-20201113231139677.png)]

    • 原型链的作用:

      • 在查找对象属性或调用对象方法时, 会先在对象自身上查找, 找不到就会沿着原型链查找
      • 利用原型链可以实现继承

9. instanceof的内部原理和自定义实现

  • 作用:

    • 判断一个任意类型对象的具体类型
  • instanceof内部如何判断?

    • 对于 A instanceof B
    • A是实例对象, B是构造函数
    • 如果B的prototype属性所指向的原型对象是A实例对象的原型链接上的某个对象, 返回true, 否则返回false
  • 原型链详图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jL6aIXWJ-1676984567667)(./images/image-20211113081655048.png)]

  • 自定义instanceof功能函数

    /* 
    自定义instanceof工具函数: 
      语法: myInstanceOf(obj, Type)
      功能: 判断obj是否是Type类型的实例
      实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回true, 否则返回false
    */
    function myInstanceOf(obj, Type) {
      // 得到原型对象
      let protoObj = obj.__proto__
    
      // 只要原型对象存在
      while(protoObj) {
        // 如果原型对象是Type的原型对象, 返回true
        if (protoObj === Type.prototype) {
          return true
        }
        // 指定原型对象的原型对象
        protoObj = protoObj.__proto__
      }
    
      return false
    }
    

10. 编码实现继承

  • 基于构造函数的继承

    • 原型链 + 借用构造函数的组合式继承
      • 让子类的原型为父类的实例: Student.prototype = new Person()
      • 让子类型原型的构造器为子类型: Student.prototype.constructor = Student
      • 借用父类型构造函数: Person.call(this, name, age)
    // 父类型
    function Person(name, age) {
        this.name = name
        this.age = age
    }
    
    Person.prototype.fn = function () {}
    Person.prototype.sayHello = function () {
    	console.log(`我叫${this.name}, 年方${this.age}`)
    }
    // 子类型
    function Student(name, age, price) {
        // this.name = name
        // this.age = age
        // 借用父类型的构造函数
        Person.call(this, name, age)  // 相当于执行this.Person(name, age)
        this.price = price
    }
    // 让子类的原型为父类的实例
    Student.prototype = new Person()
    // 让原型对象的构造器为子类型
    Student.prototype.constructor = Student
    // 重写方法
    Student.prototype.sayHello = function () {
    	console.log(`我叫${this.name}, 年方${this.age}, 身价: ${this.price}`)
    }
    
    const s = new Student('tom', 23, 14000)
    s.sayHello()
    s.fn()
    
  • 基于ES6的类的继承

    • 子类 extends 父类: class Teacher extends Person2
    • 子类构造器中调用父类的构造: super(name, age)
    // 父类
    class Person2 {
        constructor (name, age) {
            this.name = name
            this.age = age
        }
    
        fn () {}
    
        sayHello () {
        	console.log(`我叫${this.name}, 年方${this.age}`)
        }
    }
    // 子类
    class Teacher extends Person2 {
        constructor (name, age, course) {
            super(name, age)
            this.course = course
        }
    
        // 重写父类的方法
        sayHello () {
        	console.log(`我叫${this.name}, 年方${this.age}, 课程:${this.course}`)
        }
    }
    
    const t = new Teacher('bb', 34, 'CC')
    t.sayHello()
    t.fn()
    

11. 说说面向对象的三大特征

  • 封装:

    • 将可复用的代码用一个结构包装起来, 后面可以反复使用
    • js的哪些语法体现了封装性: 函数 ==> 对象 ==> 模块 ==> 组件 ==> 库
    • 封装都要有个特点: 不需要外部看到的必须隐藏起来, 只向外部暴露想让外部使用的功能或数据
  • 继承

    • 为什么要有继承? 复用代码, 从而减少编码
    • js中的继承都是基于原型的继承: ES6的类本质也是
    • 编码实现: 原型链+借用构造函数的组合 / ES6的类继承
  • 多态: 多种形态

    • 理解
      • 声明时指定一个类型对象, 并调用其方法,
      • 实际使用时可以指定任意子类型对象, 运行的方法就是当前子类型对象的方法
    • JS中有多态:
      • 由于JS是弱类型语言, 在声明时都不用指定类型
      • 在使用时可以指定任意类型的数据 ==> 这已经就是多态的体现了

12. 说说JS的垃圾回收机制

  • 在JS中对象的释放(回收)是靠浏览器中的垃圾回收器来回收处理的
  • 垃圾回调器
    • 浏览器中有个专门的线程, 它每隔很短的时间就会运行一次
    • 主要工作:判断一个对象是否是垃圾对象, 如果是, 清除其内存数据,并标记内存是空闲状态
  • 如何判断对象是垃圾对象呢?
    • 机制1:引用计数法
    • 机制2:标记清除法
  • 垃圾回收机制1:引用计数法
    • 最初级的垃圾收集算法,现在都不用了,把“对象是否不再需要”简化定义为“对象有没有引用指向它“
    • 每个对象内部都标记一下引用它的总个数, 如果引用个数为0时,即为垃圾对象
    • 有循环引用问题:如果2个对象内部存在相互引用,断开对象的引用后, 它们还不是垃圾对象
  • 垃圾回收机制2:标记-清除法
    • 当前垃圾回收算法的基础, 它“对象是否不再需要”简化定义为“对象是否可以获得”
    • 从根对象(也就是window)开始查找所有引用的对象, 并标记为‘使用中’,没有标记为使用中的对象就是垃圾对象
    • 没有循环引用问题

13. 区别一下内存溢出与内存泄漏

  • 内存溢出

    • 运行程序需要分配的内存超过了系统能给你分配的最大剩余内存

    • 抛出内存溢出的错误,程序中断运行

    • 演示代码

      const arr = []
      for (let index = 0; index < 100000000; index++) {
      	arr[index] = new Array(1000)
      }
      
  • 内存泄漏

    • 理解: 当程序中的某个内存数据不再需要使用, 而由于某种原因, 没有被释放

    • 常见情况:

      • 意外的全局变量

        function fn () {a = new Array(100000)}
        fn()
        
      • 没有及时清除的定时器

    this.intervalId = setInterval(() => {}, 1000)
    // clearInterval(this.intervalId)
    ```

    • 没有及时解绑的监听

    this. b u s . bus. bus.on(‘xxx’, this.handle)
    // this. b u s . bus. bus.off(‘xxx’)
    ```

    • 没有及时释放的闭包

14. 区别一下同步与异步

  • 同步:
    • 从上往下按顺序依次执行, 只有将这个任务完全执行完后, 才执行后面的
    • 会阻塞后面的代码执行
    • 同步回调: 只有执行完回调函数后任务的函数才结束
  • 异步
    • 启动任务后, 立即向下继续执行, 等同步代码执行完后才执行回调函数
    • 不会阻塞后面的代码执行
    • 异步回调函数即使触发了, 也是要先放入队列中待执行, 只有当同步任务或前面的异步任务执行完才执行

15. 说一下JS的事件循环机制

  • JS内部是通过事件循环机制来实现单线程异步执行执行的
  • 分析事件循环机制
    • 所有任务(同步/异步)都在主线程上执行,形成一个执行栈
    • 执行栈之外有用于存储待执行异步回调的任务队列(task queue) ==> 准备的说是2个(宏列队与微队列)
    • 浏览器中有在其它分线程执行相关管理模块
      • 定时器管理模块
      • ajax请求管理模块
      • DOM事件管理模块
    • JS引擎执行代码的顺序为
      • 在执行栈中执行初始化同步代码(也就是script宏任务)
      • 执行过程中如果有启动异步任务, 交给对应的管理模块处理, 管理模块会在后面特定时间将回调函数放入任务队列中待执行
      • 在执行栈中所有代码都执行完后, 依次取出任务队列中的回调到执行栈中依次执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VJ7SplP-1676984567669)(file://D:\work\毕业精讲-code\note\images\event loop(宏任务与微任务)].png?lastModify=1658125218)

  • 宏任务与微任务

    • script(整体代码)
    • setTimeout / setInterval,
    • Ajax
    • DOM事件监听
    • postMessage (H5, 向其它窗口分发异步消息)
    • setImmediate(Node.js 环境)
  • 宏队列与微队列

    • Promise
    • async & await
    • mutationobserver(H5, 监视DOM元素变化)
  • 整体的执行顺序

    1. script(整体代码)

    2. 所有微队列中的微任务

    3. 宏队列中的第一个宏任务

    4. 所有微队列中的微任务

      后面3-4循环处理

16. 说说ES6的promise

  • ES6推出的更好的异步编程解决方案(相对于纯回调的方式)
    • 解决嵌套回调的回调地狱问题 ==> 通过promise.then的链式调用
    • 内部使用的是微任务, 效率更高
    • 指定读取结果数据的回调函数时机更灵活 ==> 请求后/请求前/请求完成后都可以
  • promise对象有3种状态
    • pending
    • fulfilled/resolved
    • rejected
  • promise状态的2种变化
    • pending --> fulfilled
    • pending --> rejected
  • 如何改变promise的状态
    • 调用resolve(value)
    • 调用reject(reason)
    • throw error

17. 说说ES6中Promise的then方法

  • 指定成功或失败的回调函数来读取promise成功的value或失败的reason数据
  • then()总是返回一个新的promise
  • 新promise的结果状态由then指定的回调函数执行的结果决定
    • 抛出错误 => 失败且reason就是抛出的错误
    • 返回失败的promise => 失败且reason是返回的promise的reason
    • 返回成功的promise => 成功且value是返回的promise的value
    • 返回其它任何非Promise值 => 成功且value是返回的值
    • 返回pending的promise => pending的promise

18. 区别一下Promise的all方法与race方法

  • Promise.all([p1, p2, p3])
    • 接收包含多个promise的数组, 返回一个新的promise
    • 只有当所有接收的promise都成功了, 返回的promise才成功, 且成功的value为所有成功promise的value组成的数组
    • 一旦有一个失败了, 返回的promise就失败了, 且失败的reason就是失败promise的reason
  • Promise.race([p1, p2, p3])
    • 接收包含多个promise的数组, 返回一个新的promise
    • 返回的promise的结果由第一个完成的promise决定

19. 说说async与await的理解和使用

  • 作用: 简化promise对象的使用, 不用再使用then/catch来指定回调函数
  • async与await是异步编程的终极解决方案 => 消灭回调函数
  • 使用
    • await一般在结果为promise的表达式左侧
    • async在await所在函数定义的左侧
  • 注意:
    • 调用async函数得到是一个promise, 其结果状态由async函数体执行的结果决定
    • await的右侧也可以不是promise, 如果不是, 直接返回表达式的值

20. var 、 let 、const之间的区别

  • const定义常量, let和var定义变量
  • let相对于var
    • 有块作用域
    • 没有变量提升
    • 不会添加到window上
    • 不能重复声明

21. 箭头函数的特点

  • 编码简洁
  • 没有自己的this, 使用外部作用域中的this, 不能通过bind来绑定this
  • 不能通过new来创建实例对象
  • 内部没有arguments, 可以通过rest参数来代替

22. ES6的模块化语法

  • 导出语法: 整个模块总是一个对象, 所有导出语法都是向模块对象中添加属性或方法
    • 分别导出: export const a = 2
    • 默认导出: export default 3
    • 统一导出: export {b, c}
  • 导入
    • 静态导入
      • import {a} from ‘./test’
      • import value from ‘./test’ import {default as xxx} from ‘./test’
      • import * as test from ‘./test’
      • 静态导入的模块打包到一起
    • 动态导入
      • import(‘./test’).then(module => {})
      • 动态导入的模块会被单独打包

23. 说出几个ES6常用新语法

  • const与let
  • 解构赋值
  • 模板字符串
  • 箭头函数
  • 形参默认值
  • rest/剩余参数
  • 对象的属性与方法简写
  • 扩展运算符: …
  • 类语法: class / extends / constructor / static /super
  • 异步语法: promise / async & await
  • 模块化语法: export / default / import / import()
  • 新容器: Set / Map

24. 说说事件冒泡与事件委托

  • 事件冒泡
    • 事件在传递给目标元素后, 会由内向外传递给外层的元素处理
  • 事件委托
    • 不直接给多个子元素绑定多个事件监听, 而是给它们共同的父元素绑定一个监听
    • 当操作任意子元素时, 事件会冒泡到父元素上处理
    • 在事件回调中通过event.target得到发生事件的目标元素, 并进行相关处理

25. HTTP请求的常用响应状态码

  • 2XX: 表示成功处理请求
    • 200 成功的通用响应码
    • 201 添加成功
  • 3XX: 重定向,表明浏览器需要执行某些特殊的处理以正确处理请求
    • 302 让浏览器重定向到location响应头所指定的地址
    • 304 告诉浏览器资源没有变化, 可以使用缓存资源
  • 4XX: 客户端请求错误
    • 401 未授权, 也就身份校验失败, 一般是token过期或非法
    • 404 请求的资源不存在
  • 5XX: 服务器端错误
    • 500 服务器运行出错,无法完成请求处理

26. 对原生ajax进行简单的封装

/* 
xhr + promise 封装一个异步ajax请求的通用函数  简洁版
ajax ('xxx.json')
*/
function ajax(url) {
  return new Promise((resolve, reject) => {
    // 创建一个XHR对象
    const xhr = new XMLHttpRequest()
    // 初始化一个异步请求(还没发请求)
    xhr.open('GET', url, true)
    // 绑定状态改变的监听
    xhr.onreadystatechange = function () { 
        /*
        ajax引擎得到响应数据后
        	将xhr的readyState属性指定为4
        	将响应数据保存在response / responseText属性上
        	调用此回调函数
        */
        
      // 如果状态值不为4, 直接结束(请求还没有结束)
      if (xhr.readyState !== 4) {
        return
      }
      // 如果响应码在200~~299之间, 说明请求都是成功的
      if (xhr.status>=200 && xhr.status<300) {
        // 指定promise成功及结果值
        resolve(JSON.parse(xhr.responseText))
      } else { // 请求失败了
        // 指定promise失败及结果值
        reject(new Error('request error staus '+ request.status))
      }
    }
    xhr.send()
  })
}

27. 说说你对跨域问题的理解和解决办法

  • 同源: 协议, 域名, 端口, 三者都相同
  • 同源策略
    • ajax请求时, 浏览器要求当前网页和Server必须同源(安全), 否则会抛出跨域的错误
    • 加载image/link/script不受同源策略限制
  • 解决ajax跨域问题的办法
    • JSONP
    • CORS
    • Proxy

28. 说说你知道的ajax跨域解决方案

  • JSONP: 利用script发跨域请求目标接口, 得到响应数据
  • CORS: 浏览器直接请求跨域的目标接口, 服务器返回响应头告诉浏览器允许跨域
  • Proxy: 浏览器发同源请求代理服务器, 代理服务器转发请求跨域的目标接口

29. 说说你项目中的axios的二次封装

  1. 配置通用的基础路径和超时
  2. 每个请求自动携带userTempId的请求头: 在请求拦截器中实现
  3. 如果当前有token, 自动携带token的请求头
  4. 显示请求进度条
    1. 显示进度条: 请求拦截器回调
    2. 结束进度条: 响应拦截器回调
  5. 成功返回的数据不再是response, 而直接是响应体数据response.data
  6. 统一处理请求错误, 具体请求也可以选择处理或不处理
  7. 对token过期的错误进行处理

30. 说说axios的整体执行流程

  • 基本执行顺序

    • 请求拦截器的回调函数
    • xhr发请求
    • 响应拦截器成功/失败的回调
    • 具体请求成功/失败的回调
  • 内部原理: 通过promise.then的链式调用将这4个任务串连越来, 依次执行并进行数据传递

    axios({})
    
    Promise.resolve(config)
    .then((config) => {  // 请求拦截器
        // 显示进度/携带token/userTempId
        return config
    })
    .then((config) => {  // 使用xhr发ajax请求
        return new Promise((resolve, reject) => {
            // 根据config创建xhr对象发送异步ajax
            // 如果请求成功(响应状态码200--299之间)
            const response = {
                data: JSON.parse(xhr.responseText),
                status: xhr.statusCode
            }
            resolve(response)
            // 如果请求失败
            reject(new Error('请求失败 code ' + xhr.statusCode))
        })
    })
    .then( // 响应拦截器
        response => {  
        	return response.data
        },
        error => {
        	throw error
        }
    )
    .then(result => { // 特定请求的回调
    
    }).catch(error => {
    
    

})




## 31. 从输入url到渲染出页面的整个过程

1. 得到服务器对应的IP地址 ==> DNS解析
2. 通过IP连接上服务器: 3次握手
3. 向服务器发请求, 接收服务器返回的响应
4. 解析响应数据(html/css/js)显示页面
  解析html => dom树
  解析css => cssom树
  解析js => 更新dom树/cssom树 
  生成渲染树 = dom树 + cssom树
  布局
  渲染
5. 断开连接:4次挥手

## 32. 说说前台数据存储

存储方式:
cookie: 会话/持久化两种
sessionStorage
localStorage
数据库(很少用)
区别:
---------------------sessionStorage VS localStorage
刷新在/ 关闭不在 关闭还在
---------------------cookie VS sessionStorage与localStorage
- 容量 小
- 请求时是否自动携带 会
- API易用性 不好用
- 浏览器是否可禁用 可




## 33. 自定义call和bind

​```js
/* 
自定义函数对象的call方法
*/
function call (fn, obj, ...args) {
  // 如果传入的是null/undefined, this指定为window
  if (obj===null || obj===undefined) {
    // obj = window
    return fn(...args)
  }
  // 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象
  obj.tempFn = fn
  // 通过obj调用这个方法
  const result = obj.tempFn(...args)
  // 删除新添加的方法
  delete obj.tempFn
  // 返回函数调用的结果
  return result
}

/* 
  自定义函数对象的bind方法
  重要技术:
    闭包
    call()
    三点运算符
*/
function bind (fn, obj, ...args) {
  return function (...args2) {
    return call(fn, obj, ...args, ...args2)
  }
}

34. 区别函数节流与防抖

1. 事件高频触发处理的问题
	如果更新界面 => 界面更新卡顿
	如果发送ajax请求 => 发送了很多没必要的请求
2. 解决办法
	函数节流
	函数防抖
3. 区别:
	当事件高频发生很多次时, 防抖只执行最后一次, 而节流执行少量几次
4. 应用场景
	节流: 窗口调整(resize)/ 页面滚动(scroll)/ OM元素的拖拽功能实现(mousemove)
	防抖: 输入搜索联想功能

35. 自定义函数节流与防抖

/* 
用于产生节流函数的工具函数
*/
function throttle (callback, delay) {
  // 用于保存处理事件的时间, 初始值为0, 保证第一次会执行
  let start = 0
  // 返回事件监听函数 ==> 每次事件发生都会执行
  return function (event) {
    console.log('---throttle')
    // 发生事件的当前时间
    const current = Date.now()
    // 与上一次处理事件的时差大于delay的时间
    if (current-start>delay) {
      // 执行处理事件的函数
      callback.call(event.target, event)
      // 保证当前时间
      start = current
    }
  }
}

/* 
用于产生防抖函数的工具函数
*/
function debounce (callback, delay) {
  // 返回事件监听函数 ==> 每次事件发生都会执行
  return function (event) {
    console.log('---debounce')
    // 如果还有未执行的定时器, 清除它
    if (callback.timeoutId) {
      clearTimeout(callback.timeoutId)
    }
    // 启动延时delay的定时器, 并保证定时器id
    callback.timeoutId = setTimeout(() => {
      // 执行处理事件的函数
      callback.call(event.target, event)
      // 删除保存的定时器id
      callback.timeoutId = null
    }, delay);
  }
}

36. 自定义数组扁平化

/* 
数组扁平化: 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中
  如: [1, [3, [2, 4]]]  ==>  [1, 3, 2, 4]
*/
/*
方法一: 递归 + reduce() + concat() + some()
*/
function flatten1 (array) {

  return array.reduce((pre, item) => {
    if (Array.isArray(item) && item.some((cItem => Array.isArray(cItem)))) {
      return pre.concat(flatten1(item))
    } else {
      return pre.concat(item)
    }
  }, [])
}

/*
方法二: ... + some() + concat()
*/
function flatten2 (arr) {
  // 只要arr是一个多维数组(有元素是数组)
  while (arr.some(item => Array.isArray(item))) {
    // 对arr进行降维
    arr = [].concat(...arr)
  }
  return arr
}

37. 自定义new

/* 
自定义new工具函数
  语法: newInstance(Fn, ...args)
  功能: 创建Fn构造函数的实例对象
  实现: 创建空对象obj, 调用Fn指定this为obj, 返回obj
*/
function newInstance(Fn, ...args) {
  // 创建一个新的对象
  const obj = {}
  // 执行构造函数
  const result = Fn.apply(obj, args) // 相当于: obj.Fn()
  // 如果构造函数执行的结果是对象, 返回这个对象
  if (result instanceof Object) {
    return result
  }
  
  // 给obj指定__proto__为Fn的prototype
  obj.__proto__ = Fn.prototype
  // 如果不是, 返回新创建的对象
  return obj
}

38. 实现数组的冒泡排序和sort排序

/* 
冒泡排序的方法
*/
function bubbleSort (array) {
  // 1.获取数组的长度
  var length = array.length;
  // 2.反向循环, 因此次数越来越少
  for (var i = length - 1; i >= 0; i--) {
    // 3.根据i的次数, 比较循环到i位置
    for (var j = 0; j < i; j++) {
      // 4.如果j位置比j+1位置的数据大, 那么就交换
      if (array[j] > array[j + 1]) {
        // 交换
        // const temp = array[j+1]
        // array[j+1] = array[j]
        // array[j] = temp
        [array[j + 1], array[j]] = [array[j], array[j + 1]];
      }
    }
  }
  return arr;
}

const products = [{price: 23, sales: 103}, {price: 22, sales: 101}]
/*
根据销量升序
*/
products.sort((p1, p2) => {  // 比较函数  ==> 返回数值   如果大于o, p2放在左边
    return p1.sales - p2.sales
})

39. 说说重排(回流)与重绘

  • 页面显示过程
    • 解析HTML生成DOM树
    • 解析CSS生成CSSOM树
    • 解析JS更新DOM树和CSSOM树
    • DOM树 + CSSOM树生成渲染树
    • 布局(也称回流, 确定个节点显示的位置)
    • 渲染绘制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iFP0u97G-1676984567670)(images/image-20220302150041228.png)]

  • 更新DOM或Style

    • 可能会导致局部重排(也称回流, 重新布局)
    • 可能会导致局部重绘
  • 注意:

    • 重排肯定会重绘, 但重绘不一定有重排
    • 重排比重绘开销更大, 更消耗性能
  • 哪些操作会导致重排

    • 元素位置和尺寸发生改变的时候
    • 新增和删除可见元素
    • 内容发生改变(文字数量或图片大小等等)
    • 元素字体大小变化。
    • 激活CSS伪类(例如::hover)。
    • 设置style属性
    • 查询某些属性。比如说:
      offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
  • 哪些操作会导致重绘

    • 更新元素的部分属性(影响元素的外观,风格,而不会影响布局),比如
      visibility、outline、背景色等属性的改变。
  • 例子代码

    var s = document.body.style;
    s.padding = "2px"; // 回流+重绘
    s.border = "1px solid red"; // 再一次 回流+重绘
    s.color = "blue"; // 重绘
    s.backgroundColor = "#ccc"; // 重绘
    s.fontSize = "14px"; // 再一次 回流+重绘
    document.body.appendChild(document.createTextNode('abc!'));// 添加node,再一次 回流+重绘
    
  • 如何减少重排次数

    • 更新节点的样式, 尽量通过类名而不是通过style来更新
  • 优质博文: https://juejin.cn/post/6844904083212468238

40. 区别HTTP与HTTPS

  • http协议的问题
    • 传输的数据都是明文, 如果攻击者截取了传输报文,就可以直接读懂其中的信息
    • 不适合传输一些敏感信息,比如:信用卡号、密码等支付信息
  • https协议
    • http的升级版, 在http外包装上SSL(Secure Sockets Layer)层
    • 是带加密传输、身份认证的网络协议,要比http协议安全
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值