20230506----重返学习-嵌套循环返回相同结构对象-转成链式compose组合聚合函数-柯里化函数-面向对象梳理-重写new方法-鸭式变形-jQuery源码-模块管理的工具

66 阅读9分钟

day-063-sixty-three-20230506-嵌套循环返回相同结构对象-转成链式compose组合聚合函数-柯里化函数-面向对象梳理-重写new方法-鸭式变形-jQuery源码-模块管理的工具

嵌套循环返回相同结构对象

/* // 6. 下面代码输出结果
function fun(n, o) {
  console.log(o);
  return {
    fun: function (m) {
      return fun(m, n);
    },
  };
}
var c = fun(0).fun(1);
c.fun(2);
c.fun(3); */

function fun(n, o) {
  console.log(o);
  return {
    fun: function (m) {
      return fun(m, n);
    },
  };
}
var c = fun(0).fun(1); //fun(0)  --> {fun:function(m){return fun(m,n)}} -|- n=>0,o=>undeined; //fun(0).fun(1) --> fun(1,0) --> {fun:function(m){return fun(m,n)}} -|- n=>1,o=>0;
// c={fun:function(m){return fun(m,n)}} -|- n=>1;
c.fun(2); //fun(2,1) --> {fun:function(m){return fun(m,n)}} -|- n=>2,o=>1;
c.fun(3); //fun(3,1) --> {fun:function(m){return fun(m,n)}} -|- n=>3,o=>1;

//undefined
//0
//1
//1
  • 函数执行时的作用域链。

    • 函数定义时,必定存在于一个当前执行上下文。

      • 它存放在堆内存中的当前函数堆的[[scope]]里,它的值就是这个函数对象被定义时所在的当前执行上下文
    • 函数执行时,必定会开辟一个新的执行上下文

      • 作用域链是存在于当前函数堆的函数体字符串被执行时产生的函数私有作用域上的。作用域链有两个值,一个是函数执行时开辟出的新的执行上下文,一个是这个函数对象被定义时所在的当前执行上下文
let fang = "全局的fang";
let f = "全局中f";
let fn1 = null;
const fn2 = function () {
  let fang = "fn2的fang";
  let f = "fn2中f";
  fn1 = function () {
    let f = "私有f";
    console.log(f, fang);
  };
};
fn2();
const fn3 = function () {
  let fang = "fn3的fang";
  let f = "fn3中f";
  fn1();
};
fn3(); //"私有f","fn2的fang"
// fn3执行结束后,它的上下文就会被销毁。而fn2上一次的执行时产生的执行上下文依旧还会被保留。


//可以看到在fn3执行过程中执行的fn1的作用域链是<EC(f1),EC(fn2)>。
// 全作用域链是<EC(f1),EC(fn2),EC(G)>,原因是fn1的作用域链是<EC(f1),EC(fn2)>,fn2的作用域链是<EC(fn2),EC(G)>,当寻找到fn2的时候,就会去堆内存中fn2函数对象上的[[scope]]上找。直到找到全局执行上下文EC(G)。

转成链式compose组合聚合函数

/* // 7. 按照需求,实现相应的代码


    在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
    const add1 = (x) => x + 1;
    const mul3 = (x) => x * 3;
    const div2 = (x) => x / 2;
    div2(mul3(add1(add1(0)))); //=>3
​
    而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
    const operate = compose(div2, mul3, add1, add1)
    operate(0) //=>相当于div2(mul3(add1(add1(0)))) 
    operate(2) //=>相当于div2(mul3(add1(add1(2))))
​
    简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x),请你完成 compose函数的编写 

const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;*/



const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;

/* //for循环暴力逻辑。
const compose = function compose(...arg1) {
  return function (...arg2) {
    let res;
    for (let i = arg1.length - 1; i >= 0; i--) {
      if(res===undefined){
        res = arg1[i](...arg2);
      }else{
        res = arg1[i](res);
      }
      
    }
    return res;
  };
}; */
/* //for循环-仿reduceRight。
const compose = function compose(...arg1) {
  return function (arg2) {
    let res=arg2;
    for (let i = arg1.length - 1; i >= 0; i--) {
      res = arg1[i](res);
    }
    return res;
  };
}; */

/* //reduceRight。
const compose = function compose(...arg1) {
  return function (arg2) {
    let res = arg1.reduceRight((res, current) => current(res), arg2);
    return res;
  };
}; */


/* // 简易版-reduceRight
const compose = function compose(...params) {
    // params:数组,存储了需要执行的函数及其顺序 -> [div2, mul3, add1, add1]
    return function operate(x) {
        // x:第一个函数执行传递的实参 -> 0
        return params.reduceRight((result, item) => {
            // 第一轮:result=0 item=add1 -> add1(0)/1
            // 第二轮:result=1 item=add1 -> add1(1)/2
            // 第三轮:result=2 item=mul3 -> mul3(2)/6
            // 第四轮:result=6 item=div2 -> div2(6)/3
            return item(result);
        }, x);
    };
};
console.log(compose(div2, mul3, add1, add1)(0)); //3
 */


/* // 完整版-reduceRight-做了边界为0与1的处理,性能优化
const compose = function compose(...params) {
    let len = params.length;
    if (len === 0){ return x => x;}
    if (len === 1){ return params[0];}
    return function operate(x) {
        return params.reduceRight((result, item) => {
            return item(result);
        }, x);
    };
};
console.log(compose(div2, mul3, add1, add1)(0)); //3
console.log(compose(div2, mul3)(3)); //4.5
console.log(compose(div2)(10)); //5
console.log(compose()(10)); //10 
*/


let number = 9;
let res1 = div2(mul3(add1(number)));
let res2 = compose(div2, mul3, add1)(number);
console.log(res1, res2);

const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;
/* 
// 课后扩展:分析这段代码是如何处理的
const compose = function compose(...funcs) {
    if (funcs.length === 0){ return (arg) => arg;}
    if (funcs.length === 1){ return funcs[0];}
    return funcs.reduce((a, b) => {
        return (x) => {
            return a(b(x));
        };
    });
};
console.log(compose(div2, mul3, add1, add1)(0)); //3
// (x) => div2(mul3(x))//(x) => (x) => div2(mul3(add1(x)))//(x) => (x) => (x) => div2(mul3(add1(add1(x))))
console.log(compose(div2, mul3)(3)); //4.5
console.log(compose(div2)(10)); //5
console.log(compose()(10)); //10  
*/
// [].reduce()
//无第二个参数时,只有第一个回调函数作为入参。
let arr = [10, 20, 30, 40];
let res = arr.reduce((result, item, index) => {
    // 第一轮:result=10 item=20 index=1 -> 30「相当于从数组的第二项开始迭代」
    // 第二轮:result=30 item=30 index=2 -> 60
    // 第三轮:result=60 item=40 index=3 -> 100
    return result + item;
});
console.log(res); //100

//有第二个参数时。
let arr = [10, 20, 30, 40];
let res = arr.reduce((result, item, index) => {
    // 第一轮:result=0 item=10 index=0 -> 10「相当于从数组的第一项开始迭代」
    // 第二轮:result=10 item=20 index=1 -> 30
    // ...
    return result + item;
}, 0);
console.log(res); //100 

柯里化函数

  • 柯理化函数「Curring,高阶函数」思想,其实就是闭包保存功能的一种运用

    1. 我们基于闭包,把一些值事先存储起来
    2. 这样其下级上下文,想用的时候,直接基于作用域链去获取即可
const fn = function fn(...params) {
    // params:[1,2]
    return function (...args) {
        // args:[3]
        return params.concat(args).reduce((res, item) => res + item);
    };
};
let res = fn(1, 2)(3);
console.log(res); //=>6  1+2+3   
  • 无限执行函数

    const curring = function curring() {
        const operate = () => {
            return operate;
        };
        return operate;
    };
    //这样处理,可以保证:每一次函数执行完毕,都会返回一个operate函数,这样就可以一直执行下去了!!!
    
    • 这样处理,可以保证:每一次函数执行完毕,都会返回一个operate函数,这样就可以一直执行下去了!
// 8. 按照需求,实现相应的代码;
// 柯里化函数--不限入参个数与调用个数;
const curring = function curring(...params) {
    const operate = (...args) => {
        // 把每一次函数执行传递的实参,都存储到params中
        params = params.concat(args);
        return operate;
    };
    // 在部分浏览器中,当我们基于 console.log 输出函数的时候,会自动调用函数的 toString 方法「即便不支持这个操作的浏览器,我们只要基于 “+函数” 操作,也可以调用函数的 toString 方法」
    operate.toString = () => {
        // 把函数之前每一次函数执行,传递进来的所有值,进行求和处理
        return params.reduce((res, item) => res + item);
    };
    return operate;
};
let res = curring(10)(1)(2)(3);
console.log(+res); //->16

res = curring(5)(1, 2, 3)(4);
console.log(+res); //->15

res = curring(10, 20)(1)(2)(3)(4)(5);
console.log(+res); //->45
  • 在部分浏览器中,当我们基于 console.log 输出函数的时候,会自动调用函数的 toString 方法

    • 新版本的浏览器好像不是不会调用toString()方法了,而是不会再把toString()方法返回的值直接打印出来了。
    • 即便不支持这个操作的浏览器,我们只要基于+函数操作,也可以调用函数的 toString 方法
  • 柯里化自定义

    // 8. 按照需求,实现相应的代码;
    // 柯里化函数--不限入参个数与调用个数;
    const curring = function curring(...params) {
      // params[0]必定是一个函数
        const operate = (...args) => {
            // 把每一次函数执行传递的实参,都存储到params中
            params = params.concat(args);
            return operate;
        };
        // 在部分浏览器中,当我们基于 console.log 输出函数的时候,会自动调用函数的 toString 方法「即便不支持这个操作的浏览器,我们只要基于 “+函数” 操作,也可以调用函数的 toString 方法」
        operate.toString = () => {
            // 把函数之前每一次函数执行,传递进来的所有值,进行求和处理
            return params.slice(1).reduce((res, item) => params[0](res,item));
        };
        return operate;
    };
    let res = curring((a,b)=>a+b,10)(1)(2)(3);
    console.log(+res); //->16
    
    res = curring((a,b)=>a+b,5)(1, 2, 3)(4);
    console.log(+res); //->15
    
    res = curring((a,b)=>a+b,10, 20)(1)(2)(3)(4)(5);
    console.log(+res); //->45
    

面向对象梳理

  • 函数带与不带new执行的区别

    • 不带new函数执行
    • 带new函数执行
  • 函数

    • 大部分函数都具备prototype属性[原型对象]

      • 不具备:

        • 箭头函数

        • ES6为对象的某个成员快速赋值函数

          • let obj = {fn(){...}} fn就没有原型对象
    • 函数作为一个对象

      • 所有的对象(包含函数对象),都具备__proto__这个属性[原型链],属性值指向自己所属类的prototype
  • 构造函数执行 与 普通函数执行

    1. 构造函数也会和普通函数执行一样,产生私有的上下文,一步步进行处理
/* 
function Fn(x, y) {
    let sum = 10;
    this.total = x + y;
    this.say = function () {
        console.log(`我计算的和是:${this.total}`);
    };
}
let res = Fn(10, 20); //普通函数执行
let f1 = new Fn(10, 20); //构造函数执行
let f2 = new Fn;
console.log(f1.sum);
console.log(f1.total);
console.log(f1.say === f2.say); 
*/
// 打印输出的结果是?
function Foo(){
    getName = function () {
       console.log(1);
    };
    return this;
}
Foo.getName = function () {
    console.log(2);
};
Foo.prototype.getName = function () {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName() {
    console.log(5);
}
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3
  • 函数具备多种角色

    • 普通函数[私有作用域、闭包作用域、私有变量…]
    • 构造函数[私有作用域、类、实例、prototype、proto…]
    • 普通对象[静态私有属性方法…]
  • 函数变量

    • 由function声明,变量提升–声明和赋值都提升
    • 由var声明,变量提升-声明提升,赋值原地等候
    • 由等号赋值变成,不存在变量提升

重写new方法

// 5.重写new方法.js
function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}

const _new = function _new() {
    //=>完成你的代码   
}
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true
// 5.重写new方法.js
// 正常的new;
function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}
let sanmao = new Dog('三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true
  • 基础版本

    // 简易版new
    function Dog(name) {
        this.name = name;
    }
    Dog.prototype.bark = function () {
        console.log('wangwang');
    }
    Dog.prototype.sayName = function () {
        console.log('my name is ' + this.name);
    }
    // let sanmao = new Dog('三毛');  //我们的需求是重写内置的NEW操作,实现和其一样的效果
    
    const isObject = function isObject(obj) {
        return obj !== null && /^(object|function)$/.test(typeof obj);
    };
    /* 
    * _new 模拟内置的new操作,创造某个类的实例
    * @params 
    *   Ctor:操作的构造函数(类) //Ctor -> constructor 的缩写
    *   params:数组,存储给构造函数传递的实参
    * @return 
    *   我们创建的实例对象
    */
    const _new = function _new(Ctor, ...params) {
        // @1 创建 Ctor(Dog) 的实例对象「其__proto__指向Ctor.prototype」
        let obj = {};
        obj.__proto__ = Ctor.prototype;
    
        // @2 把构造函数执行:传递实参值 && 函数中的this需要指向创建的实例对象
        let result = Ctor.call(obj, ...params);
    
        // @3 监测其返回值,来决定最后返回实例还是其内部的返回值
        if (isObject(result)) return result;
        return obj;
    };
    let sanmao = _new(Dog, '三毛');
    sanmao.bark(); //=>"wangwang"
    sanmao.sayName(); //=>"my name is 三毛"
    console.log(sanmao instanceof Dog); //=>true
    
    • 模拟内置的new操作,创造某个类的实例

      • params: 操作的构造函数(类) 、数组,存储给构造函数传递的实参
      • return: 创建的实例对象
      • 步骤
        1. 创建 Ctor(Dog) 的实例对象「其__proto__指向Ctor.prototype」
        2. 把构造函数执行:传递实参值 && 函数中的this需要指向创建的实例对象
        3. 监测其返回值,来决定最后返回实例还是其内部的返回值
// Object.create(proto)
// new 的优化---做限制
function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}

const isObject = function isObject(obj) {
    return obj !== null && /^(object|function)$/.test(typeof obj);
};
const _new = function _new(Ctor, ...params) {
    if (typeof Ctor !== "function" || !Ctor.prototype || Ctor === Symbol || Ctor === BigInt) {
        throw new TypeError("Ctor is not a constructor");
    }
    let result, obj;
    obj = Object.create(Ctor.prototype);
    result = Ctor.apply(obj, params);
    if (isObject(result)) return result;
    return obj;
};
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true

鸭式变形

  • 在js中有很多伪xxx类xxx的概念

    1. 伪数组类数组

      • 对于数组来讲,它的结构特征:数字作为索引、索引从0开始逐级递增,具备length属性记录其长度

      • 每一个数组都是Array类的实例,其__proto__指向Array.prototype

        • 所以其可以直接使用Array原型对象上的push()/pop()/forEach()…等方法
      • 为数组在结构上和数组几乎一模一样,但是其__proto__并不指向Array.prototype

        • 所以不能直接调用数组提供的方法
        • 常见的伪数组有:arguments、元素集合、节点集合…
    2. 伪promise、类promise或者thenable

      • 基于new Promise()所创造出来的实例,被称为标准的promise实例对象,其拥有状态和值,也可以调用Promise.prototype上的then/catch/finally这些方法
      • 可以遵照PromiseA+规范,自己创造一个同样具备状态和值,也具备then方法的实例,此实例被称为类promise或thenable
  • 在平时开发中,往往会出现这样的需求:让伪数组调用数组的方法,去实现相应的功能,此时就需要对伪数组做一些特殊的处理,而这个过程,有人把其叫做鸭式变形/鸭子类型/鸭式辨型

    • 不是鸭子,但却和鸭子差不多。像鸭子一样走路并且嘎嘎叫的就叫鸭子。
  • 特殊的处理方案

    1. 把需要借用的方法,赋值给伪数组的私有属性

      • 原理:首先要保证伪数组可以访问到这个方法;然后把方法执行,让方法中的this变为要操作的伪数组;因为伪数组和数组的“结构几乎一模一样”,所以操作数组的那些代码,“对于伪数组也基本上都生效”;
      let obj = { 0: 10, 1: 20, length: 2 };
      obj.push(30) //报错:obj.push is not a function
      obj.push = Array.prototype.push;
      obj.push(30) //正常处理
      
    2. 基于上述原理的分析,我们只需要把数组的方法执行,“让方法中的this变为伪数组”,这样就相当于伪数组直接借用数组的方法,去实现相应的效果了!

      let obj = { 0: 10, 1: 20, length: 2 };
      Array.prototype.push.call(obj,30)
      [].push.call(obj,30)
      [].forEach.call(obj,(item,index)=>{
        //...
      })
      //...
      
    3. 其实我们还可以直接修改伪数组的原型指向,让其指向 Array.prototype,这样数组的所有方法,伪数组都可以直接调用了!!

      let obj = { 0: 10, 1: 20, length: 2 };
      // obj.__proto__=Array.prototype
      Object.setPrototypeOf(obj, Array.prototype);
      
    4. 把伪数组直接转换为数组后,再去进行相应的操作即可

      let obj = { 0: 10, 1: 20, length: 2 };
      let arr = Array.from(obj);
      arr = [...obj];
      arr = [].slice.call(obj,0);
      //...
      
/* //
obj是一个类(伪)数组,不能直接使用数组的方法!但是我们的目的:期望它可以使用数组的方法! ==> “鸭式辨型、鸭子类型”
  方案一:把类数组转换为数组 或者 让其原型链指向Array.prototype
    让其原型链指向Array.prototype
      + obj.__proto__ = Array.prototype
      + Object.setPrototypeOf(obj, Array.prototype)  推荐
    把类数组转换为数组
      + Array.from(obj)
      + [...obj] 不是所有的类数组对象都支持,类似于arguments这样的类数组集合才可以这样处理
      + 基于循环,把类数组中的每一项分别赋值给数组
      + 借用数组原型上的slice,把slice方法执行的时候,让方法中的this指向obj类数组,这样就可以“把类数组转换为数组” 
        [].slice.call(obj) 或者 Array.prototype.slice.call(obj)
        
  方案二:类数组借用数组方法,去实现想要的效果
    原理:把数组原型上的方法执行,让方法中的this指向类数组,这样就相当于在操作类数组
    前提:this指向的值,它的结构和相关操作,需要和数组保持一致
    Array.prototype.xxx.call(类数组)
    [].xxx.call(类数组)
    ---
    把需要借用的方法赋值给类数组的某个私有属性
*/

jQuery源码

  • JavaScript代码可以运行的环境

    • 浏览器

      • 具备window全局对象:typeof window==="object"

      • 可以使用ES6Module(简称ES)模块规范,但是不支持CommonJS模块规范

        • ES模块规范:import、export、export default…
        • CommonJS模块规范:require、module.exports…
    • NodeJS

      • 全局对象是global,没有window对象:typeof window==="undefined"
      • 支持CommonJS模块规范,但是不支持ES模块规范
    • webpack

      • 原理:基于NodeJS实现模块的打包,最后把打包后的内容部署到服务器,交给浏览器运行

      • 具备window全局对象

      • 支持ES模块规范和CommonJS模块规范,而且可以让两者混淆使用

        • CommonJS导出的或ES模块导出的,都可以让CommonJS或ES模块导入

模块管理的工具

  • npm 于node.js中自带,官方node模块版本管理,个人推荐。毕竟出了问题,也好找解决方案。

    • 2023年5月要求:[node版本>=18]

      • nodejs官网
      • 2023年05月推荐使用18.16.0这个稳定版本
    • 查看node版本

      node -v
      
  • yarn 速度快,兼容性也有,还有脸书背书。

    • 不过个人不推荐,有点老了,也不是官方的。但老的兼容还是可以的

    • 安装yarn

      npm i yarn -g
      
    • 安装模块

      $ yarn add xxx
      //....
      
  • pnpm 新出的,速度快,有新特性,类npm。

    • 安装pnpm

      $ npm i pnpm -g //windows
      $ sudo npm i pnpm -g //MAC
      //....
      

进阶参考