JS手写代码

78 阅读3分钟

手写call

Function.prototype.myCall = function (context: any, ...args: any[]) {
    if (typeof this !== 'function') {
      return;
    }

   if (context === null || context === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        context = window 
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    
    // 生成唯一的fn
    const fn = Symbol('myCall');
    // contextObj增加fn属性为this,this就是要调用的函数。其实也就是为了后面调用时改变函数的this指向,因为函数的this是谁调用指向谁
    contextObj[fn] = this;
    // 调用函数
    const result = contextObj[fn](...args);
    // 删除fn属性
    delete contextObj[fn];
    // 返回结果
    return result;
};

// 测试
const test = {
  name: "xxx",
  hello: function () {
    console.log(`hello,${this.name}!`);
  },
  add: function (a, b) {
    return a + b;
  },
};
const obj = { name: "world" };
test.hello.myCall(obj); // hello,world!
test.hello.call(obj); // hello,world!
console.log(test.add.myCall(null, 1, 2)); // 3
console.log(test.add.call(null, 1, 2)); // 3

手写apply

Function.prototype.myApply = function (context: any, argsArr: any[]) {
    if (typeof this !== 'function') {
      return;
    }

    if (!argsArr || !Array.isArray(argsArr)) {
      return;
    }

    if (context === null || context === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        context = window 
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    // 生成唯一的fn
    const fn = Symbol('myApply');
    // contextObj增加fn属性为this,this就是要调用的函数。其实也就是为了后面调用时改变函数的this指向,因为函数的this是谁调用指向谁
    contextObj[fn] = this;
    // 调用函数
    const result = contextObj[fn](...argsArr);
    // 删除fn属性
    delete contextObj[fn];
    // 返回结果
    return result;
};

// 测试
const test = {
  name: "xxx",
  hello: function () {
    console.log(`hello,${this.name}!`);
  },
};
const obj = { name: "world" };
test.hello.myApply(obj); // hello,world!
test.hello.apply(obj); // hello,world!
const arr = [2,3,6,5,1,7,9,5,0]
console.log(Math.max.myApply(null,arr)); // 9
console.log(Math.max.apply(null,arr)); // 9

手写bind

Function.prototype.myBind = function (context, ...args) {
    if (typeof this !== 'function') {
      throw 'fn must be a function';
    }

    // 获取当前上下文
    const conttextObj = context || globalThis;

    // 保存this,也就是最后要调用的函数
    const _this = this;

    // 返回一个函数作为最后调用的函数
    return function fn(...arguments) {
      // 判断是不是把函数new了(new调用时this指向实例对象)
      if (this instanceof fn) {
        return new _this(...args, ...arguments);
      }
      return _this.apply(conttextObj, [...args, ...arguments]);
    };
};

Function.prototype.myBind = function (context, ...args) {
    if (typeof this !== 'function') {
      throw 'fn must be a function';
    }

    // 获取当前上下文
    const conttextObj = context || globalThis;

    // 生成唯一的fn
    const fn = Symbol('myBind');

    // 保存this,也就是最后要调用的函数
    conttextObj[fn] = this;

    // 返回一个函数作为最后调用的函数
    return function fn(...arguments) {
      // 判断是不是把函数new了(new调用时this指向实例对象)
      if (this instanceof fn) {
        return new conttextObj[fn](...args, ...arguments);
      }
      return conttextObj[fn](...args, ...arguments);
    };
};

// 测试
const test = {
  name: "xxx",
  hello: function (a,b,c) {
    console.log(`hello,${this.name}!`,a+b+c);
  },
};
const obj = { name: "world" };
let hello1 = test.hello.myBind(obj,1);
let hello2 = test.hello.bind(obj,1); 
hello1(2,3) // hello,world! 6
hello2(2,3) // hello,world! 6
console.log(new hello1(2,3));
// hello,undefined! 6
// hello {}
console.log(new hello2(2,3));
// hello,undefined! 6
// hello {}

手写new

function myNew(fn, ...args) {
    if (typeof fn !== 'function') {
      throw 'fn must be a function';
    }
    // 创建一个js对象,并且将该js对象的隐式原型指向构造函数的原型
    const obj = Object.create(fn.prototype);
    // 执行构造函数,并且还更新了this指向
    const result = fn.apply(obj, args);

    // 判断函数是否返回有返回对象,如果有就返回该函数的返回对象,否则返回新对象
    return result && typeof result === 'object' ? result : obj;
}

function myNew(fn, ...args) {
    if (typeof fn !== 'function') {
      throw 'fn must be a function';
    }
    // 创建一个j空对象
    const obj = Object.create(null);
    // 将对象的原型设为fn的原型
    Object.setPrototypeOf(obj, fn.prototype);
    // 执行构造函数,并且还更新了this指向
    const result = fn.apply(obj, args);

    // 判断函数是否返回有返回对象,如果有就返回该函数的返回对象,否则返回新对象
    return result && typeof result === 'object' ? result : obj;
}

function myNew(fn, ...args) {
    if (typeof fn !== 'function') {
      throw 'fn must be a function';
    }
    // 1.创建一个新对象
    const obj = {};
    // 2.新对象原型指向构造函数原型对象
    obj.__proto__ = fn.prototype;
    // 3.将构建函数的this指向新对象
    const result = fn.apply(obj, args);
    // 4.根据返回值判断
    return result instanceof Object ? result : obj;
}

手写instanceof

// instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
function myInstanceof(obj, ctor) {
    let proto = Object.getPrototypeOf(obj);
    const ctorProto = ctor.prototype;

    while (true) {
      if (!proto) {
        return false;
      }

      if (proto === ctorProto) {
        return true;
      }

      proto = Object.getPrototypeOf(proto);
    }
}

简单的双向绑定

const input = document.getElementById('input');
const span = document.getElementById('span')
let obj = {};
input.onchange = function inputChange(e) {
    obj.text = e.target.value
}
Object.defineProperty(obj, 'text', {
    configurable: true,
    enumerable: true,
    get() {
        return obj.text;
    },
    set(newVal) {
        span.innerText = newVal
    }
})

const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
  get: function(target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver) {
    console.log(target, key, value, receiver);
    if (key === 'text') {
      input.value = value;
      p.innerHTML = value;
    }
    return Reflect.set(target, key, value, receiver);
  },
});

input.addEventListener('keyup', function(e) {
  newObj.text = e.target.value;
});

手写Object.create

function create(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

请求并发控制

function concurrentReq(urls, limit) {
    return new Promise((resolve, reject) => {
        let num = 0; // 当前在跑的请求数量(在跑的请求数量要小于限制的数量)
        let task = urls; // 并发任务数组
        let results = []; // 最终并发请求结果存放的数组
        // 递归闭包函数调用发请求,Promise返回最终结果
        function goNext() {
            if (task.length === 0 && num === 0 ) {
                // 当没有更多任务且没有请求正在进行时
                resolve(results); // 所有请求已完成,resolve吐出去返回结果
                return;
            }
            while (num < limit && task.length > 0) { // 当请求任务小于3且还有任务继续干时,goNext
                let url = task.shift(); // 把并发任务数组中第一项剔除掉,并拿到第一项(请求接口)
                num = num + 1 // 并记录一下当前的请求数量
                axios.get(url) // 发请求
                    .then((res) => {
                        num = num - 1 // 请求成功,就把计数减一
                        results.push(res.data); // 把请求的结果依次存起来
                        goNext(); // 递归调用,继续执行下一个请求任务
                    })
                    .catch((err) => {
                        num = num - 1 // 请求失败,也把计数减一
                        console.error(`此接口:${url}请求失败,报错信息:${err}`);
                        goNext(); // 递归调用,继续执行下一个请求任务
                    })
            }
        }
        goNext();
    });
}

// 笔者提供的请求接口
let urls = [
    'http://ashuai.work/api/getIdName?id=1',
    'http://ashuai.work/api/getIdName?id=2',
    'http://ashuai.work/api/getIdName?id=3',
    'http://ashuai.work/api/getIdName?id=4',
    'http://ashuai.work/api/getIdName?id=5',
    'http://ashuai.work/api/getIdName?id=6',
    'http://ashuai.work/api/getIdName?id=7',
    'http://ashuai.work/api/getIdName?id=8',
    'http://ashuai.work/api/getIdName?id=9',
    'http://ashuai.work/api/getIdName?id=10',
    'http://ashuai.work/api/getIdName?id=11',
    'http://ashuai.work/api/getIdName?id=12',
    'http://ashuai.work/api/getIdName?id=13',
    'http://ashuai.work/api/getIdName?id=14',
    'http://ashuai.work/api/getIdName?id=15',
    'http://ashuai.work/api/getIdName?id=16',
    'http://ashuai.work/api/getIdName?id=17',
    'http://ashuai.work/api/getIdName?id=18',
]
let limit = 3; // 假设控制并发请求数量每次发三个

concurrentReq(urls, limit)
    .then((results) => {
        console.log('所有请求执行结果:', results);
    })
    .catch((error) => {
        console.error('请求执行出错:', error);
    });
    

使用Promise封装Ajax请求

// promise 封装实现:
function getJSON(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}

手写判断类型函数

function getType(value) {
    if (typeof value !== 'object') {
      return typeof value;
    }
    return Object.prototype.toString.call(value).slice(8, -1).toLocaleLowerCase();
  }

解析URL为对象

function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
  const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
  let paramsObj = {};
  // 将 params 存到对象中
  paramsArr.forEach(param => {
    if (/=/.test(param)) { // 处理有 value 的参数
      let [key, val] = param.split('='); // 分割 key 和 value
      val = decodeURIComponent(val); // 解码
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
      if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else { // 如果对象没有这个 key,创建 key 并设置值
        paramsObj[key] = val;
      }
    } else { // 处理没有 value 的参数
      paramsObj[param] = true;
    }
  })
  return paramsObj;
}

Promise实现图片加载

function loadImage(url) {  
    return new Promise((resolve, reject) => {  
        let img = new Image();  
        img.onload = () => {  
            resolve(img);  
        };  
        img.onerror = (error) => {  
            reject(error);  
        };  
        img.src = url;  
    });  
}  
  
// 使用方式  
loadImage('https://example.com/path/to/image.jpg')  
    .then(img => {  
        document.body.appendChild(img);  
        console.log('图片加载成功');  
    })  
    .catch(error => {  
        console.error('图片加载失败', error);  
    });