[深入20] 手写函数

1,329 阅读26分钟

导航

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

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程

前置知识

(1) 包装对象

  • 包装对象:Number,String,Boolean三个对象可以把原始类型的值变成对象 -> new 调用
  • number,string,boolean三个原始类型的值,在一定的条件下也会自动转为对象,也就是原始类型的包装对象
  • 包装对象的目的
    • 使得js中的对象涵盖所有的值
    • 使得 ( 原始类型 ) 的值,可以方便的 ( 调用某些方法 )
      • 比如 toString valueOf
  • 总结 Nubmer,String,Boolean
    • 作为构造函数:通过new命令调用,可以将 ( 原始类型 ) 的值转为 ( 对象 )
    • 作为普通函数:可以将 ( 任何类型 ) 的值,转换成 ( 原始类型 ) 的值

(2) 构造函数

  • 特点:
    • 构造函数首字母通常大写
    • 使用 new 命令调用,new命令调用时,可以在函数名后面加小括号,也可以不加
    • 构造函数内部的this指向实例对象
    • 使用构造函数,属性和方法都生成在实例上的,彼此之间不共享
    • new 命令总会返回一个对象,要么是实例对象,要么是return后指定的对象
      • 如果构造函数内 ( return ) 返回的是一个 ( 对象 ),new命令就会返回回这个 ( 对象 )
      • 如果构造函数内 ( return ) 返回的是 ( 原始类型的值 ),new命令就会返回 ( this对象 )
      • ( 普通函数 ) 使用 ( new )命令,会返回一个 ( 空对象 )
  • new命令的原理
    • 创建一个空对象,即是需要返回的那个实例对象

    • 将空对象的原型,指向构造函数的prototype属性

    • 将构造函数内部的this绑定到这个空对象上

    • 执行构造函数

(3) 函数中 arguments 和 length 的区别

  • argumens表示函数 ( 实参个数 )
  • Function.length 表示函数 ( 形参个数 )
function fn(name){ 
    console.log(arguments) // 实参相关 Arguments { 0: "woow_wu7", 1: "woow_wu8", ...}
    console.log(fn.length) // 形参相关 1
}
fn('woow_wu7', 'woow_wu8')

一些单词

immediate: 立即的
shuffle: 洗牌

flatten:扁平的
infinity:无穷

recursive:递归

(一) 2022/03/31 - 手写 JSON.stringify()

前置知识 - 手写JSON.strinfify()
---
1
JSON
- 概念
  - JSON 是一种数据交换的 ( 文本格式 )
- 类型和格式
  - 1. 引用类型 - 复合类型的值只有 2 种
    - 只能是 ( 对象 和 数组 )
    - 不能是 ( 函数,正则对象,日期对象 )
  - 2. 原始类型的值只有 4 种
    - 只能是 ( number string boolean null )
    - 不能是 ( undefined NaN Infinity -Inifinity )
  - 3. 字符串
    - 只能是 ( 双引号 ),不能是 ( 单引号 )
  - 4. 对象
    - 对象的 ( 键名 ) 必须放在 ( 双引号 ) 中
  - 5. 最后一个成员
    - 数组或者对象的最后一个成员 不能加逗号 ,
   
   
2
JSON.stringify()
- 函数签名
  - JSON.stringify(value[, replacer [, space]])
- 对象
  - 属性的值是 undefined function xml对象时,------------- 直接忽略
- 数组
  - 属性的值是 undefined function xml对象时,------------- 都转成 null
- 正则对象
  - 转成空对象
- 第二个参数
  - 数组 ----- 表示白名单
  - 函数 ----- 表示处理函数,处理函数递归处理每个键
- 例子
var obj = { a: undefined, b: function () {} };
JSON.stringify(obj) // "{}" --------------- 因为对象的属性是 undefined function xml对象时,会被过滤
--
var arr = [undefined, function () {}];
JSON.stringify(arr) // "[null,null]" ------ 因为数组的成员是 undefined function xml对象时,都会被转成null
--
JSON.stringify(/abc/) // {} --------------- 因为正则对象会被转化成空对象


3
JSON.parse(a,b)
------
JSON.parse("'1'")
- 报错
  - Uncaught SyntaxError: Unexpected token ' in JSON at position 0
- 原因
  - 因为 '1' 不符号JSON格式,JSON中的字符串必须是 ( 双引号 ),即 ( "1" )
- 对比
  - JSON.stringify('"1"') // 正确
  - JSON.stringify("'1'") // 错误
手写JSON.stringify()

---
1. 函数签名 - JSON.stringify(value[, replacer [, space]])

---
2. JSON.stringify() 各种参数类型的返回值
- undefined ------------- undefined
- function -------------- undefined
- Symbol ---------------- undefined
- 对象中有 undefined function Symbol ---- 忽略者几种值
- 数组中有 undefined function Symbol ---- 'null'
- null --------------------------------- 'null'
- NaN ---------------------------------- 'null'
- Infinity ----------------------------- 'null'
- 正则对象 ------------------------------ '{}'
- number 'number的值'
- string 'string的值'
- boolean 'boolean的值'
- 引用类型的值
  - 1. 如果有 toJSON() 方法,那么序列化 toJSON() 的返回值
  - 2. 属性的值是 undefined function symbol 类型,直接略过
- 注意以上是有规律的
  - undefined function Symbol 单独作为参数,作为对象的值,作为数组成员的不同区别
    - 单独作为参数 ------ 返回 'undefined'
    - 对象中的属性值 ---- 直接略过
    - 数组中的成员 ------ 转成 null


---
3. 手写
function JSONStringify(params) {
  const type = Object.prototype.toString.call(params);
  const isNumber = type.includes("Number");
  const isString = type.includes("String");
  const isBoolean = type.includes("Boolean");
  const isNull = type.includes("Null");
  const isUndefined = type.includes("Undefined");
  const isSymbol = type.includes("Symbol");
  const isFunction = type.includes("Function");
  const isArray = type.includes("Array");
  const isObject = type.includes("Object");

  const isUndefinedFunctionSymbol = (data) => {
    return typeof data === 'undefined' ||
    typeof data === 'function' ||
    typeof data === 'symbol'
    }

  // BigInt ---- const isBigInt = type.includes('BigInt') ------ 注意 Json.stringify() 不能处理BigInt
  // NaN ------- 通过 Number.isNaN() 来判断
  // Infinity -- 通过直接是否相等来判断
  // string ---- string返回的是 "string" 双引号的字符串
  if (isUndefined || isFunction || isSymbol) return undefined;
  else if (isNull || Number.isNaN(params) || params === Infinity) return "null";

  // string
  else if (isString) return '"' + params + '"'; // 这里必须是双引号

  // number boolean
  else if (isNumber || isBoolean) return String(params);


  // function
  else if (params.toJSON && isFunction) { // 对象可能内置toJSON方法来自定义序列化对象
    return jsonStringify(data.toJSON());
  }

  // array
  else if (isArray) {
    let resultArr = [];
    for (let i = 0; i < params.length; i++) {
      if (isUndefinedFunctionSymbol(params[i])) { // 数组中有 undefined function symbol 转成 "null"
        resultArr[i] = "null";
      } else {
        resultArr[i] = JSONStringify(params[i]); // 递归调用自己
      }
    }
    console.log('resultArr', resultArr)
    resultArr = "[" + resultArr + "]";
    return resultArr.replace(/'/g, '"'); // 将单引号改成双引号
  }

  // object
  else if (isObject){
    console.log('3', 3)
    let result = [];
    Object.keys(params).forEach((item, index) => {
      if (typeof item !== "symbol") {
        //key 如果是 symbol 对象,忽略
        if (
          params[item] !== undefined && typeof params[item] !== 'function' && typeof params[item] !== 'symbol'
        ) {
          //键值如果是 undefined、function、symbol 为属性值,忽略
          result.push('"' + item + '"' + ":" + JSONStringify(params[item]));
        }
      }
    });
    return ("{" + result + "}").replace(/'/g, '"');
  }
}

const res = JSONStringify({a: 1, b: "1", c: false,  e: undefined, f: function(){}, g: Symbol(1), h: null, i: NaN, j: [1,'1',false, undefined, function(){}, Symbol(1), null, NaN]});
console.log("res: ", res);
console.log('typeof res', typeof res)

image.png

image.png

call 模拟实现

  • call方法的作用

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

    • 应该是一个 ( 对象 )
    • 如果传入 ( null,undefined,空 ),则默认传入 ( 全局对象 ) window/global
    • 如果参数是 ( 原始值 ) 那么这个原始值会自动转成对应的 ( 包装对象 ),然后传入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() 的参数
    - 当为空,nullundefined时,表示传入全局对象window或者global
    - 当为原始类型的值时,会被转为包装对象再传入fn
    - 第一个参数是fn中this指向的对象,其他参数将作为调用fn时的参数
2. fn可以有返回值
3. 实现思路
    - 在对象上 (添加) 函数 (方法)
    - 在该对象上(执行)该函数,此时this的指向就是需要绑定的函数(运行时确定this指向),fn._call(obj)中this指向fn
        - 因为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时可以只传部分参数
      • 剩余的参数在返回的新函数被调用时传入
    • 返回的新函数,可以使用new命令调用,即返回的新函数可以作为 ( 构造函数 ),这种情况下
      • bind时绑定的this失效,因为在构造函数中,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>
        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>

2021/09/28复习优化 - bind

// bind
// 1
// 概念
//  - a. 将函数体中的 this 绑定到某个对象
//  - b. 返回一个新的函数
//  - c. 可以只传入部分参数,即bind时可以传入部分参数,剩余的参数在返回的新函数被调用时c
//  - d. bind返回的函数,可以作为 ( 构造函数 ) 使用
//        - 即可以通过 ( new ) 命名来调用bind返回的函数
//        - 当bind返回的函数作为 ( 构造函数 ) 使用时,fn.bind()所绑定的对象失效,因为在构造函数中this指向了实例对象,而不是bind绑定的对象了

// 第一版
// - 先解决ab两个,a绑定this,b返回新函数
Function.prototype.bind1 = function () {
  const self = this;
  const params = Array.prototype.slice.call(arguments);
  const context = params.shift();
  return function () {
    return self.apply(context, params);
  };
};
function fn1() {
  return this.age;
}
const obj1 = { age: 30 };
const resFn1 = fn1.bind(obj1);
const res1 = resFn1();
console.log(`res1`, res1);

// 第二版
// - 解决abc,添加解决传惨的逻辑
// - 收集参数:主要通过在两层函数中分别收集参数,然后在最后调用的时,拼接收集到的所有参数
Function.prototype.bind2 = function () {
  const paramsBind = [...arguments]; // 第一层函数的参数
  const context = paramsBind.shift();
  const self = this;
  return function () {
    const paramsOther = [...arguments]; // 第二层函数的参数
    const paramsTotal = paramsBind.concat(paramsOther); // 拼接
    return self.apply(context, paramsTotal);
  };
};
function fn2(number, number2) {
  return this.age + number + number2;
}
const obj2 = { age: 300 };
const resFn2 = fn2.bind2(obj2, 100);
const res2 = resFn2(100);
console.log(`res2`, res2);

// 第三版
// - 解决d,bind返回的函数可以作为构造函数
// - 注意点:当bind返回的函数作为 ( 构造函数 ) 使用时,fn.bind()所绑定的对象失效,因为在构造函数中this指向了实例对象,而不是bind绑定的对象了
// - 实现原理:修改返回函数的原型来实现
//    - 将 ( bind返回函数callbackFn的prototype ) 绑定在 ( bind被绑定函数fn3的prototype ) 上,这样如果通过new调用callbackFn,则实例this就能继承fn3的prototype上的属性和方法
//    - 优化的话:可以用 ( 寄生组合式继承 ) 代替 ( 原型链式继承 )
Function.prototype.bind3 = function () {
  const paramsBind = Array.prototype.slice.apply(arguments); // 除了obj3以外的其他参数,这些参数会作为部分参数,传入fn3
  const context = paramsBind.shift(); // fn3中this需要绑定的对象
  const self = this; // 外层函数的this,函数运行时决定this指向,这里this执行fn3,即self就是fn3

  // 兼容性 - bind必须通过函数调用
  if (typeof self !== "function") {
    throw new Error("bind must be invoked by a function");
  }

  function callbackFn() {
    const paramsOther = Array.prototype.slice.apply(arguments);
    const paramsTotal = paramsBind.concat(paramsOther);

    return self.apply(this instanceof self ? this : context, paramsTotal); // 调用fn3
    // 1 callbackFn被调用时
    //    - this instanceof self 如果是true,说明是通过new调用的callbackFn,因为下面已经有了 callbackFn.prototype = self.prototype,---> 最终绑定 this 实例对象
    //    - this instanceof self 如果是false,说明不是通过new调用,作为普通函数调用,则传入被绑定的对象,----------------------------------> 最终绑定 context 传入的参数对象
    // 2 instance instanceof constructor 原理
    //    - 判断 ( 右边构造函数.prototype ) 是否在 ( 左边实例对象 ) 的原型链上
    //    - 这里:通过new调用的话,fn3.prototype 是 this 的原型对象,所以返回true,返回true就绑定在实例对象this上,否则正常函数调用绑定在参数对象上
  }

  // callbackFn.prototype = self.prototype
  // 1 如果 callbackFn 是作为构造函数调用,即通过new命令来调用的话,( 实例 ) 就能继承 ( 被绑定函数prototype ) 上的属性和方法
  // 2 如果 callbackFn 不是作为构造函数调用,那么self.prototype也是函数的prototype,只要不使用继承属性和方法,影响也不大

  // 优化:用 ( 寄生式继承 - 更进一步的话-寄生组合式继承 ) 代替 ( 原型链式继承 ),这样解决实现 ( 多继承,父类被多次调用 ) 等
  function ParasiticFn() {}
  ParasiticFn.prototype = self.prototype;
  callbackFn.prototype = new ParasiticFn();

  return callbackFn;
};
function fn3(name, age) {
  this.name = name;
  return this.name + age;
}
fn3.prototype.age = 30000;
const obj3 = {
  name: "woow_wu7",
};
const resFn3 = fn3.bind3(obj3, "woow_wu8");

// new调用
const res3 = new resFn3(200);
console.log(`res3.name`, res3.name);
console.log(`res3.age`, res3.age);

// 普通函数调用
const res33 = resFn3(200);
console.log(`res33`, res33);

图片.png

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)
                    // 我之前在写啥啊?在箭头函数中用this???
                }
                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>

2021/09/28复习优化 - debounce

 // debounce
      // 需求
      // 1. 可以 ( 立即执行 ) immediate
      // 2. 可以 ( 取消执行 ) cancel
      // 3. 可以传参 e ...rest

      /**
       * @description: 防抖函数
       * @param {*} fn
       * @param {*} configs delay延时 immediate是否立即执行
       * @param {array} params fn的参数
       * @return {*}
       */
      function debounce(fn, configs, ...params) {
        let timer = null;

        const { delay = 1000, immediate = false } = configs || {};

        // 取消函数 - 取消fn的执行
        debounce.cancel = function () {
          globalThis.clearTimeout(timer);
        };

        function callback(e) {
          if (immediate && !timer && fn) {
            // 1. immediate = true 立即执行 fn
            // 2. timer 不能存在,因为存在的话,证明不是第一次执行,之后的执行不能再是立即执行了
            // 3. fn 必须存在,因为要立即执行 fn

            timer = true;
            // timer = true 是为了保证立即执行只会出现在第一次,第二次timer存在则不再进入

            return fn.call(this, e, ...params);
            // - 将fn中的this绑定到调用callback时所在的对象上
            // - 传入 params 立即执行 fn
            // - return 是为了不再往下执行
          }

          if (timer) globalThis.clearTimeout(timer); // 先清掉,再计时
          timer = globalThis.setTimeout(() => fn(e, ...params), delay);
        }

        return callback;
      }

      const runButton = document.getElementById("run");
      const cancelButton = document.getElementById("cancel");
      runButton.addEventListener(
        "click",
        debounce(fn, { delay: 4000, immediate: true }, "params1", "params2"),
        false
      );
      cancelButton.addEventListener("click", () => debounce.cancel(), false);

      function fn(e, ...rest) {
        console.log(`e.target`, e.target);
        console.log(`...rest`, ...rest);
      }

图片.png

2022/06/04 复习优化 - debounce

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="button">点击-触发debounce</button>
    <button id="cancelButton">点击-取消debounce</button>
    <script>
      // debounce 防抖函数
      // - 防抖
      // - 取消防抖
      // - 立即执行
      // - 处理函数可以传参
      // - 处理函数可以是异步函数
      const debounce = (fn, config, ...params) => {
        let { delay, immediate } = config;
        let timer = null;

        debounce.cancel = () => clearTimeout(timer);

        return async (event) => {
          if (immediate) {
            await fn(event, ...params);
            immediate = false;
            return;
          }
          if (timer) {
            clearTimeout(timer);
          }
          timer = setTimeout(async () => {
            await fn(event, ...params);
          }, delay);
        };
      };

      const fn = async (e, number) => {
        console.log("e.target", e.target);
        await new Promise((resolve) =>
          setTimeout(() => resolve(11111), 1000)
        ).then((value) => console.log(value));
        console.log(number);
      };

      const button = document.getElementById("button");
      const cancelButton = document.getElementById("cancelButton");
      button.addEventListener(
        "click",
        debounce(fn, { delay: 3000, immediate: true }, 3),
        {
          capture: false,
        }
      );
      cancelButton.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>

2021/09/28复习优化 - throttle

 // throttle
      // 需求
      // 1. 正常的throttle功能
      // 2. 可以传参
      // 3. 可以取消 cancel
      // 4. 可以立即执行 immediate
      // 5. 需要支持 异步函数

      function throttle(fn, options, ...params) {
        let isRun = true;
        let timer = NaN; // timer 是供给 cancel 和 clearTimeout 消费

        let { delay = 1000, immediate = false } = options || {};

        // 取消执行
        throttle.cancel = function () {
          globalThis.clearTimeout(timer);
          isRun = false; 
          // 注意:这里当取消请求后,需要将 isRun 重置为 true,不然永远不会重新执行
        };

        return async function (e) {
          if (immediate) {
            await fn.call(this, e, ...params);
            immediate = false; // 除了次一次后,不再执行immediate=true的逻辑
            return;
          }
          if (isRun) {
            isRun = false;
            // const  timer = globalThis.setTimeout() 注意这里不能在这里声明timer,因为还要支持cancel功能,即cancel函数中也需要拿到timer,所以只能放在外层
            timer = globalThis.setTimeout(async () => {
              await fn(e, ...params);
              isRun = true;
              globalThis.clearTimeout(timer);
            }, delay);
          }
        };
      }

      const button = document.getElementById("throttle-button");
      button.addEventListener(
        "click",
        throttle(
          fnAsync,
          { delay: 2000, immediate: true },
          "params1",
          "params2"
        ),
        false
      );

      // 异步
      function fnAsync() {
        console.log("1111");
        return new Promise((resolve) =>
          setTimeout(() => {
            console.log("2222");
            resolve(1);
          }, 2000)
        );
      }

      // 同步
      // function fnSync() {
      //   console.log("33333");
      // }

图片.png

Object.create() 模拟实现

  • 以参数对象为原型,生成实例对象
<!DOCTYPE html> 
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // Object.create()
      // 功能:以参数对象为原型,生成实例对象,实例对象完全继承参数对象的属性和方法
      // 使用:const res = Object.create(target)
      
      // 2022.09.07 更新
      Object.create 模拟实现
        - 作用:
          - 以参数对象为原型,新建实例对象
          - 即可以让一个对象继承另一个对象,实例对象完全继承参数对象的属性和方法
        - 实现原理
          - 利用 ( 中间函数 - 中间函数没有任何属性和方法 ) 式 ( 原型链继承 )
          
      ---
      function _create(target) {
        function Parasitic() {} 
        // parasitic是寄生的意思,这里就是一个 ( 空的构造函数 )
        // 因为中间函数内没有任何代码,所以不存在 ( 构造函数式继承,即生成的实例本身没有任何属性和方法,只有继承的属性和方法 )
        
        Parasitic.prototype = target;
        // 将 中间函数的 prototype 属性指向 参数对象,这样中间函数的实例就能继承参数对象的属性和方法

        return new Parasitic(); // 其实就是实现一个 ( 原型链继承 )
      }

      const obj = { name: "woow_wu7" };
      const instance = _create(obj);
      console.log(`instance`, instance);
    </script>
  </body>
</html>

数组乱序 - shuffle

  • sort
    • 默认会按照字典进行排序,数字会先被转成字符串,在排序
    • 改变原数组,没有返回值
    • 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')
3. 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)
4. 
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"

5. 使用 ...展开运算符

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是一个构造函数

(1) xhr.open()
- 初始化 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 时
  - 2. 在 open() 方法之后,send() 方法之前
  - 3. 其实 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状态
0 UNSENT ------------- xhr对象成功构造,open() 方法未被调用
1 OPEND  ------------- open() 方法被调用,send() 方法未被调用,setRequestHeader() 可以被调用
2 HEADERS_RECEIVED --- send() 方法已经被调用,响应头和响应状态已经返回
3 LOADING ------------ 响应体 ( response entity body ) 正在下载中,此状态下通过 xhr.response 可能已经有了响应数据
4 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>

2022/10/06 - typeof

  • 2022-10-06
// 手写 typeof
// - 返回值: number string boolean undefined object function symbol bigint

// 思路: 利用 Object.prototype.toString.call() 来实现

// 注意点
//  - 因为 Object.prototype.toString.call() 是对象类型时,会返回具体的对象,比如返回 Date RegExp Error Array
//  - 所以我们应该枚举其他类型,剩下的都返回object,我们使用 map 来枚举非对象类型

// 测试
console.log(typeof Symbol());
console.log(typeof BigInt(1));
console.log(BigInt(1) + 1n);

const _typeof = (data) => {
  const typeStr = Object.prototype.toString
    .call(data)
    .slice(8, -1)
    .toLowerCase();

  // 除了map中枚举的类型外,typeof 都返回 object
  const map = {
    number: true,
    string: true,
    boolean: true,
    undefined: true,
    symbol: true,
    bigint: true,
    function: true,
  };

  return map[typeStr] ? typeStr : "object";
};

const type = _typeof([]);
console.log("type: ", type);

资料

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