关于javascript:深入20-手写函数

37次阅读

共计 20577 个字符,预计需要花费 52 分钟才能阅读完成。

导航

[[深刻 01] 执行上下文](https://juejin.im/post/684490…
[[深刻 02] 原型链](https://juejin.im/post/684490…
[[深刻 03] 继承](https://juejin.im/post/684490…
[[深刻 04] 事件循环](https://juejin.im/post/684490…
[[深刻 05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490…
[[深刻 06] 隐式转换 和 运算符](https://juejin.im/post/684490…
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…
[[深刻 08] 前端平安](https://juejin.im/post/684490…
[[深刻 09] 深浅拷贝](https://juejin.im/post/684490…
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…
[[深刻 11] 前端路由](https://juejin.im/post/684490…
[[深刻 12] 前端模块化](https://juejin.im/post/684490…
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490…
[[深刻 14] canvas](https://juejin.im/post/684490…
[[深刻 15] webSocket](https://juejin.im/post/684490…
[[深刻 16] webpack](https://juejin.im/post/684490…
[[深刻 17] http 和 https](https://juejin.im/post/684490…
[[深刻 18] CSS-interview](https://juejin.im/post/684490…
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…
[[深刻 20] 手写函数](https://juejin.im/post/684490…

[[react] Hooks](https://juejin.im/post/684490…

[[部署 01] Nginx](https://juejin.im/post/684490…
[[部署 02] Docker 部署 vue 我的项目](https://juejin.im/post/684490…
[[部署 03] gitlab-CI](https://juejin.im/post/684490…

[[源码 -webpack01- 前置常识] AST 形象语法树](https://juejin.im/post/684490…
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程](https://juejin.im/post/684490…
[[源码] Redux React-Redux01](https://juejin.im/post/684490…
[[源码] axios ](https://juejin.im/post/684490…
[[源码] vuex ](https://juejin.im/post/684490…
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…

前置常识

包装对象

  • <font color=red> 包装对象:Number,String,Boolean 三个对象能够把原始类型的值变成对象 -> new 调用 </font>
  • number,string,boolean 三个原始类型的值,在肯定的条件下也会主动转为对象,也就是原始类型的包装对象
  • 包装对象的目标

    • 使得 js 中的对象涵盖所有的值
    • 使得 (原始类型) 的值,能够不便的 (调用某些办法)

      • 比方 toString valueOf
  • <font color=red>总结</font> Nubmer,String,Boolean

    • 作为构造函数:通过 new 命令调用,能够将 (原始类型) 的值转为 (对象)
    • 作为一般函数:能够将 (任何类型) 的值,转换成 (原始类型) 的值

构造函数

  • 特点:

    • 构造函数首字母通常大写
    • 应用 new 命令调用,new 命令调用时,能够在函数名前面加小括号,也能够不加
    • 构造函数外部的 this 指向实例对象
    • 应用构造函数,属性和办法都生成在实例上的,彼此之间不共享
    • <font color=red>new 命令总会返回一个对象 </font>,要么是实例对象,要么是 return 后指定的对象

      • 如果构造函数内 (return) 返回的是一个 (对象),new 命令就会返回回这个 (对象)
      • 如果构造函数内 (return) 返回的是 (原始类型的值),new 命令就会返回 (this 对象)
      • (一般函数) 应用 (new)命令,会返回一个 (空对象)
  • new 命令的原理

    • 创立一个空对象,即是须要返回的那个实例对象
    • 将空对象的原型,指向构造函数的 prototype 属性
    • 将构造函数外部的 this 绑定到这个空对象上
    • 执行构造函数

一些单词

immediate: 立刻的
shuffle: 洗牌

flatten:扁平的
infinity:无穷

recursive:递归

call 模仿实现

  • call 办法的作用

    • 绑定函数的 this 指向
    • 执行该函数 (在指定的作用域中执行该函数)
  • call 的参数

    • 应该是一个 (<font color=red> 对象 </font>)
    • 如果传入 (<font color=red>null,undefined,空 </font>),则默认传入 (<font color=red> 全局对象 </font>) window/global
    • 如果参数是 (<font color=red> 原始值 </font>) 那么这个原始值会主动转成对应的 (<font color=red> 包装对象 </font>),而后传入 call 办法
    • 能够接管多个参数

      • 第一个参数是 this 要绑定的对象
      • 前面的参数是函数调用时须要的参数
  • call 办法的作用利用 (重要)

    • call 办法的一个利用就是 - 调用对象的原生办法
    • 当一个对象调用继承的属性时,如果该对象本身也有一个同名的属性时,将会产生笼罩
    • call 办法能够把继承的办法的原始定义放在该对象上执行,这样无论该对象是否有同名的属性,都不会受影响
    [1]
    // call 办法的利用
    // - call 办法的一个利用就是 - 调用对象的原生办法
    -------
    
    var obj = {};
    obj.hasOwnProperty('toString') 
    // false,留神 toString 是继承的办法,空对象本身并没有该办法
    // hasOwnProperty 示意是否含有本身属性
    
    
    // 笼罩掉继承的 hasOwnProperty 办法
    // 通过在 obj 本人定义一个办法,这样就会笼罩原型链上的同名办法,并且这样就是本身属性
    obj.hasOwnProperty = function () {return true;};
    
    obj.hasOwnProperty('toString') 
    // true,调用时先找本身是否有该办法,而本意是调用原型链上的 hasOwnProperty 办法
    // 这样就造成了后果偏差,能够用 call 办法解决
    // call 办法的一个次要利用 - 调用对象的原生办法
    
    Object.prototype.hasOwnProperty.call(obj, 'toString') // false
    
    
    下面代码中,hasOwnProperty 是 obj 对象继承的办法,如果这个办法一旦被笼罩,就不会失去正确后果。- call 办法能够解决这个问题 - 调用对象的原生办法
      - 它将 hasOwnProperty 办法的原始定义放到 obj 对象上执行,这样无论 obj 上有没有同名办法,都不会影响后果。
  • call 办法的模仿实现 – es5
### call 办法的模仿实现 - es5

留神点:1. fn.call() 的参数
    - 当为空,null,undefined 时,示意传入全局对象 window 或者 global
    - 当为原始类型的值时,会被转为包装对象再传入 fn
    - 第一个参数是 fn 中 this 指向的对象,其余参数将作为调用 fn 时的参数
2. fn 能够有返回值
3. 实现思路
    - 在对象上 (增加) 函数 (办法)
    - 在该对象上 (执行) 该函数,此时 this 的指向就是该对象(运行时确定 this 指向)- 因为 fn.call()是能够接管不定个数的参数的,怎么办?- 能够用 arguments 对象获取所有参数,这里须要去除第一个参数即 this 绑定的对象,前面的才是传入 fn 函数的参数
        - 记得留神下 fn 也能够有返回值
    - (删除)该函数
4. eval(string)
    - 将字符串当作语句来执行
    - 留神:参数必须是字符串
    


代码:const obj = {
  name: 'woow_wu7',
  age: 18
}
function fn(address, sex) {return this.name + this.age + sex + address}

Function.prototype._call = function(obj) {
  const context = obj || window
  // 相当于 context = context ? context : window;
  // 相当于 const context = obj ? obj : window;
  // fn.call() 当参数为空,null,undefined 时,默认是传入 window 对象
  // 空,null,unfined 的布尔值都是 false

  const params = []
  // 用于收集 arguments 中进来第一个参数外,剩下的每一个参数

  for(let i = 1; i < arguments.length; i++) {params.push('arguments[' + i + ']')
  }
  // 留神:循环是从 1 开始的,因为 arguments[0] 是 fn 中 this 绑定的对象,这里是收集传入 fn 的参数
  // arguments 是相似数组的对象,具备 length 属性
  // params 相当于 ['arguments[1]', 'argument[2]']



  context.fn = this
  // --- 第一步:在对象上增加一个函数属性 fn(对象中应该叫做办法)// 函数的值是 this,即 fn 函数
  // this 这里指的是 fn 函数,因为 this 就是函数调用时所在的对象,fn._call()是 fn 在调用,所以 this 指向 fn



  const res = eval('context.fn(' + params + ')')
  // fn 函数可能有返回值
  // + 号是存在(重载)的,即能够是(相加)或者(相连)// 当 + 运算子存在对象时,会先将对象转成原始类型的值,即先执行 valueOf -> 对象自身 -> toStirng -> 出 object 外,都是返回该类型对象的字符串模式
  // params 数组会被转成 'arguments[1],arguments[2]' 这样的模式
  // 例如:[1,'2',3,'4'].toString() -> "1,2,3,4"->  数组返回数组的字符串模式
  // 最终是这样的:const res = eval('context.fn(arguments[1],arguments[2])')

  Reflect.deleteProperty(context, 'fn')
  // delete context.fn
  // 删掉对象上的函数
  // 留神:delete context.fn 这样的旧的写法会逐步被摈弃

  return res
  // fn 可能有返回值,须要返回后果
}

const res = fn._call(obj, 'chongqing', 'man')
console.log(res, 'res')
  • call 办法的模仿实现 – es6

    ### call 办法的模仿实现 - es6
    
    const obj = {
    name: 'woow_wu7',
    age: 18
    }
    function fn(address, sex) {return this.name + this.age + sex + address}
    
    Function.prototype._call = function(context) {
    context = context ? context : window;
    context.fn = this;
    const res = context.fn(...[...arguments].slice(1));
    
    Reflect.deleteProperty(context, 'fn')
    //delete context.fn;
    
    return res;
    }
    const res = fn._call(obj, 'chongqing', 'man')
    console.log(res, 'res')
    

apply 模仿实现

const obj = {name: 'wang',};
function fn(name) {
  return {name: name || this.name}
}
Function.prototype._apply = function (context, arr) {
  context = context ? context : window;
  context.fn = this;
  let res = null;
  if (!arr) { //_apply 第二个参数不存在,就不给 fn 传参,间接执行 fn
    res = context.fn()} else {
    arr instanceof Array
    ?
    res = context.fn(...arr)
    :
    console.log('第二个参数只能是数组')
  }
  delete context.fn;
  return res;
}
const result = fn._apply(obj, ['woow-wu']);
console.log(result)

bind 模仿实现

  • bind 函数的作用

    • 返回一个新的函数
    • 绑定 this 的指向
  • bind 函数的参数

    • 第一个参数:须要绑定的对象
    • 前面的参数:作为函数的参数
  • 留神点:

    • 当第一个参数是 null,undefined 时,相当于讲 this 绑定到全局对象上(window|global)
    • 除去第一个参数外,残余的参数作为 (须要绑定 this 的函数的全副或者局部参数)

      • bind 时能够只传局部参数
      • 残余的参数在返回的新函数被调用时传入
    • <font color=red> 返回的新函数,能够应用 new 命令调用,即返回的新函数能够作为 (构造函数)</font>,这种状况下

      • bind 时绑定的 this 生效,<font color=red> 因为在构造函数中,this 指向的是实例对象 </font>
      • 然而传入的参数无效
  • 代码模仿实现

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
          const obj = {
              name: 'woow_wu7',
              age: 20
          }
          function fn(sex, address) {
              this.sex = sex
              this.address = address
              return this.name + this.age + sex + address
          }
          fn.prototype.go = function() {console.log('hangzhou')
              return 'hangzhou'
          }
          // 当_bind()返回的新函数通过 new 命令调用时,生成的实例能够继承 fn.prototype 上的办法
    
          Function.prototype._bind = function(context) {
              context = context ? context : widnow
              // 传入的第一个参数
              // 如果是 null 或者 undefined 时,相当于传入全局对象 (window|global)
    
              const params = Array.prototype.slice.call(arguments, 1)
              // params:是外层函数承受的要传入被绑定函数的参数,除去第一个参数即除去绑定的对象
              // arguments:实参列表
    
              const self = this
              // 固定外层函数的 this
    
              const Ftemp = function(){}
              // 寄生式继承
              // 构造函数首字母通常大写
    
              const fbind = function() {const bindParams = Array.prototype.slice.call(arguments)
                  // fbind 函数的实参
    
                  const totalParams = params.concat(bindParams)
                  // 所有要传入被绑定函数的参数数组
    
                  return self.apply(this instanceof self ? this : context, totalParams)
                  // self:是外层的 this,调用时确定指向,即 fn 函数
                  // this:fbind 函数中的 this,如果是构造函数 this 就指向实例,如果不是就执行须要绑定的对象
                  
                  // this instanceof self ? this : context
                  // 因为:上面将 fbind.prototype => new Ftemp => this.prototype
                  // 所以:如果是 new 命令在调用 fbind 的话,判断是 true,绑定到实例 this 上,否则绑定到传入的对象上
    
                  // 可能有返回值,须要 return
              }
    
              Ftemp.prototype = this.prototype
              fbind.prototype = new Ftemp()
              // 将 fbind.prototype 绑定到 Ftemp 的实例上
              // 这样 fbind 作为构造函数时,fbind 的实例能继承 Ftemp 实例上的属性和 Ftemp 原型链上的属性
    
              return fbind
              // 返回一个新的函数
          }
    
          const resFn = fn._bind(obj, 'man')
          const res = resFn('hangzhou')
          const res2 = new resFn('chongqing')
          const xx = res2.go()
          console.log(res, 'res')
          console.log(res2, 'res2')
          console.log(xx, 'xx')
      </script>
    </body>
    </html>

new 命令的模仿实现

  • new 命令的作用

    • 执行构造函数
    • 返回实例对象
  • 构造函数返回值

    • return 后跟一个对象,new 命令返回这个对象
    • return 后跟一个简略数据类型,new 命令会不论这个值,返回 this 对象
  • 继承相干

    • 构造函数中的属性和办法,都是间接生成在实例上的,实例之间不共享,造成资源节约
    • 实例能继 constructor.prototype 上的属性和办法,多个实例能够共享,批改则相互影响
  • arguments

    • 应用 argumetns 的益处是在参数个数不确定的状况下,获取任意多的参数
  • 手写原理

    • 1. 新建一个空对象
    • 2. 将空对象的隐式原型指向构造函数的显示原型
    • 3. 将构造函数中的 this 绑定到空对象上
    • 4. 执行构造函数
    • 5. 判断返回值,如果构造函数 return 一个对象,就返回构造函数返回的对象,否则返回空对象即 this 对象

      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      </head>
      <body>
      <script>
          function Constructor(name, age) {
              this.name = name
              this.age = age
              return this         
          }
      
          function _new() {const obj = {}
              // 第一步
              // 新建一个空对象
              // 相当于 const obj = new Object()
      
              const paramsConstructor = Array.prototype.shift.call(arguments)
              // 将 arguments 转化成数组,并且取除数组中的第一个元素,即传入的构造函数
              // 相当于 ([]).prototype.shift.call(arguments)
              // 相当于 ([]).shift.call(arguments) => 因为数组实例是继承了 Array.prototype 上的属性和办法
              // 相当于 Array.prototype.shift.apply(arguments) => call 和 apply 都能够
              // 留神:// push unshift pop shift 都会扭转原数组
              // push unshift 返回值是操作后,数组的长度
              // pop shift 返回值是增加或者删除的元素
      
              obj.__proto__ = paramsConstructor.prototype
              // 第二步
              // 将 (空对象的隐式原型) 指向 (构造函数的显示原型)
              // 这样空对象就能够继承构造函数 prototype 上的属性和办法
      
              // 留神:// const obj = {} 和 obj.__proto__ = paramsConstructor.prototype
                  // 能够简写为:const obj = Object.create(paramsConstructor.prototype)
                  // b = Object.create(a)作用是以参数对象 a 为原型,生成实例对象 b - 即能够用一个对象创立实例对象
      
      
              const res = paramsConstructor.apply(obj, arguments)
              // 第三步
              // 将构造函数中的 this 绑定到空对象上,并执行构造函数
              // 留神:// 这里是 argumets 是去除了结构函数参数后的,残余参数的汇合
              // _new(constructor, p1, p2, ...) 
      
              return /Object|Function/.test(Object.prototype.toString.call(res)) ? res : obj
              // 如果构造函数的返回值
                  // 是对象,就返回这个对象
                  // 是原始类型的值,就返回 this 对象,即空对象
          }
      
          const instance = _new(Constructor, 'woow_wu7', 20)
          console.log(instance, 'instance')
      </script>
      </body>
      </html>

Debounce 防抖函数

  • 性能:

    • 延时执行,如果在延时的工夫内屡次触发,则从新计时
    • 能够获取 event 事件对象作为参数和传入其余参数
    • 第一点击立刻触发,而不是延时执行
    • 能够勾销 debounce 的执行
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    
    <body>
      <div id="debounce">Debounce</div>
      <div id="cancel" style="margin-top: 20px">Debounce-cancel</div>
      <script>
          const fn = () => {console.log('fn running')
          }
          // 传入 debounce 须要延时执行的函数 fn
    
          /**
           * @param {function} fn 须要 debounce 防抖函数解决的函数
           * @param {number} delay 定时器延时的工夫
           * @param {boolean} immediate 是否立刻执行
           */
          function debounce(fn, delay, immediate) {
              let timer = null
    
              debounce.cancel = () => {console.log('cancel running')
                  clearTimeout(timer)
              }
              // 能够手动勾销 debounce 的执行
    
              return (...args) => {if (immediate && !timer) {
                      // 立刻执行的状况
                          // immediate = true
                          // timer = false
                          // 满足这两个条件就立刻执行
                      fn.apply(this, args)
                  }
                  if (timer) {console.log('timer exist')
                      clearTimeout(timer)
                      // timer 存在,就革除定时器
                  }
                  timer = setTimeout(() => {if (immediate) {
                          // 立刻执行条件下
                              // 第一次执行
                                  // 第一次立刻执行,则在延时的工夫内不触发第二次
                              // 第二次执行
                                  // immediate = false,则不再进入该判断条件了
                          immediate = false
                          return
                      }
                      console.log(args[0].target, 'e.target')
                      fn()}, delay)
              }
          }
    
    
          const button = document.getElementById('debounce')
          const buttonCancel = document.getElementById('cancel')
          button.addEventListener('click', debounce(fn, 2000, true), false)
          buttonCancel.addEventListener('click', debounce.cancel, false)
      </script>
    
    </body>
    </html>

Throttle 节流函数

  • 性能:

    • 每隔一段时间,只执行一次
    • 即在外层函数增加标记位,依据标记位的状况决定是否进入闭包
    根底版
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div id="throttle">Throttle</div>
      <script>
          const fn = () => {console.log('fn running')
          }
          function throttle(fn, delay) {
              let isRun = true // 标记位
              let timer = null
              return (...args) => {
                  // 标记位是 false,就 return
                  if (!isRun) {return}
                  isRun = false
                  timer = setTimeout(() => {fn.apply(this, args)
                      isRun = true // 执行完标记位改为 true,则下次点击又能够执行了
                      clearTimeout(timer) // 革除定时器
                  }, delay)
              }
          }
          const button = document.getElementById('throttle')
          button.addEventListener('click', throttle(fn, 1000), false)
      </script>
    
    </body>
    </html>
    工夫戳版
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div id="throttle">Throttle</div>
      <script>
          const fn = () => {console.log('fn running')
          }
          function throttle(fn, delay) {
              let previous = null
              return (...args) => {const now = + new Date()
                  // + new Date() 获取当初的工夫戳,即间隔 1970.1.1 00:00:00 的毫秒数字
                  // 留神:单位是毫秒数,和定时器的第二个参数吻合,也是毫秒数,尽管这里没有定时器
                  // (+) 能够把任意类型的数据转成数值,只有两种可能,即 (数值) 和 (NaN)
                  // + new Date() === new Date().valueOf() === new Date().getTime()
                  if (now - previous > delay) {fn.apply(this, arguments)
                      previous = now // 执行完 fn,同步工夫,用于下一次计算
                      // 第一次:now - previous > delay 是 true,所以立刻执行一次
                      // 而后 previous = now
                      // 第二次:第二次能进来的条件就是差值毫秒数超过 delay 毫秒
                      // 这样频繁的点击时,就能依照固定的频率执行,当然是升高了频率
                  }
              }
          }
          const button = document.getElementById('throttle')
          button.addEventListener('click', throttle(fn, 1000), false)
      </script>
    </body>
    </html>

数组乱序

  • sort

    • 默认会依照字典进行排序,数字会先被转成字符串,在排序
    • <font color=red> 扭转原数组,没有返回值 </font>
    • 2021/03/18 更正,sort 扭转原数组,有返回值,返回值就是排序过后的数组即被扭转过后的数组
    • 参数

      • 能够没有
      • 也能够是一个函数(自定义形式排序就承受函数为参数),参数函数又有两个参数

        • 返回值大于 0,示意比拟的第一个成员排在第二个成员的前面(a-b>0 示意升序
        • 返回值小于 0,示意比拟的第一个成员排在第二个成员的后面(a-b<0 示意降序
        • 返回值等于 0,地位不变
  • 乱序的利用

    • 换一批
    • 猜你喜爱
    • 中奖计划
    办法 1
    arr.sort(() => Math.random() - .5)
    
    const arr = [1, 3, 7, 2, 5, 4, 6]
    arr.sort(() => Math.random() - .5)
    // 因为:Math.random() 的值得范畴区间是 [0, 1)
    // 所以:Math.random() - 0.5  ===> 大于 0 或小于 0 的概率都是 50%
      // Math.random()参数参数的返回值:// 函数返回值大于 0,示意两个比拟的成员,第一个排在第二个的前面
      // 函数返回值小于 0,示意两个比拟的成员,第一个排在第二个的后面
      // 函数返回值等于 0,示意两个比拟的成员,地位不变
      // 当小数是 0 点几时,能够省略后面的 0
    console.log(arr, 'arr')
    
    
----
办法 1 存在的问题:1. 概率不均等
2. 造成概率不均等的起因:- 因为 sort()办法底层因为不同浏览器实现形式不一样,v8 中
        - 当数组长度小于 10 时,用的是插入排序
        - 否则,应用的是插入排序和疾速排序的混合排序
    - 插入排序就会造成概率不均等
        - 因为把无序局部的元素插入到有序局部中时,如果一旦找到了地位,剩下的元素就没有在和缓存的值就行比拟了
        - 即没有机会比拟所有元素,造成概率不均等,就得不到齐全随机的后果

3. 如何晓得 sort()排序造成概率不均等呢?能够用上面的办法统计
const countArr = [0, 0, 0, 0, 0] // 用来寄存数组 arr 最初一个地位,各数字呈现的次数
for (let i = 0; i < 100000; i++) {const arr = [0, 1, 2, 3, 4]
  arr.sort(() => Math.random() - .5) // 随机打乱
  countArr[arr[4]]++
  // arr[4] 的可能值是 0 1 2 3 4
  // 如果呈现过一个值,就在 countArr 绝对应的地位加 1,用来统计各数字呈现的次数
}
console.log(countArr)
// [24977, 6951, 20999, 18784, 28289]
// 从后果中能够看出:arr 数组的最初一个地位,呈现 1 的概率显著要小很多,即  arr[4] = 1  的概率显著小很多
// 其余地位上也会得出相应的后果 arr[x] = 1 的概率都比其余数字呈现的概率小

----
间接插入排序温习

插入排序
- 思维:将一个数组分成有序局部和无序局部,将无序局部的一个值插入到有序局部,有序局部依然有序 (联想打牌时插牌)
- 原理:- 1. 将数组分成两个局部,右边是有序局部(初始成员个数为 1),左边是无序局部(下标从 1 开始,有序数组中取了一个)
    - 2. 从左边的无序局部顺次取出一个值,插入到有序局部,直到取完无序局部
    - 3. 如何找有序局部的插入地位?1. 先缓存须要插入的无序局部的值,用一个变量来缓存 let temp = arr[i]
        2. 从有序局部的最初地位找(arr[i-1]),如果 arr[i-1] > arr[i] 则该元素往后挪动一位
        3. 如果有序该地位依然比须要插入的值大,有序中该地位的值,也后挪动一位,直到 j>=0

const arr = [1, 4, 3, 2]
const insert_sort = (arr) => {for(let i = 1, len = arr.length; i < len; i++) { // 循环数组的无序局部,从 1 开始,因为假如初始化时有序局部有一个元素
        let temp = arr[i] // 缓存须要插入有序局部的这个无序局部的值,因为有序局部可能会往后挪动地位,将其笼罩
        let j = i - 1 
        // j = i - 1 示意有序局部的最初一个元素的地位,有序局部从最初的地位顺次往前查找须要插入的地位
        // 这里采纳有序局部从后往前寻找,也能够从前往后找
        while(j >= 0 && arr[j] > temp) { 
        // 有序局部循环条件,如果有序的地位的值比无序中的大并且要保障 j >=0,就把有序的值往后挪动一位
            arr[j+1] = arr[j] // 有序该地位值大于 temp,则往后挪动一位
            j-- // 顺次往前查找
        }
        arr[j+1] = temp // 循环完后,j+ 1 就是须要插入的地位,因为条件是大于 temp,不满足时,j+ 1 就是要插入的地位
    }
    return arr // 最初返回数组
}
console.log(insert_sort(arr))
----
乱序改进版

Fisher–Yates

1. 应用 sort(() =>  Math.random() - .5)存在的问题就是不能齐全实现全副元素的比拟,造成概率不均等
2. 为什么叫 Fisher–Yates 呢?- 因为这个算法是由 Ronald Fisher 和 Frank Yates 首次提出的。3.shuffle:是洗牌的意思

4.Fisher–Yates 的原理:- 首先选取(数组最初一个地位)的元素和(数组长度随机地位)上的元素替换
- 接着选取(数组倒数第二个地位)的元素和(除去最初一个地位剩下的数组地位,即倒数第二到第一个元素)上的元素替换
- 反复以上步骤,直到 length >= 1

5. 代码
// 数组乱序
const arr = [1, 2, 3, 4, 5, 6]
function shuffle(arr) {
    let length = arr.length
    while(length > 1) {const pivot = Math.floor(Math.random() * length--) 
        // 1. 留神这里是先赋值,再减 1,即 Math.floor(Math.random() * 6) 
        // (Math.random() * 6 的区间[0, 6)
        // Math.floor(Math.random() * 6) 区间是 [0-5]

        // 2. 语句以分号结尾,一个分号就示意一个语句完结。所以这里赋值语句前面记得加分号
        // 3. ;[arr[pivot], arr[length]] = [arr[length], arr[pivot]]这里条语句前加上分号,因为后面也是语句
        // 在小括号结尾,或者中括号结尾的语句,后面的语句开端须要加分号,或者加到本条语句后面
        //4. const pivot = Math.floor(Math.random() * length--); 这样也行
        ;[arr[pivot], arr[length]] = [arr[length], arr[pivot]] // 这里的 length 是减 1 之后的 length
        // const temp = arr[length]
        // arr[length] = arr[pivot]
        // arr[pivot] = temp
    }
    return arr
}
console.log(shuffle(arr))

数组扁平化

  • Array.prototype.flat(拉平的层数)

    • 参数

      • 整数,示意拉平的层数
      • Infinity:示意任意层数的数组都拉平,都开展成一元数组
    • 返回值:

      • 一个新数组,不扭转原数组
  • 前置常识

    • concat

      • 作用:用于多个数组的合并,将新数组的成员增加到原数组的尾部
      • 返回值:新的数组
      • 不扭转原数组

        办法 1
        Array.prototype.flat(Infinity)
        
        -------
        2021/03/18 更新如下
        Array.prototype.flat.call(arr, Infinity)
        办法 2
        递归 - recursive
        
        const arr = [1, [2, [3, 4, 5, [6]]], 7, 8]
        function flat(arr) {let result = []
        for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {
        // 如果每一项还是数组,就递归,最终把数据都收集到 result 中
        result = result.concat(flat(arr[i]))
        }
        else { 
        // 递归完结条件
        result.push(arr[i])
        }
        }
        return result
        }
        const res = flat(arr)
        console.log(res, 'res')
  • toString
  • 如果数组元素都是数值(留神所有的数组中的所有元素都要是数字),能够应用 toString()
  • 然而这种办法应用的场景却十分无限,如果数组是 [1, ‘1’, 2, ‘2’] 的话,这种办法就会产生谬误的后果。

    const arr = [1, [2, 3, [4, [5]]]]
    function flat(arr) {
    return arr.toString().split(‘,’).map(item => +item)
    // arr.toString()数组的字符串模式,会展平。=> 1,2,3,4,5
    // arr.toString().split(‘,’)以,为分隔符,将字符串转成数组 [“1”, “2”, “3”, “4”, “5”]
    // +item 相当于 Number(item)
    }
    const res = flat(arr)
    console.log(res)

  • reduce((累积变量, 当面变量,以后地位 index, 原数组), 累计变量的初始值)
  • 参数

    • 函数

      • 参数
      • 累积变量 prev:如果 reduce()没有第二个参数,就是数组的第一个元素
      • 以后变量 next:如果 reduce()没有第二个参数,就是数组的第二个元素
    • 累计变量初始值

      • 累计变量初始值,即赋值给 prev,此时 next 就是数组的第一个成员

    const arr = [1, [2, [3, 4, 5, [6]]], 7, 8, 9]
    function flat(arr) {
    return arr.reduce((prev, next) => {
    return prev.concat(Array.isArray(next) ? flat(next) : next)
    // 返回拼接后的数组,如果以后变量是数组,recursive 执行
    }, [])
    // 这里指定了初始值是 []
    // prev = []
    // next = 1,因为 prev 初始值是空数组,所以 next 的初始值是 1

    // reduce((accumulate, currentValue, index, arr) => {….}, [])
    // 第一个参数:是一个函数
    // 第一个参数:累积变量,默认是数组的第一个元素
    // 第二个参数:以后变量,默认是数组的第二个元素
    // 第三个参数:以后地位(以后变量在数组中的地位)
    // 第四个参数:原数组
    // 第二个参数:累积变量的初始值,留神如果指定了初始值,那么以后变量就从数组的第一个元素开始
    }
    const res = flat(arr)
    console.log(res, ‘res’)
    // [1, 2, 3, 4, 5, 6, 7, 8, 9] “res”

  • 应用 … 开展运算符

    let arr = [1, [2, [3, 4]]];
    function flat(arr) {
    while (arr.some(item => Array.isArray(item))) {// 循环判断是不是数组,是数组就开展
    arr = [].concat(…arr); // 每次都扁平一层
    }
    return arr;
    }
    console.log(flat(arr))

XMLHttpRequest

  • 如何获取 response???

    • xhr.response
    • xhr.responseText
    • xhr.responseXML
    • xhr.responseText

      • 在 xhr.responseType = ‘text’,”,不设置时,xhr 实例对象上才有此属性,此时能力调用
    • xhr.response

      • 在 xhr.responseType = ‘text’,” 时,值是 (”)
      • 在 xhr.resposneType 是其余值时,值是 (null)
  • xhr.responseType

    • text
    • document
    • json
    • blob
    • arrayBuffer
    const xhr = new XMLHttpRequest()
    // new 命令总是返回一个对象,要么是 this 对象,要么是 return 前面跟的对象
    // new 调用的是构造函数,阐明 XMLHttpRequest 是一个构造函数
    
  • 初始化 HTTP 申请参数,比方 url,http 申请办法等,但并 (不发送申请)
  • xhr.open() 办法次要供 xhr.send() 办法应用

    xhr.open(method, url, async, username, password)

  • 参数

    • method:http 申请的办法,包含 GET POST HEAD
    • url:申请的地址
    • async:是否异步

      • true,默认值,异步申请,通常须要调用 onreadystatechange() 办法
      • false,对 send() 办法的调用将阻塞,直到响应齐全接管

    (2) xhr.send()

  • 发送一个 http 申请

    xhr.send(body)

  • get 申请:get 申请的参数能够间接写在 open() 办法中
  • post 申请:post 申请的参数写在 send() 办法中

    • 留神:

      • body 参数的数据类型会影响 requestHeader 中的 Content-Type 的默认值,如何手动指定则会笼罩默认值
      • 如果 data 是 Document 类型,同时也是 HTML Document 类型,则 content-type 默认值为 text/html;charset=UTF-8; 否则为 application/xml;charset=UTF-8;
      • 如果 data 是 DOMString 类型,content-type 默认值为 text/plain;charset=UTF-8;
      • 如果 data 是 FormData 类型,content-type 默认值为 multipart/form-data; boundary=[xxx]
      • 如果 data 是其余类型,则不会设置 content-type 的默认值

    (3) xhr.setRequestHeader()

  • 指定一个 http 申请的头部,只有在 readState = 1 时能力调用
  • setRequestHeader 能够调用的机会

      1. 在 readyStaet = 1 时
      1. 在 open() 办法之后,send() 办法之前
      1. 其实 1 2 是一个意思

    xhr.setRequestHeader(‘name’, ‘value’)

  • 参数

    • name:头部的名称
    • value:头部的值
  • 留神

    • setRequestHeader() 办法能够 ( 屡次调用),值不是 (笼罩 override) 而是 (追加 append)
    • setRequestHeader() 只有在 readyState = 1 时能力调用,即 open() 办法之后,send() 办法之前

    (4) xhr.getResponseHeader()

  • 指定 http 响应头部的值

    (5) xhr.abort()

  • 勾销以后响应,敞开连贯并且完结任何未决的网络流动
  • xhr.abort()会将 readyState 重置为 0
  • 利用:勾销申请,在申请耗时太长,响应不再有必要时,调用该办法
  • abort:是终止的意思

    (6) xhr.onreadystatecange()

  • 在 readyState 状态扭转时触发
  • xhr.onreadystatechange() 在 readyState = 3 时,可能屡次调用
  • onreadystatechange 都是小写
  • readyState 驼峰

    readyState 状态

  • UNSENT ————- xhr 对象胜利结构,open() 办法未被调用
  • OPEND ————- open() 办法被调用,send() 办法未被调用,setRequestHeader() 能够被调用
  • HEADERS_RECEIVED — send() 办法曾经被调用,响应头和响应状态曾经返回
  • LOADING ———— 响应体 (response entity body) 正在下载中,此状态下通过 xhr.response 可能曾经有了响应数据
  • NODE ————- 整个数据传输过程完结,不论本次申请是胜利还是失败

    (7) xhr.onload

  • 申请胜利时触发,此时 readyState = 4
  • 留神:重点!!!

    • 1. 除了在 xhr.onreadystatechange 指定的回调函数的 readyState = 4 时取值
    • 2. 还能够在 xhr.onload 事件中取值
      xhr.onload = function() {

      // 申请胜利
      if (xhr.status === 200) {// do successCallback}

      }

    • 3. 判断 xhr.status === 200 是有坑的,因为胜利时返回的状态码不只有 200,上面的写法更靠谱
      xhr.onload = function () {

      // 如果申请胜利
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
        // 304 not modified 资源未被批改 协商缓存
        // 2 结尾的状态码,示意申请胜利
        //do successCallback
      }

      }

    (8) xhr.timeout

  • 设置过期工夫
  • 问题 1:申请的开始工夫怎么确定?是 (xhr.onloadstart) 事件触发的时候,也就是 xhr.send()调用的时候
  • 解析:因为 xhr.open()只是创立了链接,当并没有真正传输数据,只有调用 xhr.send()时才真正开始传输
  • 问题 2:什么时候是申请完结?
  • 解析:(xhr.loadend) 事件触发时完结

    (9) xhr.onprogress 下载进度信息
    (10) xhr.upload.onprogress 上传进度信息
    xhr.upload.onprogress = function(e) {
    if (e.lengthComputable) {

    const present = e.loaded / e.total * 100;

    }
    }

  • XMLHttpRequest 申请案例

    XMLHttpRequest 申请案例
    
    ----
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <button id="buttonId"> 点击,申请数据 </button>
    <script>
      const button = document.getElementById('buttonId')
      button.addEventListener('click', handleClick, false)
    
      function handleClick() {const xhr = new XMLHttpRequest()
        xhr.open('GET', 'http://image.baidu.com/channel/listjson?pn=0&rn=30&tag1= 明星 &tag2= 全副 &ie=utf8', true) // open()办法
        xhr.setRequestHeader('Content-Type', 'application/json') // setRequestHeader 必须在 open()办法后,send()办法前调用,即 readyState === 1 时
        xhr.responseType = 'text'
        xhr.timeout = 10000
        xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {
            // 这里通过 this 代替 xhr 其实是一样的
            // 因为 this 在运行时确定指向,xhr 实例在调用 onreadystatechange 办法,所以 this 指向 xhr 实例
            console.log(JSON.parse(this.responseText)) // 等价于 console.log(JSON.parse(xhr.responseText))
          }
        }
        xhr.onload = function () {if ((xhr.status >= 200 && xhr.status < 300) || (xhr.status === 304)) {console.log(JSON.parse(xhr.responseText), 'xhr.onload 是在申请实现时触发的回调')
          }
        }
        xhr.send() // 发送申请}
    </script>
    </body>
    </html>

材料

call,apply 模仿实现 https://github.com/mqyqingfen…
bind 模仿实现 https://juejin.im/post/684490…
new 模仿实现 https://github.com/mqyqingfen…
new 模仿实现
1.https://github.com/mqyqingfen…
2.https://segmentfault.com/a/11…
3.https://segmentfault.com/a/11…
Throttle Debounce 我的掘金 https://juejin.im/post/684490…
数组乱序 https://juejin.im/post/684490…
数组扁平化 https://github.com/mqyqingfen…

正文完
 0